Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework: Writable nested serializers with Generic Foreign Key

There are examples how to create a writable nested serializer like this and then how to serialize a generic foreign key (here).

But I cannot find how to do both at the same time, i.e how to create a nested writable serializer for a generic foreign key field.

In my models there is a Meeting model with a GenericForeignKey which can be either DailyMeeting or WeeklyMeeting like:

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

Then I created a custom field in my 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__'

I am passing a JSON which looks like:

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

But the problem is that I am getting the following error:

AssertionError: Relational field must provide a queryset argument, override get_queryset, or set read_only=True.

Why does the Relational field has to be read_only? If I set it as read_only then it is not passed in the data in the serializer.

And what type of queryset do I have to provide?

like image 449
Galil Avatar asked Nov 24 '17 17:11

Galil


1 Answers

You need to implement to_internal_value as well, and you can use just plain Field class.

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

If validation went well then you'll get created object in the MeetingSerializer validated data in other case RecurringMeetingRelatedField will raise an exception.

like image 166
Ivan Semochkin Avatar answered Oct 21 '22 05:10

Ivan Semochkin