一,为什么需要视图类(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 % 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)
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.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 = 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 setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods."""
self.request = request
self.args = args
self.kwargs = kwargs
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)]
- 关于重定向302与301,可以参考这篇文章,
官方示例:
官方示例:
urls.py:
from django.urls import path
from django.views.generic.base import RedirectView
from article.views import ArticleCounterRedirectView, ArticleDetail
urlpatterns = [
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_404
from django.views.generic.base import RedirectView
from articles.models import Article
class ArticleCounterRedirectView(RedirectView):
permanent = False
query_string = True
pattern_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 path
from 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 render
from django.views.generic.base import RedirectView
def index(request):
return render(request, 'index.html')
class turnTo(RedirectView):
permanent = False
pattern_name = None
query_string = True
def 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 = 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
官方示例:
views.py:
from django.views.generic.base import TemplateView
from articles.models import Article
class 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 context
urls.py:
from django.urls import path
from myapp.views import HomePageView
urlpatterns = [
path('', HomePageView.as_view(), name='home'),
]
写自己的例子:
views.py:
class index(TemplateView):
template_name = 'index.html'
template_engine = None
content_type = None
extra_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 path
from 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._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
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 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.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.
获取ordering 的值
"""
return self.ordering
def 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_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)
官方示例:
views.py:
from django.utils import timezone
from django.views.generic.list import ListView
from articles.models import Article
class ArticleListView(ListView):
model = Article
paginate_by = 100 # if pagination is desired
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['now'] = timezone.now()
return context
urls.py:
from django.urls import path
from article.views import ArticleListView
urlpatterns = [
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 ListView
from .models import PersonInfo
class index(ListView):
"""
这里直接继承自ListView,由于它继承自BaseListView,故只能处理HTTP GET的能力,需要处理HTTP POST可以先直接在ListView中写出来。
"""
# 设置模版文件
template_name = 'index.html'
# 设置模型外的数据
extra_context = {'title': '这是张人员信息表'}
# 查询模型PersonInfo,产生查询结果对象
queryset = PersonInfo.objects.all()
# 每页的展示一条数据
paginate_by = 1
index.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 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 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 = None
queryset = None
slug_field = 'slug' # 设置模型中某字段为查询对象,默认为slug
context_object_name = None
slug_url_kwarg = 'slug'
pk_url_kwarg = 'pk' # 设置url的某个参数,默认为pk
query_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 DateDetailView
if 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 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)
自己的例子:
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 DetailView
from .models import PersonInfo
class index(DetailView):
# 设置模版文件
template_name = 'index.html'
# 设置模型外的数据
extra_context = {'title': '人员信息表'}
# 设置模型的查询字段
slug_field = 'age'
# 设置路由的变量名,与属性slug_field实现模型的查询操作
slug_url_kwarg = 'age'
pk_url_kwarg = 'pk'
# 设置查询模型PersonInfo
model = 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除了拥有它的所有属性与方法之外,还能从请求中取出参数进行使用,查询更加细节:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/98157.html