Django中CBV和Restful API中的APIView源码分析

Django中CBV和Restful API中的APIView源码分析

 

python的Django框架的视图处理可以用FBV, 也可以采用CBV。首先定义一个CBV视图:

from django.views import View
from django.http import JsonResponse
class Book(View):
   def get(self, request):
       ll = [{'key':value}]
       return JsonResponse(ll, safe=false, json_dumps_params={'ensure':false})
   def post(self, request):
       return HttpResponse('ok')
   

#前台路由的配置
url(r'^books/', views.Books.as_view()),

 

首先,一个CBV(class base views 类方式完成视图响应)视图定义时类方法的名称必须有请求名,为什么要这样定义?

首先,由路由的分发可以看出,

从继承的父类View开始:

views.Books.as_view() ,其实是调用了我们自定义视图模型类Book的as_view函数,可是明明自己未定义该函数,那么需要去父类观察。

 

源码:--------------分析以注释说明-----------------------

class View(object):
   """
  Intentionally simple parent class for all views. Only implements
  dispatch-by-method and simple sanity checking.
  """

   http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

   def __init__(self, **kwargs):
       """
      Constructor. Called in the URLconf; can contain helpful extra
      keyword arguments, and other things.
      """
       # Go through keyword arguments, and either save their values to our
       # instance, or raise an error.
       for key, value in six.iteritems(kwargs):
           setattr(self, key, value)

   @classonlymethod
   def as_view(cls, **initkwargs):
       """
      Main entry point for a request-response process.
      """
       for key in initkwargs:
           if key in cls.http_method_names:
               raise TypeError("You tried to pass in the %s method name as a "
                               "keyword argument to %s(). Don't do that."
                               % (key, cls.__name__))
           if not hasattr(cls, key):
               raise TypeError("%s() received an invalid keyword %r. as_view "
                               "only accepts arguments that are already "
                               "attributes of the class." % (cls.__name__, key))

       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)
       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

   def dispatch(self, request, *args, **kwargs):
       # Try to dispatch to the right method; if a method doesn't exist,
       # defer to the error handler. Also defer to the error handler if the
       # request method isn't on the approved list.
       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)

   def http_method_not_allowed(self, request, *args, **kwargs):
       logger.warning(
           'Method Not Allowed (%s): %s', request.method, request.path,
           extra={'status_code': 405, 'request': request}
      )
       return http.HttpResponseNotAllowed(self._allowed_methods())

   def options(self, request, *args, **kwargs):
       """
      Handles responding to requests for the OPTIONS HTTP verb.
      """
       response = http.HttpResponse()
       response['Allow'] = ', '.join(self._allowed_methods())
       response['Content-Length'] = '0'
       return response

   def _allowed_methods(self):
       return [m.upper() for m in self.http_method_names if hasattr(self, m)]

 

 

Restful API中的APIView

在Django中的RestfulAPI 的CBV定义

from rest_framework.views import APIView
class Books(APIView):
   def get(self, request):
       # 获取所有的图书:
       books = Book.objects.all()
       # print(books)
       ll = []
       for book in books:
           # print(book.id)
           # print(book.name)
           # print(book.author)
           dic = {
               "id": book.id,
               "name": book.name,
               "author": book.author
          }
           ll.append(dic)
           return render(request, 'show_books.html', locals())
       # return JsonResponse(ll, safe=False, json_dumps_params={'ensure_ascii': False}
           # 增加图书
   def post(self, request):
       id = request.data['id']
       name = request.data['name']
       author = request.data['author']
       print(id, name, author)
       Book.objects.create(id=id, name=name, author=author)
       return redirect('/books/')

# 路由分发
url(r'^books/', views.Books.as_view()),

定义方式和Django继承的父类不同,但是路由分发相同

那么我们需要看继承的父类APIView如何处理

源码:

 

Request源码

