Django REST Framework mixins for ViewSets and APIViews - APIMixin, ModelMixin, RelationshipFilterMixin, RoleBasedFilterMixin. Simplify Django API development with reusable mixins.
Project description
django-api-mixins
Django REST Framework API Mixins - A collection of powerful, reusable mixins for Django REST Framework ViewSets and APIViews to simplify common API development patterns. Perfect for building REST APIs with Django.
📦 Available on PyPI: https://pypi.org/project/django-api-mixins/
Keywords: Django REST Framework, DRF, Django API, ViewSets, APIViews, Mixins, Django Mixins, REST API, Serializers, Queryset Filtering
Table of contents
- Features
- Installation
- Requirements
- Quick Start
- FieldLookup Enum
- Combining Mixins
- Contributing
- License
Features
- ModelMixin: Automatic filter field generation for Django models
- ModelFilterFieldsMixin: Automatically sets
filterset_fieldsfrom models withget_filter_fields()(requiresdjango-filter) - OpenAPIFilterParametersMixin: Adds OpenAPI/Swagger filter parameters for APIView (requires
django-filteranddrf-spectacular) - APIMixin: Dynamic serializer selection based on action (create, update, list, retrieve)
- RelationshipFilterMixin: Automatic filtering for reverse relationships and direct fields
- RoleBasedFilterMixin: Role-based queryset filtering for multi-tenant applications
Installation
Basic installation:
pip install django-api-mixins
With optional dependencies:
# For ModelFilterFieldsMixin (requires django-filter)
pip install django-api-mixins[filters]
# For OpenAPIFilterParametersMixin (requires drf-spectacular)
pip install django-api-mixins[spectacular]
# Install all optional dependencies
pip install django-api-mixins[all]
Upgrade to the latest version:
pip install --upgrade django-api-mixins
Requirements
Core dependencies (required):
- Python 3.8+
- Django 3.2+
- Django REST Framework 3.12+
Optional dependencies:
django-filter>=23.0- Required forModelFilterFieldsMixinandOpenAPIFilterParametersMixindrf-spectacular>=0.26.0- Required forOpenAPIFilterParametersMixin
Quick Start
ModelMixin
Automatically generate filter fields for all model fields:
from django.db import models
from django_api_mixins import ModelMixin
class Product(models.Model, ModelMixin):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
# Automatically generates filter fields:
# - name: exact, in, isnull
# - price: exact, in, isnull, gte, lte
# - created_at: exact, in, isnull, gte, lte
# - is_active: exact, in, isnull
Use in your ViewSet with Django Filter Backend:
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import ModelMixin
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
filter_backends = [DjangoFilterBackend]
filterset_fields = Product.get_filter_fields() # Use auto-generated fields
Filter related models:
# Get filter fields for a related model if Product has a ForeignKey to Category
filter_fields = Product.get_filter_fields_for_related_model('category')
# Returns: {'category__name': [...], 'category__id': [...], ...}
# Get filter fields for a foreign key field if Product is a ForeignKey to Order
filter_fields = Order.get_filter_fields_for_foreign_fields('product')
# Returns: {'product__name': [...], 'product__price': [...], ...}
ModelFilterFieldsMixin
⚠️ Requires: django-filter package. Install with pip install django-filter or pip install django-api-mixins[filters]
Automatically sets filterset_fields from a model that uses ModelMixin (or any model with a get_filter_fields() class method). Works with APIView, GenericAPIView, and ViewSets.
You must set model on the view so the mixin can resolve filter fields (e.g. model = Unit). For ViewSets, use the same model as your queryset.
APIView: default get() — list and detail in one view
When used with APIView, the mixin provides a default get(request, *args, **kwargs) so a single view can serve both list and detail:
- Detail: If the URL includes the lookup key (default
pk) inkwargs— e.g.GET /units/5/— the mixin returns the single object (serialized) or 404 if not found. Filtering still applies to the base queryset before fetching the object. - List: If the lookup key is not in
kwargs— e.g.GET /units/— the mixin returns the filtered list (no pagination). Query params are applied viadjango-filterandfilterset_fields.
You must set serializer_class on the view for the default get() to work. Optionally set detail_not_found_message (default "Not found"), lookup_url_kwarg (default "pk"), and lookup_field (default "pk").
Example: use the default get() (no override)
from rest_framework.views import APIView
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import ModelFilterFieldsMixin
class UnitListDetailAPIView(ModelFilterFieldsMixin, APIView):
model = Unit # required
serializer_class = UnitSerializer
filter_backends = [DjangoFilterBackend]
# Optional: detail_not_found_message = "Unit not found"
# Optional: override to customize queryset; otherwise mixin uses model.objects.all()
def get_queryset(self):
return Unit.objects.all()
# Do NOT override get() — mixin handles list and detail
GET /units/→ filtered list (e.g.?name=fooapplied).GET /units/42/→ single unit with id 42, or 404.
APIView: override get() for custom behavior
You can override get() and still reuse the mixin's helpers:
get_filtered_queryset()— base queryset with all filter backends applied.get_object(pk=None)— single instance by pk (from URL kwargs ifpkomitted); raisesHttp404if not found.get_detail_data(request, *args, **kwargs)— returns(body, status_code)for the detail response (serialized object or 404 body).get_list_data(request, *args, queryset=None, **kwargs)— returns(data, status_code)for the list response; ifquerysetis passed (e.g. a paginated page), that is used instead of the full filtered queryset.
Example: add pagination for list only; detail unchanged
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import ModelFilterFieldsMixin
class UnitListDetailAPIView(ModelFilterFieldsMixin, APIView):
model = Unit
serializer_class = UnitSerializer
filter_backends = [DjangoFilterBackend]
pagination_class = PageNumberPagination
def get_queryset(self):
return Unit.objects.all()
def get(self, request, *args, **kwargs):
pk = kwargs.get(self.lookup_url_kwarg) # default: 'pk'
if pk is not None:
# Detail: single object or 404
data, status_code = self.get_detail_data(request, *args, **kwargs)
return Response(data, status=status_code)
# List: paginate filtered queryset, then serialize
queryset = self.get_filtered_queryset()
paginator = self.pagination_class()
page = paginator.paginate_queryset(queryset, request)
if page is not None:
data, _ = self.get_list_data(request, *args, queryset=page, **kwargs)
return paginator.get_paginated_response(data)
data, status_code = self.get_list_data(request, *args, **kwargs)
return Response(data, status=status_code)
Example: fully custom get() using get_object() and get_filtered_queryset()
from rest_framework.views import APIView
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import ModelFilterFieldsMixin
class UnitListDetailAPIView(ModelFilterFieldsMixin, APIView):
model = Unit
serializer_class = UnitSerializer
filter_backends = [DjangoFilterBackend]
def get_queryset(self):
return Unit.objects.all()
def get(self, request, *args, **kwargs):
pk = kwargs.get(self.lookup_url_kwarg)
if pk is not None:
# Detail: use get_object(); returns 404 if not found
obj = self.get_object(pk=pk)
serializer = self.get_serializer(obj)
return Response(serializer.data)
# List: use get_filtered_queryset() and serialize
queryset = self.get_filtered_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Example: list + detail without pagination (with Swagger/OpenAPI filter params)
If you want list and detail in one APIView without pagination and you want filter parameters to appear in Swagger/OpenAPI docs, combine OpenAPIFilterParametersMixin with ModelFilterFieldsMixin and override get():
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import OpenAPIFilterParametersMixin, ModelFilterFieldsMixin
# Optional: add to Swagger UI tags (requires drf-spectacular)
# from drf_spectacular.utils import extend_schema
# @extend_schema(tags=["Examples - ModelFilterFieldsMixin"])
class UnitListAPIView(OpenAPIFilterParametersMixin, ModelFilterFieldsMixin, APIView):
"""
Plain APIView with ModelFilterFieldsMixin + OpenAPIFilterParametersMixin.
List and detail in one view; no pagination. Filter params appear in Swagger.
URL example: path('units-api/', UnitListAPIView.as_view(), name='units-api'),
"""
model = Unit
serializer_class = UnitSerializer
filter_backends = [DjangoFilterBackend]
# Optional: set filterset_fields manually; otherwise mixin uses model.get_filter_fields()
# filterset_fields = Unit.get_filter_fields()
def get(self, request, *args, **kwargs):
pk = kwargs.get(self.lookup_url_kwarg)
if pk is not None:
data, status_code = self.get_detail_data(request, *args, **kwargs)
return Response(data, status=status_code)
queryset = self.get_filtered_queryset()
serializer = self.get_serializer_class()(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
ViewSet / GenericAPIView
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import ModelFilterFieldsMixin
# ViewSet / GenericAPIView: set model (filter fields come from model.get_filter_fields())
class UnitViewSet(ModelFilterFieldsMixin, viewsets.ModelViewSet):
queryset = Unit.objects.all()
model = Unit # required — Unit must have ModelMixin or get_filter_fields()
serializer_class = UnitSerializer
filter_backends = [DjangoFilterBackend]
# filterset_fields auto-set from Unit.get_filter_fields()
# Optional: use a different model for filter fields than the queryset model
class MyViewSet(ModelFilterFieldsMixin, viewsets.ModelViewSet):
queryset = SomeProxy.objects.all()
model = Unit # required
filterset_model = Unit # use Unit.get_filter_fields() instead of SomeProxy
filter_backends = [DjangoFilterBackend]
Note: If django-filter is not installed, you'll get a clear error message with installation instructions when you try to use this mixin. You can still set filterset_fields explicitly on the view to override the auto-generated fields.
OpenAPIFilterParametersMixin
⚠️ Requires: Both django-filter and drf-spectacular packages. Install with pip install django-filter drf-spectacular or pip install django-api-mixins[all]
Adds OpenAPI/Swagger filter parameters for plain APIView so Swagger shows the same filter query params as GenericAPIView/ViewSet. For GenericAPIView/ViewSet this mixin is a no-op (Spectacular already adds params).
from rest_framework.views import APIView
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import OpenAPIFilterParametersMixin, ModelFilterFieldsMixin
# With Swagger/OpenAPI filter parameters
class UnitListAPIView(OpenAPIFilterParametersMixin, ModelFilterFieldsMixin, APIView):
model = Unit # required
filter_backends = [DjangoFilterBackend]
def get_queryset(self):
return Unit.objects.all()
def get(self, request):
queryset = self.filter_queryset(self.get_queryset())
# ... rest of your logic
# Filter parameters will appear in Swagger/OpenAPI docs
# Without Swagger params: use only ModelFilterFieldsMixin
class UnitListAPIView(ModelFilterFieldsMixin, APIView):
model = Unit
filter_backends = [DjangoFilterBackend]
# Filtering works, but params won't appear in OpenAPI docs
Note: If django-filter or drf-spectacular is not installed, you'll get a clear error message with installation instructions when you try to use this mixin.
APIMixin
Use different serializers for different actions (create, update, list, retrieve):
from rest_framework import viewsets
from django_api_mixins import APIMixin
class UserViewSet(APIMixin, viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer # Default serializer
create_serializer_class = UserCreateSerializer # For POST requests
update_serializer_class = UserUpdateSerializer # For PUT/PATCH requests
list_serializer_class = UserListSerializer # For GET list requests
retrieve_serializer_class = UserDetailSerializer # For GET detail requests
The mixin also automatically handles list data in requests:
# POST /api/users/
# Body: [{"name": "User1"}, {"name": "User2"}]
# Automatically sets many=True for list data
RelationshipFilterMixin
Automatically apply filters for reverse relationships and direct fields:
from rest_framework import viewsets
from django_api_mixins import RelationshipFilterMixin
class OrderViewSet(RelationshipFilterMixin, viewsets.ModelViewSet):
queryset = Order.objects.all()
# Define which reverse relationship filters to allow
reverse_relation_filters = [
'customer__name',
'customer__email',
'product__category__name',
]
# Define which filters support list/array values
listable_filters = ['customer__id', 'product__id']
Important: Place the mixin BEFORE the ViewSet class:
# ✅ Correct
class OrderViewSet(RelationshipFilterMixin, viewsets.ModelViewSet):
pass
# ❌ Wrong
class OrderViewSet(viewsets.ModelViewSet, RelationshipFilterMixin):
pass
Usage examples:
# Filter by customer name
GET /api/orders/?customer__name=John
# Filter by multiple customer IDs (listable filter)
GET /api/orders/?customer__id=1,2,3
# or
GET /api/orders/?customer__id=[1,2,3]
# Filter by product category
GET /api/orders/?product__category__name=Electronics
RoleBasedFilterMixin
Automatically filter querysets based on user roles:
from rest_framework import viewsets
from django_api_mixins import RoleBasedFilterMixin
class OrderViewSet(RoleBasedFilterMixin, viewsets.ModelViewSet):
queryset = Order.objects.all()
# Required: field name to filter on
role_filter_field = 'operator_type'
# Optional: roles that see all data
admin_roles = ['admin', 'super_admin']
# Optional: roles that see no data
excluded_roles = ['guest']
# Optional: custom role to field value mapping
role_mapping = {
'custom_role': 'custom_value',
'manager': 'MGR',
}
Important: Place the mixin BEFORE the ViewSet class:
# ✅ Correct
class OrderViewSet(RoleBasedFilterMixin, viewsets.ModelViewSet):
pass
# ❌ Wrong
class OrderViewSet(viewsets.ModelViewSet, RoleBasedFilterMixin):
pass
How it works:
- Extracts the user's role from
user.role.name - Admin roles see all data (no filtering)
- Excluded roles see no data (empty queryset)
- Other roles are filtered by
role_filter_field = role_name(or mapped value)
Example:
# User with role.name = 'operator'
# Model has operator_type field
# Automatically filters: Order.objects.filter(operator_type='operator')
# User with role.name = 'admin'
# Sees all orders (no filtering)
# User with role.name = 'guest'
# Sees no orders (queryset.none())
FieldLookup Enum
The package includes a FieldLookup enum for consistent lookup naming:
from django_api_mixins import FieldLookup
FieldLookup.EXACT # "exact"
FieldLookup.ICONTAINS # "icontains"
FieldLookup.CONTAINS # "contains"
FieldLookup.ISNULL # "isnull"
FieldLookup.GTE # "gte"
FieldLookup.LTE # "lte"
FieldLookup.IN # "in"
Combining Mixins
You can combine multiple mixins:
from rest_framework import viewsets
from django_filters.rest_framework import DjangoFilterBackend
from django_api_mixins import (
ModelFilterFieldsMixin,
APIMixin,
RelationshipFilterMixin,
RoleBasedFilterMixin,
)
class OrderViewSet(
ModelFilterFieldsMixin, # Auto-set filterset_fields from model
RelationshipFilterMixin,
RoleBasedFilterMixin,
APIMixin,
viewsets.ModelViewSet
):
queryset = Order.objects.all()
model = Order # required for ModelFilterFieldsMixin
serializer_class = OrderSerializer
create_serializer_class = OrderCreateSerializer
filter_backends = [DjangoFilterBackend]
role_filter_field = 'operator_type'
reverse_relation_filters = ['customer__name', 'product__category__name']
listable_filters = ['customer__id']
Note: The order of mixins matters! Place filtering mixins before the ViewSet class.
Optional Dependencies: If using ModelFilterFieldsMixin or OpenAPIFilterParametersMixin, make sure to install the required dependencies:
ModelFilterFieldsMixinrequires:django-filterOpenAPIFilterParametersMixinrequires:django-filteranddrf-spectacular
Contributing
Contributions are welcome! Here’s how to contribute:
-
Fork the repository on GitHub and clone your fork locally:
git clone https://github.com/YOUR_USERNAME/django-api-mixins.git cd django-api-mixins
-
Create a virtual environment and install the package in editable mode with dev dependencies:
python -m venv venv # On Windows: venv\Scripts\activate # On macOS/Linux: source venv/bin/activate pip install -e ".[all]" pip install -r requirements-dev.txt # if present
-
Create a branch for your changes:
git checkout -b feature/your-feature-name # or: git checkout -b fix/your-bugfix-name
-
Make your changes — keep code style consistent and add/update tests as needed.
-
Run tests (and linting, if configured):
pytest # or: python -m pytest -
Commit and push to your fork:
git add . git commit -m "Brief description of your change" git push origin feature/your-feature-name
-
Open a Pull Request from your branch to the main repository’s default branch. Describe what you changed and why; link any related issues.
Contributors
License
MIT License - see LICENSE file for details.
Support
For issues, questions, or contributions, please visit the GitHub repository.
PyPI
This package is published on PyPI and can be installed via pip:
pip install django-api-mixins
PyPI Project Page: https://pypi.org/project/django-api-mixins/
Latest Version: 1.0.4 (Released: May 03, 2026)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_api_mixins-1.0.4.tar.gz.
File metadata
- Download URL: django_api_mixins-1.0.4.tar.gz
- Upload date:
- Size: 22.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40f9e154d09a61381c73c1897f58b9bcbdc317c106711a3dc8b1f443de606bf3
|
|
| MD5 |
245201416a44c6066feed614787e0cc7
|
|
| BLAKE2b-256 |
0e4157ee12beed7ec25b46145a35c2848a060add3ddf08625ec447c6e95fb99b
|
File details
Details for the file django_api_mixins-1.0.4-py3-none-any.whl.
File metadata
- Download URL: django_api_mixins-1.0.4-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3702dd40ad471e481a38768081a59bc94c02edbd0ffd08fc97d748575e744463
|
|
| MD5 |
b0acf16763157d06a4dcf3cd20fc133a
|
|
| BLAKE2b-256 |
da3c8ed77533fd862c88a701fa3c710f86db8e9e3ac5d5717620939c6853b563
|