Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

ViewSet Actions

Give Feedback

00:00 In the previous lesson, I showed you nested serialization. In this lesson, I’m going to show you more about ViewSets and how to do custom actions.

00:10 A common pattern in the REST world is to declare an API that includes multiple objects. Up until now, the ViewSets and the routers you’ve seen have been object-specific.

00:21 If you were going to be creating a single-page web application, you might want one REST call that includes all of the objects that you want back. So it would contain a dictionary and the dictionary would then have keys for each type of object that’s coming back in your page.

00:38 The ViewSets methods that I’ve shown you up until now have been object-specific, so that technique won’t work in this case. Instead, I’m going to start by showing you how to declare a view and nest those multiple serializers inside of it. New lesson, new app.

00:55 This time, I’m going to create a new app called api. As always with a new app, you need to install it in the INSTALLED_APPS listing, add the path for the app into the urls file, and just to throw you a curve ball this time, I’m going to add a little wrinkle.

01:12 It’s a good idea to include a version number in your API path. This allows you to do backwardly-incompatible changes with your API by declaring a new version number, while maintaining backward compatibility while someone hits the old URLs.

01:28 An easy way of doing this is inside of the path() declaration inside of your urls file. I’ll show you what I mean in a moment. As before, if you’re coding along with me, the changes to the views and urls files will need to be done in conjunction. For the moment, I’m going to be using the data that’s already in the database, so you shouldn’t need to do any migrations this time. Here’s the the new view that I’m going to include inside of my api app.

01:56 Let me just scroll down.

01:59 The view starts on line 12 with the @api_view decorator indicating that this is going to be a GET view. This view is going to include two different types of information, both the doctors in the Person objects and the vehicles. For a little variety, this time I’m changing the queryset that’s being serialized to be just the doctors, so I’m filtering on anyone with the title="Dr.". And one other complication arises here as well. I hinted at this in the previous lesson.

02:30 You may recall that the VehicleSerializer uses the url field. In order to properly populate the url field, the serializer needs to be aware of the request. When you use a ViewSet, the ViewSet does this automatically for you. Because I’m not using a ViewSet here, I need to pass extra information into the VehicleSerializer so that it can see the request object. This is done through the context parameter.

02:58 The context parameter is similar to the context parameter that you would use in a Django template. It’s a dictionary containing, in this case, just the "request" field.

03:08 So, the serializer includes the vehicles being serialized. Because it’s multiple vehicles, many=True, as well as the context that includes the request object. The serializer then uses the request object out of the context to construct the host name inside of the URL so that you can get a fully qualified URL inside of the serialized payload. On line 23, I’m using the PersonSerializer to serialize the doctors queryset.

03:38 Notice that what goes in the dictionary is actually the .data attribute, so I have a shortcut here. Rather than creating a object called the serializer and then calling .data on the object, I’m just doing it directly on the constructor in one line. Then on line 24, I serialize the vehicles, like you’ve seen me do before, and return all of this inside of a REST Response object.

04:04 Inside the api app, I create an urls file and I register the listing that I just created. Notice that I’ve named the path "v1/listing/".

04:14 This is me versioning the API. In the future, if I were going to create a new backwardly incompatible listing/, I could continue to use the old code under v1/ and create a new path under v2/.

04:28 This allows me to deprecate an old API without removing it yet, maintaining backward compatibility for my users.

04:38 In a window offscreen, I’m running the Django development server, and here I’m going to hit it with curl. Version 1 of the api, piping it through the json pretty printer,

04:51 and there’s the results. Let me just scroll this back up so you can see everything. The return is a dictionary containing the field "doctors" and the field "vehicles".

05:02 The "doctors" field contains an array with all of the doctors, and the "vehicles" field contains an array with all of the vehicles. Scrolling back down, as before, the "vehicles" includes the nested "part_set" inside of it.

05:17 And notice the URLs being used in both the vehicle and part objects.

05:23 Because I used a view instead of a ViewSet that time, I’m not able to register the view with a router. As a result, you don’t get the meta listing of all of the possible views in the root path.

05:37 But what if I wanted it? Well, you can do anything with a ViewSet that you could do with a view, so this is possible.

05:46 Here’s a new ViewSet that I’ve added to the views file. I’m not doing anything here that you haven’t seen before. I’m just doing it in a slightly different format.

05:55 The declaration of the class of the DoctorsViewSet inherits from two classes. The first class is the GenericViewSet. Because this is a ViewSet, that kind of makes sense.

