Правильное настраиваемое поле записи ModelSerializer - PullRequest
1 голос
/ 29 апреля 2020

Я работаю над проектом, который управляет персоналом школы. На мой взгляд, отношения между ними плохо реализованы, поэтому я пытаюсь их реорганизовать. Тем не менее, чтобы минимизировать влияние, поскольку это не является приоритетом, я пытаюсь сделать это, не нарушая интерфейс, который должен быть реорганизован позднее. Текущая модель Staff выглядит следующим образом:

class Staff(models.Model):
    principal_at = models.ForeignKey(School, null=True)
    teacher_at = models.ForeignKey(School, null=True)
    # I'm only showing these two roles for the sake of simplicity but there are more.

Я рефакторинг ее следующим образом:

class Staff(models.Model):
    ROLE_CHOICES = (
        "principal",
        "teacher",
        # ...
    )
    school = models.ForeignKey(School)
    role = models.CharField(choices=ROLE_CHOICES)
    # ...

    @property
    def principal_at(self):
        return self.school if self.role == "principal" else None
    # ...

Мой StaffSerializer выглядит следующим образом:

class StaffSerializer(serializers.ModelSerializer):

    class Meta:
        model = Staff
        fields = (
            "principal_at",
            "teacher_at",
            # ...
        )

    # There is also some custom validation to ensure the Staff can only have
    # one role.

Если я просто оставлю его без изменений, поле principal_at станет ReadOnlyField, и оно не будет сохранено, что имеет смысл, поскольку мой атрибут модели теперь является свойством, а не фактическим записываемым полем. Поэтому я изменил свой сериализатор следующим образом:

class StaffSerializer(serializers.ModelSerializer):

    kwargs = {"required": False, "queryset": School.objects.all()}

    principal_at = serializers.PrimaryKeyRelatedField(**kwargs)
    teacher_at = serializers.PrimaryKeyRelatedField(**kwargs)

    class Meta:
        model = Staff
        fields = (
            "principal_at",
            "teacher_at",
            # ...
        )

    def validate(self, data):
        principal_at = data.pop("principal_at", None)
        teacher_at = data.pop("teacher_at", None)
        # ...
        if principal_at:
            data.update(school_id=principal_at, role="principal")
        elif teacher_at:
            data.update(school_id=teacher_at, role="teacher")
        # ...
        return data

Теперь модель сохранена правильно (я проверил с помощью ipdb), но в процессе построения ответа я получаю следующее:

TypeError: Object of type School is not JSON serializable

После этого я, честно говоря, не помню, что я пробовал, но я пробовал много вещей, и ничего не работает. Одна вещь, которую я помню, пытаясь, и это поразило меня, было создание настраиваемого поля и переопределение to_representation:

class RelatedSchoolSerializer(serializers.PrimaryKeyRelatedField):
    def to_representation(self, value):
        import ipdb; ipdb.set_trace()
        return value.pk

class StaffSerializer(serializers.ModelSerializer):

    kwargs = {"required": False, "queryset": School.objects.all()}

    principal_at = serializers.PrimaryKeyRelatedField(**kwargs)
    teacher_at = serializers.PrimaryKeyRelatedField(**kwargs)
    # ...

Это продолжало давать сбой с той же ошибкой, однако, побудив меня добавить точку останова и проверить, что F ** K продолжается. В отладчике, к моему ужасу, я получил следующее:

ipdb> value                                                                                                                                                                                
<rest_framework.relations.PKOnlyObject object at 0x7f9563c87f90>
ipdb> value.pk                                                                                                                                                                             
<School: School 0>
ipdb> value.pk.pk                                                                                                                                                                          
1
ipdb>

Если я верну value.pk.pk вместо value.pk, все работает. Но хотя мой набор тестов проходит, я боюсь, что такой странный взлом может иметь загадочные непредвиденные последствия в будущем. Что на земле происходит? Как правильно это сделать?

...