Django通用视图

1.通用视图基类

generic/base.py
class View:  # 请求反射,对应到视图处理类定义的处理方法

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

    def __init__(self, **kwargs
		
		# **kwargs:来自于url中as_view()方法中的参数
        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)
		# cls:url中视图处理类
        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):
        # 请求通过反射对应到我们视图处理类内部定义的方法
        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):
		# 返回请求方法
        # hasattr(self, m)  :判断我们定义的视图处理类是否实现了http_method_names初始定义的方法
        # ['GET', 'POST', 'HEAD', 'OPTIONS']
        # 循环列表内允许的请求方法,如果在我们定义的类视图内部实现了该方法,则将其返回
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]
  • 请求进来首先调用url中的as_view()方法,该方法是一个闭包,将我们视图处理类以及其属性传递给view函数,然后将request请求对象以及这些参数传递给dispatch方法,该方法匹配请求方式method(get、post、delete、put…),匹配成功带着这些参数返回,我们在类试图中只需重写这些方法中我们用到的即可,需要自己实现response方法,该类是所有通用视图的基类。
  • 该方法没有对模板进行处理,在前后端不分离项目中,我们需要自己处理模板。
class ContextMixin:  # 获取额外传递的变量
	"""接受额外传递变量保存到全局上下文中"""
	extra_context = None

    def get_context_data(self, **kwargs):
        kwargs.setdefault('view', self)
        if self.extra_context is not None:
            kwargs.update(self.extra_context)
        return kwargs
  • 该方法比较简单,主要用来接收需要额数据对象,将其保存在全局上下文context中,方便后续在模板中使用。
class TemplateResponseMixin:  # 将全局上下文对象渲染到模板中,将其展示给前端
	"""返回模板"""
    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]
  • 该方法重点实现了render_to_response方法,封装response方法,将数据渲染到模板并返回,我们继承他后只需要给定模板名即可,不需要我们再对模板单独处理,弥补了view视图的不足。
  • 注意,render_to_response方法中的context数据对象需要在调用该方法时进行传递。
class TemplateView(TemplateResponseMixin, ContextMixin, View):  # 通过指定请求方式,将数据传递给TemplateResponseMixin基类中的render_to_response方法
    """
	接受数据,渲染模板
    """
    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)
        return self.render_to_response(context)
  • 该方法继承了以上三个基类
  • 重写模板名称template_name覆盖TemplateResponseMixin中的默认名称即可实现模板的返回
  • 重写请求方法method(get、post、delete、update、put…)来自view视图,处理对应请求
  • 重写get_context_data方法来自ContextMixin视图,实现额外变量的传递
  • 最终通过TemplateResponseMixin模板中的render_to_response端方法实现数据渲染并返回
  • 该基类可以直接使用在url中,当我们只需要返回一个页面,不需要任何数据填充时,这样做时很好的选择:如下所示
  • path('', TemplateView.as_view(template_name='user/login.html'), name='login')
class RedirectView(View):  # 对所有请求提供重定向服务
    """对所有请求提供重定向服务"""
    permanent = False
    url = None
    pattern_name = None
    query_string = False

    def get_redirect_url(self, *args, **kwargs):
        """
        将获取到的url参数作为查询关键字添加进url中
        """
        if self.url:
            url = self.url % kwargs
        elif self.pattern_name:
            url = reverse(self.pattern_name, args=args, kwargs=kwargs)
        else:
            return None

        args = self.request.META.get('QUERY_STRING', '')
        if args and self.query_string:
            url = "%s?%s" % (url, args)
        return url

    def get(self, request, *args, **kwargs):
        url = self.get_redirect_url(*args, **kwargs)
        if url:
            if self.permanent:
                return HttpResponsePermanentRedirect(url)
            else:
                return HttpResponseRedirect(url)
        else:
            logger.warning(
                'Gone: %s', request.path,
                extra={'status_code': 410, 'request': request}
            )
            return HttpResponseGone()

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

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

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

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

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

    def patch(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)
  • 继承view重写所有请求方法,匹配成功后根据请求路径跳转到对应视图去处理
  • path('', RedirectView.as_view(url='index/'), name='index')

2.通用视图展示类

1. ListView

