Enhanced PUT Request in Django Rest Framework

Prithoo Medhi
7 min readOct 1, 2022

The U in CRUD stands for Update and is one of the most basic operations one might need to deploy in a web-application backend. But just because it happens to be one of the fundamental operations does not mean that implementing it has been made simple in various frameworks; I do not know about the other popular backend frameworks but Django via Rest Framework provides a truly bizarre implementation, let us take a look.

Implementing the Model

Let us start by implementing a new model first.

Creating the Model

To create the model, simply define a new model in the models.py file of your Django App like so.

Fig: A basic Django model.

Do not worry about the TemplateClass parent we are inheriting from instead of models.Model, it is just an Abstract Model that I use to automatically add the fields UUID id (Primary Key), Datetime created (auto_now_add) and Datetime updated (auto_now) so that I do not have to keep manually typing the code.

Now let us add a few more lines to make this a fully-fleshed-out model, mainly by adding some extra meta options and an over-ridden save() function to auto-process some data upon model creation:

Fig: Advanced configuration for a Django model.

Creating the ModelSerializer

We will keep the serializer simple by implementing a simple serializers.ModelSerializer that comes pre-packaged in Rest Framework instead of creating a custom serializers.Serializer class.

Fig: A basic ModelSerializer.

Creating and Registering the ModelAdmin

Now that we have created the model and its associated serializer, we need to make the table visible in the Admin Panel to make inspecting the data a bit easier. Let us do that in the admin.py page of our Django app.

Reminder

Do not forget to run:

  1. python manage.py makemigrations
  2. pyhton manage.py migrate

so that our model actually shows up in our Database as a table.

Implementing the CRUD API

Great, now that we have created the model and its associated configuration, we can start working on the API part of the rest API.

To get to the point sooner, I will assume that you already know how to implement the basic Create, List, Retrieve and Destroy functionalities and instead will primarily focus on the Update functionality.

Creating the PUT API

Let us now create a PUT method handler in the API (you need not use the PUT method, but using the proper methods does make the intention of the endpoint clearer and makes the documentation easier to understand).

As seen in the picture, we do the following steps in order

  1. Retrieve the object id (primary key) from the request.
  2. Retrieve the object by querying the database via its primary key.
  3. Insert the new data from the request into the object via an instance of its ModelSerializer that we defined earlier.
  4. Check if the serializer object is valid; If it is not valid, return the error as an API response.
  5. Save the serializer instance, thus committing the changes to the model object and hence the database table row.

Simple, right? Now let us fire up Postman and make a simple PUT request to the server.

Here, now we know that it is working with a full set of data in the body, namely :

  1. name:str
  2. char_field:str
  3. decimal_field:float
  4. integer_field:int
  5. boolean_field:bool

But what happens if we only want to change a single column/field in the row/object? Let us see for ourselves by trying to alter only the field name in the object.

Hmm, it seems we cannot use a serializer to update only a single field in the model object. This can be a problem; now, of course, we can simply write the following:

if "name" in request.data.keys():
instance.name = request.data.get("name")
if "char_field" in request.data.keys():
instance.name = request.data.get("char_field")
if "decimal_field" in request.data.keys():
instance.name = request.data.get("decimal_field")
if "integer_field" in request.data.keys():
instance.name = request.data.get("integer_field")
if "boolean_field" in request.data.keys():
instance.name = request.data.get("boolean_field")

This, as with all other working examples, is a valid implementation of the logic we want to implement. But do you not think that this code looks a bit cumbersome? And I do not know about you, but I do not like the idea of giving up on Serializers for a single type of operation and all the benefits that come with it.

Enhancing the PUT API

So how do we overcome this problem? See the code snippet above? Let us take it one step further with the following algorithm.

Now, what have we done differently this time around? Let us examine the steps.

  1. Retrieve the object id (primary key) from the request.
  2. Retrieve the object by querying the database via its primary key.
  3. Turn the object into a python dictionary by using its serializer.
  4. Updated the values in the object dictionary with the values in the request body.
  5. Inserted the new values into the object via its serializer from its own object dictionary with the updated values copied to it from the request body.
  6. Check if the serializer object is valid; If it is not valid, return the error as an API response.
  7. Save the serializer instance, thus committing the changes to the model object and hence the database table row.

The key functionality of this code comes on stage in line:190 as seen in the picture above.

for key in request.data.keys():
obj_data[key] = request.data.get(key)

Now, let us fire up Postman again and see if this work. We will try to update the name field again.

HEY! What do you know? It works. We used the dictionary update method and the dictionary keys method to overcome a major limitation of Django Rest Framework!

Expanded Functionality

But wait, this is not the end of it. This algorithm does double duty. Can you guess what would happen if we sent a blank request body? Think about it…the body is only used to update the values in the object dictionary, which is itself used to update the model instance. What would happen if the following were to happen?

keys = request.data.keys()
=> keys = None if request.data = dict()

So now, there are no keys in the request body therefore, the object dictionary is not updated, so according to our code, the response body would be nothing more than the object’s original value, right?

Let us check.

Hey! So this method handler also does double duty as a perfectly functional RETRIEVE API!

So with a tiny bit of Python know-how, we not only overcame the basic update method’s limitation(s) but expanded its functionality as well.

Adding API Constraints

But Author, Author.”, I hear you screaming out loud, “What if the user tries to alter a field that we do not want them to be able to alter?” To which I reply with the following picture.

Simply add a list or tuple of allowed fields in the function/class and add the following line to the object dictionary update loop.

if key not in self.ALLOWED_FIELDS:
return Response(
{
"error": f"Field '{key}' is not permitted to be altered"
}
)

This would make the complete loop look something like the following.

for key in request.data.keys():
if key not in self.ALLOWED_FIELDS:
return Response(
{
"error": f"Field '{key}' is not permitted to be altered"
}
)
obj_data[key] = request.data.get(key)

This assures that if the API requester tries to alter a field that they are not supposed to be able to alter, the API will return an error response.

Conclusion

So in this article, we discussed how we can overcome a significant headache regarding the model update method in Rest Framework and also how to expand its functionality to make it reusable in multiple use cases. I apologize if this is common knowledge but I could not find many or any resources discussing this and this was a significant point of frustration for me, personally when I started with API.

Anyway, I hope this article may be of some use to you. The full source code for this article can be found on my personal GitHub.

Until next time.

--

--

Prithoo Medhi

Perennial idiot, occasional freelancer and sporadic poster!