Django rest framework source code analysis -- version

Keywords: Django JSON

Version 1.

1. Create a new project Myproject and an app named api

(1)api/models.py

from django.db import models

class UserInfo(models.Model):
    USER_TYPE = (
        (1,'Ordinary users'),
        (2,'VIP'),
        (3,'SVIP')
    )

    user_type = models.IntegerField(choices=USER_TYPE)
    username = models.CharField(max_length=32,unique=True)
    password = models.CharField(max_length=64)
    group = models.ForeignKey('UserGroup',on_delete=models.CASCADE)
    roles = models.ManyToManyField('Role')


class UserToken(models.Model):
    user = models.OneToOneField('UserInfo',on_delete=models.CASCADE)
    token = models.CharField(max_length=64)


class UserGroup(models.Model):
    title = models.CharField(max_length=32)


class Role(models.Model):
    title = models.CharField(max_length=32)

(2)Myproject/urls.py

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    #path('admin/', admin.site.urls),
    path('api/',include('api.urls') ),
]

(3)api/urls.py

# api/urls.py

from django.urls import path
from .views import UserView

urlpatterns = [
    path('users/', UserView.as_view()),
]

(4)views.py

# api/views.py

from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.versioning import QueryParameterVersioning

class UserView(APIView):

    versioning_class = QueryParameterVersioning

    def get(self,request,*args,**kwargs):
        #Get version
        print(request.version)
        return HttpResponse('User list')

(5)settings.py

#Edition
REST_FRAMEWORK = {
    "DEFAULT_VERSION":'v1',               #Default version
    "ALLOWED_VERSIONS":['v1','v2'],       #Allowed versions
    "VERSION_PARAM":'version'             #Name of parameter in GET method url? version=xxx
}

2. GET parameter in URL

QueryParameterVersioning is used to GET the version in the GET parameter
http://127.0.0.1:8000/api/users/?version=v2
The current version can be seen in the background


If the version passed by the url exceeds the allowed range in settings, an error will be reported
http://127.0.0.1:8000/api/users/?version=v3

2, Get in URLPATH

(1) Modify api/urls.py

In general, our door should use URLPATH instead of GET() to pass parameters
Which versions are defined by regular expressions in the url,

# api/urls.py

from django.urls import path,re_path
from .views import UserView

urlpatterns = [
    re_path('(?P<version>[v1|v2]+)/users/', UserView.as_view()),
]

Modify views.py
URLPathVersioning: get the version in the url path

# api/views.py

from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.versioning import URLPathVersioning

class UserView(APIView):

    versioning_class = URLPathVersioning

    def get(self,request,*args,**kwargs):
        #Get version
        print(request.version)
        return HttpResponse('User list')

This URLPathVersioning can be put into settings, global configuration, instead of being written into views. Each class needs to be written once
settings.py

# Edition
# REST_FRAMEWORK = {
#     "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
#     "DEFAULT_VERSION":'v1',               #Default version
#     "ALLOWED_VERSIONS":['v1','v2'],       #Allowed versions
#     "VERSION_PARAM":'version'             #Name of parameter in get method url? version=xxx
# }

#Overall situation
REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
}

Modify views.py

# api/views.py

from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
from rest_framework.request import Request

class UserView(APIView):

    def get(self,request,*args,**kwargs):
        #Get version
        print(request.version)
        return HttpResponse('User list')

Browser access address

http://127.0.0.1:8000/api/v1/users/
Then the background gets the version information

3, url for reverse parsing access

(1)api/urls.py

Add name = 'API user'

# api/urls.py

from django.urls import path,re_path
from .views import UserView

urlpatterns = [
    re_path('(?P<version>[v1|v2]+)/users/', UserView.as_view(),name = 'api_user'),
]

(2)views.py

# api/views.py

from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
from rest_framework.request import Request

class UserView(APIView):

    def get(self,request,*args,**kwargs):
        #Get version
        print(request.version)
        #Get object to process version
        print(request.versioning_scheme)
        #Get the url accessed by the browser, reverse parsing
        #Two parameters are required: viewname is the alias in the url, and request=request is the parameter to be passed in the url
        #(? P < version > [v1| V2] +) / users /. The parameter of version should be passed here, but version is included in the request (you can see in the source code). All you need is request=request
        url_path = request.versioning_scheme.reverse(viewname='api_user',request=request)
        print(url_path)
        # self.dispatch
        return HttpResponse('User list')

Browser access

http://127.0.0.1:8000/api/v1/users/
Background acquisition

4, Source flow

(1)dispatch

def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        #Processing the original request enriches some functions
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #Request (original request,[BasicAuthentications object,])
        #Get native request, request
        #Get the object of the authentication class, request.authenticators
        #1. Package request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            #2. Authentication, version Verification
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            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

            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

(3)initial

def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        #request.version get version information
        #Request.versioning scheme get the processing version of your object
        # No matter which class gets the version, it gets the version by calling the determine? Version function
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        #4. Achieve authentication
        self.perform_authentication(request)
        #5. Authority judgment
        self.check_permissions(request)
        #6. Control access frequency
        self.check_throttles(request)

(3)determine_version

def determine_version(self, request, *args, **kwargs):
        """
        If versioning is being used, then determine any API version for the
        incoming request. Returns a two-tuple of (version, versioning_scheme)
        """
        if self.versioning_class is None:
            return (None, None)
        scheme = self.versioning_class()
        return (scheme.determine_version(request, *args, **kwargs), scheme)

(4)versioning_class
If there is no versioning u class in the instance's class, it will be found in the configuration file setting by default

5, URLPathVersioning source code

class URLPathVersioning(BaseVersioning):
    """
    To the client this is the same style as `NamespaceVersioning`.
    The difference is in the backend - this implementation uses
    Django's URL keyword arguments to determine the version.

    An example URL conf for two views that accept two different versions.

    urlpatterns = [
        url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),
        url(r'^(?P<version>[v1|v2]+)/users/(?P<pk>[0-9]+)/$', users_detail, name='users-detail')
    ]

    GET /1.0/something/ HTTP/1.1
    Host: example.com
    Accept: application/json
    """
    invalid_version_message = _('Invalid version in URL path.')

    def determine_version(self, request, *args, **kwargs):
        version = kwargs.get(self.version_param, self.default_version)
        if not self.is_allowed_version(version):
            raise exceptions.NotFound(self.invalid_version_message)
        return version

    def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
        if request.version is not None:
            kwargs = {} if (kwargs is None) else kwargs
            kwargs[self.version_param] = request.version

        return super(URLPathVersioning, self).reverse(
            viewname, args, kwargs, request, format, **extra
        )

Can see
(1)url configuration

(2)determine_version

There is an is "allowed" version in it. Click here to see some basic parameters (inherit the BaseVersioning base class)

class BaseVersioning(object):
    #Default version
    default_version = api_settings.DEFAULT_VERSION
    #Allowed versions
    allowed_versions = api_settings.ALLOWED_VERSIONS
    #Default parameter (version, for example, you can customize it to v)
    version_param = api_settings.VERSION_PARAM

    def determine_version(self, request, *args, **kwargs):
        msg = '{cls}.determine_version() must be implemented.'
        raise NotImplementedError(msg.format(
            cls=self.__class__.__name__
        ))

    def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
        return _reverse(viewname, args, kwargs, request, format, **extra)

    def is_allowed_version(self, version):
        if not self.allowed_versions:
            return True
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))
Published 7 original articles, won praise 1, 4171 visitors
Private letter follow

Posted by shazam on Tue, 11 Feb 2020 09:13:08 -0800