Skip to content
Tutorial Step 3 of 7

Add relations

In this step you add a Category model, link each article to a category and return the category inside the article.

Add the Category model

blog/models.py
class Category(ModelSerializer):
    name = models.CharField(max_length=100, unique=True)

    class Schemas:
        create = SchemaConfig(fields=["name"])
        read = SchemaConfig(fields=["id", "name"])

    def __str__(self):
        return self.name

Then add a foreign key to Article and list it in the schemas:

blog/models.py
class Article(ModelSerializer):
    # other fields as before
    category = models.ForeignKey(
        Category, on_delete=models.PROTECT, related_name="articles"
    )

    class Schemas:
        create = SchemaConfig(
            fields=["title", "body", "category"],
            optionals=[("is_published", bool)],
        )
        update = SchemaConfig(
            optionals=[("title", str), ("body", str), ("is_published", bool), ("category", int)],
        )
        read = SchemaConfig(
            fields=["id", "title", "is_published", "created_at", "category"],
        )
        # detail as before, plus "category"

Run makemigrations and migrate, then register a viewset for categories:

blog/api.py
from .models import Article, Category


@api.viewset(model=Category)
class CategoryViewSet(APIViewSet):
    pass

To create an article, send the category's id as category_id:

Bash
curl -X POST localhost:8000/api/articles/ \
  -H "Content-Type: application/json" \
  -d '{"title": "Hello", "body": "...", "category_id": 1}'

To move an article to another category, send category in a PATCH:

Bash
curl -X PATCH localhost:8000/api/articles/1/ \
  -H "Content-Type: application/json" \
  -d '{"category": 2}'

An id that doesn't exist returns 404.

Because Category has a read schema, the article includes the whole category:

JSON
{
  "id": 1,
  "title": "Hello",
  "is_published": false,
  "created_at": "2026-09-26T10:00:00Z",
  "category": {"id": 1, "name": "Django"}
}

The framework loads the category in the same query, so listing many articles doesn't run one query per article.

Return only the id

List the relation in relations_as_id to return the id instead of the object:

Python
read = SchemaConfig(
    fields=["id", "title", "is_published", "created_at", "category"],
    relations_as_id=["category"],
)
JSON
{"id": 1, "title": "Hello", "is_published": false, "created_at": "...", "category": 1}

Show articles in a category

Reverse relations work the same way. Add articles, the related_name of the foreign key, to the category's read schema:

Python
class Category(ModelSerializer):
    ...

    class Schemas:
        create = SchemaConfig(fields=["name"])
        read = SchemaConfig(fields=["id", "name", "articles"])
JSON
{
  "id": 1,
  "name": "Django",
  "articles": [{"id": 1, "title": "Hello", "is_published": false, "created_at": "..."}]
}

Each nested article leaves out its category, so the response doesn't loop.

Tip

To create a category and its articles in one request, see nested writes.

Next: add authentication