class Request(object):
   """
  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__)
      )

       self._request = 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 stream(self):
       """
      Returns an object that may be used to stream the request content.
      """
       if not _hasattr(self, '_stream'):
           self._load_stream()
       return self._stream

   @property
   def query_params(self):
       """
      More semantically correct name for request.GET.
      """
       return self._request.GET

   @property
   def data(self):
       if not _hasattr(self, '_full_data'):
           self._load_data_and_files()
       return self._full_data

   @property
   def user(self):
       """
      Returns the user associated with the current request, as authenticated
      by the authentication classes provided to the request.
      """
       if not hasattr(self, '_user'):
           with wrap_attributeerrors():
               self._authenticate()
       return self._user

   @user.setter
   def user(self, value):
       """
      Sets the user on the current request. This is necessary to maintain
      compatibility with django.contrib.auth where the user property is
      set in the login and logout functions.

      Note that we also set the user on Django's underlying `HttpRequest`
      instance, ensuring that it is available to any middleware in the stack.
      """
       self._user = value
       self._request.user = value

   @property
   def auth(self):
       """
      Returns any non-user authentication information associated with the
      request, such as an authentication token.
      """
       if not hasattr(self, '_auth'):
           with wrap_attributeerrors():
               self._authenticate()
       return self._auth

   @auth.setter
   def auth(self, value):
       """
      Sets any non-user authentication information associated with the
      request, such as an authentication token.
      """
       self._auth = value
       self._request.auth = value

   @property
   def successful_authenticator(self):
       """
      Return the instance of the authentication instance class that was used
      to authenticate the request, or `None`.
      """
       if not hasattr(self, '_authenticator'):
           with wrap_attributeerrors():
               self._authenticate()
       return self._authenticator

   def _load_data_and_files(self):
       """
      Parses the request content into `self.data`.
      """
       if not _hasattr(self, '_data'):
           self._data, self._files = self._parse()
           if self._files:
               self._full_data = self._data.copy()
               self._full_data.update(self._files)
           else:
               self._full_data = self._data

           # if a form media type, copy data & files refs to the underlying
           # http request so that closable objects are handled appropriately.
           if is_form_media_type(self.content_type):
               self._request._post = self.POST
               self._request._files = self.FILES

   def _load_stream(self):
       """
      Return the content body of the request, as a stream.
      """
       meta = self._request.META
       try:
           content_length = int(
               meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0))
          )
       except (ValueError, TypeError):
           content_length = 0

       if content_length == 0:
           self._stream = None
       elif not self._request._read_started:
           self._stream = self._request
       else:
           self._stream = io.BytesIO(self.body)

   def _supports_form_parsing(self):
       """
      Return True if this requests supports parsing form data.
      """
       form_media = (
           'application/x-www-form-urlencoded',
           'multipart/form-data'
      )
       return any([parser.media_type in form_media for parser in self.parsers])

   def _parse(self):
       """
      Parse the request content, returning a two-tuple of (data, files)

      May raise an `UnsupportedMediaType`, or `ParseError` exception.
      """
       media_type = self.content_type
       try:
           stream = self.stream
       except RawPostDataException:
           if not hasattr(self._request, '_post'):
               raise
           # If request.POST has been accessed in middleware, and a method='POST'
           # request was made with 'multipart/form-data', then the request stream
           # will already have been exhausted.
           if self._supports_form_parsing():
               return (self._request.POST, self._request.FILES)
           stream = None

       if stream is None or media_type is None:
           if media_type and is_form_media_type(media_type):
               empty_data = QueryDict('', encoding=self._request._encoding)
           else:
               empty_data = {}
           empty_files = MultiValueDict()
           return (empty_data, empty_files)

       parser = self.negotiator.select_parser(self, self.parsers)

       if not parser:
           raise exceptions.UnsupportedMediaType(media_type)

       try:
           parsed = parser.parse(stream, media_type, self.parser_context)
       except Exception:
           # If we get an exception during parsing, fill in empty data and
           # re-raise. Ensures we don't simply repeat the error when
           # attempting to render the browsable renderer response, or when
           # logging the request or similar.
           self._data = QueryDict('', encoding=self._request._encoding)
           self._files = MultiValueDict()
           self._full_data = self._data
           raise

       # Parser classes may return the raw data, or a
       # DataAndFiles object. Unpack the result as required.
       try:
           return (parsed.data, parsed.files)
       except AttributeError:
           empty_files = MultiValueDict()
           return (parsed, empty_files)

   def _authenticate(self):
       """
      Attempt to authenticate the request using each authentication instance
      in turn.
      """
       for authenticator in self.authenticators:
           try:
               user_auth_tuple = authenticator.authenticate(self)
           except exceptions.APIException:
               self._not_authenticated()
               raise

           if user_auth_tuple is not None:
               self._authenticator = authenticator
               self.user, self.auth = user_auth_tuple
               return

       self._not_authenticated()

   def _not_authenticated(self):
       """
      Set authenticator, user & authtoken representing an unauthenticated request.

      Defaults are None, AnonymousUser & None.
      """
       self._authenticator = None

       if api_settings.UNAUTHENTICATED_USER:
           self.user = api_settings.UNAUTHENTICATED_USER()
       else:
           self.user = None

       if api_settings.UNAUTHENTICATED_TOKEN:
           self.auth = api_settings.UNAUTHENTICATED_TOKEN()
       else:
           self.auth = None

   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:
           return getattr(self._request, attr)
       except AttributeError:
           return self.__getattribute__(attr)

   @property
   def DATA(self):
       raise NotImplementedError(
           '`request.DATA` has been deprecated in favor of `request.data` '
           'since version 3.0, and has been fully removed as of version 3.2.'
      )

   @property
   def POST(self):
       # Ensure that request.POST uses our request parsing.
       if not _hasattr(self, '_data'):
           self._load_data_and_files()
       if is_form_media_type(self.content_type):
           return self._data
       return QueryDict('', encoding=self._request._encoding)

   @property
   def FILES(self):
       # Leave this one alone for backwards compat with Django's request.FILES
       # Different from the other two cases, which are not valid property
       # names on the WSGIRequest class.
       if not _hasattr(self, '_files'):
           self._load_data_and_files()
       return self._files

   @property
   def QUERY_PARAMS(self):
       raise NotImplementedError(
           '`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
           'since version 3.0, and has been fully removed as of version 3.2.'
      )

   def force_plaintext_errors(self, value):
       # Hack to allow our exception handler to force choice of
       # plaintext or html error responses.
       self._request.is_ajax = lambda: value

 

class Request(object):
"""
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__)
)

