Introduction and Orientatiation
Published October 20, 2021
This video features Drew Winstel at DjangoCon US 2018 in San Diego, California, USA.
DjangoCon US 2018 - Django REST Framework: Moving Past the Tutorial to Production by Drew Winstel
So you’ve made your first attempt at creating a DRF API, but now you need to figure out how to put the hair on the proverbial pony. You want to make things easier on your client developers so they can get exactly what they need. I’ll walk through things that made our lives better developing a Django REST Framework API serving a React frontend.
I’ll include optimizations such as embedding related fields into serializers, using different serializers for different users and use cases (HTTP methods), and using DRF’s actions decorator to provide easy access to related tasks. I’ll also touch on some third-party libraries that made life way easier, such as rest-framework-filters, django-rest-swagger, and django-simple-history.
This talk was presented at: https://2018.djangocon.us/talk/django-rest-framework-moving-past-the-to/
LINKS:
Follow Drew Winstel 👇
On Twitter: https://twitter.com/hops_and_smoke
Official homepage: https://github.com/drewbrew/
Follow DjangCon US 👇
https://twitter.com/djangocon
Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/
Moving a Django REST Framework API from a tutorial to production requires treating client needs, performance, consistency, and documentation as design concerns. Drew Winstel explains how to represent related data with nested serializers, writable fields, or custom relationship fields; avoid excessive queries with separate list/detail serializers, `select_related`, `prefetch_related`, and query-count tests; tailor serializers and querysets by action or user permissions; and use viewset actions for convenient domain-specific endpoints. He also covers nested filtering with `django-rest-framework-filters`, the risk of exposing data through filters, and documentation options including the browsable API, Swagger, generated schemas, and MkDocs, with a few additional recommendations for audit history, Markdown content, and country fields.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: Thank you, Russell. Morning everyone, and welcome to my first ever conference talk. Today, I'll be talking about moving uh Django REST framework from the tutorial to production. Django Rest framework, it's a great piece of software, but it's a bit of a mouthful. So I'm just gonna say DRF from here on out. So why are we here today? What's the goal behind the presentation? Are you here just to see me do a song and dance? Yes. Too bad you're all in the wrong room. You've made your first stab at creating a DRF API, but what's next? You want to make things easier on your client developers so they can get actually the data they want. I'm here to show you the things that helped me and my team move from the DRF tutorial into production while converting a homegrown PHP stack into a DRF and React excuse me, a DRF into React web
Speaker 1: app. I'm not here to authoritatively say this is the only way to get things done, but it worked for me. So by now you've probably seen jokes like this on the web before. How to draw a horse. Step one, draw two circles. Step two, draw the legs. Step three, draw the face. And step four, draw the hair. Step five, add small details. Well, today I'm gonna help you put some of those small details into your project to help you move towards production. So a little bit of assumptions. I'm assuming that you at least have a basic familiarity with DRF terminology. And um you've at least touched Django filter and how it works with DRF. If you were at uh Phil's API driven to uh API-driven Django tutorial on Sunday, you'll be fine.
Speaker 1: So uh here's a quick overview about what I'll be talking today. I'll introduce myself, talk about making the APIs nice for end users, and talk about a couple of useful libraries for documentation. Before I get started though, a couple quick notes. If you have questions, but you see you're a little camera shy or you don't want your voice recorded, that's fine. I understand that feeling entirely. You can either use that link up at the top of the slide or um send a Slack DM or Twitter message to Russ, our conference chair. He is at Freakboy3742. That's freakboy3742 on both Twitter and Slack, and he'll be happy to ask for you. And I also just tweeted out that link a few minutes ago. You can find me on Twitter at HopsandSmoke. Like any good Python developer, that's in Snake Case.
Speaker 1: So here's a rough overview of how I got to where I am today in front of you. I've had about four years of DRF experience starting way back when South migrations were still a thing. It wasn't built into Django yet and uh kept going all the way through to Django 2. 0. My degree is in Wireless and Electrical Engineering from Auburn, Waragle, in uh 2008. I've done a mix of defense and Internet of Things work and before since then, before coming to Rackspace, where I've been since June of this year, where I'm developing REST APIs using Flask and MongoDB. And I've got code up on GitHub and GitLab already. Uh you can find me there and uh take a look at the example code. Sorry, read it and hopefully learn from it. No guarantees. And as I mentioned, I'm on Twitter, also on Mastodon as well.
Speaker 1: So a quick recap. Serializers. These are probably the most powerful thing in DRF. It's wonderful. They're responsible for converting your data between the Django instances you know and love, and then formats which can easily be uh transferred over the web like JSON, XML. Or you can write your own renderers if you really feel like doing something unusual. They delegate that responsibility to the individual fields, which are defined in the s in your serializer classes. They use the two internal value and two representation methods to convert to and from serializable types, respectively. On the left, it's a JSON submitted from a client. It's been converted to a Python dictionary by the viewset. It has a date and a couple of decimal objects. Uh quick tip if you're using uh coordinates in your uh methods in your models but don't actually have Geo Django installed, make sure you save them as decimal fields.
Speaker 1: Otherwise you'll lose data to rounding errors. My uh the previous database found that out the hard way. And um also JSON does not have a decimal object, which is why you see them as strings in there. And then um this is with the results from the validated data pro validated data property of the serializer. As you can see, the serializer has converted the date into a datetime. date object and the two decimal strings into uh Python decimal objects. Then the serializers use either the create or update methods to save those fields into the database using your Django models. So two internal value, like I mentioned, takes serializable types like um numbers and strings and turns them into Python types like date times, decimal.
Speaker 1: You name it, you can build as a translator for it. And then there's two representation, which does the same thing only in reverse. Then DRF serializers save that data using create and update. Typically, you won't have to modify these unless you're modifying multiple models in the same serializer, which I'm going to demonstrate a little bit later. The view sets are extremely powerful combinations of generic classes that uh provide general-purpose methods for basic API access. You get auth, access control, query set generation, serializer selection, and filtering, all just by providing class attributes like these. If your first exposure to DRF was Phil's tutorial on Sunday, just combine the list and detailed view generic classes that you use during that tutorial, and that gives you the concept behind a view set.
Speaker 1: It also includes very simple uh create, retrieve, update, and destroy actions that are known as CRUD that will serve the majority of your needs really well. So quick re so let's talk about what it does in detail. After authenticating the user, the viewset's first task is to call each permission class's hasPermission method. If any of these methods don't return true, the request stops and returns a 403 forbidden. Next up, the viewset will refresh the query set by calling dot all on this attribute. If it's looking for a specific object, like um you know if we're doing an update or a retrieve action, then the viewset will also call each permission class as has object permission on that up with that object as an argument. That's another chance for a 403 forbidden to fallout. In the case of list actions, the viewset will then pass the query through the filter
Speaker 1: each filter backend you specified, which modifies the query set appropriately. If you if like me you're using the Django filter backend, which you probably will be, um it c the Django filter backend looks up the filter class attribute and uses that to modify the query set. Next up, the viewset passes the query set result into the serializer class, which converts from complex data types into easily serializable types, which you'll remember they're things like strings, numbers, booleans, and null. And if you're using a list route, it'll feed the paginet pagination class into the serializer, so it only has to serialize a subset of the query set and assuming you have more results than your pagination allows. In this talk, I'm going to use some horribly, horribly contrived Fed Clinic examples.
Speaker 1: This has nothing to do with the market that our app was working in. It's just a convenient excuse for animal pictures. So please enjoy. This is sassy. She was my grandparents half black lab, half rottweiler. She wouldn't ever be caught dead without a toy in her hands or in her mouth at any time. She was probably the sweetest dog you could ever could have met. So what do I mean by making the API client-friendly? It's about knowing your users. If you're working with end users accessing your data via web or a mobile app, speed is usually far more of a concern than just if you're dealing with automated services, where the extra second delay for getting all the data at once It is worth a while. But um for mobile apps especially, speed is so important that an extra delay of say a second while fetching data is the difference between a four-star and two-star rating in the app stores.
Speaker 1: Nobody wants a bad rating. So, talk about related fields. There's a problem you'll always run into when dealing with information transfer. Your model of the data never matches what your users m uh how your users see the data. It's not a bad thing. Just a fact of life. Your users care about different things than you do. For example, in a vet clinic, your user only cares that Fido is a black lab, not that he is breed ID forty-two. How do we simultaneously give the user the information she needs Black Lab, and the information she needs in case she needs to update things later. Breed ID42. We use nested serializers. So here's a trivial example of how you embed those into the serializer. You can just call the serializer and then DRF will take care of the rest for get
Speaker 1: operations. But wait, remember how I only said get requests? DRF documentation specifically says they don't provide an implementation for saving serializers with writable nested fields. So how do we work around that? By the way, that is my dog Henry. He's a half corgi, half something. He was uh badly abused as a puppy, um left outside in the uh during those terrific tornadoes that came through Alabama in 2011. Um horribly afraid of people. It took almost a year before I could pet him. But with my three-year-old daughter, he's like her best friend ever, so it's kind of fun. Of course, she drops food all the time, so that makes it easier. Now you're not gonna like this part. There is no single right answer for
Speaker 1: all use cases. You've got three really viable options for handling updated related data. You can either override the create and update methods in the serializer that host the relationship, such as the breed serializer hosting the species relationship, or you could create a separate write-only field like species ID. Or last, you could create a separate field entirely to describe the relationship where you actually build a field class. If you choose option one, overriding create an update, you use this if your most lik users are most likely to create new data rather than updating existing data. How does you have a few questions to answer though? Number one, how does the user create data? The easy answer, you post a dictionary without a primary key. Should the next question is, should the user be allowed to update existing data if the data in that related dictionary does not match what's in the database for the object?
Speaker 1: You can say yes, you can say no, either way is fine. Just make sure you document it and be extremely firm and consistent with your decision. So don't have one endpoint where you can update things and one where you can't. That will just confuse your users and make everyone miserable. So the create method is basic for relatively simple. You have to pull out the related field and look up the source data. Let's dig in. The first thing we do is we pull out the related object, which will be a Python dictionary. You don't have to worry about the related object being missing because the DRF validation will return a 400 bad request before this code even runs. if it's missing because we said required equal because we did not let specify it as an optional field. Next, we've got to handle that related object. If it's a new object, we need to create it.
Speaker 1: If you have nested fields inside those nested fields, first of all, I'm sorry. Second of all, you have to go into that serializer and handle those related fields. You can call that serializers create method where I just call uh species. objects. create. You don't have note also to note that you don't have to manually validate those serialize-related object. DRF took care of that for you already. You also have a big design decision to make here. What happens if the user provides the related the the data the user provides for the related object does not match what's already in the database? You can either reject the request giving a 400-bad request, or you can implicitly update the related data. Both options have their pros and cons. Just make sure you document your design decision extremely well. Also, if you're a fan of functional programming, you'll hate the side effect late option late
Speaker 1: the side effect latent option of you updating the related models. And you're probably cringing right now. And then lastly , we have to uh just drop that Django model instance with back into the validated data dictionary and pass it to the DRF-base implementation where that'll take care of saving everything. The update method is pretty much the same as create, but we have to consider what edge one case where the user does a patch where you don't have to include the entire body of the object in the in your when you're submitting the request. So just uh pop it out first and use a data that use a value that's completely invalid if it's not present. Like I used false there for example. And then um if it's actually that if that it is that no that bogus value, just turn it return it upstream, let upstream do its thing.
Speaker 1: It's easier that way than writing it yourself. Other than that, it is exactly the same as update. Now the option two is creating a separate write-only field. This requires firm agreement with your API clients that what you receive via a GET as a user is not what you post or put. And that's a convention I've seen in a lot of APIs where you could take the result of a get and put that into a put, and it will just work and be a completely null operation, but it does, but it is a valid operation. This breaks that convention. It's not a huge deal. You just have to document it thoroughly and um g show good examples in the documentation. And then if your users complain about it, point to the docs and say, hey, you didn't read them. That's on you.
Speaker 1: It's not the end of the world, just a caveat you have to be aware of. And then you'll also have to write a very small validate method, which I'll show in a moment. In this option, it's pretty simple. First, you define two separate fields. The read-only um expanded serializer, just like in the previous, just like in the read-only version. And then secondly, you use a write-only primary key-related field. This field's two internal value method looks up the object using the given query given query set, also does validation, and then returns an instance of the model being looked up. Remember that I said it returns an instance of the related model? That's important. Because if you try and save it as is, the Django ORM will barf at you because you're trying to save an instance to a field, the under the ID field, where Django is expecting a primary key, like an integer. or EU
Speaker 1: ID. Working around that is very simple. All you have to do is just move the species ID into the species key in the dictionary. After that, DRF takes care of everything for us. Sorry. This is the option that we chose to use in our API that we pre-deployed to production because our primary front end uh was an in-house web developer. So your mileage may vary. And then one thing I want to point out that may not be readable in the back, um, you even though we set species ID is required in the serializer, it is possible for that to be missing in our body in the case of patches again. So all that means we have to do is handle the case where it didn't provide the species ID by just doing a try and catch on it. You'll be fine ignoring this error and moving on unless you really like making your users miserable. In which case, why are you developing APIs?
Speaker 1: Shouldn't you be forcing them to write to HTML scrapers instead? So creating a separate relationship field, the third option, is nice because it doesn't require clients to use a separate field for sending versus receiving of data, but it has its own trade-offs. You can accept a primary key or a dictionary with a data type coming in. But if you accept a primary key, you prevent the user from creating new data. Or if you do accept a dictionary, you have to handle the same question as an overriding update and create. If you get a primary key of existing instances, do you update that? Do you create a new object or do you return 400 bad requests? And you can create a field that does both and just use an if statement to switch back and forth. But again, you have to be clear with your users about what'll happen. Okay, that's all great, but
Speaker 1: this means you have to do extra database lookups, right? As you probably know, you need to use select related and or prefetch related to look up extra data. However, it might not make up set make sense to dump everything when you're looking at a list route, particularly when you're dealing with lots of data. Like I had one endpoint that returned probably a hundred fields over F by the time it was done expanding everything. That would take 10 seconds to return 200 entries in a list route list route. How do we deal with this? We use separate serializers for list and detail routes and queries to match. I saw these penguins at the um Lincoln Park Zoo last month in Chicago. It's a night little zoo. It's a little on the small side, but it has the upside of being free to end Which is great when you're going with five kids. Well, yes and no. For a single related object, you know there's select
Speaker 1: related. It's um pretty much almost free. The only cost is the SQL join. It's um much faster than doing a second database lookup unless your tables are horribly misconfigured. In which case you may need to go talk to the Postgres people out there that might be able to help you. I can't. If you're traversing a many minute many relationship or looking across a reverse foreign key lookup, then you need to use prefetch related to look up the data. This causes an extra database lookup and then makes Django do the merging of data in Python land. If that sounds slow to you, you're right. But it is way faster than not using prefetch related. In which case, Django does one database hit per record returned to the main query. That's what's referred to as the N plus one problem, the bane of many developers. Prefetch objects are absolutely wonderful.
Speaker 1: They let you filter the related model lookup and also run select related as part of the prefetch, which could save you an extra database hit if you do it right. But be careful with using prefetch related and make sure you cover every related field lookup. If you don't, things will get hairy quickly. Now what do I mean by that? Thanks to uh Jeff for letting me use him as an example here. I've made this mistake many times as well. I just didn't have the foresight to tweet about it. I don't have time to cover it today, but I highly, highly, highly recommend using test to count the number of queries used in a particular API test. Django's test case has the method to count the number of queries run. It's easy. Just a good way to make sure you don't accidentally trigger an N plus one problem when you Modify a view. So next up we'll talk about using different serializers for different actions.
Speaker 1: Now you've probably seen this pattern before where you have get serializer class looking at the action of the request. If it's a detail aut route, you do uh you simply return the my serial the detail serializer. Otherwise return the list serializer. This is in the view set. But, you know, it's simple and obvious, but what if I told you DRF provides a way to differentiate between list and serializer classes with one line of code? This was during a road trip last year where um the dogs objected to being left in the car while we went inside to take the kid to the bathroom. So they jumped they jumped over the back seat and both of them somehow fit in my kid's car seat. It was very fun getting them back over willingly. DRF provides a very handy list
Speaker 1: serializer class attribute in the meta class. How does it work though? You use the attribute in your detail serializer to point to your list serializer class Here's how it works. When the viewset initializes a serializer instance with many equals true as an argument, the serializer will actually switch out the instance created and replace it with the class defined by that list serializer class attribute. Now wait, you may be saying, aren't you supposed to get uh get an instance of the class you instantiate when you construct a class construct an instance of a class? Let's take a look at the DRF source code and see what happens. It uses a little bit of Python magic and overrides the double underscore new method to call a different init method entirely, which is too long to show here, but it ultimately returns an instance of that class's list
Speaker 1: serializer class if it's specified. It's pretty nifty, and it was a nice little um light bulb moment when I discovered this. Now here's the view set using a serializer that has the list serializer class defined. There are two things I want to point out here. When you're specifying the serializer class attribute, you want to use the detail serializer. It's a little bit counterintuitive, but DRF doesn't know how to go from the list serializer back to find the detail serializer because that relationship is only a one-way relationship. And then also because you have two different serial lights are showing different data, you should definitely override get query set to um return just the data you want and nothing else. And also, when you override get query set in the view set, this means you have control over what data is looked up for different methods.
Speaker 1: It's pretty easy, you know, just uh pick based on the uh method, the action being chosen and go from there. Now, in addition to changing what you do based on the HTTP action, you can also change based on who's looking at your API. If you have different classes of users that need different data, You can override getSerializer class and get query set to limit or expand data as needed. Here are the trivial serializers I'm using for this example. Nothing fancy, I'm just extending the animal detail serializer. to add an extra field that only matters to staff. Just a uh just an appointment serializer. There is, excuse me, a lot to go through here, so I'm gonna break it into a couple of chunks. I just wanted to show it all so you can get a quick glance as to how it interacts. So here is get serializer class
Speaker 1: only at a readable zoom level. It's uh pretty simple. Just uh check to see if a user has permission. Remember that you define those at the model level. And then if the user has the permission in question, you give them the expanded serializer. Otherwise you fall back through to the normal DRF implementation, which is just refreshing that dot all from the ser from the query set attribute. And then here is get query set. Sorry, that was get serializer class, not get query set, my bad. And then here is get query set, only slightly more readable. Now, you what do you do is you can look do the permission check and uh make whatever changes you want. I snipped those out here because otherwise it was way too small to read. And then you can do the same thing with them based on the action as well. And I'm leaving, you know, and then you can see the full list on my uh GitHub to see what
Speaker 1: was going on. And then next up, we will talk about view set actions. Which are additional HTTP endpoints you can define to default to relate it to a model or an instance. So when your user needs to take action on a model that's related to the one you care about, use an action to make your user's life easier. For instance, I probably don't want to pass a primary key when booking an appointment at the groomer when Ringo here decided to roll in Canadian goose poop for the third time in as many weeks. That was a lovely smell. This isn't the only way to use actions, but it uh definitely has been the most convenient for me. Here's a uh simple ex example of booking an appointment Using an action. The user does not have to specify the animal in the request body at all because it's already in the URL, thereby reducing the chance of error.
Speaker 1: You still have to write your own validation and access code. I'm not going to write that for you, you've got to do something here. And the code looks up the animal, passes it to a serializer, validates that serializer, saves a new instance, serializes the new instance, and then renders that response back to the user. The detail argument determines whether the action operates on a single instance or a list of instances. You can set detail equals false to perform an action on a list of instances. Why would you do this? A couple ideas. You can use it for pre-canned filter, such as looking up and getting all dogs who are overdue for their uh ser for their shots, or um alternative output formats like spreadsheets or PDFs. Like say you've got a manager who demands. Everything be in Excel format, even if your web tables are much easier to use. That's one way you can do this. OpenPy Excel
Speaker 1: is quite ha quite useful for that, by the way. So the other act so the action decorator also takes a couple other very useful arguments. Methods is just a list of strings, you know, get, put, patch, delete, dot dot dot. If you do not use that method methods argument, the default is just get and get only. And then permission classes is a list of classes that you can um, you know, that will be applied just to that uh particular uh action. However, the larger view set permission classes are also enforced. So if you have say an endpoint that is accessible to people who don't have access to the larger outer endpoint, you'll need to put in a code to let that um single endpoint fall through the permission classes. So we've uh so far we only cover presenting data.
Speaker 1: How do we help users find the right data, like helping Celine here find the right perch to sleep on while trying to stay out of reach of Ringo and Henry? This was actually taken by my wife last night. She was sewing her Halloween costume and Celine decided to help by um pulling the pins out with her teeth. Yeah, she's uh about two years old and um very fluffy, very lovey, but also very obnoxious. So a cat. So we'll talk about filtering. Writing filters for each model each model gets uh tedious rather quickly. It also doesn't easily handle looking up attributes based on the related objects like Searching for a species name of dog while you're looking at the animal view set, because it has to go through breed to get there. Sh this is Sherlock.
Speaker 1: He wasn't my cat, but he belonged to one of my wife's best friends. He's probably the most stereotypical cat possible. When it came to sitting in boxes. If there was an open box anywhere, he was in there within 30 seconds, no matter how small the box may have been compared to his body. Rest framework filters is a very handy library for extending Django filter to make it even more powerful. Its headlighting feature is the ability to nest filter sets, allowing you to traverse related models in your query parameters, like that little filter expression right there. That way, you can wire your species filters into your breed filters, meaning you don't have to write a second filter to look up only dogs, which are two levels deep in this example. Now there is a risk of information disclosure when you're listening like this. Read the docs very carefully and make sure you know what you're doing. Now here is a quick code example of how to implement REST framework filter.
Speaker 1: filters in the view layer. It is literally a drop-in replacement for Django filters. It's bas it's all codes based on Django filter. So you just swap out how you're importing it and then the um everything else is exactly the same. The base uh filter set class is actually a subclass of Jenka filters version 2. So here's the uh target filter, which is just a trivially simple Species filters. Those constants I defined earlier up further up in the class, you can see them on GitHub. It's just, you know, for like numeric is just equals, not equal, um, is null greater than, less than, etc. Pretty simple stuff. And then next, you will um just use that related filter class to tell Django where to look, and the rest is almost magic. Remember what I said about information disclosure risk? That is in the query set
Speaker 1: breed filter that I showed in a couple slides back. You have to be very careful about what you expose in your filters, especially to untrusted users. A clever adversary can use well-built filter expressions to determine the instance of the existence of objects they wouldn't have access to, like, say, unpublished drafts in a blog app. I took this uh the next last but not least, the uh most important thing, um documentation. I took this polar bear picture at the Cincinnati Zoo back in 2008. If you've never been there, I highly recommend it. It's probably the second best zoo I've been to behind the one right here in San Diego. And if you're looking to get a trip together to go to the zoo while you're here, I think Andrew Carl's organizing one for tomorrow. So you may want to check with me. There are many ways you can present documentation. There are far too many for me to even list here.
Speaker 1: But uh the built-in browsible API, it's a Spartan but functional, it works. You can make requests, fill in form data, make test requests with relative ease. You can use a Jenko RustSwagger to provide an easy-to-use playground for users to test out requests and provide slightly better formatting than the browsable UI provides. It also lets you show what method exactly what methods you can use with a given endpoint all in one large very long list. Now DRF 3. 7 did add a schema-based documentation generation that mostly renders um Jenkins Restwagger obsolete. However, it requires you to manually update the schema uh before it can read from it. So you actually have manually run a command from the command line. Just integrate that in your tool. and you're done. Now if you're a fan of readthhecs. io, you can use Make Docs, which is actually included with the template, the cookie cutter template I bait built this example code product on.
Speaker 1: If you're starting a new project, CS definitely use it. It's called cookie cutter dash Django dash rest on GitHub. Definitely use it. It doesn't quite work with pipend yet, although you can use my code base to um that that is modified to work with pipend if that's your style. So here is REST Framework Swagger. It's a nice way to generate your standard swagger UIs using your view sets and the filter sets they reference. It requires basically dev work aside from making them write good doc strings. In the view sets themselves. Now you are enforcing good doc strings in your pull request, right? Me neither. Here's a screenshot of my example code using REST Framework Swagger. Each API action is expandable, letting you play with filtering options and posting data where appropriate. Think of it as the built-in browsable API on steroids.
Speaker 1: Also, quick tip: if you have API resources you don't want visible to users, like say they're for internal use only, you just you put the attribute exclude from schema and set that to true in your view set, and that will hide it from this documentation entirely. So here's a quick ex you know, clicking on one of those particular endpoints, and you can see what Rest Framework Swagger offers. Gives you almost all the things you would normally use Postman for. And uh one thing to note, it does not seem to discover auto does to automatically discover nested filters well, like from Rust Framework filters. Not the end of the world, just slightly disappointing. And then there's also make docs, which I as I mentioned came pre-install pre-installed with that cookie cutter template. It's pre-configured, runs in a separate Docker container inside that template, very easy. And uh this is what the homepage looks like. It's just your README
Speaker 1: formatted very, very nicely. And the template also comes with um auth and uh the user API pre A preconfigured. Now Make Docs requires you to write all of your docs in Markdown, which Which is wonderful. And it's nice that you get the classic control over what goes where, but it also requires you to do all the work manually. If you have lazy developers like me, that might be troublesome. So let's see, I've got a few minutes, a few minutes extra time, so I'll talk about a couple of other useful libraries we had. Django 's Simple History was written originally by Trey Hunter, who's actually talking next in this room. It's a great little audit tool for tracking when users make changes. So if your user comes back to you and says, hey, where'd my dog go? And you can look at the history for that dog and say, you deleted that.
Speaker 1: That's on you. And then a Django markup field is great if you say you want to be able to let your users create craft like announcements or general purpose messages that um would be sent up to the users, but you don't want to go through the trouble of putting in a full CMS. They can just you know use markdown to put in their message and then you can just read that from the API endpoint. It gives you both the raw markdown and HTML formatted output. And then Django countries, if you've ever had to deal with addresses, you know that countries are a pain. For example, is England a country? Depends on who you ask. Like for soccer? Yes. For the Olympics, no. Okay, so go ahead and wrap it up. Um talking about I talked about making your API user-friendly with related fields, list in detail serializers, and actions.
Speaker 1: I also talked about improving filtering. You can use REST framework filter to um get a little bit extra niceties. And then talk about documentation. And I've got example code over there on GitHub. Feel free to take a look and the link to the slides is there as well. I'd like to take a moment to give special thanks to uh Lacey, Anna, and Jeff for reviewing. Doing my talk and pre my talk and my proposal. This would not have gotten anywhere near this way without their help. And then also thank my wife, Donnie, for putting up with me going to San Diego without her. All right, that's it. So
Speaker 2: Django REST framework exists. It's a wonderfully flexible framework. There is also GraphQL out there.
Speaker 1: Yes.
Speaker 2: Are you able to comment on why you would use one or the other other than Buzzword compliance?
Speaker 1: Buzzword compliance is exactly correct. But realistically, I mean I don't have enough good I do not have enough experience with GraphQL to provide an educated answer on that one. Alright, then I'll just there's no other questions. I'll just show a couple extra pictures.
Speaker 2: Which one is your favorite dog?
Speaker 1: Definitely Henry.
Speaker 3: Thank you. Excellent talk by the way. Thank you. Um my question is, have you have you has your has your company done any work with using uh binary serializers with DRF?
Speaker 1: Uh we have not. Um we've been pretty much entirely fortunate enough that we've been able to use um JSON for everything. They haven't really had a format that anyway need something for binary for.
Speaker 4: Do you have any recommended patterns for testing serializers?
Speaker 1: Yeah, I'd like to, yeah. The um I what I typically do will um Just you know, give a feed it in a t um actually Phil's example code uh from his uh tutorial on Sunday has a great example of just testing the serial You basically feed a dictionary in and then that could walk through the validation steps on it and then make sure that the returned um validated data matches what you'd expect.
Speaker 2: All right. That being the case, uh, thank you again, Drew, for the wonderful presentation.
Speaker 1: Thank you so much.
DRF does not provide a built-in implementation for saving writable nested fields. The speaker recommends choosing among overriding the parent serializer’s create/update methods, using a separate write-only primary-key field, or defining a custom relationship field—and documenting the behavior consistently.
Discussed at 9:31Define a detail serializer with a `list_serializer_class` in its metadata, then use the detail serializer as the viewset’s serializer class. Override `get_queryset` as well so each action retrieves only the data its serializer needs.
Discussed at 18:48Override `get_serializer_class` to select an expanded or restricted serializer based on the user’s permissions, and override `get_queryset` to limit or expand the underlying data accordingly. The same methods can also vary behavior by action.
Discussed at 20:22Use the `@action` decorator for model- or instance-related endpoints, such as booking an appointment without requiring the client to repeat an object ID in the request body. Set `detail` to control whether the action targets one instance or a collection, and specify methods and per-action permissions as needed.
Discussed at 21:57The browsable API is a basic but functional option, while REST Framework Swagger provides an interactive Swagger UI for endpoints, parameters, and request data. Schema-based documentation in newer DRF versions and Make Docs with Markdown are additional choices, each requiring different levels of manual maintenance.
Discussed at 27:20Feed a dictionary into the serializer, run its validation, and verify that the resulting `validated_data` matches the expected Python values. The speaker points to the tutorial example as a useful pattern for these tests.
Discussed at 32:40Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026