Overview and usage patterns of the APIView in Django Ninja CRUD
APIView
class APIView(abc.ABC)
Base class for creating declarative and reusable API views like components.
This class provides a framework for defining API views with a declarative syntax
and minimal boilerplate code. It handles setting up the HTTP methods, path, response
status/body, among other configurations. It supports both synchronous and
asynchronous handlers, allowing for flexible view implementations.
This class is intended to be subclassed, not used directly.
By subclassing, you can create custom API views that leverage the provided
functionality, enabling you to refactor common view logic into reusable components.
Arguments:
name
str | None - View function name. If None, uses class attribute name in
viewsets or "handler" for standalone views (unless decorator-overridden).methods
List[str] - HTTP methods.path
str - URL path.response_status
int - HTTP response status code.response_body
Type | None - Response body type.model
Type[django.db.models.Model] | None, optional - Associated Django model.
Inherits from viewset if not provided. Defaults toNone
.decorators
List[Callable] | None, optional - View function decorators
(applied in reverse order). Defaults toNone
.operation_kwargs
Dict[str, Any] | None, optional - Additional operation
keyword arguments. Defaults toNone
.
Examples:
Thanks to the flexibility of APIView
, you can create a wide variety of views
with minimal boilerplate code. Here are examples of how you can create simple
reusable read views supporting only models with a UUID primary key, in both
synchronous and asynchronous variants:
Synchronous Example:
from typing import Optional, Type
from uuid import UUID
import pydantic
from django.http import HttpRequest
from django.db import models
from ninja_crud.views import APIView
class ReusableReadView(APIView):
def __init__(
self,
name: Optional[str] = None,
model: Optional[Type[models.Model]] = None,
response_body: Optional[Type[pydantic.BaseModel]] = None,
) -> None:
super().__init__(
name=name,
methods=["GET"],
path="/{id}/reusable",
response_status=200,
response_body=response_body,
model=model,
)
def handler(self, request: HttpRequest, id: UUID) -> models.Model:
return self.model.objects.get(id=id)
Asynchronous Example:
class ReusableAsyncReadView(APIView):
def __init__(
self,
name: Optional[str] = None,
model: Optional[Type[models.Model]] = None,
response_body: Optional[Type[pydantic.BaseModel]] = None,
) -> None:
super().__init__(
name=name,
methods=["GET"],
path="/{id}/reusable/async",
response_status=200,
response_body=response_body,
model=model,
)
async def handler(self, request: HttpRequest, id: UUID) -> models.Model:
return await self.model.objects.aget(id=id)
You can then use these views with a simple Django model:
# examples/models.py
import uuid
from django.db import models
class Department(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=100, unique=True)
And a ninja.Schema
for serializing the response body:
# examples/schemas.py
from uuid import UUID
import ninja
class DepartmentOut(ninja.Schema):
id: UUID
name: str
Now, you can declaratively create endpoints using these view classes:
# examples/views.py
from ninja import NinjaAPI
from examples.models import Department
from examples.schemas import DepartmentOut
from examples.reusable_views import ReusableReadView, ReusableAsyncReadView
api = NinjaAPI()
ReusableReadView(
name="read_department",
model=Department,
response_body=DepartmentOut
).add_view_to(api)
ReusableAsyncReadView(
name="read_department_async",
model=Department,
response_body=DepartmentOut
).add_view_to(api)
Or, you can create a viewset to group related views:
# examples/views.py
from ninja import NinjaAPI
from ninja_crud.viewsets import APIViewSet
from examples.models import Department
from examples.schemas import DepartmentOut
from examples.reusable_views import ReusableReadView, ReusableAsyncReadView
api = NinjaAPI()
class DepartmentViewSet(APIViewSet):
api = api
model = Department
read_department = ReusableReadView(response_body=DepartmentOut)
read_department_async = ReusableAsyncReadView(response_body=DepartmentOut)
Finally, add the API to your URL configuration:
# examples/urls.py
from django.urls import path
from examples.views import api as department_api
urlpatterns = [
path("departments/", department_api.urls),
]
This setup provides both synchronous and asynchronous endpoints for reading
department data, demonstrating the flexibility of the APIView class in handling
different types of request handlers.
add_view_to
def add_view_to(api_or_router: Union[ninja.NinjaAPI, ninja.Router]) -> None
Add the view to an API or router. This method is here for convenience and
allows you to add the view to an API or router without having to manually
convert the view to an API operation dictionary (see as_operation
).
Arguments:
api_or_router
Union[ninja.NinjaAPI, ninja.Router] - The API or router to
add the view to.
Example:
from ninja import Router
from examples.models import Department
from examples.schemas import DepartmentOut
from examples.reusable_views import ReusableReadView
router = Router()
ReusableReadView(Department, DepartmentOut).add_view_to(router)
as_operation
def as_operation() -> Dict[str, Any]
Return a dictionary representation of the API operation that can be added to a
router or API by using add_api_operation
method. Used internally by
add_view_to
, but can also be called manually to add the view to a router.
Returns:
Dict[str, Any]: The API operation dictionary.
Example:
from ninja import Router
from examples.models import Department
from examples.schemas import DepartmentOut
from examples.reusable_views import ReusableReadView
router = Router()
read_department = ReusableReadView(Department, DepartmentOut)
router.add_api_operation(**read_department.as_operation())
handler
@abc.abstractmethod
def handler(*args: Any, **kwargs: Any) -> Any
The handler function for the view. This method must be implemented in
subclasses and should contain the view's business logic.
This method can be implemented as either a synchronous or an asynchronous
function, depending on the needs of the specific view. The APIView class
will automatically adapt to the chosen implementation.
For synchronous implementation:
def handler(self, request: HttpRequest, ...) -> Any:
Synchronous logic here
For asynchronous implementation:
async def handler(self, request: HttpRequest, ...) -> Any:
Asynchronous logic here
Arguments:
*args
Any - Variable positional arguments.**kwargs
Any - Variable keyword arguments.
Returns:
Any
- The response data. This can be a simple value, a model instance,
or any other data that can be serialized by Django Ninja.
get_api_viewset_class
def get_api_viewset_class() -> Type["APIViewSet"]
Get the viewset class that the view is associated with, if the view is of
course bound to a viewset class.
Returns:
Type[APIViewSet]
- The viewset class.
Raises:
ValueError
- If the view is not bound to a viewset class.
set_api_viewset_class
def set_api_viewset_class(api_viewset_class: Type["APIViewSet"]) -> None
Bind the view to a viewset class. This method sets the model based on the
viewset class if not already defined. Can be overridden in subclasses to
provide additional behavior, like setting default values for the view.
Arguments:
api_viewset_class
Type[APIViewSet] - The viewset class to bind to.
Raises:
ValueError
- If the view is already bound to a viewset class.
Notes:
This method is called internally and automatically by the viewset when
defining views as class attributes. It should not be called manually.
resolve_path_parameters
def resolve_path_parameters() -> Optional[Type[pydantic.BaseModel]]
Resolve path parameters to a pydantic model based on the view's path and model.
Designed for subclasses, this method enables dynamic path parameter handling
without explicit type specification. It supports any paths, like "/{name}",
"/{id}", or "/{related_model_id}", automatically resolving types from model
fields.
This feature significantly reduces boilerplate code and allows for easy
creation of reusable views with varied path structures, especially useful when
refactoring repetitive endpoints into an APIView subclass.
Utilizes Django Ninja utilities to extract and map path parameters to model
fields. Returns None
if no parameters are found.
Returns:
Optional[Type[pydantic.BaseModel]]
- Path parameters pydantic model type.
Example:
For path "/{department_id}/employees/{id}"
and Employee
model:
```python
class PathParameters(pydantic.BaseModel):
department_id: UUID
id: UUID
```
Notes:
- Supports various Django field types (e.g., AutoField, CharField,
DateField, UUIDField). - For ForeignKey, uses the primary key type of the related model.
- Supports both real field names (e.g., /{department_id}) and related
model names (e.g., /{department}).
Important:
Prefer simple types (strings, integers, UUIDs) for path parameters to
ensure proper URL formatting and web standard compatibility.