A set of viewset mixins for the Django REST Framework.
Fully type hinted — the package ships py.typed, so your type checker sees the mixin signatures.
Supported versions
Python |
3.10, 3.11, 3.12, 3.13, 3.14 |
Django |
5.2, 6.0 |
DRF |
3.16+ |
Installation
pip install djangorestframework-mango
Usage
ActionMixin
It has six action methods that can be used instead of comparing self.action.
is_create_action()
is_retrieve_action()
is_list_action()
is_update_action()
is_partial_update_action()
is_destroy_action()
class ViewSet(ActionMixin, viewsets.GenericViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
def get_queryset(self):
queryset = super().get_queryset()
if self.is_create_action():
# change queryset for create
queryset = queryset.change_for_create()
elif self.is_retrieve_action():
# change queryset for retrieve
queryset = queryset.change_for_retrieve()
elif self.is_list_action():
# change queryset for list
queryset = queryset.change_for_list()
return queryset
QuerysetMixin
It finds the <action>_queryset method and runs it. Any action works, including custom @action methods. Actions without a matching method leave the queryset untouched.
class ViewSet(QuerysetMixin, viewsets.GenericViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
# this method runs automatically when this viewset gets the create action
def create_queryset(self, queryset):
return queryset.change_for_create()
# this method runs automatically when this viewset gets the list action
def list_queryset(self, queryset):
return queryset.change_for_list()
# this method runs automatically when this viewset gets the partial update action
def partial_update_queryset(self, queryset):
return queryset.change_for_partial_update()
# this method runs automatically when this viewset gets the update_extra_profile action
def update_extra_profile_queryset(self, queryset):
return queryset.change_for_update_extra_profile()
@action(methods=['POST'], detail=True)
def update_extra_profile(self, request, pk=None):
# this method calls update_extra_profile_queryset() internally
queryset = self.get_queryset()
return Response(serializer.data)
SerializerMixin
You can define multiple serializers by action. Unmapped actions fall back to serializer_class.
class ViewSet(SerializerMixin, viewsets.GenericViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
serializer_class_by_actions = {
'create': {
'v1': ModelCreateSerializerV1,
'v2': ModelCreateSerializerV2,
},
'list': ModelListSerializer,
'retrieve': ModelRetrieveSerializer,
'update': ModelUpdateSerializer,
'partial_update': ModelPartialUpdateSerializer,
'destroy': ModelDestroySerializer,
'update_extra_profile': ModelUpdateExtraProfileSerializer,
}
@action(methods=['POST'], detail=True)
def update_extra_profile(self, request, pk=None):
# self.get_serializer returns ModelUpdateExtraProfileSerializer
serializer = self.get_serializer()
return Response(serializer.data)
A nested dict maps by API version, so it requires a versioning scheme to be configured — otherwise ImproperlyConfigured is raised.
PermissionMixin
You can define multiple permissions by action. Unmapped actions fall back to permission_classes.
class ViewSet(PermissionMixin, viewsets.GenericViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_by_actions = {
'create': [IsAuthenticated],
'list': [ReadOnly],
'retrieve': [AllowAny],
'update': [Owner],
'partial_update': [Owner],
'destroy': [Owner],
'update_extra_profile': [Owner],
}
@action(methods=['POST'], detail=True)
def update_extra_profile(self, request, pk=None):
# this method requires Owner permission
serializer = self.get_serializer()
return Response(serializer.data)
MangoMixin
QuerysetMixin, SerializerMixin and PermissionMixin combined.
class ViewSet(MangoMixin, viewsets.GenericViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
serializer_class_by_actions = {'list': ModelListSerializer}
permission_by_actions = {'destroy': [Owner]}
def list_queryset(self, queryset):
return queryset.change_for_list()
SessionMiddleware
Share data within a request life cycle — useful where the request is not reachable, such as a model. The session is created per thread and cleared when the request finishes, even if the view raises.
# settings.py
MIDDLEWARE = [
...,
'django_rest_framework_mango.middlewares.SessionMiddleware',
]
class ViewSet(QuerysetMixin, viewsets.GenericViewSet):
queryset = Model.objects.all()
serializer_class = ModelSerializer
def list_queryset(self, queryset):
SessionMiddleware.get_session()['current_user'] = self.request.user
return queryset
class Model(models.Model):
@property
def current_user(self):
# the model cannot reach the request, so it reads what the view stored
session = SessionMiddleware.get_session() or {}
return session.get('current_user')
Development
Tests run against every supported Python and Django combination through tox. tox-uv downloads the interpreters, so none of them need to be installed beforehand.
uvx --with tox-uv tox # every combination
uvx --with tox-uv tox -f py314 # Python 3.14 x Django 5.2, 6.0
uvx --with tox-uv tox -e py312-dj60 # a single combination
uvx --with tox-uv tox -e mypy # type check
uv run --python 3.14 pytest # quick single run, without tox
Metadata
Release files for djangorestframework-mango 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| djangorestframework_mango-0.3.0.tar.gz | 7.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| djangorestframework_mango-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 13.5 kB
Release files / djangorestframework_mango-0.3.0.tar.gz
| Download URL | djangorestframework_mango-0.3.0.tar.gz |
|---|---|
| Size | 7.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7149daf795c6e97e50c392c4a9150e86c2a6bac190a26d08c5e2320f364123f1
|
|
BLAKE2b-256 checksum How to use checksums |
ddcc1a0f056c34d43edf0f4548dd5246f774eb1f9f414ae6bfb6456c2a242a0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 6, 2026.
Transparency logRelease files / djangorestframework_mango-0.3.0-py3-none-any.whl
| Download URL | djangorestframework_mango-0.3.0-py3-none-any.whl |
|---|---|
| Size | 6.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
903f4c82e3f825b7f916a1818cdf231e93f4182070c0f3376db6419595c08a76
|
|
BLAKE2b-256 checksum How to use checksums |
7b3c1558548d553d15c967942a34518a2009625b82f54ffe8d1ead79972956a3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 6, 2026.
Transparency log