一,为什么需要视图类(Class Base Views)
面对功能和业务逻辑具有相同过程的需求时, 使用视图函数来完成的话,可能需要编写大量重复代码,而且视图函数过多时,不方便后期修护,所以django贴心地提供了视图类来封装一些列相同逻辑,开发者只需要继django提供的父类,就能使用更加完善、统一的功能。
二,django提供的数据显示视图类
用于将表单或模型的数据展示到网页中。
1,重定向视图RedirectView
**功能:**指定url与请求参数后,实现HTTP重定向哦功能。
**源码:**为了方便理解,所有源码都调整了顺序
源码:class RedirectView(View):\"\"\"Provide a redirect on any GET request.\"\"\"permanent = False # True,301。False,302。url = None # 要重定向到的字符串作为字符串。如果是None引发410(Gone)HTTP错误。pattern_name = None # 要重定向到的URL名称。query_string = False # 是否将GET查询字符串传递给新位置。如果 True,则查询字符串将附加到URL。如果False,则丢弃查询字符串。def get_redirect_url(self, *args, **kwargs):\"\"\"Return the URL redirect to. Keyword arguments from the URL pattern match generating the redirect request are provided as kwargs to this method.\"\"\"if self.url:url = self.url % kwargselif self.pattern_name:url = reverse(self.pattern_name, args=args, kwargs=kwargs)else:return Noneargs = self.request.META.get(\'QUERY_STRING\', \'\')if args and self.query_string:url = \"%s?%s\" % (url, args)return urldef 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)class View:\"\"\"Intentionally simple parent class for all views. Only implementsdispatch-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 extrakeyword 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)@classonlymethoddef 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.getself.setup(request, *args, **kwargs)if not hasattr(self, \'request\'):raise AttributeError(\"%s instance has no \'request\' attribute. Did you override \"\"setup() and forget to call super()?\" % cls.__name__)return self.dispatch(request, *args, **kwargs)view.view_class = clsview.view_initkwargs = initkwargs# take name and docstring from classupdate_wrapper(view, cls, updated=())# and possible attributes set by decorators# like csrf_exempt from dispatchupdate_wrapper(view, cls.dispatch, assigned=())return viewdef setup(self, request, *args, **kwargs):\"\"\"Initialize attributes shared by all view methods.\"\"\"self.request = requestself.args = argsself.kwargs = kwargsdef 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_allowedreturn 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 responsedef _allowed_methods(self):return [m.upper() for m in self.http_method_names if hasattr(self, m)]
- 关于重定向302与301,可以参考这篇文章,
官方示例:
官方示例:urls.py:from django.urls import pathfrom django.views.generic.base import RedirectViewfrom article.views import ArticleCounterRedirectView, ArticleDetailurlpatterns = [path(\'counter/<int:pk>/\', ArticleCounterRedirectView.as_view(), name=\'article-counter\'),path(\'details/<int:pk>/\', ArticleDetail.as_view(), name=\'article-detail\'),path(\'go-to-django/\', RedirectView.as_view(url=\'https://djangoproject.com\'), name=\'go-to-django\'),]views.py:from django.shortcuts import get_object_or_404from django.views.generic.base import RedirectViewfrom articles.models import Articleclass ArticleCounterRedirectView(RedirectView):permanent = Falsequery_string = Truepattern_name = \'article-detail\'def get_redirect_url(self, *args, **kwargs):article = get_object_or_404(Article, pk=kwargs[\'pk\'])article.update_counter()return super().get_redirect_url(*args, **kwargs)
写自己的例子:
urls.py:from django.urls import pathfrom index.views import *urlpatterns = [# 定义路由path(\'\', index, name=\'index\'),path(\'turnTo\', turnTo.as_view(url=\'https://djangoproject.com\'), name=\'turnTo\')]views.py:from django.shortcuts import renderfrom django.views.generic.base import RedirectViewdef index(request):return render(request, \'index.html\')class turnTo(RedirectView):permanent = Falsepattern_name = Nonequery_string = Truedef get_redirect_url(self, *args, **kwargs):print(\'This is get_redirect_url:\', self.url)return super().get_redirect_url(*args, **kwargs)def get(self, request, *args, **kwargs):print(request.META.get(\'HTTP_USER_AGENT\'))return super().get(request, *args, **kwargs)index.html:<!DOCTYPE html><html><body><h3>Hello RedirectView</h3><a href=\"{% url \'index:turnTo\' %}\">ToTurn</a></body></html>
运行起来,点击链接会跳转到指定的页面:
成功跳转:
2,基础试图TemplateView
功能: 渲染模板。将关键字参数从URLconf传递到上下文。
源码:
class TemplateView(TemplateResponseMixin, ContextMixin, View):\"\"\"Render a template. Pass keyword arguments from the URLconf to the context.\"\"\"def get(self, request, *args, **kwargs):context = self.get_context_data(**kwargs)return self.render_to_response(context)class TemplateResponseMixin:\"\"\"A mixin that can be used to render a template.渲染模板\"\"\"template_name = None # 设置模板名template_engine = None # 设置模板引擎response_class = TemplateResponse # 设置HTTP请求响应类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.获取template_names值\"\"\"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]class ContextMixin:\"\"\"A default context mixin that passes the keyword arguments received by get_context_data() as the template context.获取模板上下文内容。\"\"\"extra_context = Nonedef get_context_data(self, **kwargs):kwargs.setdefault(\'view\', self)if self.extra_context is not None:kwargs.update(self.extra_context)return kwargs
官方示例:
views.py:from django.views.generic.base import TemplateViewfrom articles.models import Articleclass HomePageView(TemplateView):template_name = \"home.html\"def get_context_data(self, **kwargs):context = super().get_context_data(**kwargs)context[\'latest_articles\'] = Article.objects.all()[:5]return contexturls.py:from django.urls import pathfrom myapp.views import HomePageViewurlpatterns = [path(\'\', HomePageView.as_view(), name=\'home\'),]
写自己的例子:
views.py:class index(TemplateView):template_name = \'index.html\'template_engine = Nonecontent_type = Noneextra_context = {\'title\': \'我是你刚点进来时从类中直接被传递过来的内容\'}# 重新定义模版上下文的获取方式def get_context_data(self, **kwargs):context = super().get_context_data(**kwargs)context[\'value\'] = \'我一直叫小黑\'return context# 定义HTTP的POST请求处理def post(self, request, *args, **kwargs):self.extra_context = {\'title\': \'我是你点击submit后从类中直接被传递过来的内容\'}context = self.get_context_data(**kwargs)return self.render_to_response(context)urls.py:from django.urls import pathfrom index.views import *urlpatterns = [# 定义路由path(\'\', index.as_view(), name=\'index\'),]index.html:<!DOCTYPE html><html><body><h3>Hello,{{ title }}</h3><div>{{ value }}</div><br><form action=\"\" method=\"post\">{% csrf_token %}<input type=\"submit\" value=\"点我,我的来源就变了\"></form></body></html>
运行起来,会将指定的模板上下文内容传递给指定的模板:
3,列表视图ListView
前面的两个数据展示视图通常都是联系视图与模板的,django也提供了联系视图与模型的视图ListView。
**功能:**将数据表中的数据以列表形式显示。
源码:
class ListView(MultipleObjectTemplateResponseMixin, BaseListView):\"\"\"Render some list of objects, set by `self.model` or `self.queryset`.`self.queryset` can actually be any iterable of items, not just a queryset.\"\"\"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._metanames.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 namesclass 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_listif 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 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 # 对queryset内容进行排序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.获取queryset 的值\"\"\"if self.queryset is not None:queryset = self.querysetif 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 querysetdef get_ordering(self):\"\"\"Return the field or fields to use for ordering the queryset.获取ordering 的值\"\"\"return self.orderingdef paginate_queryset(self, queryset, page_size):\"\"\"Paginate the queryset, if needed.据queryset值进行分页处理\"\"\"paginator = self.get_paginator(queryset, page_size, orphans=self.get_paginate_orphans(),allow_empty_first_page=self.get_allow_empty())page_kwarg = self.page_kwargpage = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1try:page_number = int(page)except ValueError:if page == \'last\':page_number = paginator.num_pageselse: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_bydef 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_orphansdef 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_emptydef 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_nameelif hasattr(object_list, \'model\'):return \'%s_list\' % object_list.model._meta.model_nameelse:return Nonedef 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_listpage_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] = querysetcontext.update(kwargs)return super().get_context_data(**context)
官方示例:
views.py:from django.utils import timezonefrom django.views.generic.list import ListViewfrom articles.models import Articleclass ArticleListView(ListView):model = Articlepaginate_by = 100 # if pagination is desireddef get_context_data(self, **kwargs):context = super().get_context_data(**kwargs)context[\'now\'] = timezone.now()return contexturls.py:from django.urls import pathfrom article.views import ArticleListViewurlpatterns = [path(\'\', ArticleListView.as_view(), name=\'article-list\'),]article_list.html:<h1>Articles</h1><ul>{% for article in object_list %}<li>{{ article.pub_date|date }} - {{ article.headline }}</li>{% empty %}<li>No articles yet.</li>{% endfor %}</ul>
自己的例子:
models.py:from django.db import models# Create your models here.class PersonInfo(models.Model):id = models.AutoField(primary_key=True)name = models.CharField(max_length=20)age = models.IntegerField()views.py:from django.views.generic import ListViewfrom .models import PersonInfoclass index(ListView):\"\"\"这里直接继承自ListView,由于它继承自BaseListView,故只能处理HTTP GET的能力,需要处理HTTP POST可以先直接在ListView中写出来。\"\"\"# 设置模版文件template_name = \'index.html\'# 设置模型外的数据extra_context = {\'title\': \'这是张人员信息表\'}# 查询模型PersonInfo,产生查询结果对象queryset = PersonInfo.objects.all()# 每页的展示一条数据paginate_by = 1index.html:<!DOCTYPE html><html><head><title>{{ title }}</title><body><h3>{{ title }}</h3><table border=\"1\">{% for i in personinfo_list %}<tr><th>{{ i.name }}</th><th>{{ i.age }}</th></tr>{% endfor %}</table><br>{% if is_paginated %}<div class=\"pagination\"><span class=\"page-links\">{% if page_obj.has_previous %}<a href=\"/?page={{ page_obj.previous_page_number }}\">上一页</a>{% endif %}{% if page_obj.has_next %}<a href=\"/?page={{ page_obj.next_page_number }}\">下一页</a>{% endif %}<br><br><span class=\"page-current\">第{{ page_obj.number }}页,共{{ page_obj.paginator.num_pages }}页。</span></span></div>{% endif %}</body></html>
运行起来,仅需要简单的设置,就能在后台将数据提取出来交给模板进行展示:
4,详情视图DetailView
功能: 功能上较ListView更强。
源码:
class DetailView(SingleObjectTemplateResponseMixin, BaseDetailView):\"\"\"Render a \"detail\" view of an object.By default this is a model instance looked up from `self.queryset`, but the view will support display of *any* object by overriding `self.get_object()`.\"\"\"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 becalled 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 theobject 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._metanames.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:raisereturn namesclass BaseDetailView(SingleObjectMixin, View):\"\"\"A base view for displaying a single object.处理HTTP GET请求\"\"\"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 SingleObjectMixin(ContextMixin):\"\"\"Provide the ability to retrieve a single object for further manipulation.\"\"\"model = Nonequeryset = Noneslug_field = \'slug\' # 设置模型中某字段为查询对象,默认为slugcontext_object_name = Noneslug_url_kwarg = \'slug\'pk_url_kwarg = \'pk\' # 设置url的某个参数,默认为pkquery_pk_and_slug = False # True时,据slug_url_kwarg和pk_url_kwarg组合查询def get_object(self, queryset=None):\"\"\"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 DateDetailViewif queryset is None:queryset = self.get_queryset()# Next, try looking up by primary key.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 querysetobj = queryset.get()except queryset.model.DoesNotExist:raise Http404(_(\"No %(verbose_name)s found matching the query\") %{\'verbose_name\': queryset.model._meta.verbose_name})return objdef 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_fielddef get_context_object_name(self, obj):\"\"\"Get the name to use for the object.\"\"\"if self.context_object_name:return self.context_object_nameelif isinstance(obj, models.Model):return obj._meta.model_nameelse:return Nonedef get_context_data(self, **kwargs):\"\"\"Insert the single object into the context dict.\"\"\"context = {}if self.object:context[\'object\'] = self.objectcontext_object_name = self.get_context_object_name(self.object)if context_object_name:context[context_object_name] = self.objectcontext.update(kwargs)return super().get_context_data(**context)
自己的例子:
models.py:from django.db import models# Create your models here.class PersonInfo(models.Model):id = models.AutoField(primary_key=True)name = models.CharField(max_length=20)age = models.IntegerField()views.py:from django.views.generic import DetailViewfrom .models import PersonInfoclass index(DetailView):# 设置模版文件template_name = \'index.html\'# 设置模型外的数据extra_context = {\'title\': \'人员信息表\'}# 设置模型的查询字段slug_field = \'age\'# 设置路由的变量名,与属性slug_field实现模型的查询操作slug_url_kwarg = \'age\'pk_url_kwarg = \'pk\'# 设置查询模型PersonInfomodel = PersonInfo# 属性queryset可以做简单的查询操作# queryset = PersonInfo.objects.all()index.html:<!DOCTYPE html><html><head><title>{{ title }}</title><body><h3>{{ title }}</h3><table border=\"1\"><tr><th>{{ personinfo.name }}</th><th>{{ personinfo.age }}</th></tr></table><br></body></html>
运行起来,相对ListView而言,DetailView除了拥有它的所有属性与方法之外,还能从请求中取出参数进行使用,查询更加细节: