Loading video player…

DRF ViewSets

00:00 In the previous lesson, I talked about the Django REST Framework serialization mechanism and how to use the @api_view decorator to create a view. In this lesson, I’m going to be drilling down more on views and introducing the DRF ViewSets class. As a quick refresher, here are the DRF components so far. You’ve seen the Serializer.

00:23 The Serializer is responsible for taking an object and turning it into a text payload, or also to take text payloads and turning them back into objects.

00:32 Most of the time you’re going to want to do this with a Django model, but it doesn’t have to be. The entry point for your REST call is a view, just like a regular Django view.

00:42 The DRF provides decorators to make this a little easier. The heart of it is you have to return a DRF Response object with your serialized data inside of it for what to respond to with the view. To make your life easier, the DRF also has a concept called ViewSets.

01:00 A ViewSet is a class that encapsulates the common REST HTTP method calls, things like the gets, the posts, the patches, et cetera. Using a ViewSet makes it far faster to implement a series of REST calls on a class of objects. Going with the ViewSet is the concept of a Router.

01:19 A Router allows you to use a ViewSet to declare a series of URLs. This means you have far less work to do. You just map the ViewSet to a Router, and the DRF takes care of the rest of it for you.

01:32 Here’s an example base ViewSet, taken straight out of the DRF docs. As you can see, it declares six methods on the ViewSet class: .list(), .create(), .retrieve(), .update(), .partial_update(), and .destroy(). .list() maps to the GET method for listing the objects.

01:51 This would be used to do something similar to the people list view that I showed you in the previous lesson. .create() maps to the POST, allowing you to create a new object. .retrieve() maps to the GET, but for a specific identifier, so to get book number 12.

02:08 .update() maps to HTTP PUT, taking an object as well as some fields, those fields updating the values inside of the object specified. .partial_update() maps to PATCH, taking an object as well as a subset of fields, only updating those fields that are given. And .destroy() maps to the HTTP DELETE method. Throughout this course, I’m going to continue building on the Fedora project. For the most part, I’m going to create a new app for each lesson.

02:40 I won’t stepwise go through the process of creating each app—I will just quickly summarize it in order to save time. If you need guidance on how to do this, see the previous lesson or dig into one of the other Django courses. To get going for this lesson, I’m going to create another app, this one called artifacts.

02:58 I’m going to have to edit the settings.py file, adding artifacts to the INSTALLED_APPS list. I’m going to have to edit the Fedora/urls file to add the artifacts.urls file in the urlpatterns list. And then for the rest of this lesson, I’m going to show you the contents of models.py, serializers.py, views.py, and urls.py.

03:18 You can code along with me as I go. In order to play with the REST interface, you’re going to need some data, so you can either create an admin.py file to go with the models.py or you can use the one supplied in the supplemental materials.

03:32 Don’t forget to run makemigrations and migrate as necessary, and then use the admin or loaddata command to add some data to play with.

03:43 Inside of your newly created artifacts app, edit the models.py file and add this code. What I’ve done here is created a new Django model called Artifact.

03:54 It has two fields, a CharField for name and a BooleanField to indicate whether or not the artifact is shiny.

04:04 Like before, I need a serializer. Inside of a new file called serializers.py, I’m creating the ArtifactSerializer. As in the previous lesson, all I need to do here is identify the model that’s being serialized, which in this case is the Artifact, and the list of fields that I want serialized. The DRF provides a handy shortcut.

04:27 Instead of fields being a list, you can give it the string "__all__", and all the fields in the Artifact will be serialized.

04:36 The DRF is all about making your life easier and writing less code.

04:43 Instead of writing a view, this time I’m going to write a ViewSet. A ViewSet is a view class, and to keep things short and sweet, all you have to do is provide the Serializer that you want associated with the ViewSet and the queryset associated with that Serializer, and the DRF will create everything else for you. So on line 7, by convention, I name it the ArtifactViewSet. You’ll notice this pattern seems to hold—ArtifactSerializer goes with our Artifact model.

