Skip to content

Viewsets

Full CRUD

ServiceViewSet

Bases: ServiceCreateMixin, ServiceUpdateMixin, ServiceDestroyMixin, SelectorListMixin, SelectorRetrieveMixin, ActionSerializerResolver, GenericViewSet

Router-compatible viewset wiring services and selectors.

Composes :class:ServiceCreateMixin, :class:ServiceUpdateMixin, :class:ServiceDestroyMixin, :class:SelectorListMixin, :class:SelectorRetrieveMixin, and :class:ActionSerializerResolver over :class:~rest_framework.viewsets.GenericViewSet. See those classes for the configurable attributes.

SelectorViewSet

Bases: SelectorListMixin, SelectorRetrieveMixin, ActionSerializerResolver, GenericViewSet

Read-only viewset for list + retrieve.

Composes :class:SelectorListMixin, :class:SelectorRetrieveMixin, and :class:ActionSerializerResolver over :class:~rest_framework.viewsets.GenericViewSet.

Per-action mixins

ServiceCreateMixin

Bases: MutationFlowMixin, _ActionSpecsMixin

Provides the create action; reads its config from action_specs.

Set action_specs["create"] to a :class:~rest_framework_services.types.service_spec.ServiceSpec. When the "create" key is absent the action raises :exc:~rest_framework.exceptions.MethodNotAllowed. A non-ServiceSpec entry (e.g. a :class:~rest_framework_services.types.selector_spec.SelectorSpec) raises :exc:~django.core.exceptions.ImproperlyConfigured.

ServiceUpdateMixin

Bases: MutationFlowMixin, _ActionSpecsMixin

Provides update (PUT) and partial_update (PATCH) actions.

Looks up the instance via DRF's get_object(). Reads its config from action_specs["update"]; when that key is absent both actions raise :exc:~rest_framework.exceptions.MethodNotAllowed. A non-ServiceSpec entry raises :exc:~django.core.exceptions.ImproperlyConfigured.

ServiceDestroyMixin

Bases: MutationFlowMixin, _ActionSpecsMixin

Provides the destroy action.

Looks up the instance via DRF's get_object(). Reads its config from action_specs["destroy"]; when that key is absent the action raises :exc:~rest_framework.exceptions.MethodNotAllowed. A non-ServiceSpec entry raises :exc:~django.core.exceptions.ImproperlyConfigured.

SelectorListMixin

Bases: ListModelMixin, _ActionSpecsMixin

Compose with :class:~rest_framework.viewsets.GenericViewSet.

When action_specs["list"] is a :class:~rest_framework_services.types.selector_spec.SelectorSpec with a non-None selector, get_queryset() invokes it instead of returning the configured queryset. The rest of DRF's list flow — filter backends, pagination, serialization — is unchanged.

action_specs["list"] = SelectorSpec(selector=None) or an absent "list" key both fall through to DRF's default get_queryset(). Any other entry type raises :exc:~django.core.exceptions.ImproperlyConfigured.

get_selector_kwargs

get_selector_kwargs() -> dict[str, Any]

Hook for additional kwargs available to the selector signature.

SelectorRetrieveMixin

Bases: RetrieveModelMixin, _ActionSpecsMixin

Compose with :class:~rest_framework.viewsets.GenericViewSet.

When action_specs["retrieve"] is a :class:~rest_framework_services.types.selector_spec.SelectorSpec with a non-None selector, get_object() invokes it instead of falling through to DRF's standard lookup. The selector receives the URL kwargs plus the standard pool. Returning None or raising Model.DoesNotExist results in a 404.

action_specs["retrieve"] = SelectorSpec(selector=None) or an absent "retrieve" key both fall through to DRF's default get_object(). Any other entry type raises :exc:~django.core.exceptions.ImproperlyConfigured.

The selector applies wherever get_object() is called, including from update/destroy actions composed alongside this mixin. If you need an action-specific override, do it explicitly in your own get_object().

Action-serializer dispatch

ActionSerializerResolver

Bases: _ActionSpecsMixin

Resolve get_serializer_class() from action_specs.

Consults the action_specs map for an output_serializer on the active action's :class:SelectorSpec or :class:ServiceSpec entry, then falls back to DRF's standard serializer_class attribute (and raises the usual DRF AssertionError if neither is set).

Example::

class InvoiceViewSet(ActionSerializerResolver, GenericViewSet):
    action_specs = {
        "list": SelectorSpec(output_serializer=InvoiceListSerializer),
        "retrieve": SelectorSpec(output_serializer=InvoiceDetailSerializer),
    }

Custom actions

service_action

service_action(
    spec: ServiceSpec,
    *,
    detail: bool = False,
    methods: list[str] | None = None,
    url_path: str | None = None,
    url_name: str | None = None,
    **action_kwargs: Any,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]

Wrap a viewset method as a service-backed custom action.

The decorated method's body is not executed — the decorator supplies the handler. The method exists so that @service_action can attach DRF @action metadata and pick up the action name from __name__.

Pass a :class:ServiceSpec for the service wiring. detail, methods, url_path, url_name, and any extra **action_kwargs are forwarded to DRF's @action.