Django Rest框架:使用通用外键实现可写的嵌套序列化器

7

有一些示例如何创建可写的嵌套序列化程序,如这个,然后如何序列化通用外键(在这里)。

但是我找不到如何同时做到这两点,即如何为通用外键字段创建一个嵌套可写序列化程序。

在我的模型中,有一个 Meeting 模型和一个 GenericForeignKey,可以是 DailyMeetingWeeklyMeeting,例如:

class Meeting(models.Model):
    # More fields above
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    recurring_meeting = GenericForeignKey('content_type', 'object_id')

class DailyMeeting(models.Model):
    meeting = GenericRelation(Meeting)
    # more fields

class WeeklyMeeting(models.Model):
    meeting = GenericRelation(Meeting)
    # more fields

然后我在我的serializers.py中创建了一个自定义字段:

class RecurringMeetingRelatedField(serializers.RelatedField):
    def to_representation(self, value):
        if isinstance(value, DailyMeeting):
            serializer = DailyMeetingSerializer(value)
        elif isinstance(value, WeeklyMeeting):
            serializer = WeeklyMeetingSerializer(value)
        else:
            raise Exception('Unexpected type of tagged object')
        return serializer.data


class MeetingSerializer(serializers.ModelSerializer):
    recurring_meeting = RecurringMeetingRelatedField()

    class Meta:
        model = Meeting
        fields = '__all__'

我正在传递一个看起来像这样的JSON:

{
    "start_time": "2017-11-27T18:50:00",
    "end_time": "2017-11-27T21:30:00",
    "subject": "Test now",
    "moderators": [41],
    "recurring_meeting":{
        "interval":"daily",
        "repetitions": 10,
        "weekdays_only": "True"
        }
}

但问题是我遇到了以下错误:

断言错误:关系字段必须提供一个queryset参数,覆盖get_queryset或设置read_only=True

为什么关系字段必须是read_only?如果我将其设置为read_only,那么它将不会在序列化器的data中传递。
我需要提供什么类型的查询集?

我还没有检查过这种情况,但你不必从API中传递recurring_meeting..(它已经在序列化器的create()方法中添加了),所以你可以设置read_only - Jay Dave
如果将其设置为“read_only”,则在传递给“create()”方法的“validated_data”中没有任何内容。因此,在这种情况下,recurring_meeting完全被忽略。 - Galil
2个回答

5

您需要实现to_internal_value,并且可以使用普通的Field类。

from rest_framework.fields import Field

class RecurringMeetingRelatedField(Field):
    def to_representation(self, value):
        if isinstance(value, DailyMeeting):
            serializer = DailyMeetingSerializer(value)
        elif isinstance(value, WeeklyMeeting):
            serializer = WeeklyMeetingSerializer(value)
        else:
            raise Exception('Unexpected type of tagged object')
        return serializer.data

    def to_internal_value(self, data):
        # you need to pass some identity to figure out which serializer to use
        # supose you'll add 'meeting_type' key to your json
        meeting_type = data.pop('meeting_type')

        if meeting_type == 'daily':
            serializer = DailyMeetingSerializer(data)
        elif meeting_type == 'weekly':
            serializer = WeeklyMeetingSerializer(data)
        else:
            raise serializers.ValidationError('no meeting_type provided')

        if serializer.is_valid():
            obj = serializer.save()
        else:
            raise serializers.ValidationError(serializer.errors)

        return obj

如果验证通过,您将在MeetingSerializer的验证数据中获得创建的对象;否则,RecurringMeetingRelatedField将引发异常。


非常好的答案!谢谢!我刚刚发现,当我更新整个“meeting”对象时,嵌套的“recurring_meeting”字段没有被更新,而是创建了一个新的“recurring_meeting”。不确定它是否与您的解决方案有关,只是想问一下是否有任何相关性... - Galil
@Galil 我没有考虑到更新和删除的情况。我现在身边没有电脑,所以无法测试这个解决方案。但你可以尝试在更新调用中排除json中的 recurring_meeting 字段,并在 RecurringMeetingSerializer 中添加 required=False,这样你就可以跳过对它的 to_internal_value 调用。 - Ivan Semochkin

0
在这种情况下,您可以像这样定义一个嵌套的序列化器,而不是在Meeting序列化器中使用RecurringMeetingRelatedField。
class RecurringMeetingSerializer(serializers.Serializer):
    interval = serializers.CharField()
    repetitions = serializers.IntegerField()
    weekdays_only = serializers.BooleanField()

    class Meta:
        fields = '__all__'


class MeetingSerializer(serializers.ModelSerializer):
    recurring_meeting = RecurringMeetingSerializer()

    class Meta:
        model = Meeting
        exclude = ['object_id', 'content_type']

    def create(self, validated_data):
        recurring_meeting = validated_data.pop('recurring_meeting')

        if recurring_meeting['interval'] == 'daily':
            instance = DailyMeeting.objects.create(**recurring_meeting)
            type = ContentType.objects.get_for_model(instance)
        else:
            instance = WeeklyMeeting.objects.create(**recurring_meeting)
            type = ContentType.objects.get_for_model(instance)

        meeting = Meeting.objects.create(content_type=type,
                                         object_id=instance.id)

        return meeting

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接