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

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands 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 your subtitle preferences 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 refer to our video player troubleshooting guide for assistance.

Serialization Through Schema and ModelSchema Classes

The Schema class in Ninja defines how to group typed fields together into serializable objects. The ModelSchema class allows for the definition of a Schema based on an existing Django ORM object, minimizing the amount of code necessary to serialize your database objects.

00:00 In the previous lesson, I showed you how arguments are handled in Ninja. In this lesson, I’ll dive into serialization with Schema and ModelSchema classes.

00:11 So far, you’ve seen strings turned into JSON strings. Exciting it may be, but in the real world, you’re typically going to have to turn data into JSON objects.

00:21 This process is called serialization. Ninja is built on top of a serialization library called Pydantic. Pydantic uses the type annotation features of Python to describe objects, which it calls models.

00:37 This can be a bit confusing in the Django world, as Django already has something called a model. To get around this, Ninja renames Pydantic models as schemas. A Schema is a Python class used to describe some fields with type information.

00:54 This is very similar to Django ORM models. When an API needs to respond with some data, it uses a Schema to describe the payload.

01:07 To play with some data, I’ll need some data. So another lesson, another Django app. This time, I’ll be playing with dragon fire. The data in question will be a Person. The model for it is on the screen here.

01:20 It can also be found in the sample code if you want to just grab it. If you’re coding along with me, you’ll need to create a targaryen Django app, add the Person class in the models file, and of course, register all this stuff … so, add it to installed apps in your settings, register an API route, and of course, because you’re adding a model, you’ll need to do migrations as well.

01:42 Inside of my targaryen app, I’ve created schemas.py. Ninja doesn’t care where you create this, and if you’re only doing a little bit, sticking your schemas in models.py actually makes a little more sense. But for larger projects, you may want to organize your schemas together, like I’ve done here.

02:01 My first schema has nothing to do with the Person model. I’ll come back to those later. I’ve defined something called DragonOut.

02:08 This is a schema containing two fields: name and birth_year. The type annotations indicate what type of data will be in those two fields.

02:19 I named this DragonOut out of habit. Quite commonly in APIs, you’re going to need to have data coming in from a POST and going out from a GET.

02:29 Most of the time, this will be describing the same kind of object, but usually will contain different fields. Stuff going out has an ID field, whereas stuff you’re creating, something new, typically doesn’t have an ID field. One way of handling this is to use two different serialization classes, one for In and one for Out.

02:51 This is the pattern used in most of Ninja’s documentation, so I’ve stuck with it.

03:00 Inside of the targaryen API file, I’ve defined the dragons view. I tell the dragons view what I want to return by setting the response parameter in the GET decorator.

03:12 As this is going to return a list of DragonOut objects, the response type is set to list[DragonOut]. Inside of the view, I can create an instance of a DragonOut data object, or like I’ve done here, three of them.

03:27 I then return that list, and Ninja takes care the rest.

03:42 And taking a look at the curl output, you see the resulting JSON, a list with three serialized objects.

03:54 Since Ninja is a library for Django, the most likely thing you’re going to need to serialize is Django ORM models. As an ORM model already has type information inside of it, Ninja can do most of the work for you.

04:08 The ModelSchema class works a lot like a form object in Django. You inherit from the base class. Then in a subclass, you indicate what ORM model to serialize. In this case, I’m serializing the targaryen Person model.

04:24 The model_fields attribute indicates which of the fields from Person I’m going to include in the output. In addition to using fields from Person, you can also add new ones simply by declaring them.

04:37 I’ve declared the full_name attribute and then created a magical little method called .resolve_full_name(). Ninja automatically looks for static methods that start with resolve and will use them to populate a field. A resolve method takes an object as an argument, the thing being serialized, and then it returns the value of the field in question. Here, I’ve created full_name by combining the name and title of the Person object into a single string.

05:07 That’s why I didn’t include "name" or "title" in the model_fields list.

05:16 Now inside of the API endpoint, I declare that I’m returning a PersonOut as a response, then return a Person object. Ninja takes the Person ORM object, uses the PersonOut ModelSchema and serializes it all into JSON. Note that what I’ve done here isn’t the best coding practice.

05:37 If an ID is given that doesn’t correspond to a person, you’ll get a 500 error. Better practice is to kick out a 404, but I’ll show you that later. Let’s take a look at the call …

