Start the Django project => load the settings file => load the models, views, and urls files, execute the urls file, and call the as_view() method of the view class.
The as_view() method of APIview inherits the as_view() method of the parent class, and adds the function of partially disabling the csrf middleware
def as_view(cls, **initkwargs): """ Store the original class on the view function. This allows us to discover information about the view when we do URL reverse lookups. Used for breadcrumb generation. """ if isinstance(getattr(cls,'queryset', None), models.query.QuerySet): def force_evaluation(): raise RuntimeError( 'Do not evaluate the `.queryset` attribute directly, ' 'as the result will be cached and reused between requests. ' 'Use `.all()` or call `.get_queryset()` instead.' ) cls.queryset._fetch_all = force_evaluation #Assign the parent class as_view to your own view space, and add its class name and parameters to the properties of your own class view = super().as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs # Note: session based authentication is explicitly CSRF validated, # all other authentication is CSRF exempt. # #Partially disable csrf authentication return csrf_exempt(view)
The parent class of APIview is Django's view class view. as_view() inherits the as_view function of the parent class and calls the dispatch method to distribute requests. The following is the as_view() of the parent class
@classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ '''Omit the code''' .................................................. ...... 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 #Distribution request return self.dispatch(request, *args, **kwargs) # Here self is the object instantiated by the view class, make no mistake view.view_class = cls view.view_initkwargs = initkwargs # take name and docstring from class update_wrapper(view, cls, updated=()) # and possible attributes set by decorators # like csrf_exempt from dispatch update_wrapper(view, cls.dispatch, assigned=()) return view
The object instantiated by the view class finds the dispatch method in its own name space. If not, find the dispatch method in the base class APIview. The dispatch method of APIview is an overwrite of the dispatch method of the view class, and the dispatch method of the view class is optimized. Let's look at the source code of APIview's dispatch method with specific optimization:
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 # initialize_request re-encapsulates the request object and parses the content of the request object request = self.initialize_request(request, *args, **kwargs) self.request = request self.headers = self.default_response_headers # deprecate? try: #3.major certifications (authentication, authority, frequency), used to replace csrf security certification, which is much stronger than csrf certification 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) #If there is a corresponding method in the request of wsgi, use wsgi, if not, use your own, commonly used methods in the source code else: handler = self.http_method_not_allowed #If you don't have it, report http_method_not_allowed exception response = handler(request, *args, **kwargs) except Exception as exc: #Exception handling module, handling abnormal branches response = self.handle_exception(exc) #Secondary package response, processing the content of the response, and rendering the results of the processing self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
Summarize the functions completed in the rewritten dispatch:
1. Encapsulate the request object twice and parse the content of the request object
2. Call the function initial to perform three major authentications on the request, and capture exceptions in the process
3. Execute authenticated custom requests such as get, post, patch, delete, etc. through reflection
4. If there is an exception during the execution of steps 2 and 3 above, call the handle_exception method to handle the caught exception.
5. Process the content of the response through the finalize_response method, and whether to render the content
The above is the request process of the Django rest framework source code. Let's take a rough look at the source code of the request module, parsing module, corresponding module, exception handling module, and rendering module.
The general functions of the request module are as follows:
1. Convert the request object of wsgi into an object of the request class of drf
2. The encapsulated request object is fully compatible with wsgi's request object, and the original request object is saved in the new request._request
3. Reformatting the storage location of the requested data
Splicing parameters: request.query_params
Data packet parameters: request.data
# Source code analysis: # Entry: request of the dispatch method of APIVIew=self.initialize_request(request, *args, **kwargs) # print(request._request.method) # Assign WSGI request to request._request internally class Request: """ Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs: -request(HttpRequest). The original request instance. -parsers_classes(list/tuple). The parsers to use for parsing the request content. -authentication_classes(list/tuple). The authentications used to try authenticating the request's user. """ def __init__(self, request, parsers=None, authenticators=None, negotiator=None, parser_context=None): assert isinstance(request, HttpRequest), ( 'The `request` argument must be an instance of ' '`django.http.HttpRequest`, not `{}.{}`.' .format(request.__class__.__module__, request.__class__.__name__) #Store all the name space of the request class of wsgi into your own name space to achieve full compatibility with the request of wsgi. ) self._request = request #Store the request of the parent class in our own _request, so that we can use the attributes and methods of the object point attributes and methods of the wsgi request, or directly manipulate the wsgi request object through the object point _request. self.parsers = parsers or () self.authenticators = authenticators or () self.negotiator = negotiator or self._default_negotiator() self.parser_context = parser_context self._data = Empty self._files = Empty self._full_data = Empty self._content_type = Empty self._stream = Empty if self.parser_context is None: self.parser_context = {} self.parser_context['request'] = self self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET force_user = getattr(request,'_force_auth_user', None) force_token = getattr(request,'_force_auth_token', None) if force_user is not None or force_token is not None: forced_auth = ForcedAuthentication(force_user, force_token) self.authenticators = (forced_auth,) def _default_negotiator(self): return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS() @property def content_type(self): meta = self._request.META return meta.get('CONTENT_TYPE', meta.get('HTTP_CONTENT_TYPE','')) @property def query_params(self): """ More semantically correct name for request.GET. """ #Rename the _request.GET attribute to query_params return self._request.GET @property def data(self): if not _hasattr(self,'_full_data'): self._load_data_and_files() #Rename _full_data return self._full_data def __getattr__(self, attr): """ If an attribute does not exist on this instance, then we also attempt to proxy it to the underlying HttpRequest object. """ try:#Get the attributes and methods of _request through the method of __getattr__ return getattr(self._request, attr) except AttributeError: return self.__getattribute__(attr)
The parsing module only processes data packet parameters
# Source code analysis: # Entry: request of the dispatch method of APIVIew=self.initialize_request(request, *args, **kwargs) # Get the parsing class: parsers=self.get_parsers(), # Perform local global default configuration search order to search: return [parser() for parser in self.parser_classes] def get_parsers(self): """ Instantiates and returns the list of parsers that this view can use. """ return [parser() for parser in self.parser_classes] #We click on parser_classes to reach the self.parser_classes attribute, class APIView(View): # The following policies may be set at either globally, or per-view. renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES parser_classes = api_settings.DEFAULT_PARSER_CLASSES#Here you can see that the parser is configured in the api_settings configuration authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS metadata_class = api_settings.DEFAULT_METADATA_CLASS versioning_class = api_settings.DEFAULT_VERSIONING_CLASS #In the settings, you can see that the default parser configuration is as follows, that is, the data types supported by the default parser are form-data, urlencoded, json DEFAULTS = { # Base API policies 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', ], 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser',#json 'rest_framework.parsers.FormParser',#urlencoded 'rest_framework.parsers.MultiPartParser'#file form-data ], #Here is the global configuration, we can customize the parser we use in the project's settings file
When we start the project after the following configuration in the drf settings file, our own configuration will be used first.
REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES': [ 'rest_framework.parsers.JSONParser',#json 'rest_framework.parsers.FormParser',#urlencoded 'rest_framework.parsers.MultiPartParser'#file form-data ] }
We can also directly import the parser into our own view class, and use the parser_classes in our class first.
from rest_framework.views import APIView from rest_framework.parsers import JSONParser, FormParser, MultiPartParser class Book(APIView): parser_classes = [JSONParser,FormParser,MultiPartParser]
In summary, we can know the search order of the parser configuration: local (class attribute of the view class) => global (drf configuration of the settings file) => default (drf default configuration)
class Response(SimpleTemplateResponse): """ An HttpResponse that allows its data to be rendered into arbitrary media types. """ def __init__(self, data=None, status=None, template_name=None, headers=None, exception=False, content_type=None): """ Alters the init arguments slightly. For example, drop'template_name', and instead use'data'. Setting'renderer' and'media_type' will typically be deferred, For example being set automatically by the `APIView`. """ super().__init__(None, status=status) if isinstance(data, Serializer): msg = ( 'You passed a Serializer instance as data, but' 'probably meant to pass serialized `.data` or ' '`.error`. representation.' ) raise AssertionError(msg) self.data = data self.template_name = template_name self.exception = exception self.content_type = content_type if headers: for name, value in headers.items(): self[name] = value @property def status_text(self): """ Returns reason text corresponding to our HTTP response status code. Provided for convenience. """ return responses.get(self.status_code,'') def __getstate__(self): """ Remove attributes from the response that shouldn't be cached. """ state = super().__getstate__() for key in ( 'accepted_renderer','renderer_context','resolver_match', 'client','request','json','wsgi_request' ): if key in state: del state[key] state['_closable_objects'] = [] return state # data: Response data # status: response network status code # ------------------------- # template_name: Before and after the drf is completed, the back-end will not be separated to return to the page, but data cannot be returned (understand) # headers: response headers, generally not specified, default # exception: general exception response, it will be set to True, the default is False (nothing is fine if it is not set) # content_type: The default is application/json, no need to deal with
def dispatch(self, request, *args, **kwargs): ''' ... ''' try: #3.major certifications (authentication, authority, frequency), used to replace csrf security certification, which is much stronger than csrf certification 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) #If there is a corresponding method in the request of wsgi, use wsgi, if not, use your own, commonly used methods in the source code else: handler = self.http_method_not_allowed #If you don't have it, report http_method_not_allowed exception response = handler(request, *args, **kwargs) except Exception as exc: #Exception handling module, handling abnormal branches response = self.handle_exception(exc) #Secondary package response, processing the content of the response, and rendering the results of the processing self.response = self.finalize_response(request, response, *args, **kwargs) return self.response
It can be seen from the source code that the scope of exception capture has been introduced when the request enters the three major authentications, but the exception here will not be better for the client-side exception handling, and for the server-side exception, a wave of code will be returned to the front-end, we enter handle_exception, look at the code for exception handling.
def handle_exception(self, exc): """ Handle any exception that occurs, by returning an appropriate response, or re-raising the error. """ #Handle the response header for authentication exception if isinstance(exc, (exceptions.NotAuthenticated, exceptions.AuthenticationFailed)): # WWW-Authenticate header for 401 responses, else coerce to 403 auth_header = self.get_authenticate_header(self.request) if auth_header: exc.auth_header = auth_header else: exc.status_code = status.HTTP_403_FORBIDDEN #Get exception handling function exception_handler exception_handler = self.get_exception_handler() context = self.get_exception_handler_context() #Provide additional parameters for exception handling, in fact, is to get the view object that throws the exception and the requested parameters, see the source code of get_exception_handler_context below response = exception_handler(exc, context)#Exception object, view object and requested parameters #The default exception_handler function only processes the response object formed by the client exception, the server exception is not processed, and None is returned if response is None: #When response is none, hand it over to Django middleware for processing self.raise_uncaught_exception(exc) response.exception = True return response #View Object and Requested Parameters def get_exception_handler_context(self): """ Returns a dict that is passed through to EXCEPTION_HANDLER, as the `context` argument. """ return { 'view': self, 'args': getattr(self,'args', ()), 'kwargs': getattr(self,'kwargs', {}), 'request': getattr(self,'request', None) }
In order to customize the content of the exception thrown by the system when the server is abnormal, we need to rewrite the exception handling function. Steps:
1. Configure EXCEPTION_HANDLER in the drf configuration of settings to point to the custom exception_handler function
2. When an exception occurs in drf, the exception_handler function will be called back, carrying the exception object and exception related information, and the exception information return and the logging log of the exception information will be completed in the exception_handler function.
Configure in Django's settings file:
REST_FRAMEWORK = {'EXCEPTION_HANDLER':'api.exception_handler.exception_handler'}
Rewrite exception_handler in the exception_handler file
# Be sure to configure your own exception handling function for the exception module in the settings file from rest_framework.views import exception_handler as drf_exception_handler from rest_framework.response import Response # First hand in a drf to handle the client exception, if the response is None, it means that the server is abnormal, handle it yourself # Finally, we must record the abnormal phenomenon in the log file def exception_handler(exc, context): response = drf_exception_handler(exc, context) detail ='%s-%s-%s'% (context.get('view'), context.get('request').method, exc) if not response: # server error response = Response({'detail': detail}) else: response.data = {'detail': detail} # Core: To record response.data.get('detail') information to the log file # logger.waring(response.data.get('detail')) return response
The import method of the rendering module in APIView renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES, its function is that the result of Postman request is json, and the result of the browser request is the rendered page. In actual projects, the application scene is not large and can be partially integrated like the parsing module. Global configuration.