Skip to main content

Django Ninja AIO CRUD - Rest Framework

Project description

django-ninja-aio-crud

Async CRUD framework for Django Ninja
Automatic schema generation · Filtering · Pagination · Auth · M2M management

Tests Quality Gate Status codecov PyPI - Version PyPI - License Ruff Performance

Documentation · PyPI · Framework Comparison · Performance Benchmarks · Example Project · Issues


Features

Feature Description
🔒 Type Safety Generic classes Full IDE autocomplete and type checking with generic ModelUtil, Serializer, and APIViewSet
Meta-driven Serializer Dynamic schemas Generate CRUD schemas for existing Django models without changing base classes
Async CRUD ViewSets Full operations Create, list, retrieve, update, delete — all async
Auto Schemas Pydantic generation Automatic read/create/update schemas from ModelSerializer
Dynamic Query Params Runtime schemas Built with pydantic.create_model for flexible filtering
Per-method Auth Granular control auth, get_auth, post_auth, etc.
Async Pagination Customizable Fully async, pluggable pagination classes
M2M Relations Add/remove/list Endpoints via M2MRelationSchema with filtering support
Reverse Relations Nested serialization Automatic handling of reverse FK and M2M
Lifecycle Hooks Extensible before_save, after_save, custom_actions, on_delete, and more
Schema Validators Pydantic validators @field_validator and @model_validator on serializer classes
ORJSON Renderer Performance Built-in fast JSON rendering via NinjaAIO

Quick Start

Option A: Meta-driven Serializer (existing models)

Use this if you already have Django models and don't want to change their base class.

from ninja_aio.models import serializers
from ninja_aio.views import APIViewSet
from ninja_aio import NinjaAIO
from . import models

class BookSerializer(serializers.Serializer):
    class Meta:
        model = models.Book
        schema_in = serializers.SchemaModelConfig(fields=["title", "published"])
        schema_out = serializers.SchemaModelConfig(fields=["id", "title", "published"])
        schema_update = serializers.SchemaModelConfig(
            optionals=[("title", str), ("published", bool)]
        )

api = NinjaAIO()

@api.viewset(models.Book)
class BookViewSet(APIViewSet):
    serializer_class = BookSerializer

Option B: ModelSerializer (new projects)

Define models with built-in serialization for minimal boilerplate.

models.py

from django.db import models
from ninja_aio.models import ModelSerializer

class Book(ModelSerializer):
    title = models.CharField(max_length=120)
    published = models.BooleanField(default=True)

    class ReadSerializer:
        fields = ["id", "title", "published"]

    class CreateSerializer:
        fields = ["title", "published"]

    class UpdateSerializer:
        optionals = [("title", str), ("published", bool)]

views.py

from ninja_aio import NinjaAIO
from ninja_aio.views import APIViewSet
from .models import Book

api = NinjaAIO()

@api.viewset(Book)
class BookViewSet(APIViewSet):
    pass

Visit /docs — CRUD endpoints ready.


Query Filtering

@api.viewset(Book)
class BookViewSet(APIViewSet):
    query_params = {"published": (bool, None), "title": (str, None)}

    async def query_params_handler(self, queryset, filters):
        if filters.get("published") is not None:
            queryset = queryset.filter(published=filters["published"])
        if filters.get("title"):
            queryset = queryset.filter(title__icontains=filters["title"])
        return queryset
GET /book/?published=true&title=python

Many-to-Many Relations

from ninja_aio.schemas import M2MRelationSchema

class Tag(ModelSerializer):
    name = models.CharField(max_length=50)
    class ReadSerializer:
        fields = ["id", "name"]

class Article(ModelSerializer):
    title = models.CharField(max_length=120)
    tags = models.ManyToManyField(Tag, related_name="articles")
    class ReadSerializer:
        fields = ["id", "title", "tags"]

@api.viewset(Article)
class ArticleViewSet(APIViewSet):
    m2m_relations = [
        M2MRelationSchema(
            model=Tag,
            related_name="tags",
            filters={"name": (str, "")}
        )
    ]

    async def tags_query_params_handler(self, queryset, filters):
        n = filters.get("name")
        if n:
            queryset = queryset.filter(name__icontains=n)
        return queryset

Endpoints:

GET  /article/{pk}/tag?name=dev
POST /article/{pk}/tag/    body: {"add": [1, 2], "remove": [3]}

Authentication (JWT)

from ninja_aio.auth import AsyncJwtBearer
from joserfc import jwk

class JWTAuth(AsyncJwtBearer):
    jwt_public = jwk.RSAKey.import_key("-----BEGIN PUBLIC KEY----- ...")
    jwt_alg = "RS256"
    claims = {"sub": {"essential": True}}

    async def auth_handler(self, request):
        book_id = self.dcd.claims.get("sub")
        return await Book.objects.aget(id=book_id)

@api.viewset(Book)
class SecureBookViewSet(APIViewSet):
    auth = [JWTAuth()]
    get_auth = None  # list/retrieve remain public

Lifecycle Hooks

Available on every save/delete cycle:

Hook When
on_create_before_save Before first save
on_create_after_save After first save
before_save Before any save
after_save After any save
on_delete After deletion
custom_actions(payload) Create/update custom field logic
post_create() After create commit

Custom Endpoints

from ninja_aio.decorators import api_get

