Hkan
Hkan

Reputation: 3383

Create/Update operations with nested serializers

I, as a newbie Django developer, am trying to build a RESTful API for a mobile app. I've took over an existing project and previous developers have used Django REST Framework. Super cool package, easy to work with so far. Except for one thing...

There is this problem when I want to create new resources, which happen to have nested serializers. I'm not great on explaining software issues with words, so here is the simplified version of my case:

class UserSerializer(serializers.ModelSerializer):
    company = CompanySerializer()
    # other props and functions are unrelated


class CompanySerializer(serializers.ModelSerializer):
    # props and functions are unrelated

Now with this structure, GET /users and GET /users/{id} endpoints work great and I get the results I expect. But with POST /users and PATCH /users/{id} I get a response that says I need to provide an object for company prop and it should resemble a Company object with all the required props, so that it can create the company too. And I'm sure it tries to create a new company because I've tried sending { company: { id: 1 } } and it simply ignores the ID and requires a name to create a new one. This is obviously not what I want because I just want to create a user (who may or may not belong to a company), not both a user and a company.

I've tried switching that CompanySerializer to a serializers.PrimaryKeyRelatedField and it seems like it works on create endpoint but now I don't get the Company object on list and detail endpoints.

What am I missing here? I'm 99% sure that they did not intend this framework to work this way.

Upvotes: 0

Views: 943

Answers (2)

Mad Wombat
Mad Wombat

Reputation: 15105

You need to override create() and update() methods on nested serializers to make them writable. Otherwise DRF is not sure what to do with nested objects. The simplest override would go something like this:

class UserSerializer(serializers.ModelSerializer):
    company = CompanySerializer()
    ...

    def create(self, validated_data):
        return User.objects.create(**validated_data)

    def update(self, instance, validated_data):
        user = instance
        user.__dict__.update(validated_data)
        user.save()
        return user

Note: haven't tested this variant of update() might need adjustments.

Upvotes: 1

Daniel Roseman
Daniel Roseman

Reputation: 599628

The trick is to use a different serializer class for retrieving vs updating - one with a PrimaryKeyRelatedField and one with a nested serializer. You can override get_serializer_class to do this. Assuming you are using a viewset:

class BaseUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = (...)

class WriteUserSerializer(BaseUserSerializer):
    company = CompanySerializer()

class ReadUserSerializer(BaseUserSerializer):
    company = PrimaryKeyRelatedField()

class UserViewSet(viewsets.ViewSet):
    def get_serializer_class(self):
        if self.action in ["create", "update", "partial_update", "destroy"]:
            return WriteUserSerializer
        else:
            return ReadUserSerializer

Upvotes: 0

Related Questions