Skip to content

Mixins

The five async model mixins implement the action methods for create, list, retrieve, update, and destroy. The generic views and the AsyncModelViewSet viewset are built by combining them with AsyncGenericAPIView (or its viewset cousin). Mix and match to build non-standard views without giving up the async pipeline.

AsyncCreateModelMixin

Provides the create action.

async def create(self, request, *args, **kwargs):
    serializer = self.get_serializer(data=request.data)
    await avalidate_or_is_valid_serializer(serializer, raise_exception=True)
    await self.aperform_create(serializer)
    headers = self.get_success_headers(serializer.data)
    return Response(serializer.data, status=201, headers=headers)

Validates the request body and calls aperform_create, which saves the instance. Returns 201 with the serialized data. Override aperform_create to add side effects without changing the response shape.

async def aperform_create(self, serializer):
    instance = await serializer.asave(created_by=self.request.user)
    await notify_created(instance)

AsyncListModelMixin

Provides the list action.

async def list(self, request, *args, **kwargs):
    queryset = await self.afilter_queryset(self.get_queryset())
    page = await self.apaginate_queryset(queryset)
    if page is not None:
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)
    serializer = self.get_serializer(queryset, many=True)
    return Response(serializer.data)

Filters the queryset, paginates it, and returns the serialized page. When no paginator is configured, the full queryset is serialized and returned directly.

The mixin does not call the helper methods from APIViewHelpersMixin; it uses DRF's classic get_serializer(...) contract directly so it stays compatible with viewsets that do not need separate request/response serializers. When custom response shapes or PostFetch joins are needed, override list and call apaginated_response(...) from the helper surface instead.

AsyncRetrieveModelMixin

Provides the retrieve action.

async def retrieve(self, request, *args, **kwargs):
    instance = await self.aget_object()
    serializer = self.get_serializer(instance)
    return Response(serializer.data)

Fetches the object via aget_object (filtering, lookup, and object-level permission checks), serializes it, and returns 200.

AsyncUpdateModelMixin

Provides update (PUT) and partial_update (PATCH).

async def update(self, request, *args, **kwargs):
    partial = kwargs.pop("partial", False)
    instance = await self.aget_object()
    serializer = self.get_serializer(instance, data=request.data, partial=partial)
    await avalidate_or_is_valid_serializer(serializer, raise_exception=True)
    await self.aperform_update(serializer)
    if getattr(instance, "_prefetched_objects_cache", None):
        instance._prefetched_objects_cache = {}
    return Response(serializer.data)

async def partial_update(self, request, *args, **kwargs):
    kwargs["partial"] = True
    return await self.update(request, *args, **kwargs)

Fetches the object, validates the request body, and calls aperform_update to save. Returns 200 with the serialized data. partial_update delegates to update with partial=True, giving PATCH semantics. Override aperform_update to add side effects.

async def aperform_update(self, serializer):
    instance = await serializer.asave()
    await audit_log.arecord("product_updated", instance.id)

AsyncDestroyModelMixin

Provides the destroy action.

async def destroy(self, request, *args, **kwargs):
    instance = await self.aget_object()
    await self.aperform_destroy(instance)
    return Response(status=204)

Fetches the object and calls aperform_destroy to delete it, then returns 204. Override aperform_destroy for soft-delete semantics, archive flags, or cascade hooks.

Composing mixins

A custom view can be assembled by mixing the relevant action mixins on top of AsyncGenericAPIView and exposing the wanted HTTP methods.

from restflow.views import (
    AsyncCreateModelMixin,
    AsyncListModelMixin,
    AsyncRetrieveModelMixin,
    AsyncGenericAPIView,
)

class ProductReadAndCreateView(
    AsyncListModelMixin,
    AsyncCreateModelMixin,
    AsyncRetrieveModelMixin,
    AsyncGenericAPIView,
):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    pagination_class = PageNumberPagination

    async def get(self, request, *args, **kwargs):
        if "pk" in kwargs:
            return await self.retrieve(request, *args, **kwargs)
        return await self.list(request, *args, **kwargs)

    async def post(self, request, *args, **kwargs):
        return await self.create(request, *args, **kwargs)

The same pattern applies to viewsets. To build a viewset that exposes list, create, and destroy but no retrieve or update, mix only those three action mixins on top of AsyncGenericViewSet.

from restflow.views import (
    AsyncCreateModelMixin,
    AsyncDestroyModelMixin,
    AsyncListModelMixin,
    AsyncGenericViewSet,
)

class ProductSoftViewSet(
    AsyncListModelMixin,
    AsyncCreateModelMixin,
    AsyncDestroyModelMixin,
    AsyncGenericViewSet,
):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer

Override hooks

The persistence hooks (aperform_create, aperform_update, aperform_destroy) are the recommended extension points. They run after validation and before the response, which makes them the right place for audit logging, notifications, and side-channel writes.

Mixin Hook to override
AsyncCreateModelMixin aperform_create
AsyncUpdateModelMixin aperform_update
AsyncDestroyModelMixin aperform_destroy
AsyncListModelMixin list (when the standard flow is wrong)
AsyncRetrieveModelMixin retrieve (rare; aget_object is better)

For most customisation, override the perform hook rather than the action method itself. The action methods own the request/response plumbing; the perform hooks own the persistence.