06:07 The second is a mixin. This mixin indicates which actions, and therefore which HTTP methods, will be supported by this ViewSet. In this particular case, all I’m including is the list.

06:21 So this ViewSet will only support listing objects. For each mixin that you include, you will have a corresponding method. For the ListModelMixin, I have the .list() method. Inside this .list() method, I’m not doing anything you haven’t seen before.

06:36 I’m serializing the doctors, putting those inside of results, and returning a REST Response.

06:45 Here’s the new api/urls file. Since I’m now using a ViewSet instead of a view, I use the router to register it, and I include all the URLs provided by the router—in this case, this will only be one—under the "v1/" path listing, giving me version numbers for my API.

07:06 Once again hitting the dev server with curl and pretty printing it,

07:13 and there’s the result. I’ll miss you, Sir Connery.

07:18 The DRF provides an entire hierarchy of ViewSet classes for your use. There’s the base ViewSet class, the GenericViewSet class that I just showed you—which adds the .queryset attribute, the .get_object() and .get_queryset() methods—the ModelViewSet that you’ve been using in the lessons up until now, and there’s also a ReadOnlyModelViewSet that is a ModelViewSet but doesn’t allow changes to it. Several lessons ago when I introduced ViewSets, I showed you this base class. It’s probably worth reviewing now. The base ViewSet declares six methods that correspond to the actions that are being performed in REST. .list() lists objects. .create() is for a POST, creating an object. .retrieve() is to get a specific object. .update() and .partial_update() are for updating existing objects. And .destroy() is for deleting an object.

08:19 Each of these methods is declared inside of a mixin. It’s done this way so you can mix and match the mixins and create a ViewSet with only those methods that you wish to support. The mixins are CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixinwhich provides both .update() and .partial_update() methods—and DestroyModelMixin.

08:42 Essentially, there’s a mixin for each one of those methods I just showed you in the base class. Additionally, if there’s something that you want to do that isn’t covered by one of those actions, you can declare your own.

08:55 You do this by adding a method to a ViewSet subclass and then decorating it, indicating that it’s an action. One of the more common purposes for this is mass operations.

09:06 REST, by default, doesn’t support the change of more than one object at a time. For example, if you wanted to be able to delete multiple objects, you would have to make multiple calls. Instead, you can create an action which is .mass_delete(). Let me show you how to do that right now.

09:26 Here’s my mass delete ViewSet that I’m adding to the views file. First off, I have to inherit from GenericViewSet so that this is a ViewSet.

09:36 Secondly, this ViewSet is only going to support deleting objects, so I’m going to use the DestroyModelMixin. Inside of the class, I’m creating a method called .mass_delete(), and I tell DRF that I want this to be an action available in the REST API by decorating it with the @action decorator.

09:55 I’m passing two arguments to the decorator. The first is detail=False. This tells the DRF that the URL to be used with this action does not include an ID. Because I’m going to be deleting multiple objects, I just want a regular URL—I don’t want /3 on the end of it.

10:14 Secondly, I’m passing in a list of the HTTP methods that I’m supporting in this action. In this case, it’s only "delete". Because I’m deleting more than one thing, I somehow have to get multiple IDs up to the server.

10:30 There’s different ways of handling this. I could use GET with query parameters, or, in this case, what I’m showing you is using a POST with a string inside of it that is comma-separated. When this action is called, I will process the POST field named "ids" and split it on any commas (,) inside of it.

10:50 Each value that is found is assumed to be the id of an Artifact that needs to be deleted. I fetch that Artifact and then call the .delete() method on it. Finally, because there’s no payload that I want to return, I just send back an empty Response object.

11:09 And here’s the updated urls file. You’ll need to register the MassDeleteArtifactsViewSet with your router, adding it to the already existing doctors registration.

11:23 Let me run a query just to show you what data is available for the artifacts before I delete anything.

11:31 And there are my two artifacts. And now I will call .mass_delete(). I specify -X DELETE so that I use the HTTP DELETE method with curl, and the -d parameter shows the POST fields that I’m going to be sending in of "1,2". Notice that the URL is a little weird, that you’ve got the mass_delete/ twice.

11:54 This has to do with how the router constructs URL names. It’s based on both the class name and the method that is attached to it. There are things you can do to modify this, but you kind of have to jump through hoops. It’s easier just to accept it as it is. Finally, to show you that this worked, I’ll run the GET again.

12:17 And object 1 and 2 have been deleted, so there’s nothing left.

12:23 Next up, I’m going to leave Fedora behind and show you a new sample application that’s almost a single-page application to show you how this works in the real world.

Become a Member to join the conversation.