05:13 This inherits from the ModelViewSet class. On line 8, I specify the Serializer class, which is the ArtifactSerializer. And on line 10 and 11, I override the .get_queryset() method, returning the queryset of what I want. In this case, it’s all of the Artifact objects. Essentially, I’m accomplishing the same kind of thing that I did with the people list in the previous lesson. I’m listing off all of the artifacts, but this time, because I’m implementing the ModelViewSet, not only am I getting the GET for the listing, I’m also provided with retrieves, deletes, creates, and both kinds of updates.

05:59 To wire this ViewSet into the system, I use a router. Inside of artifacts/urls.py, on line 7 I create a DefaultRouter, and on line 8 I register this router.

06:11 I’m registering the ArtifactViewSet under the name r"artifacts". This means it’ll be listed under the path artifacts/ wherever my URL is mounted.

06:22 By using this router, I don’t have to specify routes for all the different URLs for creates and posts and patches. I just have to register the router, and then on line 11, include that router.

06:37 In a separate window, I’m running the Django development server. Now I’m going to demonstrate hitting those REST URLs with curl. First off, a simple GET. I’m hitting localhost port 8000 under artifacts/ and piping it through json.tool to get a pretty print.

06:55 What I get back is a dictionary with "artifacts" and a URL that says “Go to artifacts/artifacts/.” This happens because of the way I set up the router.

07:06 The first artifacts/ in that URL is part of the path from my include(). The second artifacts/ in that URL is placed there from the registration of the router. In later lessons, after you’ve registered a bunch of different routers, this listing will be helpful.

07:24 It gives the developer information about all the possible object routers that are available. Right now it seems a little redundant because there’s only one thing in there.

07:33 But I’ll take the hint, I’ll use that dictionary and get that first URL.

07:42 Once again with curl,

07:47 piping it through the json.tool, and there you go. This is the GET for the artifacts/artifacts/ path. It returns two artifacts from the database.

08:00 Notice it’s just a list in this case—there’s no dictionary to go with it. I can also do a GET on a specific value from the database by appending the ID of the object to the previous URL. Still a GET,

08:15 same localhost, same port, this time with ID 1.

08:22 And what is returned is just the information about that specific object. So far, so good. Now let’s do a .create().

08:33 The -X parameter to curl tells it to use a different kind of HTTP method, and -d are fields to pass in. In this case, I’m passing in the name and shiny fields.

08:47 It hits the URL. It’s a bit of a long command line there, so notice that it’s wrapped around to the second line. And what comes back is JSON of the new item created, the "Ark of the Covenant", with the shiny, of course, being True. This time I didn’t pass it through the pretty printer, so it just gets spit out all in one line.

09:08 If I run the listing again,

09:13 you’ll notice that the "Ark of the Covenant" has been added to our list of artifacts. Now let’s do an .update(). Once again, using the -X, this time using the HTTP PUT method, specifying fields for "Golden Idol" and "shiny=True", and the full URL of the object that I’m replacing, object 1.

09:37 This renames the "Chachapoyan Fertility Idol" to the easier-to-say "Golden Idol". As with the POST, what comes back is the object that was changed.

09:50 Let me try the same thing, but with a subset of the fields, this time only specifying shiny.

09:58 I get back an error from the server. Notice that this error is similar to how a Django form field works. If you fail to specify a required field in a Django form, Django gives you error information about what field failed. Django REST Framework is doing the same thing here.

10:18 Because I used a PUT, and a PUT maps to an .update(), you have to provide all fields for an .update().

10:24 And because I failed to provide the name field, I get back a dictionary with an error that says the name is required. If I only want to update the single field, instead of using a PUT, I have to use a PATCH. A similar concept here, this time using HTTP PATCH, provide only the shiny field and same URL.

10:49 And this time it worked. I don’t know what Belloq did to that pretty little gold statue to make it no longer shiny, but the database reflects it now. The only HTTP method left is DELETE so here it goes. Once again capital -X, this time with DELETE, specify a URL that has a specific object. In this case, you get nothing back from the server. If I run the listing, GET again,

11:20 you’ll see that the "Golden Idol" is no longer there. I guess Belloq took it out of inventory.

11:28 By using a ViewSet instead of a view, you automatically get the .list(), .retrieve(), .create(), .update(), .partial_update(), and .delete() methods for your object. In just a couple of lines of code, you fully defined a REST interface for your object.

