7

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?

2
  • I had not checked the scenario but you dont have to pass the recurring_meeting from the api.. (Its added in the serializer create() Method) so you can set read_only Commented Nov 27, 2017 at 14:53
  • If you set it as read_only there is nothing in the validated_data passed to the create() method. So, in this case the recurring_meeting is totally ignored. Commented Nov 27, 2017 at 16:45

2 Answers 2

5
+50

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Great answer! Thanks! I just found that when I update the whole meeting object, the nested recurring_meeting field is not updated, but a new recurring_meeting is created instead. Not sure if it has do anything with your solution, just asking if it is related anyhow...
@Galil I didn't think about the update and delete case. I don't have pc around right now, so I can't test the solution. But you can try just exclude the recurring_meeting field from json on update call and add required=False to the RecurringMeetingSerializer, so you'll skip to_internal_value call on it.
0

In this case instead of using a RecurringMeetingRelatedField in the Meeting serializer, you could define a nested serializer like this.

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.