@api.viewset(Book)
class BookViewSet(APIViewSet):
    @api_get("/stats/")
    async def stats(self, request):
        total = await Book.objects.acount()
        return {"total": total}

Pagination

Default: PageNumberPagination. Override per ViewSet:

from ninja.pagination import PageNumberPagination

class LargePagination(PageNumberPagination):
    page_size = 50
    max_page_size = 200

@api.viewset(Book)
class BookViewSet(APIViewSet):
    pagination_class = LargePagination

Schema Validators

Add Pydantic @field_validator and @model_validator directly on serializer classes for input validation.

ModelSerializer

Declare validators on inner serializer classes:

from django.db import models
from pydantic import field_validator, model_validator
from ninja_aio.models import ModelSerializer

class Book(ModelSerializer):
    title = models.CharField(max_length=120)
    description = models.TextField(blank=True)

    class CreateSerializer:
        fields = ["title", "description"]

        @field_validator("title")
        @classmethod
        def validate_title_min_length(cls, v):
            if len(v) < 3:
                raise ValueError("Title must be at least 3 characters")
            return v

    class UpdateSerializer:
        optionals = [("title", str), ("description", str)]

        @field_validator("title")
        @classmethod
        def validate_title_not_empty(cls, v):
            if v is not None and len(v.strip()) == 0:
                raise ValueError("Title cannot be blank")
            return v

    class ReadSerializer:
        fields = ["id", "title", "description"]

        @model_validator(mode="after")
        def enrich_output(self):
            # Transform or enrich the output schema
            return self

Meta-driven Serializer

Use dedicated {Type}Validators inner classes:

from pydantic import field_validator, model_validator
from ninja_aio.models import serializers
from . import models

class BookSerializer(serializers.Serializer):
    class Meta:
        model = models.Book
        schema_in = serializers.SchemaModelConfig(fields=["title", "description"])
        schema_out = serializers.SchemaModelConfig(fields=["id", "title", "description"])
        schema_update = serializers.SchemaModelConfig(
            optionals=[("title", str), ("description", str)]
        )

    class CreateValidators:
        @field_validator("title")
        @classmethod
        def validate_title_min_length(cls, v):
            if len(v) < 3:
                raise ValueError("Title must be at least 3 characters")
            return v

    class UpdateValidators:
        @field_validator("title")
        @classmethod
        def validate_title_not_empty(cls, v):
            if v is not None and len(v.strip()) == 0:
                raise ValueError("Title cannot be blank")
            return v

    class ReadValidators:
        @model_validator(mode="after")
        def enrich_output(self):
            return self

Validator class mapping:

Schema type ModelSerializer Serializer (Meta-driven)
Create CreateSerializer CreateValidators
Update UpdateSerializer UpdateValidators
Read ReadSerializer ReadValidators
Detail DetailSerializer DetailValidators

Disable Operations

@api.viewset(Book)
class ReadOnlyBookViewSet(APIViewSet):
    disable = ["update", "delete"]

Framework Comparison

How does Django Ninja AIO compare to other Python REST frameworks? We benchmark against Django Ninja, ADRF, and FastAPI — focusing on complex async operations like reverse FK and M2M serialization.

Framework Lines of Code Reverse FK Handling Auto Prefetch CRUD Automation
Django Ninja AIO ~20 Automatic Yes Full
FastAPI ~80+ Manual async iteration No None
ADRF ~45+ Needs serializer config Manual Partial

View Full Comparison — Code examples, benchmark results, and interactive charts


Performance

View live benchmarks tracking schema generation, serialization, and CRUD throughput:

Live Performance Report — Interactive charts with historical trends

Performance Tips

  • Use queryset_request classmethod to select_related / prefetch_related
  • Index frequently filtered fields
  • Keep pagination enabled for large datasets
  • Limit slices (queryset = queryset[:1000]) for heavy searches

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for your changes
  4. Run lint: ruff check .
  5. Open a Pull Request

Support

If you find this project useful, consider giving it a star or supporting development:

Buy me a coffee


License

MIT License. See LICENSE.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_ninja_aio_crud-2.27.0.tar.gz (2.3 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_ninja_aio_crud-2.27.0-py3-none-any.whl (74.6 kB view details)

Uploaded Python 3

File details

Details for the file django_ninja_aio_crud-2.27.0.tar.gz.

File metadata

  • Download URL: django_ninja_aio_crud-2.27.0.tar.gz
  • Upload date:
  • Size: 2.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: python-requests/2.31.0

File hashes

Hashes for django_ninja_aio_crud-2.27.0.tar.gz
Algorithm Hash digest
SHA256 1e0bf452616295b6927c0afaae27f6715bd3d04e79137eaeeec05acf5237dfe7
MD5 3023d2c1c349e033d1763f69076dd406
BLAKE2b-256 36ff19f3e87b095ab2497a8cf3838d163e3a4f9fdb0906c948449ad2105844b5

See more details on using hashes here.

File details

Details for the file django_ninja_aio_crud-2.27.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_ninja_aio_crud-2.27.0-py3-none-any.whl
Algorithm Hash digest
SHA256 54016c169fe5735f22dc01923a3ec77d40d99aa38528d82c5a4f21af874fe015
MD5 ab258e2dfbfe13541d641e818dd286a9
BLAKE2b-256 60c5c1c7b2bf9125ea1e5a529733e509126f11d1a5965180a1710c50734f31b1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page