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 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.
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.
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 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.
Here’s an example base
ViewSet, taken straight out of the DRF docs. As you can see, it declares six methods on the
.list() maps to the
GET method for listing the objects.
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
.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.
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
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
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.
Don’t forget to run
migrate as necessary, and then use the admin or
loaddata command to add some data to play with.
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
It has two fields, a
name and a
BooleanField to indicate whether or not the artifact is shiny.
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.
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.
Instead of writing a view, this time I’m going to write 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
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.
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.
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.
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
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
artifacts/ and piping it through
json.tool to get a pretty print.
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
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.
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.
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
localhost, same port, this time with ID
And what is returned is just the information about that specific object. So far, so good. Now let’s do a
-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
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,
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
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.
Let me try the same thing, but with a subset of the fields, this time only specifying
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.
Because I used a
PUT, and a
PUT maps to an
.update(), you have to provide all fields for an
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.
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,
you’ll see that the
"Golden Idol" is no longer there. I guess Belloq took it out of inventory.
By using a
ViewSet instead of a view, you automatically get the
.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.
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.
.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.
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.
So this also explains why the
BooleanField is allowed even when it’s
NULL, right? Due to the distinction between
NULL and “empty” values.
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 :)
Just out of curiosity, what were some older protocols?
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.
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.”?
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.
apurvakunkulol on Feb. 3, 2021
Hi, While executing the
PUTrequest I tried to specify just the name field and it worked. However, when I specified only the
shinyfield, it didn’t and indicated that the
namefield is a required one. We haven’t explicitly specified that the
namefield be made mandatory, so how does this internally(or automatically) happen?