1
votes

I'm using Django 2.1, DRF 3.7.7.
I've some models and their relative (model) serializers: these models are nested, and so are the serializers. Let me give an example:

# models.py
class City(models.Model):
    name = models.CharField(max_length=50)

class Person(models.Model):
    surname = models.CharField(max_length=30)
    birth_place = models.ForeignKey(City)


# serializers.py
class CitySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.CitySerializer
        fields = "__all__"

class PersonSerializer(serializers.ModelSerializer):
    birth_place = CitySerializer()

    class Meta:
        model = models.Person
        fields = "__all__"

If I submit an AJAX request with a json like:
{'surname': 'smith', 'birth_place': 42}
I get back a Bad Request response, containing: Invalid data. Expected a dictionary, but got int.

If I submit a nested json like:
{'surname': 'smith', 'birth_place': {'id': 42, 'name': 'Toronto'}}
the relation is not converted, the id field is ignored and the rest is parsed to:
OrderedDict([('birth_place', OrderedDict([('name', 'Toronto')]))])

The following is the post method I'm using on a class-based view:

def post(self, request):
    print("Original data:", request.data)
    serializer = self.serializer_class(data=request.data)
    if serializer.is_valid():
        self.data = serializer.validated_data
        print("Parsed data:", self.data)
        ...

I only need to get data from the endpoints connected to the serializers, I don't need to write/save anything through the REST interface, since the POST processing of the form is done by Django.

TL;DR: How should I correctly submit a JSON request to a nested serializer, without having to write handmade conversions? Did I commit errors in setting up the serializers?

Edit: I've discovered that by adding id = serializers.IntegerField() to the serializer parent class (e.g. City), the serializer parser now processes the id. At least now I'm able to perform actions in the backend with django.

2

2 Answers

1
votes

Generic writing for nested serializers is not available by default. And there is a reason for that:

Consider, you are creating a person with a birthplace, using a POST request. It is not clear if the submitted city is a new one or an existing one. Should it return an error if there isn't such a city? Or should it be created?

This is why, if you want to handle this kind of relationship in your serializer, you need to write your own create() and update() methods of your serializer.

Here is the relevant part of the DRF docs: http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers

1
votes

It's definitely not clearly put into the docs of django-rest. If you follow the process of serializers processing the data for creation then it becomes clear that django manages m2m by saving the parent instance first and then adding the m2m values, but somehow the m2m fields don't go through the validation if you mark them as read_only.

The solution to this is to overr run_validation method of the serializer. The serializer should look like this:

class ExampleSerializer(serializers.ModelSerializer):
    queryset = SomeModel.objects.all()
    tags = TagSerializer(many=True, read_only=True)

    class Meta:
        model = SomeModel
        fields = ['pk', 'name', 'tags']

    def run_validation(self, data):
        validated_data = super(StudyResourceSerializer, self).run_validation(data)
        validated_data['tags'] = data['tags']
        return validated_data

The request body should look like this:

{
    "tags": [51, 54],
    "name": "inheritance is a mess"
}