generic/list.py
class MultipleObjectMixin(ContextMixin):  # 排序、分页、额外数据添加到全局上下文
    """A mixin for views manipulating multiple objects."""
    allow_empty = True
    queryset = None  # 查询集如:book.objects.all()
    model = None  # 要查询的模型对象
    paginate_by = None  # 每页显示数据量
    paginate_orphans = 0
    context_object_name = None  # 传递给模板全局上下文的别名,默认object_list
    paginator_class = Paginator
    page_kwarg = 'page'  # 分页参数即:url中?后面显示内容
    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

	# 继承自ContextMixin,获取需要额外传递给模板的变量
    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)
  • 继承该类主要实现数据列表展示(queryset数据集对象)

  • 继承该类后,我们只需要传递变量queryset对象或者model对象到我们的视图类中即可

  • 如果要实现分页可指定paginate_by

  • 如果要实现排序可指定ordering

  • 如果要添加额外数据对象,只需重写get_context_data方法

  • 如果要对展示数据进行过滤,只需重写get_queryset方法

  • **重点**:该类不能单独使用,因为其没有实现response方法

  • 如果想实现数据和模板分离,则只需配合view类一起使用,重写请求方法method,在方法内实现response方法即可

  • 如果想混合模板一起使用,可配合TemplateResponseMixin即可,该类中的render_to_response实现了response方法

  • 分页使用

 {% with paginator=page_obj.paginator %}
    {% if paginator.count > 0 %}
      <nav aria-label="Page navigation">
        <ul class="pagination">
           previous button 
          {% if page_obj.has_previous %}
            <li class="page-item">
              <a class="page-link" href="?page={{ page_obj.previous_page_number }}"
                 aria-label="Previous">&laquo;</a>
            </li>
          {% else %}
            <li class="page-item disabled">
              <a class="page-link" aria-label="Previous">&laquo;</a>
            </li>
          {% endif %}
           page button 
          {% for i in paginator.page_range|custom_page_range:page_obj.number %}
            {% if page_obj.number == i %}
              <li class="page-item active" aria-current="page">
                <span class="page-link">{{ i }} <span class="sr-only">current page</span></span>
              </li>
            {% else %}
              <li class="page-item">
                <a class="page-link" href="?page={{ i }}">{{ i }}</a>
              </li>
            {% endif %}
          {% endfor %}
           next button 
          {% if page_obj.has_next %}
            <li class="page-item">
              <a class="page-link" href="?page={{ page_obj.next_page_number }}">&raquo;</a>
            </li>
          {% else %}
            <li class="page-item disabled">
              <span class="page-link">&raquo;</span>
            </li>
          {% endif %}
        </ul>
      </nav>
    {% endif %}
  {% endwith %}
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 = not self.object_list
            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)
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):  
    """Mixin for responding with a template and list of objects."""
    # template_name = None  来自TemplateResponseMixin类
    template_name_suffix = '_list'

    def get_template_names(self):  # 继承自TemplateResponseMixin,重写了该方法
        """
        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))
        elif not names:
            raise ImproperlyConfigured(
                "%(cls)s requires either a 'template_name' attribute "
                "or a get_queryset() method that returns a QuerySet." % {
                    'cls': self.__class__.__name__,
                }
            )
        return names
  • 该类实现了一个视图可以同时处理渲染多个模板
  • 示例
TAB_SUMMARY = 'summary'
TAB_VOLUME = 'volume'
TAB_NETWORKING = 'networking'
TAB_BACKUP = 'backup'
TAB_SNAPSHOT = 'snapshot'
TABS = (TAB_SUMMARY, TAB_VOLUME, TAB_NETWORKING, TAB_BACKUP, TAB_SNAPSHOT)

class xxxxxxxx(MultipleObjectTemplateResponseMixin, ContextMixin):  
    def get_template_names(self):
    	# url请求参数,根据该参数返回对应模板,如果不在定义范围内,抛出错误
        instance_tab = self.kwargs.get('instance_tab')  
        name = 'client20/dashboard/service/instance_detail_%s.html'
        if instance_tab in TABS:
            template_name = name % instance_tab
            return [template_name]
        else:
            raise Http404

	def get_context_data(self, **kwargs):
		# 获取全局上下文对象
        context = super().get_context_data(**kwargs)
        instance_tab = self.kwargs.get('instance_tab')  # url查询字符串
        # 根据请求参数获取对应context数据
        context = self.__getattribute__('_get_%s_context' % instance_tab)(context)
        return context

	def _get_volume_context(self, context):
        context.update(volumes=self.object.volume_set.all().order_by('-is_sys'))
        return context
    
    def _get_summary_context(self, context):
    	...
    	return context

	....
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):
	pass
  • BaseListView基类主要负责数据处理
  • MultipleObjectTemplateResponseMixin基类主要负责模板渲染,和response方法

2.DetailView

generic/detail.py
class SingleObjectMixin(ContextMixin):  # 返回数据对象具体信息
    """
    Provide the ability to retrieve a single object for further manipulation.
    """
    model = None
    queryset = None
    slug_field = 'slug'  # 查询字符串对应的模型字段
    context_object_name = None
    slug_url_kwarg = 'slug'  # 查询字符串
    pk_url_kwarg = 'pk'  # 查询主键,以id作为过滤条件
    query_pk_and_slug = False

    def get_object(self, queryset=None):  # 返回结果集经过条件筛选查询后得到的对象, 最好指定pk_url_kwarg而非query_pk_and_slug,值为url查询字符串
        """
        Return the object the view is displaying.

        Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
        Subclasses can override this to return any object.
        """
        # Use a custom queryset if provided; this is required for subclasses
        # like DateDetailView
		# 如果queryset没指定,则从model获取
        if queryset is None:
            queryset = self.get_queryset()

        # self.kwargs:来自as_view()方法中的view函数
        pk = self.kwargs.get(self.pk_url_kwarg)
        slug = self.kwargs.get(self.slug_url_kwarg)
        if pk is not None:
            queryset = queryset.filter(pk=pk)

        # Next, try looking up by slug.
        if slug is not None and (pk is None or self.query_pk_and_slug):
            slug_field = self.get_slug_field()
            queryset = queryset.filter(**{slug_field: slug})

        # If none of those are defined, it's an error.
        if pk is None and slug is None:
            raise AttributeError(
                "Generic detail view %s must be called with either an object "
                "pk or a slug in the URLconf." % self.__class__.__name__
            )

        try:
            # Get the single item from the filtered queryset
            obj = queryset.get()
        except queryset.model.DoesNotExist:
            raise Http404(_("No %(verbose_name)s found matching the query") %
                          {'verbose_name': queryset.model._meta.verbose_name})
        return obj

    def get_queryset(self):  # 获取查询结果集
        """
        Return the `QuerySet` that will be used to look up the object.

        This method is called by the default implementation of get_object() and
        may not be called if get_object() is overridden.
        """
        if self.queryset is None:
            if self.model:
                return 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__
                    }
                )
        return self.queryset.all()

    def get_slug_field(self):
        """Get the name of a slug field to be used to look up by slug."""
        return self.slug_field

    def get_context_object_name(self, obj):  # 查询对象绑定变量名,用于模板循环
        """Get the name to use for the object."""
        if self.context_object_name:
            return self.context_object_name
        elif isinstance(obj, models.Model):
            return obj._meta.model_name
        else:
            return None

    def get_context_data(self, **kwargs):  # 渲染模板
        """Insert the single object into the context dict."""
        context = {}
        if self.object:
            context['object'] = self.object
            context_object_name = self.get_context_object_name(self.object)
            if context_object_name:
                context[context_object_name] = self.object
        context.update(kwargs)
        return super().get_context_data(**context)
  • 继承该类主要实现单个数据对象展示
  • 继承该类后,我们需要传递变量queryset对象或者model对象到我们的视图类中
  • pk_url_kwarg,主键过滤
path('backup/<pk:backup_id>/detail/', backup.BackupDetail.as_view(), name='backup_detail'),

pk_url_kwarg = 'backup_id'
  • 如果指定了slug_url_kwarg,则以该名称作为过滤条件,通常配合slug_field(指明slug_url_kwarg给定的名称对应到model中哪个字段)一起使用。
path('volume/<slug:volume_uuid>/delete/by_message/', executer.VolumeDelete.as_view()),

slug_field = 'uuid'  # 根据模型中的uuid字段进行过滤,而非主键id
slug_url_kwarg = 'volume_uuid'
  • 注意:以上两中方式,必须指定其中一个字段作为过滤条件,否则会抛出异常ImproperlyConfigured
  • 如果要添加额外数据对象,只需重写get_context_data方法
  • 如果要对数据进行处理,只需重写get_object方法
  • **重点**:该类不能单独使用,因为其没有实现response方法
  • 如果想实现数据和模板分离,则只需配合view类一起使用,重写请求方法method,在方法内实现response方法即可
  • 如果想混合模板一起使用,可配合TemplateResponseMixin即可,该类中的render_to_response实现了response方法
class BaseDetailView(SingleObjectMixin, View):  # 在上述基础上集成View对象,反射得到请求方法,将数据返回
    """A base view for displaying a single object."""
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        context = self.get_context_data(object=self.object)
        return self.render_to_response(context)
class SingleObjectTemplateResponseMixin(TemplateResponseMixin):  
    template_name_field = None
    template_name_suffix = '_detail'

    def get_template_names(self):
        """
        Return a list of template names to be used for the request. May not be
        called if render_to_response() is overridden. Return the following list:

        * the value of ``template_name`` on the view (if provided)
        * the contents of the ``template_name_field`` field on the
          object instance that the view is operating upon (if available)
        * ``<app_label>/<model_name><template_name_suffix>.html``
        """
        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 self.template_name_field is set, grab the value of the field
            # of that name from the object; this is the most specific template
            # name, if given.
            if self.object and self.template_name_field:
                name = getattr(self.object, self.template_name_field, None)
                if name:
                    names.insert(0, name)

            # The least-specific option is the default <app>/<model>_detail.html;
            # only use this if the object in question is a model.
            if isinstance(self.object, models.Model):
                object_meta = self.object._meta
                names.append("%s/%s%s.html" % (
                    object_meta.app_label,
                    object_meta.model_name,
                    self.template_name_suffix
                ))
            elif getattr(self, 'model', None) is not None and issubclass(self.model, models.Model):
                names.append("%s/%s%s.html" % (
                    self.model._meta.app_label,
                    self.model._meta.model_name,
                    self.template_name_suffix
                ))

            # If we still haven't managed to find any template names, we should
            # re-raise the ImproperlyConfigured to alert the user.
            if not names:
                raise

        return names
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):
	pass
  • BaseDetailView基类主要负责数据处理
  • SingleObjectTemplateResponseMixin基类主要负责模板渲染,和response方法
  • 一般情况下只需重写get_object方法

3.通用视图编辑类

generic/edit.py

1. FormView

class FormMixin(ContextMixin):  
    """Provide a way to show and handle a form in a request."""
    initial = {}
    form_class = None  
    success_url = None
    prefix = None

    def get_initial(self):
        """Return the initial data to use for forms on this view."""
        return self.initial.copy()

    def get_prefix(self):
        """Return the prefix to use for forms."""
        return self.prefix

    def get_form_class(self):  # 获取form验证器
        """Return the form class to use."""
        return self.form_class

    def get_form(self, form_class=None):  # 使用form验证器对象对请求参数进行处理
        """Return an instance of the form to be used in this view."""
        if form_class is None:
            form_class = self.get_form_class()
        return form_class(**self.get_form_kwargs())

    def get_form_kwargs(self):  # 获取请求参数并返回
        """Return the keyword arguments for instantiating the form."""
        kwargs = {
            'initial': self.get_initial(),
            'prefix': self.get_prefix(),
        }

        if self.request.method in ('POST', 'PUT'):
            kwargs.update({
                'data': self.request.POST,
                'files': self.request.FILES,
            })
        return kwargs

    def get_success_url(self):  # 请求成功需要跳转的地址
        """Return the URL to redirect to after processing a valid form."""
        if not self.success_url:
            raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
        return str(self.success_url)  # success_url may be lazy

    def form_valid(self, form):  # 钩子方法
        """If the form is valid, redirect to the supplied URL."""
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form):  # 验证失败
        """If the form is invalid, render the invalid form."""
        return self.render_to_response(self.get_context_data(form=form))

    def get_context_data(self, **kwargs):
        """Insert the form into the context dict."""
        if 'form' not in kwargs:
            kwargs['form'] = self.get_form()
        return super().get_context_data(**kwargs)
  • 创建Form验证器,更新全局上下文
class ModelFormMixin(FormMixin, SingleObjectMixin):  # 重写is_valid方法,保存请求数据,跳转链接
    """Provide a way to show and handle a ModelForm in a request."""
    fields = None

    def get_form_class(self):  # 获取form验证器
        """Return the form class to use in this view."""
        if self.fields is not None and self.form_class:
            raise ImproperlyConfigured(
                "Specifying both 'fields' and 'form_class' is not permitted."
            )
        if self.form_class:
            return self.form_class
        else:
            if self.model is not None:
                # If a model has been explicitly provided, use it
                model = self.model
            elif getattr(self, 'object', None) is not None:
                # If this view is operating on a single object, use
                # the class of that object
                model = self.object.__class__
            else:
                # Try to get a queryset and extract the model class
                # from that
                model = self.get_queryset().model

            if self.fields is None:
                raise ImproperlyConfigured(
                    "Using ModelFormMixin (base class of %s) without "
                    "the 'fields' attribute is prohibited." % self.__class__.__name__
                )

            return model_forms.modelform_factory(model, fields=self.fields)

	def get_form_kwargs(self):  # 获取请求参数
        """Return the keyword arguments for instantiating the form."""
        kwargs = super().get_form_kwargs()
        if hasattr(self, 'object'):
            kwargs.update({'instance': self.object})
        return kwargs

    def get_success_url(self):  # 请求成功跳转url
        """Return the URL to redirect to after processing a valid form."""
        if self.success_url:
            url = self.success_url.format(**self.object.__dict__)
        else:
            try:
                url = self.object.get_absolute_url()
            except AttributeError:
                raise ImproperlyConfigured(
                    "No URL to redirect to.  Either provide a url or define"
                    " a get_absolute_url method on the Model.")
        return url

    def form_valid(self, form):  # 保存请求数据
        """If the form is valid, save the associated model."""
        self.object = form.save()
        return super().form_valid(form)
  • 继承了SingleObjectMixin
  • 使用Form验证器和model对象构建Form表单
  • 验证通过将数据保存进model对象,并返返回到success_url
  • 不能单独使用,没有实现response方法
class ProcessFormView(View):  
    """Render a form on GET and processes it on POST."""
    def get(self, request, *args, **kwargs):
        """Handle GET requests: instantiate a blank version of the form."""
        return self.render_to_response(self.get_context_data())

    def post(self, request, *args, **kwargs):
        """
        Handle POST requests: instantiate a form instance with the passed
        POST variables and then check if it's valid.
        """
        form = self.get_form()  # 获取form验证器
        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)

    # PUT is a valid HTTP verb for creating (with a known URL) or editing an
    # object, note that browsers only support POST for now.
    def put(self, *args, **kwargs):
        return self.post(*args, **kwargs)
  • 不能单独使用,需要结合FormMixin提供Form验证器
  • 通过请求方法POSTPUT方法实现数据处理
class BaseFormView(FormMixin, ProcessFormView):  
    """A base view for displaying a form."""
  • 可重写post方法和put方法
  • 可重写验证器的所有方法
class FormView(TemplateResponseMixin, BaseFormView):  # 将请求创建成功的数据渲染到模板
    """A view for displaying a form and rendering a template response."""
  • TemplateResponseMixinrender_to_response方法实现了模板数据渲染以及返回
  • 可重写post方法和put方法以及验证器的所有方法

2. CreateView

class BaseCreateView(ModelFormMixin, ProcessFormView):
    """
    Base view for creating a new object instance.

    Using this base class requires subclassing to provide a response mixin.
    """
    def get(self, request, *args, **kwargs):
        self.object = None
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = None
        return super().post(request, *args, **kwargs)
  • ModelFormMixin可重写get_objpk_url_kwargs…,进行数据处理
  • 可重写form验证器的所有方法,对数据进行验证,然后保存
  • 可重写postputget等方法,结合Form表单进行相关处理
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView): 
    """
    View for creating a new object, with a response rendered by a template.
    """
    template_name_suffix = '_form'
  • 一般情况下只需重写form_valid方法

3. UpdateView

class BaseUpdateView(ModelFormMixin, ProcessFormView):
    """
    Base view for updating an existing object.

    Using this base class requires subclassing to provide a response mixin.
    """
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().post(request, *args, **kwargs)
class UpdateView(SingleObjectTemplateResponseMixin, BaseUpdateView):  # 将请求更新成功的数据渲染到模板并向应给前端
    """View for updating an object, with a response rendered by a template."""
    template_name_suffix = '_form'
  • 一般情况下只需重写form_valid方法

4. DeleteView

class DeletionMixin:  # 删除对象  需要重写delete方法,找到要删除对象
    """Provide the ability to delete objects."""
    success_url = None

    def delete(self, request, *args, **kwargs):
        """
        Call the delete() method on the fetched object and then redirect to the
        success URL.
        """
        self.object = self.get_object()
        success_url = self.get_success_url()
        self.object.delete()
        return HttpResponseRedirect(success_url)

    # Add support for browsers which only accept GET and POST for now.
    def post(self, request, *args, **kwargs):
        return self.delete(request, *args, **kwargs)

    def get_success_url(self):
        if self.success_url:
            return self.success_url.format(**self.object.__dict__)
        else:
            raise ImproperlyConfigured(
                "No URL to redirect to. Provide a success_url.")
class BaseDeleteView(DeletionMixin, BaseDetailView):
    """
    Base view for deleting an object.

    Using this base class requires subclassing to provide a response mixin.
    """
class DeleteView(SingleObjectTemplateResponseMixin, BaseDeleteView):
    """
    View for deleting an object retrieved with self.get_object(), with a
    response rendered by a template.
    """
    template_name_suffix = '_confirm_delete'
  • 一般情况下只需重写delete方法即可
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值