Skip to content

View Decorators

Django Ninja AIO provides decorator utilities for composing view behavior and attaching decorators to CRUD operations declaratively.

  • @action


    Custom actions for ViewSets

  • @on


    Detail actions with automatic object pre-fetch

  • decorate_view


    Compose multiple decorators on sync/async views

  • extra_decorators


    Declarative per-operation decorators on ViewSets

  • M2M Decorators


    Custom decorators for relation endpoints

  • api_get / api_post


    Endpoint decorators with built-in decorator support


@action (custom actions)

Decorator for adding custom endpoints to ViewSets. Supports detail/list actions, multiple HTTP methods, auth inheritance, and full OpenAPI metadata.

Python
from ninja_aio.decorators import action
Python
@action(detail=True, methods=["post"], url_path="activate")
async def activate(self, request, pk):
    obj = await self.model_util.get_object(request, pk)
    obj.is_active = True
    await obj.asave()
    return Status(200, {"message": "activated"})
Python
@action(detail=False, methods=["get"], url_path="count", response=CountSchema)
async def count(self, request):
    total = await self.model.objects.acount()
    return {"count": total}
Python
@action(detail=False, methods=["post"], url_path="batch", decorators=[aatomic])
async def batch(self, request):
    ...
Python
@action(detail=False, methods=["get"], url_path="public", auth=None)
async def public_endpoint(self, request):
    return {"message": "no auth required"}

Key features:

  • detail=True auto-adds {pk} to URL, renamed to match model PK field
  • url_path defaults to method name with _-
  • auth=NOT_SET inherits from viewset per-verb auth
  • Actions survive disable=["all"] — always registered
  • Multiple methods create separate routes: methods=["get", "post"]

See APIViewSet @action for full parameter reference.


@on (detail action shorthand)

Shorthand over @action for detail endpoints that operate on a pre-fetched model instance. Instead of manually fetching the object and running permission hooks, @on does it automatically — your handler receives obj ready to use.

Python
from ninja_aio.decorators import on

Comparison with @action:

Python
@on("publish", methods=["post"], response={200: ArticleSchema})
async def publish(self, request, obj):
    obj.status = "published"
    await obj.asave(update_fields=["status"])
    return Status(200, await self.model_util.read_s(self.schema_out, request, obj))
Python
@action(detail=True, methods=["post"], url_path="publish", response={200: ArticleSchema})
async def publish(self, request, pk):
    await self.on_before_operation(request, "publish")
    obj = await self.model_util.get_object(request, pk)
    await self.on_before_object_permission(request, "publish", obj)
    obj.status = "published"
    await obj.asave(update_fields=["status"])
    return Status(200, await self.model_util.read_s(self.schema_out, request, obj))

What @on does automatically:

  1. Calls on_before_operation(request, action_name) — view-level hooks/permissions
  2. Fetches obj = await model_util.get_object(request, pk) — raises 404 if not found
  3. Calls on_before_object_operation(request, action_name, obj) — object-level hooks/permissions
  4. Passes obj to your handler

Parameters:

Parameter Default Description
action_name required URL path segment and operation name
methods ["post"] HTTP methods
url_path action_name Override the URL segment
auth NOT_SET Inherits from viewset per-verb auth
response NOT_SET Response schema
summary, description, tags, deprecated, decorators, throttle, include_in_schema, openapi_extra Standard Ninja endpoint metadata

Note

@on always sets detail=True. The handler must be async since object fetching is async.

Tip

Works seamlessly with PermissionViewSetMixinon_before_object_operation is always called regardless of _has_object_hooks.


decorate_view

Compose multiple decorators into a single view wrapper.

Python
from ninja_aio.decorators import decorate_view

Behavior:

  • Order matches normal stacking: @d1 over @d2d1(d2(view))
  • Works with both sync and async views
  • None values are ignored — useful for conditional decoration
Python
from ninja_aio.decorators import decorate_view
from ninja_aio.views import APIViewSet

class MyViewSet(APIViewSet):
    api = api
    model = MyModel

    def views(self):
        @self.router.get("health/")
        @decorate_view(authenticate, log_request)
        async def health(request):
            return {"ok": True}
Python
cache_dec = cache_page(60) if settings.ENABLE_CACHE else None

@self.router.get("data/")
@decorate_view(cache_dec, authenticate)
async def data(request):
    ...

Note

decorate_view does not add an extra wrapper layer. Each decorator should preserve metadata itself (e.g., via functools.wraps).


APIViewSet.extra_decorators

Attach decorators to auto-generated CRUD operations without redefining views:

Python
from ninja_aio.schemas.helpers import DecoratorsSchema

@api.viewset(MyModel)
class MyViewSet(APIViewSet):
    extra_decorators = DecoratorsSchema(
        list=[require_auth, cache_page(30)],
        retrieve=[require_auth],
        create=[require_auth],
        update=[require_auth],
        delete=[require_auth],
    )

Available operations

Field Applies to
list GET / — List all items
retrieve GET /{pk} — Get single item
create POST / — Create item
update PATCH /{pk} — Update item
delete DELETE /{pk} — Delete item

Tip

These decorators are applied in combination with built-in decorators (unique_view, paginate) using decorate_view internally.


M2M Relation Decorators

Apply custom decorators to Many-to-Many relation endpoints via get_decorators and post_decorators:

Python
from ninja_aio.schemas import M2MRelationSchema

M2MRelationSchema(
    model=Tag,
    related_name="tags",
    get_decorators=[cache_decorator, log_decorator],   # GET (list related)
    post_decorators=[rate_limit_decorator],             # POST (add/remove)
)
Parameter Applies to
get_decorators GET /{pk}/tag — List related items
post_decorators POST /{pk}/tag — Add/remove relations

See APIViewSet M2M Relations for more details.


api_get / api_post with Decorators

Endpoint decorators accept a decorators parameter for inline decorator composition:

Python
from ninja.pagination import PageNumberPagination
from ninja_aio.decorators.operations import api_get
from ninja_aio.decorators import unique_view
from ninja.pagination import paginate

@api.viewset(models.Book)
class BookAPI(APIViewSet):
    @api_get(
        "/custom-get",
        response={200: list[GenericMessageSchema]},
        decorators=[paginate(PageNumberPagination), unique_view("test-unique-view")],
    )
    async def get_test(self, request):
        return [{"message": "This is a custom GET method in BookAPI"}]

How decorators are applied

  • Provide decorators as a list — they are applied in reverse order internally
  • paginate(PageNumberPagination) enables async pagination on the handler
  • unique_view(name) marks the route as unique to avoid duplicate registration
  • Works with @api.viewset(Model) classes extending APIViewSet

See Also

  • APIViewSet


    Complete CRUD view generation with decorator support

    Learn more

  • APIView


    Base view class for custom endpoints

    Learn more

  • Mixins


    Reusable filtering and query behaviors

    Learn more

  • Pagination


    Async pagination support for list endpoints

    Learn more