Table of Contents
ViewSets
ModelViewset
Basic ModelViewSet
Basic usage of ModelViewset is to define 2 objects
- queryset - database query for your model
- serializer_class - class for converting model query into JSON string.
from rest_framework.viewsets import ModelViewSet
class MyViewSet(ModelViewSet):
# define queryset.
queryset = MyModel.objects.all()
# define serializer.
serializer_class = MyModelSerializer
Skip Authenitcation Checking
Use AllowAny if you want to skip authentication checking.
class MyViewSet(ModelViewSet):
# Use AllowAny if you want to skip authentication checking.
permission_classes = (AllowAny,)
Customize Seriailizer
If you want different serializer for GET/POST request, you can use following attribute:
- list_serializer_class - for GET request
- retrieve_serializer_class - for GET request with item ID
- write_serializer_class - for POST request
# Good:
class MyViewSet(ModelViewSet):
# define serializer for GET request
list_serializer_class = MyModelListSerializer
# define serializer for GET request with item ID
retrieve_serializer_class = MyModelDetailSerializer
# define serializer for POST request
write_serializer_class = MyModelWriteSerializer
# Bad:
class MyViewSet(ModelViewSet):
serializer_class = MyModelSerializer
def get_serializer_class(self):
serializer = super().get_serializer_class()
if self.request.method == 'GET':
if self.kwargs.get('pk'):
return MyModelDetailSerializer
else:
return MyModelListSerializer
elif self.request.method == 'POST':
return MyModelWriteSerializer
else
return serializer
Use Django Built-in FilterClass/Ordering
For simple search/filter/ordering, use Django built-in FilterClass. To setup, run pip install django-filter
. Then add 'django_filters'
to Django's INSTALLED_APPS
# Good:
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
class MyViewSet(ModelViewSet):
filter_backends = [filters.SearchFilter, DjangoFilterBackend, filters.OrderingFilter]
# DjangoFilterBackend used for equality filter.
# Ex. localhost:8000/api/department/?name=ABC
# it will search for name='ABC'
# Ex. localhost:8000/api/department/?name_en=ABC
# it will search for name_en='ABC'
filterset_fields = ['name', 'name_en']
# SearchFilter used for searching multiple fields at once.
# Ex. localhost:8000/api/department/?search=ABC
# it will search for field 'name' contains 'ABC' or 'name_en' contains 'ABC'
search_fields = ['name', 'name_en', ]
# OrderingFilter used for controlling order results.
# Ex. localhost:8000/api/department/?ordering=account,-username
# it will order by account ascending, then username descending
ordering_fields = ['username', 'account']
# set default ordering.
ordering = ['username']
# Bad:
class MyViewSet(ModelViewSet):
def get_queryset(self):
queryset = super().get_queryset()
name = self.request.GET.get('name')
if name:
queryset = queryset.filter(name=name)
name_en = self.request.GET.get('name_en')
if name_en:
queryset = queryset.filter(name_en=name_en)
search = self.request.GET.get('search')
...
ordering = self.request.GET.get('ordering')
...
return queryset
Viewset Actions
You can create custom url for viewset by using @action
decorator. It is better than creating separated APIViews.
class MyViewset(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
@action(detail=False, methods=['GET'], url_name='my_reverse_url_name', url_path='my_url_path')
def my_custom_viewset_action(self, request):
# do something ...
return Response( ... )
Then, in your api_urls.py
router.register(r'my-viewset', MyViewset)
You can call GET localhost:8000/my-viewset/my_url_path/
which Django will call function my_custom_viewset_action
If you set detail=True
in action parameters, you need to add pk
parameter in your action function.
@action(detail=True, methods=['GET'], url_name='my_reverse_url_name', url_path='my_url_path')
def my_custom_viewset_action(self, request, pk):
# do something ...
return Response( ... )
For example: calling GET localhost:8000/my-viewset/10/my_url_path/
which Django will call function my_custom_viewset_action
with parameter pk=10
Use Custom FilterClass
In case that Django Built-In FilterClass does not meet your requirements. You can create your own FilterClass and set it in ModelViewset below.
class MyViewSet(ModelViewSet):
# define filterclass
filter_class = MyModelFilter
def get_queryset(self):
#
# In case you want to modify queryset.
# This function will be called before queryset goes to FilterClass
#
queryset = super().get_queryset()
#
# do something here. Ex. You can add .select_related() to queryset in some conditions.
#
if some_conditions:
queryset = queryset.select_related('...')
return queryset
Customize HTTP Method
You should limit http method that user can call to the server. ModelViewset will enable all methods by default.
# Good
class MyViewSet(ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
http_method_names = ['get', 'head', 'post', 'patch']
# Bad
class MyViewset(ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
def get_queryset(self):
if not self.request.method in ['GET', 'POST', 'PATCH']:
raise Exception('method invalid')
else
return super().get_queryset()
Generic Views
ReadOnlyModelViewSet
Use ReadonlyModelViewset if you want request in GET and GET with item id only.
# Good
class MyViewSet(ReadOnlyModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
# Bad
class MyViewSet(ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
http_method_names = ['get', 'head']
Other Generic Views
Moreover, there are many generic views as follows
Generic View Class | GET (Listing) | GET (item ID) | POST | PUT/PATCH | DELETE |
---|---|---|---|---|---|
CreateAPIView | Y | ||||
ListAPIView | Y | ||||
RetrieveAPIView | Y | ||||
DestroyAPIView | Y | ||||
UpdateAPIView | Y | ||||
ListCreateAPIView | Y | Y | |||
RetrieveUpdateAPIView | Y | Y | |||
RetrieveDestroyAPIView | Y | Y | |||
RetrieveUpdateDestroyAPIView | Y | Y | Y |
APIView
Use APIView if request does not specifically belongs to some model. Use JsonResponse or Response instead of HttpResponse
# Good
from django import http
class UserViewSet(views.APIView):
def get(self, request):
return http.JsonResponse({
'users': 123,
})
# Good
from rest_framework.response import Response
class UserViewSet(views.APIView):
def get(self, request):
return Response(data={
'users': 123,
})
# Bad
class UserViewSet(views.APIView):
def get(self, request):
response_json_str = json.dumps({
'users': 123,
})
return http.HttpResponse(response_json_str, content_type="application/json", status=200)
Use HttpResponseStatus instead of status number
# Good
from rest_framework import status
class UserViewSet(views.APIView):
def post(self, request):
return Response(data={}, status=status.HTTP_201_CREATED)
# Bad
class UserViewSet(views.APIView):
def post(self, request):
return Response(data={}, status=201)
If you want to use status 200 (OK), it is not necessary to write because it is the default status code for the response.