Skip to content

ViewSets

Base classes and mixins that add consistent response wrapping, exception handling, and routing conventions to DRF ViewSets.

Mixins

PublicIDLookupMixin

Configures a ViewSet to look up objects by public_id instead of the internal primary key, keeping internal IDs out of your URLs.

Attribute Value
lookup_field public_id
lookup_url_kwarg id

Usage

class UserViewSet(PublicIDLookupMixin, ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

A detail request to /users/abc123/ resolves internally as:

User.objects.get(public_id="abc123")

Tip

Place PublicIDLookupMixin before the ViewSet in the MRO so its lookup_field takes precedence.

CommonMixin

CommonMixin is composed of three smaller mixins:

class CommonMixin(
    SuccessResponseMixin, PrefetchCacheInvalidationViewMixin, RequestActorViewMixin
):
    """
    Convenience helpers shared across ViewSets.
    """
Component Provides
SuccessResponseMixin Automatic success envelope wrapping
RequestActorViewMixin The actor property (request.user), as previously documented under CommonMixin
PrefetchCacheInvalidationViewMixin _invalidate_prefetched_cache(), new — see below

_response(data=None, status_code=200, **kwargs)

A thin wrapper around rest_framework.response.Response with a default 200 status.

Parameter Type Default Description
data any None Response payload
status_code int HTTP_200_OK HTTP status code
**kwargs Forwarded to Response
def my_action(self, request):
    return self._response({"acknowledged": True}, status_code=202)

RequestActorViewMixin

Adds convenience properties for accessing the requesting user and their profile.

actor

A cached property returning the authenticated request.user, or None if there is no request, no user, or the user isn't authenticated.

def perform_create(self, serializer):
    serializer.save(owner=self.actor)

!!! note This differs from the plain self.request.user lookup previously described under CommonMixin: an anonymous request now resolves to None rather than Django's AnonymousUser instance. Code that branches on self.actor truthiness is unaffected, but code that expects an AnonymousUser object specifically should be updated.

actor_profile

A cached property returning actor.related_profile, or None if there is no actor (unauthenticated) or the attribute doesn't exist.

def get_queryset(self):
    if self.actor_profile:
        return Order.objects.filter(profile=self.actor_profile)
    return Order.objects.none()

!!! tip Both properties are @cached_property, so they're computed once per request/view instance — safe to access repeatedly across perform_* and get_* hooks without re-hitting related_profile.

PrefetchCacheInvalidationViewMixin

Adds a helper for clearing a Django instance's cached prefetch_related() results.

def _invalidate_prefetched_cache(self, instance): ...
Parameter Type Description
instance model Instance whose _prefetched_objects_cache should be cleared

When a queryset has had prefetch_related() applied, Django caches the related results on instance._prefetched_objects_cache. If the instance is then mutated directly (e.g. a related object is added, removed, or changed) without going through that cache, later related-object lookups on the same instance return the stale, pre-mutation results. Calling _invalidate_prefetched_cache(instance) clears that cache so the next access re-queries the database.

SuccessResponseMixin

Intercepts finalize_response to wrap every successful response in the drf-corekit envelope, so action methods never need to wrap their own payloads.

Success envelope

{
  "data": { "id": 1, "name": "Alice" },
  "errors": null
}

Bypass condition

Responses with a status of 207 or ≥ 300 that already carry a structured error body are passed through unchanged:

{
  "type": "validation_error",
  "errors": [{ "field": "email", "message": "Enter a valid email." }]
}

Note

You do not need to call wrap_success_response yourself — any Response returned from your ViewSet is wrapped automatically.

ViewSets

All ViewSets below compose SuccessResponseMixin, and CommonMixin on top of the corresponding DRF base class.

ModelViewSet

A full CRUD ViewSet with response wrapping and exception mapping pre-applied.

Actions

Method Action
GET /resource/ list
POST /resource/ create
GET /resource/<id>/ retrieve
PUT /resource/<id>/ update
PATCH /resource/<id>/ partial_update
DELETE /resource/<id>/ destroy
class UserViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

ReadOnlyModelViewSet

Exposes read-only endpoints only — no writes permitted.

Actions

Method Action
GET /resource/ list
GET /resource/<id>/ retrieve
class CountryViewSet(ReadOnlyModelViewSet):
    queryset = Country.objects.all()
    serializer_class = CountrySerializer

ReadUpdateModelViewSet

Allows reading and updating records, but not creating or deleting them.

Actions

Method Action
GET /resource/ list
GET /resource/<id>/ retrieve
PUT /resource/<id>/ update
PATCH /resource/<id>/ partial_update
class ProfileViewSet(ReadUpdateModelViewSet):
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

NoUpdateModelViewSet

Allows create, and read, but no update or destroy.

Actions

Method Action
POST /resource/ create
GET /resource/ list
GET /resource/<id>/ retrieve
class CommentViewSet(NoUpdateModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

Note

Like ModelViewSet, ReadOnlyModelViewSet, and ReadUpdateModelViewSet, both new ViewSets pick up SuccessResponseMixin, and CommonMixin via _SharedMixin, so response wrapping and exception mapping apply out of the box.

APIViewSet

A plain APIView with the drf-corekit conventions (response wrapping, exception mapping, actor) applied. Use this for non-model, action-oriented endpoints that don't need a router.

class HealthCheckViewSet(APIViewSet):
    def get(self, request):
        return self._response({"status": "ok"})