Django Rest Framework: записываемые вложенные сериализаторы с общим внешним ключом

Существуют примеры того, как создать вложенный сериализатор с возможностью записи, подобный этому, и затем сериализовать общий внешний ключ ( здесь).

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

В моих моделях есть Meeting модель с GenericForeignKey который может быть DailyMeeting или же WeeklyMeeting лайк:

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"
        }
}

Но проблема в том, что я получаю следующую ошибку:

AssertionError: Реляционное поле должно содержать queryset аргумент, переопределить get_querysetили установите read_only=True,

Почему реляционное поле должно быть read_only? Если я установлю это как read_only то это не передается в data в сериализаторе.

И какой тип набора запросов я должен предоставить?

2 ответа

Решение

Вам необходимо реализовать 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 поднимет исключение.

В этом случае вместо использования 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
Другие вопросы по тегам