所谓的视图,就是MVC或MTV中的视图,负责业务逻辑,返回对应的响应,并在适当时候调用Model和Template。 官方文档
先举个例子
from django.http import HttpResponse
import datetime
def current_datetime(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
让我们来逐行解释下上面的代码:
-
首先,我们从
django.http
模块导入了HttpResponse
类,以及Python的datetime
库。 -
接着,我们定义了
current_datetime
函数。它就是视图函数。每个视图函数都使用HttpRequest
对象作为第一个参数,并且通常称之为**request
。**注意,视图函数的名称并不重要;不需要用一个统一的命名方式来命名,以便让Django识别它。我们将其命名为
current_datetime
,是因为这个名称能够比较准确地反映出它实现的功能。 -
这个视图会返回一个
HttpResponse
对象,其中包含生成的响应。每个视图函数都负责返回一个HttpResponse
对象。
当 Django 接受一个请求之后(严格来说是 HTTP 请求,只不过 HTTP 请求会被 Django 转化
为 request对象),请求会先经过所有 middleware 的 process_request 方法,然后解析 URL,
接着根据配置的 URL 和 View 的映射,把 request 对象传递到 View 中 。
FBV和CBV
- FBV (function base views)
- CBV (class base views)
单纯从技术上来说, function view (函数视图)和 class-based view 并没有高低之分,有的仅
仅是对场景的适用性。
首先需要对比的一个概念是函数和类 。
什么情况下需要使用函数,什么情况下需要封装出一个类呢?简单来说,只要代码的逻辑
被重复使用, 同时有需要共享的数据,就可以考虑封装出一个类 。 这样就可以享用类提供的好处
了一一继承和复用 。 类的通用视图的可扩展性和灵活性远超基于函数的通用视图。
而如果这种情况下依然使用函数的话,就需要定义多个子函数,通过函数级别的复用来达到
目的 。 但问题在于不够结构化,无法通过继承一个结构(类),然后修改其中某个配置,或者重
写某个方法达到复用的目的 。
function view 的处理逻辑比较好理解,就是简单的函数,流程就是函数的执行流程,只是第
一个参数是 request 对象。
class-based view 对外暴露的接口其实是 as_view,as_view 其实只做了一件事,那就是返回一个闭包。这个闭包会在同ango 解析完请求之后调用,而闭包中的逻辑是这样的:
- 给 class (也就是我们定义的 View 类)赋值一一request 、 args 和 kwargs 。
- 根据 HTTP 方法分发请求 。 比如 HTTP GET 请求会调用 class.get 方法, POST 请求会
调用 class.post 方法 。
FBV (function base views)
FBV(function base views) 就是在视图里使用函数处理请求。
def index(request):
return HttpResponse("欢迎访问我的博客首页!")
上面只是简单实现了从 URL 到 View 的数据映射 ,接着来填充简单的内容 。
from django.shortcuts import render, HttpRespons
def index(request):
return render(request, 'index.html', context={'name': 'hello'})
这里需要稍稍介绍一下 render 方法,它接收的参数如下:
render(request, template_name, context=None, content_type=None, status=None,
using=None)
其中各参数的意义如下 。
- request : 封装了 HTTP 请求的 request 对象。
- template_name : 模板名称,可以像前面的代码那样带上路径 。
- context : 字典数据,它会传递到模板中 。
- cntent_type : 页面编码类型,默认值是 text/html 。
- status :状态码,默认值是 200
- using :使用|哪种模板引擎解析,这可以在settings 中配置,默认使用 Django 自带的模板。
CBV (class base views)
CBV(class base views) 就是在视图里使用类处理请求。
Python是一个面向对象的编程语言,如果只用函数来开发,有很多面向对象的优点就错失了(继承、封装、多态)。所以 Django 在后来加入了Class-Based-View。可以让我们用类写View。这样做的优点主要下面两种:
- 提高了代码的复用性,可以使用面向对象的技术,比如 Mixin
- 可以用不同的函数针对不同的HTTP方法处理,而不是通过很多 if 判断,提高代码可读性
Django 文档中 class-based view 的具体解释:
View 就是一个能够接受请求并且返回响应的可调用对象,它不仅仅是一 个函数 。
同时 Django 提供了一些类作为 View 的示例 。 这样就九许我们结构化 View 并且通过继
承和混入( mixin )的方式来复用代码 。mixin是多重继承的一种,它将父类的行为和属性结合在一起
使用
注意:使用CBV时,urls.py 中也做对应的修改:
urlpatterns = [
url(r'^$', IndexView.as_view(), name='index'),
]
使用Class-based View有两种方法
- 一个是在as_view()中直接传入参数,它会覆盖掉该View原有的属性,这种情况只适用于处理不复杂的情况:
from django.conf.urls import patterns
from django.views.generic import TemplateView
urlpatterns = patterns('',
(r'^about/', TemplateView.as_view(template_name="about.html")),
)
- 另外一个就是直接继承该View,然后覆盖其中的方法,属性等,实现自己想要的功能,在URLconf中,直接使用as_view()就可以了,不用传递参数:
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = "about.html"
from django.conf.urls import patterns
from some_app.views import AboutView
urlpatterns = patterns('',
(r'^about/', AboutView.as_view()),
)
View分享
from django.shortcuts import render
# Create your views here.
from django.views.generic import ListView, DetailView
from .models import Article, Category, Tag
from haystack.generic_views import SearchView
from django.conf import settings
from haystack.query import SearchQuerySet
import markdown
from django.shortcuts import get_object_or_404
from django.utils.text import slugify
from markdown.extensions.toc import TocExtension
import re
class IndexView(ListView):
model = Article
template_name = 'index.html'
context_object_name = 'article_index'
paginate_by = 5
def get_context_data(self, **kwargs):
"""
在视图函数中将模板变量传递给模板是通过给 render 函数传递一个字典实现的
例如 render(request, 'blog/index.html', context={'post_list': post_list})
这里传递了一个 {'post_list': post_list} 字典给模板。
在类视图中,这个需要传递的模板变量字典是通过 get_context_data 获得的,
所以我们复写该方法,以便我们能够自己再插入一些我们自定义的模板变量进去。
"""
# 首先获得父类生成的传递给模板的字典
context = super().get_context_data(**kwargs)
# 父类生成的字典中已有 paginator、page_obj、is_paginated 这三个模板变量
# paginator 是 Paginator 的一个实例
# page_obj 是 Page 的一个实例
# is_paginated 是一个布尔变量,用于指示是否已分页。
# 例如如果规定每页 10 个数据,而本身只有 5 个数据,其实就用不着分页,此时 is_paginated=False。
# 关于什么是 Paginator,Page 类在 使用 Django Pagination 实现简单的分页功能:http://zmrenwu.com/post/23/
# 中已有详细说明。
paginator = context.get('paginator')
page = context.get('page_obj')
is_paginated = context.get('is_paginated')
# 调用自己写的 pagination_data 方法获得显示分页导航条需要的数据
pagination_data = self.pagination_data(paginator, page, is_paginated)
# 将分页导航条的模板变量更新到 context 中
context.update(pagination_data)
# 将更新后的 context 返回,以便 ListView 使用这个字典中的模板变量去渲染模板
# 记住此时字典中已有了显示分页导航条所需的数据
return context
def pagination_data(self, paginator, page, is_paginated):
if not is_paginated:
# 如果没有分页,则无需显示分页导航条,不用任何分页导航条的数据,因此返回一个空的字典
return {}
# 当前页左边连续的页码号,初始值为空
left = []
# 当前页右边连续的页码号,初始值为空
right = []
# 标示第一页页码后是否需要显示省略号
left_has_more = False
# 标示最后一页页码前是否需要显示省略号
right_has_more = False
# 标示是否需要显示第一页的页码号。
# 因为如果当前页左边的连续页码号中已经含有第一页的页码号,此时就无需再显示第一页的页码号
# 其它情况下第一页的页码是始终需要显示的。
first = False
# 标示是否需要显示最后一页的页码号。
# 需要此指示变量的理由和上面相同。
last = False
# 获得用户当前请求的页码号
page_number = page.number
# 获得分页后的总页数
total_pages = paginator.num_pages
# 获得整个分页页码列表,比如分了四页,那么就是 [1, 2, 3, 4]
page_range = paginator.page_range
if page_number == 1:
# 如果用户请求的是第一页的数据,那么当前页左边的不需要数据,因此 left=[](已默认为空)
# 获取当前页右边的连续页码号。
# 比如分页页码列表是 [1, 2, 3, 4],那么获取的就是 right = [2, 3]
# 这里只获取了当前页码后连续两个页码,你可以更改这个数字以获取更多页码。
right = page_range[page_number:page_number + 2]
# 如果最右边的页码号比最后一页的页码号减去 1 还要小,
# 说明最右边的页码号和最后一页的页码号之间还有其它页码,因此需要显示省略号,通过 right_has_more 来指示
if right[-1] < total_pages - 1:
right_has_more = True
# 如果最右边的页码号比最后一页的页码号小,说明当前页右边的连续页码号中不包含最后一页的页码
# 所以需要显示最后一页的页码号,通过 last 来指示
if right[-1] < total_pages:
last = True
elif page_number == total_pages:
# 如果用户请求的是最后一页的数据,那么当前页右边就不需要数据,因此 right=[](已默认为空)
# 获取当前页左边的连续页码号。
# 比如分页页码列表是 [1, 2, 3, 4],那么获取的就是 left = [2, 3]
# 这里只获取了当前页码后连续两个页码,你可以更改这个数字以获取更多页码。
left = page_range[(page_number - 3)
if (page_number - 3) > 0 else 0:page_number - 1]
# 如果最左边的页码号比第 2 页页码号还大,
# 说明最左边的页码号和第一页的页码号之间还有其它页码,因此需要显示省略号,通过 left_has_more 来指示
if left[0] > 2:
left_has_more = True
# 如果最左边的页码号比第一页的页码号大,说明当前页左边的连续页码号中不包含第一页的页码
# 所以需要显示第一页的页码号,通过 first 来指示
if left[0] > 1:
first = True
else:
# 用户请求的既不是最后一页,也不是第一页,则需要获取当前页左右两边的连续页码号
# 这里只获取了当前页码前后连续两个页码,你可以更改这个数字以获取更多页码。
left = page_range[(page_number - 3)
if (page_number - 3) > 0 else 0:page_number - 1]
right = page_range[page_number:page_number + 2]
# 是否需要显示最后一页和最后一页前的省略号
if right[-1] < total_pages - 1:
right_has_more = True
if right[-1] < total_pages:
last = True
# 是否需要显示第一页和第一页后的省略号
if left[0] > 2:
left_has_more = True
if left[0] > 1:
first = True
context = {
'left': left,
'right': right,
'left_has_more': left_has_more,
'right_has_more': right_has_more,
'first': first,
'last': last,
}
return context
class CategoryView(IndexView):
def get_queryset(self):
category = get_object_or_404(Category, name=self.kwargs.get('name'))
return super().get_queryset().filter(category=category)
# return self.model.objects.filter(category=category)
class TagView(IndexView):
def get_queryset(self):
tag = get_object_or_404(Tag, pk=self.kwargs.get('pk'))
return super().get_queryset().filter(tags=tag)
class ArticleView(DetailView):
model = Article
template_name = 'article.html'
context_object_name = 'article_post'
def get_object(self, queryset=None):
obj = super().get_object(queryset=None)
# 文章可以使用markdown书写,带格式的文章,像csdn写markdown文章一样
md = markdown.Markdown(extensions=[
'markdown.extensions.extra',
'markdown.extensions.codehilite',
TocExtension(slugify=slugify),
])
obj.body = md.convert(obj.body)
# m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
# obj.toc = m.group(1) if m is not None else ''
#{% if obj.toc %}
# https://juejin.im/post/5d5ddec4f265da038f4812ed
obj.toc = md.toc
return obj
# 重写搜索视图,可以增加一些额外的参数,且可以重新定义名称
class MySearchView(SearchView):
# 返回搜索结果集
context_object_name = 'search_list'
# 搜索结果以浏览量排序
queryset = SearchQuerySet()
class ArchivesView(ListView):
model = Article
template_name = 'archives.html'
context_object_name = 'article_archives'
def get_queryset(self):
sql_queryset_year = Article.objects.raw(
'select id,title,DATE_FORMAT(create_date,"%%Y") as create_date_year from blog_article order by create_date_year DESC')
return sql_queryset_year
def AboutView(request):
return render(request, 'about.html')