andrean
andrean

Reputation: 6796

Django rest framework populate fields of nested objects programmatically

When fields need to be filled programmatically in Django Rest Framework, the pre_save method may be overridden in the APIView, and the needed fields can be populated there, like:

def pre_save(self, obj):
    obj.owner = self.request.user

This works great for flat objects, but in case of nested situations, the nested object cannot be accessed in the pre_save method. The only solution I found so far is to override the save_object method, and check if the object is an instance of the nested class, and if so, populate that field there. Although this works, I don't like the solution, and would like to know if anyone found a better way?

Demonstrating the situation:

class Notebook(models.Model):
    owner = models.ForeignKey(User)

class Note(models.Model):
    owner = models.ForeignKey(User)
    notebook = models.ForeignKey(Notebook)
    note = models.TextField()

class NoteSerializer(serializers.ModelSerializer):
    owner = serializers.Field(source='owner.username')
    class Meta:
        model = Note
        fields = ('note', 'owner')

class NotebookSerializer(serializers.ModelSerializer):
    notes = NoteSerializer(many=True)
    owner = serializers.Field(source='owner.username')
    class Meta:
        model = Notebook
        fields = ('notes', 'owner')

    def save_object(self, obj, **kwargs):
        if isinstance(obj, Note):
            obj.owner = obj.notebook.owner

        return super(NotebookSerializer, self).save_object(obj, **kwargs)

class NotebookCreateAPIView(CreateAPIView):
    model = Notebook
    permission_classes = (IsAuthenticated,)
    serializer_class = NotebookSerializer

    def pre_save(self, obj):
        obj.owner = self.request.user

Before asking why don't I use different endpoints for creating notebooks and notes separately, let me say that I do that, but I also need a functionality to provide initial notes on creation of the notebook, so that's why I need this kind of endpoint as well.

Also, before I figured out this hackish solution, I actually expected that I will have to override the save_object method of the NoteSerializer class itself, but it turned out in case of nested objects, it won't even be called, only the root object's save_objects method, for all the nested objects, but I guess it was a design decision.

So once again, is this solvable in a more idiomatic way?

Upvotes: 4

Views: 2807

Answers (1)

Denis Cornehl
Denis Cornehl

Reputation: 4182

You can access the request in your serializer context.

So my approach to this would be:

class NoteSerializer(serializers.ModelSerializer):
    owner = serializers.Field(source='owner.username')

    def restore_object(self, attrs, instance=None):
        instance = super(NoteSerializer, self).restore_object(attrs, instance)
        instance.owner = self.context['request'].user
        return instance

    class Meta:
        model = Note
        fields = ('note', 'owner')

And the same on the NotebookSerializer.

The Serializer context will be made available to all used serializers in the ViewSet.

Upvotes: 4

Related Questions