Skip to content

Commit 0a66e48

Browse files
authored
Merge pull request #10 from Subhrans/dev
🎨 Updated document (1.0.0)
2 parents 3f08e7c + 765ec1a commit 0a66e48

3 files changed

Lines changed: 191 additions & 44 deletions

File tree

README.md

Lines changed: 189 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,31 @@
66
[![Python](https://img.shields.io/pypi/pyversions/django-api-mixins)](https://pypi.org/project/django-api-mixins/)
77
[![Django](https://img.shields.io/badge/Django-3.2%2B-blue)](https://www.djangoproject.com/)
88

9+
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/django-api-mixins?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/django-api-mixins)
10+
911
**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.
1012

1113
**📦 Available on PyPI**: [https://pypi.org/project/django-api-mixins/](https://pypi.org/project/django-api-mixins/)
1214

1315
**Keywords**: Django REST Framework, DRF, Django API, ViewSets, APIViews, Mixins, Django Mixins, REST API, Serializers, Queryset Filtering
1416

17+
## Table of contents
18+
19+
- [Features](#features)
20+
- [Installation](#installation)
21+
- [Requirements](#requirements)
22+
- [Quick Start](#quick-start)
23+
- [FieldLookup Enum](#fieldlookup-enum)
24+
- [Combining Mixins](#combining-mixins)
25+
- [Contributing](#contributing)
26+
- [License](#license)
27+
1528
## Features
1629

17-
- **APIMixin**: Dynamic serializer selection based on action (create, update, list, retrieve)
1830
- **ModelMixin**: Automatic filter field generation for Django models
1931
- **ModelFilterFieldsMixin**: Automatically sets `filterset_fields` from models with `get_filter_fields()` (requires `django-filter`)
2032
- **OpenAPIFilterParametersMixin**: Adds OpenAPI/Swagger filter parameters for APIView (requires `django-filter` and `drf-spectacular`)
33+
- **APIMixin**: Dynamic serializer selection based on action (create, update, list, retrieve)
2134
- **RelationshipFilterMixin**: Automatic filtering for reverse relationships and direct fields
2235
- **RoleBasedFilterMixin**: Role-based queryset filtering for multi-tenant applications
2336

@@ -58,31 +71,6 @@ pip install --upgrade django-api-mixins
5871

5972
## Quick Start
6073

61-
### APIMixin
62-
63-
Use different serializers for different actions (create, update, list, retrieve):
64-
65-
```python
66-
from rest_framework import viewsets
67-
from django_api_mixins import APIMixin
68-
69-
class UserViewSet(APIMixin, viewsets.ModelViewSet):
70-
queryset = User.objects.all()
71-
serializer_class = UserSerializer # Default serializer
72-
create_serializer_class = UserCreateSerializer # For POST requests
73-
update_serializer_class = UserUpdateSerializer # For PUT/PATCH requests
74-
list_serializer_class = UserListSerializer # For GET list requests
75-
retrieve_serializer_class = UserDetailSerializer # For GET detail requests
76-
```
77-
78-
The mixin also automatically handles list data in requests:
79-
80-
```python
81-
# POST /api/users/
82-
# Body: [{"name": "User1"}, {"name": "User2"}]
83-
# Automatically sets many=True for list data
84-
```
85-
8674
### ModelMixin
8775

8876
Automatically generate filter fields for all model fields:
@@ -135,40 +123,173 @@ filter_fields = Order.get_filter_fields_for_foreign_fields('product')
135123

136124
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.
137125

126+
**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.
127+
128+
#### APIView: default `get()` — list and detail in one view
129+
130+
When used with **APIView**, the mixin provides a default `get(request, *args, **kwargs)` so a single view can serve both list and detail:
131+
132+
- **Detail**: If the URL includes the lookup key (default `pk`) in `kwargs` — 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.
133+
- **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 via `django-filter` and `filterset_fields`.
134+
135+
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"`).
136+
137+
**Example: use the default `get()` (no override)**
138+
138139
```python
139-
from rest_framework import viewsets
140+
from rest_framework.views import APIView
140141
from django_filters.rest_framework import DjangoFilterBackend
141142
from django_api_mixins import ModelFilterFieldsMixin
142143

143-
# ViewSet / GenericAPIView: filter fields come from queryset.model
144-
class UnitViewSet(ModelFilterFieldsMixin, viewsets.ModelViewSet):
145-
queryset = Unit.objects.all() # Unit must have ModelMixin or get_filter_fields()
144+
class UnitListDetailAPIView(ModelFilterFieldsMixin, APIView):
145+
model = Unit # required
146146
serializer_class = UnitSerializer
147147
filter_backends = [DjangoFilterBackend]
148-
# filterset_fields auto-set from Unit.get_filter_fields()
148+
# Optional: detail_not_found_message = "Unit not found"
149+
150+
# Optional: override to customize queryset; otherwise mixin uses model.objects.all()
151+
def get_queryset(self):
152+
return Unit.objects.all()
153+
154+
# Do NOT override get() — mixin handles list and detail
155+
```
156+
157+
- `GET /units/` → filtered list (e.g. `?name=foo` applied).
158+
- `GET /units/42/` → single unit with id 42, or 404.
159+
160+
#### APIView: override `get()` for custom behavior
161+
162+
You can override `get()` and still reuse the mixin's helpers:
163+
164+
- **`get_filtered_queryset()`** — base queryset with all filter backends applied.
165+
- **`get_object(pk=None)`** — single instance by pk (from URL kwargs if `pk` omitted); raises `Http404` if not found.
166+
- **`get_detail_data(request, *args, **kwargs)`** — returns `(body, status_code)` for the detail response (serialized object or 404 body).
167+
- **`get_list_data(request, *args, queryset=None, **kwargs)`** — returns `(data, status_code)` for the list response; if `queryset` is passed (e.g. a paginated page), that is used instead of the full filtered queryset.
149168

150-
# APIView: set model so the mixin can resolve filter fields
169+
**Example: add pagination for list only; detail unchanged**
170+
171+
```python
151172
from rest_framework.views import APIView
173+
from rest_framework.response import Response
174+
from rest_framework.pagination import PageNumberPagination
175+
from django_filters.rest_framework import DjangoFilterBackend
176+
from django_api_mixins import ModelFilterFieldsMixin
152177

153-
class UnitListAPIView(ModelFilterFieldsMixin, APIView):
154-
model = Unit # required for APIView
178+
class UnitListDetailAPIView(ModelFilterFieldsMixin, APIView):
179+
model = Unit
180+
serializer_class = UnitSerializer
155181
filter_backends = [DjangoFilterBackend]
156-
182+
pagination_class = PageNumberPagination
183+
157184
def get_queryset(self):
158185
return Unit.objects.all()
159-
160-
def get(self, request):
161-
queryset = self.filter_queryset(self.get_queryset())
162-
# ... rest of your logic
186+
187+
def get(self, request, *args, **kwargs):
188+
pk = kwargs.get(self.lookup_url_kwarg) # default: 'pk'
189+
if pk is not None:
190+
# Detail: single object or 404
191+
data, status_code = self.get_detail_data(request, *args, **kwargs)
192+
return Response(data, status=status_code)
193+
# List: paginate filtered queryset, then serialize
194+
queryset = self.get_filtered_queryset()
195+
paginator = self.pagination_class()
196+
page = paginator.paginate_queryset(queryset, request)
197+
if page is not None:
198+
data, _ = self.get_list_data(request, *args, queryset=page, **kwargs)
199+
return paginator.get_paginated_response(data)
200+
data, status_code = self.get_list_data(request, *args, **kwargs)
201+
return Response(data, status=status_code)
202+
```
203+
204+
**Example: fully custom `get()` using `get_object()` and `get_filtered_queryset()`**
205+
206+
```python
207+
from rest_framework.views import APIView
208+
from rest_framework.response import Response
209+
from django_filters.rest_framework import DjangoFilterBackend
210+
from django_api_mixins import ModelFilterFieldsMixin
211+
212+
class UnitListDetailAPIView(ModelFilterFieldsMixin, APIView):
213+
model = Unit
214+
serializer_class = UnitSerializer
215+
filter_backends = [DjangoFilterBackend]
216+
217+
def get_queryset(self):
218+
return Unit.objects.all()
219+
220+
def get(self, request, *args, **kwargs):
221+
pk = kwargs.get(self.lookup_url_kwarg)
222+
if pk is not None:
223+
# Detail: use get_object(); returns 404 if not found
224+
obj = self.get_object(pk=pk)
225+
serializer = self.get_serializer(obj)
226+
return Response(serializer.data)
227+
# List: use get_filtered_queryset() and serialize
228+
queryset = self.get_filtered_queryset()
229+
serializer = self.get_serializer(queryset, many=True)
230+
return Response(serializer.data)
231+
```
232+
233+
**Example: list + detail without pagination (with Swagger/OpenAPI filter params)**
234+
235+
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()`:
236+
237+
```python
238+
from rest_framework.views import APIView
239+
from rest_framework.response import Response
240+
from rest_framework import status
241+
from django_filters.rest_framework import DjangoFilterBackend
242+
from django_api_mixins import OpenAPIFilterParametersMixin, ModelFilterFieldsMixin
243+
244+
# Optional: add to Swagger UI tags (requires drf-spectacular)
245+
# from drf_spectacular.utils import extend_schema
246+
# @extend_schema(tags=["Examples - ModelFilterFieldsMixin"])
247+
class UnitListAPIView(OpenAPIFilterParametersMixin, ModelFilterFieldsMixin, APIView):
248+
"""
249+
Plain APIView with ModelFilterFieldsMixin + OpenAPIFilterParametersMixin.
250+
List and detail in one view; no pagination. Filter params appear in Swagger.
251+
URL example: path('units-api/', UnitListAPIView.as_view(), name='units-api'),
252+
"""
253+
model = Unit
254+
serializer_class = UnitSerializer
255+
filter_backends = [DjangoFilterBackend]
256+
# Optional: set filterset_fields manually; otherwise mixin uses model.get_filter_fields()
257+
# filterset_fields = Unit.get_filter_fields()
258+
259+
def get(self, request, *args, **kwargs):
260+
pk = kwargs.get(self.lookup_url_kwarg)
261+
if pk is not None:
262+
data, status_code = self.get_detail_data(request, *args, **kwargs)
263+
return Response(data, status=status_code)
264+
queryset = self.get_filtered_queryset()
265+
serializer = self.get_serializer_class()(queryset, many=True)
266+
return Response(serializer.data, status=status.HTTP_200_OK)
267+
```
268+
269+
#### ViewSet / GenericAPIView
270+
271+
```python
272+
from rest_framework import viewsets
273+
from django_filters.rest_framework import DjangoFilterBackend
274+
from django_api_mixins import ModelFilterFieldsMixin
275+
276+
# ViewSet / GenericAPIView: set model (filter fields come from model.get_filter_fields())
277+
class UnitViewSet(ModelFilterFieldsMixin, viewsets.ModelViewSet):
278+
queryset = Unit.objects.all()
279+
model = Unit # required — Unit must have ModelMixin or get_filter_fields()
280+
serializer_class = UnitSerializer
281+
filter_backends = [DjangoFilterBackend]
282+
# filterset_fields auto-set from Unit.get_filter_fields()
163283

164284
# Optional: use a different model for filter fields than the queryset model
165285
class MyViewSet(ModelFilterFieldsMixin, viewsets.ModelViewSet):
166286
queryset = SomeProxy.objects.all()
287+
model = Unit # required
167288
filterset_model = Unit # use Unit.get_filter_fields() instead of SomeProxy
168289
filter_backends = [DjangoFilterBackend]
169290
```
170291

171-
**Note**: If `django-filter` is not installed, you'll get a clear error message with installation instructions when you try to use this mixin.
292+
**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.
172293

173294
### OpenAPIFilterParametersMixin
174295

@@ -203,6 +324,31 @@ class UnitListAPIView(ModelFilterFieldsMixin, APIView):
203324

204325
**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.
205326

327+
### APIMixin
328+
329+
Use different serializers for different actions (create, update, list, retrieve):
330+
331+
```python
332+
from rest_framework import viewsets
333+
from django_api_mixins import APIMixin
334+
335+
class UserViewSet(APIMixin, viewsets.ModelViewSet):
336+
queryset = User.objects.all()
337+
serializer_class = UserSerializer # Default serializer
338+
create_serializer_class = UserCreateSerializer # For POST requests
339+
update_serializer_class = UserUpdateSerializer # For PUT/PATCH requests
340+
list_serializer_class = UserListSerializer # For GET list requests
341+
retrieve_serializer_class = UserDetailSerializer # For GET detail requests
342+
```
343+
344+
The mixin also automatically handles list data in requests:
345+
346+
```python
347+
# POST /api/users/
348+
# Body: [{"name": "User1"}, {"name": "User2"}]
349+
# Automatically sets many=True for list data
350+
```
351+
206352
### RelationshipFilterMixin
207353

208354
Automatically apply filters for reverse relationships and direct fields:
@@ -336,8 +482,8 @@ You can combine multiple mixins:
336482
from rest_framework import viewsets
337483
from django_filters.rest_framework import DjangoFilterBackend
338484
from django_api_mixins import (
339-
APIMixin,
340485
ModelFilterFieldsMixin,
486+
APIMixin,
341487
RelationshipFilterMixin,
342488
RoleBasedFilterMixin,
343489
)
@@ -350,6 +496,7 @@ class OrderViewSet(
350496
viewsets.ModelViewSet
351497
):
352498
queryset = Order.objects.all()
499+
model = Order # required for ModelFilterFieldsMixin
353500
serializer_class = OrderSerializer
354501
create_serializer_class = OrderCreateSerializer
355502
filter_backends = [DjangoFilterBackend]
@@ -434,4 +581,4 @@ pip install django-api-mixins
434581

435582
**PyPI Project Page**: [https://pypi.org/project/django-api-mixins/](https://pypi.org/project/django-api-mixins/)
436583

437-
**Latest Version**: 0.1.5 (Released: Feb 24, 2026)
584+
**Latest Version**: 1.0.0 (Released: Feb 24, 2026)

django_api_mixins/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
)
1515
from .lookups import FieldLookup
1616

17-
__version__ = "0.1.5"
17+
__version__ = "1.0.0"
1818
__all__ = [
1919
"APIMixin",
2020
"ModelMixin",

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "django-api-mixins"
7-
version = "0.1.5"
7+
version = "1.0.0"
88
description = "Django REST Framework mixins for ViewSets and APIViews - APIMixin, ModelMixin, RelationshipFilterMixin, RoleBasedFilterMixin. Simplify Django API development with reusable mixins."
99
readme = "README.md"
1010
requires-python = ">=3.8"

0 commit comments

Comments
 (0)