This page looks best with JavaScript enabled

ViewSet and Serializer in restframework

 ·  ☕ 6 min read

1. View Class in Django

First, recall how Django handles a request. After receiving a request, Django creates a handler of type WSGIHandler, and the handler controls the entire processing flow.

So how are the request URL and the View associated with each other?

Django first loads the URLconf according to the ROOT_URLCONF setting, then matches the URLpatterns in the URLconf one by one in order, stopping as soon as a match is found. It then calls the view function of the URLpattern with the HttpRequest object as an argument.

Now let’s look at how class-based view routing is configured:

1
2
3
4
5
6
from django.conf.urls import url
from myapp import views

urlpatterns = [
    url(r'^fruit/', views.FruitView.as_view()),
]

django/views/generic/base.py defines the View class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class View(object):
    @classonlymethod
    def as_view(cls, **initkwargs):
	    ...
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)
		...
        return view

	def dispatch(self, request, *args, **kwargs):
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

By calling the as_view() method, the returned dispatch() function is handed to the routing function.

The dispatch() function, based on the request method, calls the function in the View class with the same name as the request method.

This makes the processing logic very clear. Compared with function views, class views have an extra dispatch step, which can be understood as a second round of routing.

2. View Class in restframework

restframework inherits from Django’s View and implements three levels of View, namely APIView, GenericAPIView, and GenericViewSet. Let’s analyze them one by one below.

2.1 APIView

rest_framewor/views.py defines the APIView class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from rest_framework.request import Request

class APIView(View):
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
	...
	def initial(self, request, *args, **kwargs):
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
	def dispatch(self, request, *args, **kwargs):
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        try:
            self.initial(request, *args, **kwargs)
            handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)

            response = handler(request, *args, **kwargs)

        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

In the initial function, the API version, authentication and authorization, permission checks, and access rate are validated. At the same time, restframework wraps the request once again.

In usage, restframework’s APIView is not very different from Django’s View, but it enhances the functionality the View provides.

2.2 GenericAPIView

rest_framework/generics.py defines the GenericAPIView class:

1
2
3
4
5
6
7
8
9
class GenericAPIView(views.APIView):
    queryset = None
    serializer_class = None
    lookup_field = 'pk'
    lookup_url_kwarg = None
    filter_backends = api_settings.DEFAULT_FILTER_BACKENDS
    pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
    def get_queryset(self):
        ...

GenericAPIView inherits from APIView and adds a number of class methods:

  • get_queryset, gets the QuerySet
  • get_object, gets a single record
  • get_serializer, gets the serialized data
  • get_serializer_class, gets the model class to be serialized
  • get_serializer_context, gets the data to be serialized, defining a dictionary of some format
  • paginator, the paginator

Besides implementing a View Class by inheriting directly from GenericAPIView, restframework also provides a large number of mixins. By inheriting from mixins, you can quickly compose the operations you need. See the example below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from rest_framework import mixins
from rest_framework import generics
class BookView(mixins.ListModelMixin,
               mixins.CreateModelMixin,
               generics.GenericAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializers

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

More mixins:

mixinsFunctionCorresponding HTTP request method
mixins.ListModelMixinDefines the list method, returns a list from querylistGET
mixins.CreateModelMixinDefines the create method, creates an instancePOST
mixins.RetrieveModelMixinDefines the retrieve method, returns a specific instanceGET
mixins.UpdateModelMixinDefines the update method, updates an instancePUT/PATCH
mixins.DestroyModelMixinDefines the delete method, deletes an instanceDELETE

2.4 GenericViewSet

GenericAPIView provides the basic operations of create, delete, update, and query, and can be used for rapid API development. But when these operations need to be combined to implement complex business logic, it may not be so convenient.
Fortunately, restframework provides GenericViewSet for handling more complex business logic.

rest_framework/viewsets.py defines the GenericAPIView class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class ViewSetMixin(object):
    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            for method, action in actions.items():
                handler = getattr(self, action)
                setattr(self, method, handler)

            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            return self.dispatch(request, *args, **kwargs)
        view.cls = cls
        view.initkwargs = initkwargs
        view.suffix = initkwargs.get('suffix', None)
        view.actions = actions
        return csrf_exempt(view)
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
    pass

There are 5 built-in ViewSets, namely

  • ViewSetMixin
  • ViewSet
  • GenericViewSet
  • ReadOnlyModelViewSet
  • ModelViewSet

There are two ways to bind a ViewSet to a specified URL:

  1. Manually bind the ViewSet to a URL

In urls.py, add:

1
2
3
4
5
6
7
8
9
from .views import SnippetViewSet

snippet_list = SnippetViewSet.as_view({
    'get': 'list',
    'post': 'create'
})

urlpatterns = [
    url(r'^snippets/$', snippet_list, name='snippet-list'),

This dispatches the get request method under the snippets/ path to the list function, and the post request method to the create method.

  1. Use the routers provided by restframework

In urls.py, add:

1
2
3
4
5
6
7
8
9
from rest_framework.routers import DefaultRouter

from .views import SnippetViewSet

router = DefaultRouter()
router.register(r'snippets', SnippetViewSet)
urlpatterns = [
    url(r'^', include(router.urls)),
]

3. Serializer

The Serializer and ModelSerializer in restframework are similar to Django’s Form and ModelForm.

3.1 Serializer

1
2
3
4
5
from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
  • Serialization: object -> Json
1
2
3
serializer = CommentSerializer(comment)
serializer.data
{'email': 'mail@example.com', 'content': 'foo bar'}
  • Deserialization: String -> Json
1
2
3
4
5
serializer = CommentSerializer(data=data)
serializer.is_valid()
True
serializer.validated_data
{'content': 'foo bar', 'email': 'mail@example.com'

It can also be used for validation, to check whether the submitted data is valid:

1
2
3
serializer = SnippetSerializer(data=request.data)
    if serializer.is_valid():
        pass

3.2 ModelSerializer

Compared with declaring field by field above, ModelSerializer can quickly build the serializer for the relevant model. It provides the following features:

  • Automatically generates model-based fileds
  • Automatically generates validators, such as the unique_together validator
  • Includes create and update methods by default
  • Foreign keys are mapped to PrimaryKeyRelatedField
1
2
3
4
5
6
from .models import Snippet

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

4. References


微信公众号
WRITTEN BY
微信公众号