Django通用视图之ListView

继承方式

  • ListView(MultipleObjectTemplateResponseMixin, BaseListView)
    • MultipleObjectTemplateResponseMixin
      • TemplateResponseMixin
    • BaseListView
      • MultipleObjectMixin
        • ContextMixin
      • View
graph TD
    ListView[ListView] --> |依赖其生成模板名称的函数| MultipleObjectTemplateResponseMixin(MultipleObjectTemplateResponseMixin) 
    ListView --> |依赖其中的get方法以及返回的查询信息| BaseListView(BaseListView)
    MultipleObjectTemplateResponseMixin --> |依赖其返回渲染模板的函数| TemplateResponseMixin(TemplateResponseMixin)
    BaseListView --> |依赖其中返回的查询信息| MultipleObjectMixin(MultipleObjectMixin)
    BaseListView --> |依赖其中初始化函数以及访问方法的调用|View(View)
    MultipleObjectMixin -->|依赖及重写其中的获取数据函数| ContextMixin(ContextMixin)
  • 第一次发博客,不知道这个里面的关系图语法是怎么样的,笔记里粘过来就这样了。哪位大神会用麻烦告诉我一下,谢谢~

ListView处理过程

url会将request对象和其他用户定义参数传入ListView,View父类负责执行初始化方法,将用户定义变量添加到类属性中,并调用as_view方法查找对应请求方式的处理函数(BaseListView中的get方法)。get方法执行是否分页操作的判断语句,并调用MultipleObjectMixin类中的get_context_data方法(父类ContextMixin)获取查询数据,然后调用TemplateResponseMixin中的render_to_response方法返回渲染后的模板。 注意,TemplateResponseMixin中需要的模板信息是在MultipleObjectTemplateResponseMixin类中定义的。
ListView类使用了多重继承,每重继承定义了对应父类的参数信息,极大的降低了耦合性。

使用方法

常用的参数:
  • model,queryset
    指定查询的模块或查询集
  • context_object_name
    指定返回的上下文名称
  • template_name
    指定需要渲染的模板文件
返回结果

返回指定的查询集,并渲染模板


代码解析

ContextMixin
主要参数
  • extra_context 添加额外的自定义数据,格式为字典
功能函数
  • get_context_data 返回上下文数据。添加’view’关键字,内容为self中所有数据(参数),并追加extra_context内容
代码
class ContextMixin:
    """
    A default context mixin that passes the keyword arguments received by
    get_context_data() as the template context.
    """
    extra_context = None

    def get_context_data(self, **kwargs):
        if 'view' not in kwargs:
            kwargs['view'] = self
        if self.extra_context is not None:
            kwargs.update(self.extra_context)
        return kwargs

MultipleObjectMixin
继承

继承自ContextMixin

主要参数
  • 定义了查询对象(queryset,model)
  • 定义分页(paginate_by,paginate_orphans)
  • 定义了上下文返回的名字(context_object_name)
  • 定义了排序标准(ordering)
  • 定义是否渲染模板(get_allow_empty)(此处不确认)
功能函数
  • get_queryset
    返回数据查询集,并排序(默认不排序)。此处queryset与model必须定义一个,否则会引发异常。如果指定了model,那么使用默认manager进行查询(models中自定义的第一个manager为默认manager,如未指定,默认为object)
  • get_ordering
    返回指定的排序,ordering的值
  • paginate_queryset get_paginate_by get_paginator get_paginate_orphans
    分页相关
  • get_allow_empty
    返回get_allow_empty的值,True或False。默认为True
  • get_context_object_name
    返回上下文的名称,context_object_name的值。默认为’数据对象’_list
  • get_context_data
    数返回数据,调用(重写get_context_data)父类’ContextMixin’,并根据参数执行分页。数据包含了“数据库查询数据”和分页相关信息(是否分页,分页数等)。
代码
class MultipleObjectMixin(ContextMixin):
    """A mixin for views manipulating multiple objects."""
    allow_empty = True
    queryset = None
    model = None
    paginate_by = None
    paginate_orphans = 0
    context_object_name = None
    paginator_class = Paginator
    page_kwarg = 'page'
    ordering = None

    def get_queryset(self):
        """
        Return the list of items for this view.

        The return value must be an iterable and may be an instance of
        `QuerySet` in which case `QuerySet` specific behavior will be enabled.
        """
        if self.queryset is not None:
            queryset = self.queryset
            if isinstance(queryset, QuerySet):
                queryset = queryset.all()
        elif self.model is not None:
            queryset = self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(
                "%(cls)s is missing a QuerySet. Define "
                "%(cls)s.model, %(cls)s.queryset, or override "
                "%(cls)s.get_queryset()." % {
                    'cls': self.__class__.__name__
                }
            )
        ordering = self.get_ordering()
        if ordering:
            if isinstance(ordering, str):
                ordering = (ordering,)
            queryset = queryset.order_by(*ordering)

        return queryset

    def get_ordering(self):
        """Return the field or fields to use for ordering the queryset."""
        return self.ordering

    def paginate_queryset(self, queryset, page_size):
        """Paginate the queryset, if needed."""
        paginator = self.get_paginator(
            queryset, page_size, orphans=self.get_paginate_orphans(),
            allow_empty_first_page=self.get_allow_empty())
        page_kwarg = self.page_kwarg
        page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
        try:
            page_number = int(page)
        except ValueError:
            if page == 'last':
                page_number = paginator.num_pages
            else:
                raise Http404(_("Page is not 'last', nor can it be converted to an int."))
        try:
            page = paginator.page(page_number)
            return (paginator, page, page.object_list, page.has_other_pages())
        except InvalidPage as e:
            raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
                'page_number': page_number,
                'message': str(e)
            })

    def get_paginate_by(self, queryset):
        """
        Get the number of items to paginate by, or ``None`` for no pagination.
        """
        return self.paginate_by

    def get_paginator(self, queryset, per_page, orphans=0,
                      allow_empty_first_page=True, **kwargs):
        """Return an instance of the paginator for this view."""
        return self.paginator_class(
            queryset, per_page, orphans=orphans,
            allow_empty_first_page=allow_empty_first_page, **kwargs)

    def get_paginate_orphans(self):
        """
        Return the maximum number of orphans extend the last page by when
        paginating.
        """
        return self.paginate_orphans

    def get_allow_empty(self):
        """
        Return ``True`` if the view should display empty lists and ``False``
        if a 404 should be raised instead.
        """
        return self.allow_empty

    def get_context_object_name(self, object_list):
        """Get the name of the item to be used in the context."""
        if self.context_object_name:
            return self.context_object_name
        elif hasattr(object_list, 'model'):
            return '%s_list' % object_list.model._meta.model_name
        else:
            return None

    def get_context_data(self, *, object_list=None, **kwargs):
        """Get the context for this view."""
        queryset = object_list if object_list is not None else self.object_list
        page_size = self.get_paginate_by(queryset)
        context_object_name = self.get_context_object_name(queryset)
        if page_size:
            paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
            context = {
                'paginator': paginator,
                'page_obj': page,
                'is_paginated': is_paginated,
                'object_list': queryset
            }
        else:
            context = {
                'paginator': None,
                'page_obj': None,
                'is_paginated': False,
                'object_list': queryset
            }
        if context_object_name is not None:
            context[context_object_name] = queryset
        context.update(kwargs)
        return super().get_context_data(**context)