11:45 This is extremely convenient and shows off the power of the DRF. Normally, I am a views-should-be-functions kind of guy, but when I use the DRF, ViewSets are the way to go.

11:58 In addition to the convenience of having the ViewSet define all of those things, the router means you don’t have to define the mappings for all of those. .list(), .retrieve(), .create(), .update(), .partial_update(), and .delete() are all created by the router in a single line.

12:14 The DRF saves you an awful lot of work and an awful lot of definition. Everything’s about the shortcuts here. So, that was ViewSets. Next up, I’ll show you the web interface, a great way to debug your REST application.

Avatar image for apurvakunkulol

apurvakunkulol on Feb. 3, 2021

Hi, While executing the PUT request I tried to specify just the name field and it worked. However, when I specified only the shiny field, it didn’t and indicated that the name field is a required one. We haven’t explicitly specified that the name field be made mandatory, so how does this internally(or automatically) happen?

Thanks, Apurva

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Feb. 3, 2021

Hi @apurvakunkulol,

Django model fields work backwards: they’re mandatory unless you specify otherwise. The model currently looks like this:

class Artifact(models.Model):
    name = models.CharField(max_length=100)
    shiny = models.BooleanField()

You would need to use the blank=True parameter to change it to be allowed to be blank.

class Artifact(models.Model):
    name = models.CharField(max_length=100, blank=True)
    shiny = models.BooleanField()

Same goes for if you’re using the Django admin. You also want to be careful as Django makes a distinction between NULL being allowed and whether a field can be blank. For most fields you want null=True, but for anything containing text there is a distinction between the field being NULL (set to None in Python) versus the field being an empty string (set to "" in Python). For Strings you usually want the empty string allowed rather than NULL.

Avatar image for apurvakunkulol

apurvakunkulol on Feb. 4, 2021

@Christopher Trudeau, So this also explains why the BooleanField is allowed even when it’s NULL, right? Due to the distinction between NULL and “empty” values.

Thanks, Apurva.

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Feb. 4, 2021

Hi @apurvakunkulol,

Almost. NULL and Boolean is tricky. Technically the Django field isn’t allowing NULL: it doesn’t have the null=True attribute. If you tried to leave that field blank in the Django admin it would complain. BUT, NULL converts to None which is a valid boolean value, it maps to False. I haven’t tested it, but I suspect if you sent up a blank it will become False.

You’re at the intersection between the database (NULLs), the Django ORM (blank for strings and NULLS, and the same attributes controlling the Django admin), and the DRF that is taking everything in as a string (all HTTP is text) and then converting it. When the DRF expects a number it attempts to convert the string to that number format. When it expects a boolean value it converts that string to boolean, hence the mapping from None or ‘’ or NULL to False.

This is part of what can make the web painful to code for, you’re always translating back and forth. There are older protocols that actually didn’t convert everything to text, but the web won and here we are :)

Avatar image for apurvakunkulol

apurvakunkulol on Feb. 8, 2021

Just out of curiosity, what were some older protocols?

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Feb. 8, 2021

Hi @apurvakunkulol

Some languages, like Java, have (had?) binary protocols like RPC. You can do similar things in Python using Pickle. These mechanisms tend to be language specific and because they’re binary you are within the space of the language rather than going back and forth.

“Older” might be a bit of a misnomer, some of these have been around for the same amount of time, but because of the success of the web HTTP won.

Avatar image for nportillo

nportillo on April 2, 2022

What do you mean when you say “11:28 By using a ViewSet instead of a view, you automatically get the .list(), .retrieve(), .create(), .update(), .partial_update(), and .delete() methods for your object. In just a couple of lines of code, you fully defined a REST interface for your object.”?

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on April 3, 2022

Hi @nportillo,

In the previous chapter you wrote a single view (people/views/list_people) that only provided the single GET HTTP method. If you also wanted to support the HTTP DELETE, PATCH, and POST methods this way, you’d have to write a view for each of them.

In this chapter, you saw ViewSets which as a class implement all this for you. By using the ViewSet and pointing at the model, you automatically get all the HTTP methods and the various ways of using them in REST: listing objects, getting specific objects (retrieve), creating, updating, and deleting.

The ViewSet is typically the way you want to go, far less code.

Become a Member to join the conversation.