self._request = 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 stream(self):
"""
Returns an object that may be used to stream the request content.
"""
if not _hasattr(self, '_stream'):
self._load_stream()
return self._stream

@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET

@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data

@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user

@user.setter
def user(self, value):
"""
Sets the user on the current request. This is necessary to maintain
compatibility with django.contrib.auth where the user property is
set in the login and logout functions.

Note that we also set the user on Django's underlying `HttpRequest`
instance, ensuring that it is available to any middleware in the stack.
"""
self._user = value
self._request.user = value

@property
def auth(self):
"""
Returns any non-user authentication information associated with the
request, such as an authentication token.
"""
if not hasattr(self, '_auth'):
with wrap_attributeerrors():
self._authenticate()
return self._auth

@auth.setter
def auth(self, value):
"""
Sets any non-user authentication information associated with the
request, such as an authentication token.
"""
self._auth = value
self._request.auth = value

@property
def successful_authenticator(self):
"""
Return the instance of the authentication instance class that was used
to authenticate the request, or `None`.
"""
if not hasattr(self, '_authenticator'):
with wrap_attributeerrors():
self._authenticate()
return self._authenticator

def _load_data_and_files(self):
"""
Parses the request content into `self.data`.
"""
if not _hasattr(self, '_data'):
self._data, self._files = self._parse()
if self._files:
self._full_data = self._data.copy()
self._full_data.update(self._files)
else:
self._full_data = self._data