View
主要参数
  • http_method_names http访问方式。包含了GET、POST等
功能函数
  • init 使用setattr将所有初始化参数设置为类属性
  • as_view 使用python闭包实现,添加了request对象和传入参数到类属性中,闭包内调用dispatch函数。并手动调用了update_wrapper函数,以保持函数签名不变。
  • dispatch 返回处理请求的函数,此函数为继承后用户定义,匹配http_method_names中声明的名称。如果对应请求类型的函数不存在,则触发http_method_not_allowed函数,返回状态吗405
  • http_method_not_allowed 拒绝请求函数。打印错误日志,返回状态吗405
  • options 为response添加头信息。调用_allowed_methods函数添加’ALLOW‘标签。
  • _allowed_methods 将http_method_names列表中的请求方式改为大写字母
代码
class View:
    """
    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 kwargs.items():
            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 HttpResponseNotAllowed(self._allowed_methods())

    def options(self, request, *args, **kwargs):
        """Handle responding to requests for the OPTIONS HTTP verb."""
        response = 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)]

BaseListView
继承

继承自 MultipleObjectMixin 和 View

功能函数
  • get 此视图仅响应GET请求,返回查询数据。判断时候允许展示,如不允许返回状态码404。
代码
class BaseListView(MultipleObjectMixin, View):
    """A base view for displaying a list of objects."""
    def get(self, request, *args, **kwargs):
        self.object_list = self.get_queryset()
        allow_empty = self.get_allow_empty()

        if not allow_empty:
            # When pagination is enabled and object_list is a queryset,
            # it's better to do a cheap query than to load the unpaginated
            # queryset in memory.
            if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
                is_empty = not self.object_list.exists()
            else:
                is_empty = len(self.object_list) == 0
            if is_empty:
                raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
                    'class_name': self.__class__.__name__,
                })
        context = self.get_context_data()
        return self.render_to_response(context)

MultipleObjectTemplateResponseMixin
继承

继承自TemplateResponseMixin

主要参数
  • template_name_suffix 定义模板名称的后缀
功能函数
  • get_template_names 以列表形式返回模板名称。该函数首先尝试调用父方法,以兼容用户声明template_name的情况。后追加此函数定义的模板名,即“app名称”+“模块名”+“模板名称后缀”.html
代码
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
    """Mixin for responding with a template and list of objects."""
    template_name_suffix = '_list'

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response is overridden.
        """
        try:
            names = super().get_template_names()
        except ImproperlyConfigured:
            # If template_name isn't specified, it's not a problem --
            # we just start with an empty list.
            names = []

        # If the list is a queryset, we'll invent a template name based on the
        # app and model name. This name gets put at the end of the template
        # name list so that user-supplied names override the automatically-
        # generated ones.
        if hasattr(self.object_list, 'model'):
            opts = self.object_list.model._meta
            names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))

        return names

TemplateResponseMixin
主要参数
  • template_name
    定义模板名称
  • template_engine 定义模板引擎
  • response_class 定义返回函数,默认为TemplateResponse
  • content_type 定义返回类型
功能函数
  • render_to_response 调用response_class返回渲染后的模板。
  • get_template_names 返回模板名称。如用户未指定则触发异常
代码
class TemplateResponseMixin:
    """A mixin that can be used to render a template."""
    template_name = None
    template_engine = None
    response_class = TemplateResponse
    content_type = None

    def render_to_response(self, context, **response_kwargs):
        """
        Return a response, using the `response_class` for this view, with a
        template rendered with the given context.

        Pass response_kwargs to the constructor of the response class.
        """
        response_kwargs.setdefault('content_type', self.content_type)
        return self.response_class(
            request=self.request,
            template=self.get_template_names(),
            context=context,
            using=self.template_engine,
            **response_kwargs
        )

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. Must return
        a list. May not be called if render_to_response() is overridden.
        """
        if self.template_name is None:
            raise ImproperlyConfigured(
                "TemplateResponseMixin requires either a definition of "
                "'template_name' or an implementation of 'get_template_names()'")
        else:
            return [self.template_name]