Categories
Python

Django REST Framework: ViewSet, ModelViewSet and Router

(This post is a part of a tutorial series on Building REST APIs in Django)

In our last blog post on ModelSerializer and Generic Views, we demonstrated how we can use the generic views along with ModelSerializer classes to rapidly develop our REST APIs. In this post, we will discuss about ViewSet and ModelViewset and see how they can speed up building APIs even further. At the same time we will take a look at the concepts of Routers which allow us to manage our api routes in a cleaner fashion.

Understanding ViewSet

So far we have learned how we can use the generic views. We can now use them to create the two kinds of resources – “collections” and “elements”. It works very well but we need to write at least two different classes to handle them properly. But if we think about it, both resources focus on the same entity, the same model – “Subscriber”. Wouldn’t it make more sense if we could have all the actions related to a Subscriber in a single class? Wouldn’t that be more convenient if we can put all the Subscriber related logic in a single place?

ViewSets come to the rescue. A ViewSet is, as the name suggests, a class that provides the functionality of a set of views which are closely related. It’s one class but provides a set of views. We can handle both “collection” and “element” type of resources from the same class. And not just those, we can add even other related actions.

Since a ViewSet handles both type of resources, we can no longer think in terms of the http verbs. Because both /api/subscriber and /api/subscriber/1 can respond to  GET requests and should produce different types of response. So we can no longer work with the get, post, put etc methods. We need to think more along the actions we can take on the entity (Subscriber) as a whole. A ViewSet works with these methods instead:

  • list – list all elements, serves GET to /api/subscriber
  • create – create a new element, serves POST to /api/subscriber
  • retrieve – retrieves one element, serves GET to /api/subscriber/1
  • update and partial_update – updates single element, handles PUT/PATCH to /api/subscriber/1
  • destroy – deletes single element, handles DELETE to /api/subscriber/1

Instead of our old get and post methods in separate classes, we can now define these methods in one single class and be done with it. But with general ViewSet, we have to provide logic / code for these views. That would require more time and efforts. What if, we could generate these methods from our models / querysets? Well, we can! 😀

The ModelViewSet

The ModelViewSet would only ask for the serializer class and the queryset. And then it will provide all the functionality of the different ViewSet methods. Let’s see the example:

Our SubscriberViewSet now extends the ModelViewSet and we provided the queryset and the serializer_class. Done. Really!

But there’s one slight problem, the ViewSet and ModelViewSet both handle at least two distincts url paths – /api/subscriber (the collection path) and /api/subscriber/1 – the elements path. How do we tell Django’s URLConf to route requests to both urls to our single ViewSet? Well, we have to declare those paths ourselves. We can use the as_view to both paths, defining which http verb should be routed to which methods:

Or we can simply use a Router.

Using Routers

A Router instance let’s us register our ViewSet to it and then we can just add the router.urls to our urlpatterns. It will keep track of the view sets we register and the urls property will generate all the url patterns required for those view sets to work.  Let’s add our newly created SubscriberViewSet to a router and see for ourselves. Open the api/urls.py file and create the router there and register the viewset.

That’s all that is required. Now try visiting – http://localhost:8000/api/subscribers or http://localhost:8000/api/subscribers/1 – it all works.

Fantastic, so we just generated a full RESTful API from a model with a surprisingly short amount of code! Isn’t that wonderful? Just don’t trust my words, go and look at your SubscriberViewSet or the SubscriberSerializer or the above defined router.

With the help of ModelSerializer, ModelViewSet and a router instance, we can build elegant CRUD APIs from our Django models insanely fast. At the same time, if we need, we can just override one of those methods (list, retrieve, create etc) to alter the default behavior with our own.

What’s Next?

We have crafted a nice, functional REST API. The next stop would be securing it. In our next post, we will be discussing Authentication and Permissions.

In the mean time, I would request you to subscribe to the mailing list so I can keep you posted about new exciting contents on this site. If you liked the post, please do share with your friends!

 

Categories
Python

Django REST Framework: ModelSerializer and Generic Views

(This post is a part of a tutorial series on Building REST APIs in Django)

In our last post on Serializers, we learned how to use Serializers with APIViews. In this post we will discuss how ModelSerializer and the Generic views can take things even further.

Model + Serializer = ModelSerializer

That one line equation is probably enough to explain the concepts of ModelSerializers. In our example, we had to define similar fields on both Serializer and the Model class. We had to write codes for the same fields twice. That is 2x the efforts. ModelSerializers can help solve that problem. A ModelSerializer might remind us of ModelForm. The idea is the same. We extend ModelSerializer and pass it the model. The serializer inspects the model and knows what fields to use and what their types are.

Let’s refactor our old serializer to be a ModelSerializer:

That’s all we need – our new SubscriberSerializer now infers the fields from the model (Subscriber) we passed to it. We can however choose which fields should be used while serializing / deserializing. In this example we pass the special value “__all__” which means we want to use all fields. But in many cases we would want to selectively use some fields. For example, for Django’s default User model, we don’t want to leak the password data to public, so we will exclude the password field on our User model serializer.

If we try out our API, we would notice everything is working just like before. Except our serializer class is now shorter and more concise. Awesome, right? Let’s move on to our next topic – generic views.

Generic Views

If you have decent amount of experience with Django, you probably have already come across and used the built in generic views. They provide useful functionality around common database operations. For example, we can use the generic list view and provide it with a queryset and the template, it will do the rest for us.

In the same way, we can pass a queryset and serializer to a ListAPIView and it will create the list view for us. Same way we can use CreateAPIView to implement the create view. What’s even better, since we’re using class based views, we can use both of them together, cool, no? Let’s refactor old code to use these two classes.

