Alberto Chiusole
Alberto Chiusole

Reputation: 2784

DRF - serializer drops nested serializers

TL;DR: DRF drops the inner serialized object when validating the outermost serializer.

I'm using django 2.0, django-rest-framework 3.7.7, python 3.

I want to build a REST endpoint that performs a search in the database, using some parameters received in a POST (I want to avoid GET calls, which could be cached). The parameters should act like ORs (that's why I set all fields as not required), and I'm solving that using django Q queries when extracting the queryset.

I have the following django models in app/models.py:

class Town(models.Model):
    name = models.CharField(max_length=200)
    province = models.CharField(max_length=2, blank=True, null=True)
    zip = models.CharField(max_length=5)
    country = models.CharField(max_length=100)

class Person(models.Model):
    name = models.CharField(max_length=100)
    birth_place = models.ForeignKey(Town, on_delete=models.SET_NULL,
                                    null=True, blank=True,
                                    related_name="birth_place_rev")

    residence = models.ForeignKey(Town, on_delete=models.SET_NULL,
                                  null=True, blank=True,
                                  related_name="residence_rev")

And I wrote the following serializers in app/serializers.py:

class TownSerializer(serializers.ModelSerializer):

    class Meta:
        model = models.Town
        fields = ("id", "name", "province", "zip", "country")

    def __init__(self, *args, **kwargs):
        super(TownSerializer, self).__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].required = False


class PersonSerializer(serializers.ModelSerializer):
    birth_place = TownSerializer(read_only=True)
    residence = TownSerializer(read_only=True)

    class Meta:
        model = models.Person
        fields = ("id", "name", "birth_place", "residence")

    def __init__(self, *args, **kwargs):
        super(PersonSerializer, self).__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].required = False

Then I wrote a view to provide the REST interface, in api/views.py:

class PersonSearchList(views.APIView):
    model_class = Person
    serializer_class = PersonSerializer
    permission_classes = (permissions.AllowAny,)

    def post(self, request, format=None):
        serializer = self.serializer_class(data=request.data)
        print("initial_data", serializer.initial_data)  ########

        if serializer.is_valid():
            self.data = serializer.validated_data
            print(self.data)                            ########
            queryset = self.get_queryset()
            serialized_objects = self.serializer_class(queryset, many=True)

            return Response(serialized_objects.data, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def get_queryset(self, *args, **kwargs):
        orig_queryset = self.model_class.objects.all()
        query_payload = self.data

        # .. perform filtering using the query_payload data.

        return queryset

And when I try to perform a query using e.g. curl:

$ curl -s -X POST -H "Content-Type: application/json" --data '{"birth_place": {"name": "Berlin", "country": "Germany"}}' http://127.0.0.1:8000/persons/ |python -m json.tool
[]

even though a Person object with birth_place set accordingly has just been created. The two print statements that I placed in the post method of the view return:

initial_data:   {'birth_place': {'name': 'Berlin', 'country': 'Germany'}}
after is_valid: OrderedDict()

So it seems like DRF drops the nested relation when validates.

How should I specify to parse and validate also the nested relation? Any suggestion is appreciated.

PS: Am I forcing a wrong design by making the request with a POST? I thought that since a search is not idempotent, and it may contain sensitive data (name, surname, birth date, etc) of a person. I need an action which is safe (a search doesn't change the data) but not idempotent (a search in two different times can be different).

Initially I started using a generics.ListAPIView, but list() works only with GET. If there's a way to make it accept POST requests it would work like a charm.

Upvotes: 0

Views: 714

Answers (1)

JPG
JPG

Reputation: 88469

As @Jon Clements♦ mentioned in comments, this would solve your problem

class PersonSerializer(serializers.ModelSerializer):
    birth_place = TownSerializer()
    residence = TownSerializer()

    class Meta:
        model = Person
        fields = ("id", "name", "birth_place", "residence")

    def __init__(self, *args, **kwargs):
        super(PersonSerializer, self).__init__(*args, **kwargs)
        for field in self.fields:
            self.fields[field].required = False

Upvotes: 1

Related Questions