Wait, where’s my pineapple?
Multiple serializers vs multiple views in Django Rest Framework
When you create simple CRUD API views in Django Rest Framework I guess that most of the time you create only two endpoints: one for list/create actions and the second one for retrieve/update/destroy actions.
Implementing only one view for multiple actions means having also only one serializer for them. Less code, more ‘DRY’, isn’t it? However, sometimes it’s not so good, because our API may require different formats of data in every action. So what to do if that happens to us?
There are many solutions and different approaches to that problem and I’ll try to present a few of them.
Brief introduction
Let’s assume we have a pizza restaurant (who doesn’t love pizza?) and we need to implement an app in which our clients could make the orders.
The models look like the ones below:
So we have aPizza
model with fields:
name
,price
(not important in this case),ingredients
(ManyToManyField toIngredient
, because every pizza can have many ingredients),sauce
(ForeignKey) toSauce
model because every pizza can have only one sauce).
Let’s implement a very simple PizzaAPIView
inheriting from generics.ListCreateAPIView
that would handle list/create actions.
And very basic PizzaSerializer
inheriting from ModelSerializer
:
One more line of code in urls.py
:
And quick look into our Ingredients
and Sauces
tables to check what’s available in our restaurant’s kitchen :).
Ok, let’s create the best pizza ever :).
Here’s how the JSON (for a POST request) should look:
{
"name": "Hawaiian",
"price": 29.99,
"sauce": 1,
"ingredients": [1,2,5]
}
And send a GET request to fetch the list. Let’s take a look at the response:
Hmm, everything works but…where is my pineapple?! I don’t want to see on our list ids of sauces and ingredients but their names.
How to do that with not so much effort and keeping the code clean?
Multiple views — multiple serializers
The first solution is to split our view into two separate ones:
This way we can create a new serializer that will handle PizzaListAPIView
:
I have overridden the field sauce
to return the name of the sauce (using source
property) and added depth = 1
to Meta
class to get as a result the full object.
There’s one thing left. We need to create a separate endpoint for every view.
Now our result will look like that:
Yeah, pizza with pineapple! That’s what I like :)
But this approach required creating an extra view and endpoint. Maybe there’s a better solution?
One view — one serializer
Why not give it a try and mix everything in one view and one serializer? Let’s get back to our basic PizzaAPIView
and modify PizzaSerializer
a little bit:
I’ve added two extra fields: selected_sauce
and selected_ingredients
to return the objects in our desired format. I also assigned them the read_only
property so that they are available only in the response of the GET method.
There’s one more thing. By assigning to sauce
and ingredients
fields the write_only
property, I’m sure that these two fields will be required in the POST request data but they won’t be displayed in the response.
This approach allows us to handle two methods in one view and one serializer. But is it always the best solution? The answer depends on you.
If you ask for my opinion, I feel it’s quite messed up when there are different names for the same field in the list and create views. Also, the moment when I need to take a look at every field definition and check if it’s read_only
or write_only
makes me feel overwhelmed with too much information gathered in one class what makes the code not clean enough.
That’s why I was looking for some other solutions and here’s what I found…
One view — multiple serializers
Let’s get back to our basic view and modify it a little bit. Django Rest Framework comes out with many methods that we can simply override to achieve our goals. (If you want to know more about them, check out here.
One of these methods is get_serializer_class()
. This method is being called when we send a request and Django is looking for the serializer class. By default, it returns the serializer_class
that is defined in the view. But if we override the method, the real magic happens…
What’s going on here? Just a simple checking if our request method is POST (it means that we are creating a new object). If yes, we tell Django to use PizzaCreateSerializer
. In any other case, PizzaListSerializer
will be used.
Then we can create our serializers:
And update urls.py:
Wow, that’s amazing! We’ve just used two serializers in one view. And we can go even further and use as much as we want just by implementing any logic we can imagine in the get_serializer_class()
method.
Now the code looks much cleaner and every serializer is responsible only for one action.
Conclusion
As you can see almost every problem has many solutions and every solution can be right in some contexts, so it’s really helpful to know different approaches and use them alternatively depending on your needs. Don’t hesitate to experiment with your code — it’s the best way to discover something new and (hopefully) something better! :)
P.S. I hope you won’t hate me for that pineapple on pizza, but I truly love it ;).