Please note how we no longer need to provide the get and post methods but the API still works. The generic views just know how to deal with those.

There is actually a ListCreateAPIView which is the combination of these two as you can probably understand from the class name. We can just use that one.

As you can see this generic view is quite helpful for generating “collection” resources (/api/subscriber) easily. There’s also RetrieveUpdateDestroyAPIView which we can use to generate “element” resources. As you can guess from the name, they provide get, put, patch and delete handler for single items (/api/subscriber/12).

Generic views are very handy for quickly generating resources from our models (querysets actually). This allows rapid API development.

What’s Next?

We have seen how we can very quickly create working REST APIs using model serializers and generic views. Things will be even more amazing when we learn about ViewSets, specially ModelViewSets. You will have to wait for our next post for those 🙂

In case you didn’t notice, we have a mailing list where you can subscribe to get latest updates. We don’t spam, only contents we post. Also if you enjoyed reading the post and found it informative, shouldn’t you share it with your friends? 😀

Categories
Python

Django REST Framework: Serializers

(This post is a part of a tutorial series on Building REST APIs in Django)

In our last blog post, Getting started with Django REST Framework, we saw how we could use the APIView and accept inputs from users using request.data. In our example, we dealt with string, so it was pretty straightforward. But consider the case of age or account_balance – one has to be integer, the other has to be float / decimal. How do we properly validate the incoming data?

We can manually check every input field and send an error if the field type doesn’t match. But soon we’re going to have a problem at our hand – when the number of inputs will grow, we can’t just keep doing this kind of manual validation. In Django, we would probably use Django Forms for validation. Does DRF provide us with something similar? Yes, it does. The solution to our problem is Serializers.

What can a Serializer do for us?

Have you ever tried JSON serializing a Django model? Or a queryset? You can’t directly because they are not JSON serializable. So what do we do instead? We convert them to Python’s native data structures which could be serialized into JSON. We can serialize querysets into lists and model instances to dictionaries. But doing that by hand is cumbersome.

On the other hand, we saw how we can get incoming data from request.data – we get this data as key value pairs. We can’t just store them in database directly – we have to transform them into Django’s data structures like models and querysets. Doing that by hand is also cumbersome.

Serializers can help us with that. It can serialize complex types into Python natives types and then again deserialize native types into those complex types. Besides that, it also does basic validation based on the serializer field types. If a field is defined as an integer field, it will raise an error if we pass a string to that field. If we need more advanced validation rules, we can plug in the built in Validators or even write our own. Let’s see code examples to understand the use case better.

Defining a Serializer

Create a file named serializers.py inside the api app directory. Put the following codes into it.

We’re creating a HelloWorldSerializer which extends serializers.Serializer. We’re defining two fields on this serializer –

  • name is a CharField so it accepts string. It has a max_length of 6.
  • age is an optional integer field. The value must be at least 10 if provided. If not provided, default value will be 10.

With this serializer setup, let’s modify our view to use it.

We pass the request.data as the data parameter to HelloWorldSerializer so it can read all the request data and parse them. Then we check if the serializer is valid. If you have used Django Forms, this will feel very similar. If the serializer is valid, that means we have a valid set of data available. So we can take the value of name and age and show a pretty message. On the other hand, if the serializer is not valid, we can pass the serializer.errors back to the client, which will contain elaborate error messages.

Let’s try out the API to see what happens. Let’s first send an empty request:

The errors say the name field is required. Of course it is! Let’s pass the name.

We just passed the name but didn’t pass the age. Since it is not required and has a default value set, we get the default value. But what if we set a low value?

So we passed 8 and it’s not happy about that. Please note we passed the 8 as a string but DRF doesn’t mind as long as it can convert it to an integer successfully. What if we pass a value that is no number?

That works too! Cool, okay then let’s give it a rest and pass a valid value.

 Serializer with Model

How does Serializers help us in working with models? To understand that, let’s first create one model.

Creating the Subscriber Model

Open api/models.py and add the Subscriber model like this:

Now create and run the migration.

That should setup the table for our new model.

Update The Serializer

We added an email field to our model, also the max length for name is now 50 chars. Let’s update our serializer to match these constraints. Also rename it as SubscriberSerializer.

Update The View

And now let’s refactor our view.

The code is very simple and straightforward. If the serializer validation succeeds, we create a new subscriber out of the validated data.

Update URLConf

Let’s update the urls.py to update our url end point.

Now let’s try it out. We will post the following JSON using curl or postman:

And we will get back the following response:

With the serializer, we needed so much less codes. And we did it in a very clean way.

List All Subscribers

According to the REST Best Practices, the GET call to a resource route (/api/subscriber) should return a list of all the items (subscribers). So let’s refactor the get method to return the subscribers list.

We are fetching all subscribers and then passing the queryset to the serializer constructor. Since we’re passing a query set (not just a single model instance rather a list of model instances), we need to set  many=True . Also note, we don’t need to call is_valid – the data is coming from database, they’re already valid. In fact, we can’t call is_valid unless we’re passing some value to the data parameter (SubscriberSerializer(data=request.data)). When we pass queryset or model, the data is automatically available as serializer.data.

What’s Next?

We have so far learned how to use APIView and Serializer to build beautiful APIs with clean code. But we still have some duplication of efforts, for example when we had to define fields on both the model and the serializer. Also, we have to implement both collection and element resources. So that’s like 5 method implementation. Wouldn’t it be great if, by some mechanism we could make things simpler, shorter and cleaner?

We’ll see, in our next blog post 🙂 Please subscribe to our mailing list for email updates. Also if you liked the content, please don’t forget to share with your friends!