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
- Features
- Installation
- Requirements
- Quick Start
- FieldLookup Enum
- Combining Mixins
- Contributing
- License
- 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
Basic installation:
pip install django-api-mixinsWith 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-mixinsCore 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
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, isnullUse 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 fieldsFilter 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': [...], ...}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.
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 detailGET /units/→ filtered list (e.g.?name=fooapplied).GET /units/42/→ single unit with id 42, or 404.
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)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.
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 docsNote: 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.
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 requestsThe mixin also automatically handles list data in requests:
# POST /api/users/
# Body: [{"name": "User1"}, {"name": "User2"}]
# Automatically sets many=True for list dataAutomatically 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):
passUsage 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=ElectronicsAutomatically 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):
passHow 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())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"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
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.
MIT License - see LICENSE file for details.
For issues, questions, or contributions, please visit the GitHub repository.
This package is published on PyPI and can be installed via pip:
pip install django-api-mixinsPyPI Project Page: https://pypi.org/project/django-api-mixins/
Latest Version: 1.0.2 (Released: Mar 05, 2026)