# if a form media type, copy data & files refs to the underlying
# http request so that closable objects are handled appropriately.
if is_form_media_type(self.content_type):
self._request._post = self.POST
self._request._files = self.FILES

def _load_stream(self):
"""
Return the content body of the request, as a stream.
"""
meta = self._request.META
try:
content_length = int(
meta.get('CONTENT_LENGTH', meta.get('HTTP_CONTENT_LENGTH', 0))
)
except (ValueError, TypeError):
content_length = 0

if content_length == 0:
self._stream = None
elif not self._request._read_started:
self._stream = self._request
else:
self._stream = io.BytesIO(self.body)

def _supports_form_parsing(self):
"""
Return True if this requests supports parsing form data.
"""
form_media = (
'application/x-www-form-urlencoded',
'multipart/form-data'
)
return any([parser.media_type in form_media for parser in self.parsers])

def _parse(self):
"""
Parse the request content, returning a two-tuple of (data, files)

May raise an `UnsupportedMediaType`, or `ParseError` exception.
"""
media_type = self.content_type
try:
stream = self.stream
except RawPostDataException:
if not hasattr(self._request, '_post'):
raise
# If request.POST has been accessed in middleware, and a method='POST'
# request was made with 'multipart/form-data', then the request stream
# will already have been exhausted.
if self._supports_form_parsing():
return (self._request.POST, self._request.FILES)
stream = None

if stream is None or media_type is None:
if media_type and is_form_media_type(media_type):
empty_data = QueryDict('', encoding=self._request._encoding)
else:
empty_data = {}
empty_files = MultiValueDict()
return (empty_data, empty_files)

parser = self.negotiator.select_parser(self, self.parsers)

if not parser:
raise exceptions.UnsupportedMediaType(media_type)

try:
parsed = parser.parse(stream, media_type, self.parser_context)
except Exception:
# If we get an exception during parsing, fill in empty data and
# re-raise. Ensures we don't simply repeat the error when
# attempting to render the browsable renderer response, or when
# logging the request or similar.
self._data = QueryDict('', encoding=self._request._encoding)
self._files = MultiValueDict()
self._full_data = self._data
raise

# Parser classes may return the raw data, or a
# DataAndFiles object. Unpack the result as required.
try:
return (parsed.data, parsed.files)
except AttributeError:
empty_files = MultiValueDict()
return (parsed, empty_files)

def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise

if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return

self._not_authenticated()

def _not_authenticated(self):
"""
Set authenticator, user & authtoken representing an unauthenticated request.

Defaults are None, AnonymousUser & None.
"""
self._authenticator = None

if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None

if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None

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:
return getattr(self._request, attr)
except AttributeError:
return self.__getattribute__(attr)

@property
def DATA(self):
raise NotImplementedError(
'`request.DATA` has been deprecated in favor of `request.data` '
'since version 3.0, and has been fully removed as of version 3.2.'
)

@property
def POST(self):
# Ensure that request.POST uses our request parsing.
if not _hasattr(self, '_data'):
self._load_data_and_files()
if is_form_media_type(self.content_type):
return self._data
return QueryDict('', encoding=self._request._encoding)

@property
def FILES(self):
# Leave this one alone for backwards compat with Django's request.FILES
# Different from the other two cases, which are not valid property
# names on the WSGIRequest class.
if not _hasattr(self, '_files'):
self._load_data_and_files()
return self._files

@property
def QUERY_PARAMS(self):
raise NotImplementedError(
'`request.QUERY_PARAMS` has been deprecated in favor of `request.query_params` '
'since version 3.0, and has been fully removed as of version 3.2.'
)

def force_plaintext_errors(self, value):
# Hack to allow our exception handler to force choice of
# plaintext or html error responses.
self._request.is_ajax = lambda: value

猜你喜欢

转载自www.cnblogs.com/5j421/p/10597744.html