django-ninja-aio-crud

CRUD framework for Django Ninja, sync or async
Serializer-first schemas · Relations · Filtering · Pagination · Auth · Permissions

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

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

--- django-ninja-aio-crud builds complete REST APIs on top of Django Ninja. You say which fields each operation reads and writes, register a viewset, and get create, list, retrieve, update and delete endpoints with validation, pagination and interactive docs. The same serializer is also a CRUD API you can call from Python, in sync or async code. ## Features | | Feature | Description | |---|---|---| | ⚡ | Sync and async | Every viewset runs async by default, or sync with `execution_mode = "sync"` | | 🐍 | Python CRUD API | `create`, `get`, `update`, `destroy`, `model_dump` and their async versions on every serializer | | 📐 | Schemas | Declare `create`, `update`, `read` and `detail` with `class Schemas` and `SchemaConfig` | | 🔗 | Relations | Nested reads, relations as ids, and nested writes for child objects | | 📦 | Bulk operations | Bulk create, update and delete with per-item results | | 🔍 | Filtering | Filter, search, ordering and pagination mixins | | 🛡️ | Permissions | Viewset and object-level permission checks, including role-based access | | 🗑️ | Soft delete | Mark rows as deleted instead of removing them | | 🔑 | Authentication | JWT bearer and cookie auth, with per-method settings | | 🪝 | Hooks | Reactive `@on_create`, `@on_update` and `@on_delete` hooks on your models | | 🧰 | Django admin | Register a `ModelSerializer` in the admin with `@register_admin` | | 🤖 | MCP server | Expose your viewsets as [MCP](https://modelcontextprotocol.io) tools | | 🔒 | Type safety | Generic `Serializer` and `APIViewSet` classes for IDE autocomplete | ## Install ```bash pip install django-ninja-aio-crud ``` Requires Python 3.10-3.14, Django 5.2 or 6.0, and Django Ninja 1.7.x. ## Quick start Define the model. A `ModelSerializer` is a normal Django model with a `Schemas` class that says which fields each operation reads and writes. ```python # blog/models.py from django.db import models from ninja_aio import ModelSerializer, SchemaConfig class Article(ModelSerializer): title = models.CharField(max_length=200) body = models.TextField() is_published = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) class Schemas: create = SchemaConfig( fields=["title", "body"], optionals=[("is_published", bool)], ) update = SchemaConfig( optionals=[("title", str), ("body", str), ("is_published", bool)], ) read = SchemaConfig( fields=["id", "title", "body", "is_published", "created_at"], ) ``` Create the API and add the URLs: ```python # blog/api.py from ninja_aio import NinjaAIO, APIViewSet from .models import Article api = NinjaAIO(title="Blog API") @api.viewset(model=Article) class ArticleViewSet(APIViewSet): pass ``` ```python # project/urls.py from django.urls import path from blog.api import api urlpatterns = [ path("api/", api.urls), ] ``` Run `python manage.py runserver` and open http://localhost:8000/api/docs. | Method | Path | What it does | | --- | --- | --- | | `GET` | `/api/articles` | List articles, with pagination | | `POST` | `/api/articles/` | Create an article | | `GET` | `/api/articles/{id}` | Get one article | | `PATCH` | `/api/articles/{id}/` | Update some fields | | `DELETE` | `/api/articles/{id}/` | Delete an article | ## Keep your models as they are Use a `Serializer` when you don't want to change your model's base class: ```python # blog/serializers.py from ninja_aio import Serializer, SchemaConfig from .models import Article class ArticleSerializer(Serializer[Article]): class Meta: model = Article class Schemas: create = SchemaConfig(fields=["title", "body"]) update = SchemaConfig(optionals=[("title", str), ("body", str)]) read = SchemaConfig(fields=["id", "title", "body"]) ``` ```python @api.viewset(model=Article) class ArticleViewSet(APIViewSet): serializer_class = ArticleSerializer ``` ## Sync or async Viewsets are async by default. To serve the same endpoints with regular sync views, set `execution_mode`: ```python @api.viewset(model=Article) class ArticleViewSet(APIViewSet): execution_mode = "sync" ``` The model is also a CRUD API you can call in scripts, tasks and tests: ```python # Sync article = Article.create({"title": "Draft", "body": "..."}) article = Article.update(article, {"is_published": True}) data = Article.model_dump(article) ``` ```python # Async article = await Article.acreate({"title": "Draft", "body": "..."}) article = await Article.aupdate(article, {"is_published": True}) data = await Article.amodel_dump(article) ``` ## Custom actions ```python from ninja_aio import APIViewSet, action, on @api.viewset(model=Article) class ArticleViewSet(APIViewSet): @on("publish") async def publish(self, request, obj): obj.is_published = True await obj.asave(update_fields=["is_published"]) return await Article.amodel_dump(obj) @action(detail=False, url_path="stats") async def stats(self, request): return {"total": await Article.objects.acount()} ``` This adds `POST /api/articles/{id}/publish` and `GET /api/articles/stats`. ## Validators Add Pydantic validators to a `CreateValidators` or `UpdateValidators` class: ```python from pydantic import field_validator class Article(ModelSerializer): # fields and Schemas as above class CreateValidators: @field_validator("title") @classmethod def title_not_blank(cls, value: str) -> str: if not value.strip(): raise ValueError("Title cannot be blank") return value.strip() ``` Invalid input returns `422` with the error message. ## Upgrading from 2.x Version 3 replaces the inner `ReadSerializer`/`CreateSerializer` classes and `Meta.schema_in`/`schema_out` with a single `class Schemas`, adds sync viewsets and the Python CRUD API. Follow the [migration guide](https://django-ninja-aio.com/migration/) to upgrade. ## Links - [Documentation](https://django-ninja-aio.com) - [Tutorial](https://django-ninja-aio.com/tutorial/) - [Guides](https://django-ninja-aio.com/guides/schemas/) - [Release notes](https://django-ninja-aio.com/release_notes/) - [Contributing](https://django-ninja-aio.com/contributing/) ## Support If you find this project useful, consider giving it a star or supporting development: Buy me a coffee ## License MIT License. See [LICENSE](LICENSE).