05:59 and there you go. One serialized Stormborn lady, which of course I had created in the database earlier using the admin.

06:10 If you want to take advantage of the serialization feature, but you’re not in a view, you can get at it directly from the ModelSchema object.

06:17 Let me show you an example copied from Django’s REPL. I got there by running the manage shell command, imported a Person, then import the corresponding schema.

06:28 Now I’ll query a Person from a previously loaded fixture, and then I use the .from_orm() method of the PersonOut object to turn my person into a data object. Here’s the result.

06:43 I can use the .dict() method to see the serialization as a Python dictionary or the .json() method to see it as a JSON string.

06:55 You’ve got all the pieces you need now to build a proper API. Next up, I’ll show you a typical CRUD use case where you create, read, update, and delete your data.

TomDevUK on April 28, 2023

Hi, I may be missing something very obvious but I’m struggling with nested schema in Django Ninja. Let’s consider 2 models:

class Product(models.Model):
    name = models.CharField(max_length=200)

and

class Category(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    category = models.CharField(max_length=5)
    expiry_date = models.DateField()

Ninja documentation shows only one nested example, applied to my models, it would look something like that:

class CategorySchema(ModelSchema):
    class Config:
        model = Category
        model_fields = ['category', 'expiry_date]

class ProductSchema(ModelSchema):
    category = CategorySchema = None
    class Config:
        model = Product
        model_fields = ['name']

The issue I have is that it only works if there is one unique category. I tried to change it to category = List[CategorySchema] but it doesn’t work. What am I missing? How to write a Schema that will return list of nested objects within an object?

Christopher Trudeau RP Team on April 28, 2023

Hi Tom,

I wish I knew an easy answer for you. I haven’t done this myself. I took a quick look through the docs and couldn’t find an example that matched yours. Ninja is built on top of Pydantic, so I took a look through theirs as well, and it implies it might be able to be done, but the examples provided are single nesting like you’ve seen.

A few things you might try:

  1. Add another layer of indirection: have a CategoryListingSchema which is built on a list of Categories.
  2. Check the Pydantic documentation in detail to see if their “list” types can solve your problem
  3. Post to the Ninja guys posing the question (django-ninja.rest-framework.com/help/). The one time I had a question they were pretty responsive.

Sorry I couldn’t be more help. If you figure it out, post back here in case others have the same question.

TomDevUK on April 28, 2023

Thank you Christopher. I will do some more research.

I have one following question if you don’t mind. Apart from nesting, is there any other obvious way to use multiple schemas in a single response? So, 200 would return both product ProductSchema and CategorySchema regardles if one is nested in the other?

Christopher Trudeau RP Team on April 29, 2023

Hi Tom,

Your sample code is using ModelSchemas, which are actually shortcuts so you don’t have to write as much code when your Schema maps to a Django Model nicely. There is also a more basic component called just a Schema (it’s been a while since I wrote this course I can’t recall whether I cover it or not).

With a regular Schema you can build whatever you like, including multiple model references. It just means more work. There is also a mechanism called a resolver function that allows you to write a function in a schema that dictates how a field is populated, so you can do some very fine grained control.

django-ninja.rest-framework.com/guides/response/

I’m a big fan of Ninja, it does the common stuff quickly and well. The Django REST Framework (DRF) is far more comprehensive though, so if you’re finding you’re hitting limitations of what you’re trying to accomplish, the DRF might be a better fit for you. Much more coding there though.

There is a course on the DRF as well, as long as you’re not tired of listening to me talk :)

realpython.com/courses/django-rest-framework/

TomDevUK on April 29, 2023

Chris, I’ve done your DRF first and I believe you directed me to Ninja which seems far better suited to my needs. Thanks for that!

The problem is now solved. Nesting a list of schemas requires just two steps. Correct declaration in Schema is, in my case: category: List[CategorySchema] = [] Second step is in the api function, I had to convert the queryset to a list of dicts with categories = [category.__dict__ for category in categories] before passing it to the response as otherwise I received value is not a valid list (type=type_error.list) error.

Christopher Trudeau RP Team on April 30, 2023

Hey Tom,

Glad it worked out. Post a code snippet if you’ve got a minute, would definitely be helpful to others trying to accomplish the same thing. …ct

Become a Member to join the conversation.