Alex Grs
Alex Grs

Reputation: 3301

Modify Django Rest Framework ModelViewSet behavior

I basically have the following model in my project:

class ShellMessage(TimeStampedModel):
    # There is a hidden created and modified field in this model.
    ACTION_TYPE = (
        ('1' , 'Action 1'),
        ('2' , 'Action 2')
    )
    type    = models.CharField(max_length=2,choices=ACTION_TYPE,default='1')
    action  = models.CharField(max_length=100)
    result  = models.CharField(max_length=300, blank=True)
    creator = models.ForeignKey(User)

I created a serializer:

class ShellMessageSerializer(serializers.ModelSerializer):
    class Meta:
        model = ShellMessage
        fields = ('action', 'type', 'result', 'creator')

And a ModelViewSet:

class ShellListViewSet(viewsets.ModelViewSet):
    serializer_class = ShellMessageSerializer
    queryset = ShellMessage.objects.all()

My issue is the following: When I create a new ShellMessage with a POST to my API, I don't want to provide the foreignKey of 'creator' but instead just the username of the guy and then process it in my ViewSet to find the user associated with this username and save it in my ShellMessage object.

How can I achieve this using Django rest Framework? I wanted to supercharge create() or pre_save() methods but I'm stuck as all my changes overwrite 'normal' framework behavior and cause unexpected errors.

Thank you.

Upvotes: 1

Views: 3377

Answers (2)

Niel Godfrey P. Ponciano
Niel Godfrey P. Ponciano

Reputation: 10709

UPDATE: This topic seems to be a duplicate to Editing django-rest-framework serializer object before save

If you intend to intercept and perform some processing before the object gets saved in the model database, then what you're looking for is overriding the method "perform_create" (for POST) or "perform_update" (for PUT/PATCH) which is present within the viewsets.ModelViewSet class.

This reference http://www.cdrf.co/3.1/rest_framework.viewsets/ModelViewSet.html lists all available methods within viewsets.ModelViewSet where you can see that the "create" method calls "perform_create" which in turn performs the actual saving through the serializer object (the object that has access to the model):

def perform_create(self, serializer):
    serializer.save()

We can override this functionality that is present in the base class (viewsets.ModelViewSet) through the derived class (the ShellListViewSet in this example) and modify the model attribute(s) that you want to be changed upon saving:

class ShellListViewSet(viewsets.ModelViewSet):
    serializer_class = ShellMessageSerializer
    queryset = ShellMessage.objects.all()

    def findCreator(self):
        # You can perform additional processing here to find proper creator
        return self.request.user

    def perform_create(self, serializer):
        # Save with the new value for the target model fields
        serializer.save(creator = self.findCreator())

You can also opt to modify the model fields separately and then save (probably not advisable but is possible):

serializer.validated_data['creator'] = self.findCreator()
serializer.save()

Later if the object is already created and you also want to apply the same logic during an update (PUT, PATCH), then within "perform_update" you can either do the same as above through the "serializer.validated_data['creator']" or you could also change it directly through the instance:

serializer.instance.creator = self.findCreator()
serializer.save()

But beware with such updating directly through the instance as from https://www.django-rest-framework.org/api-guide/serializers/ :

class MyModelSerializer(serializers.Serializer):
    field_name = serializers.CharField(max_length=200)

    def create(self, validated_data):
        return MyModel(**validated_data)

    def update(self, instance, validated_data):
        instance.field_name = validated_data.get('field_name', instance.field_name)
        return instance

This means that whatever you assign to the "instance.field_name" object could be overriden if there is a "field_name" data set within the "validated_data" (so in other terms, if the HTTP Body of the PUT/PATCH Request contains that particular "field_name" resulting to it being present in the "validated_data" and thus overriding whatever value you set to the "instance.field_name").

Upvotes: 0

Alex Grs
Alex Grs

Reputation: 3301

I finally find my solution just after posting my question :)

So I did the following:

class ShellListViewSet(viewsets.ModelViewSet):
    serializer_class = ShellMessageSerializer
    queryset = ShellMessage.objects.all()

    def pre_save(self, obj):
        obj.creator = self.request.user
        return super(ShellListViewSet, self).pre_save(obj)

This is working as expected. I hope I did well.

Upvotes: 1

Related Questions