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