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.