一、视图层
视图函数(类)简称为视图,就是一个普通的函数(类),它的功能是接收web请求,并返回web响应.
研究视图函数需要熟练掌握请求对象(HttpRequest)和相应对象(HttpResponse)
1.1请求对象(HttpRequest)
1.1.1HttpRequest请求对象常用属性
#part1
一.HttpRequest.method
获取请求使用的方法(值为纯大写的字符串格式)。例如:"GET"、"POST"
应该通过该属性的值来判断请求方法
二.HttpRequest.GET
值为一个类似于字典的QueryDict对象,封装了GET请求的所有参数,可通过HttpRequest.GET.get('键')获
取相对应的值
三.HttpRequest.POST
值为一个类似于字典的QueryDict对象,封装了POST请求所包含的表单数据,可通过
HttpRequest.POST.get('键')获取相对应的值
针对表单中checkbox类型的input标签、select标签提交的数据,键对应的值为多个,需要用:
HttpRequest.POST.getlist("hobbies")获取存有多个值的列表,同理也有HttpRequest.GET.getlist("键")
#part2
一.HttpRequest.body
当浏览器基于http协议的POST方法提交数据时,数据会被放到请求体中发送给django,django会将接收到的请求
体数据存放于HttpRequest.body属性中,因为该属性的值为Bytes类型,所以通常情况下直接处理Bytes、并从中提
取有用数据的操作是复杂而繁琐的,好在django会对它做进一步的处理与封装以便我们更为方便地提取数据,比如
对于form表单来说,提交数据的常用方法为GET与POST
1:如果表单属性method='GET',那么在提交表单时,表单内数据不会存放于请求体中,而是会将表单数据按照
k1=v1&k2=v2&k3=v3的格式放到url中,然后发送给django,django会将这些数据封装到request.GET中,注意此
时的request.body为空、无用
2:如果表单属性method='POST',那么在提交表单时,表单内的所有数据都会存放于请求体中,在发送给django
后会封装到request.body里,此时django为了方便我们提取数据,会request.body的数据进行进一步的处理,具
体如何处理呢,需要从form表单提交数据的编码格式说起:
form表单对提交的表单数据有两种常用的编码格式,可以通过属性enctype进行设置,如下
编码格式1(默认的编码格式):enctype="application/x-www-form-urlencoded"
编码格式2(使用form表单上传文件时只能用该编码):enctype="multipart/form-data"
如果form表单提交数据是按照编码格式1,那么request.body中数据的格式类似于GET方法的数据格式,如
k1=v1&k2=v2,此时django会将request.body中的数据提取出来封装到request.POST中方便我们提取
如果form表单提交数据是按照编码格式2,那么request.body中数据的格式为b'------
WebKitFormBoundaryKtcwuksQltpNprep
Content-Disposition: form-data;......',,此时django
会将request.body中的数据提取出来封装到request.POST中,将上传的文件数据专门提取出来封装到
request.FILES属性中
强调:毫无疑问,编码格式2的数据量要大于编码格式1,如果无需上传文件,还是推荐使用更为精简的编码格式1
我们除了可以采用form表单向django提交数据外,还可以采用ajax技术,ajax可以提交的数据格式有:1、编码
格式1 2、编码格式2 3、json,当ajax采用POST方法提交前两种格式的数据时,django的处理方案同上,但是当
ajax采用POST方法提交json格式的数据时,django会将接收到的数据存放于HttpRequest.body,此时需要我们自
己对HttpRequest.body属性值做反序列化操作,
具体的,我们在讲解ajax时再做具体介绍
二.HttpRequest.FILES
如果使用form表单POST上传文件的话,文件数据将包含在HttpRequest.FILES属性中。
该属性值为一个类似于字典的对象,可以包含多组key:value(对应多个上传的文件),其中每个key为<input
type="file" name="" /> 中name属性的值,而value则为对应的文件数据
强调:HttpRequest.FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/formdata" 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。
#part3
一.HttpRequest.path
获取url地址的路径部分,只包含路径部分
二.HttpRequest.get_full_path()
获取url地址的完整path,既包含路径又包含参数部分
如果请求地址是http://127.0.0.1:8001/order/?name=ylpb&age=10#_label3,
HttpRequest.path的值为"/order/"
HttpRequest.get_full_path()的值为"/order/?name=ylpb&age=10"
#part4
一.HttpRequest.META
值为包含了HTTP协议的请求头数据的Python字典,字典中的key及期对应值的解释如下
CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。
CONTENT_TYPE —— 请求的正文的MIME类型。
HTTP_ACCEPT —— 响应可接收的Content-Type。
HTTP_ACCEPT_ENCODING —— 响应可接收的编码。
HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。
HTTP_HOST —— 客服端发送数据的目标主机与端口
HTTP_REFERER —— Referring 页面。
HTTP_USER_AGENT —— 客户端使用的软件版本信息
QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。
REMOTE_ADDR —— 客户端的IP地址。
REMOTE_HOST —— 客户端的主机名。
REMOTE_USER —— 服务器认证后的用户。
REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。
SERVER_NAME —— 服务器的主机名。
SERVER_PORT —— 服务器的端口(是一个字符串)。
从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,HTTP协议的请求头数据转换为 META 的键
时,
都会
1、将所有字母大写
2、将单词的连接符替换为下划线
3、加上前缀HTTP_。
所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。
注意:下述常用属性暂且了解即可,待我们讲到专门的知识点时再专门详细讲解
二.HttpRequest.COOKIES
一个标准的Python 字典,包含所有的cookie。键和值都为字符串。
三.HttpRequest.session
一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。
11.HttpRequest.user(用户认证组件下使用)
一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。
2.HttpRequest.is_ajax()
如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部
是否是字符串'XMLHttpRequest'。
大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),
你必须手工设置这个值来让 is_ajax() 可以工作。
如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache
middleware,
你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存
1.2响应对象(HttpResponse)
响应可以是一张HTML网页、一个404错误,一张图片,一个XML文档、重定向到其他视图等。特点:无论视图本身包含什么逻辑都必须要返回响应,另外视图函数放在views.py是约定俗成的,并不是必须要放在这里。
1.2.1HttpResponse()
括号内直接跟一个具体的字符串作为响应体。
1.2.2render()
render(request,template_name[,context])
参数:
1. request:用于生成响应的请求对象,固定必须传入的第一个参数
2.template_name:要使用模板的完整名称,必须传入,render默认回去templates目录下查找模板文件
3.context:可选参数,可以传入一个字典用来替代模板文件中的变量
render的功能可总结为:根据给定的字典渲染模板,并返回一个渲染后的HttpResponse对象。
1.2.3redirect()
重定向为指定的地址。
def home(request):
#return redirect('/login')如果重定向为本站的其他页面则可直接写本站其他页面的后缀
return redirect('https://www.cnblogs.com/ghylpb/')#如果重定向为其他网站则直接写其它网站的网址即可
1.3JsonResponse
JsonResponse内部使用json模块对传入的数据类型型进行序列化,它的默认数据类型只有字典,当将safe参数置为False时,可以序列化其它数据类型,它继承了HttpResponse类,可以对请求做出响应。
用json实现JsonResponse的功能:
def index(request):
user_dic = {'name':'小张','password':'123'}
# json内部会使用ASCII码对所有的数据进行转码,所以如果转码之后我们将无法获得中文信息处理方法如下,将json的ensure_ascii参数置为False就可以
json_str = json.dumps(user_dic,ensure_ascii=False)
return HttpResponse(json_str)
JsonResponse:
def index(request):
user_dic = {'name':'小张','password':'123'}
return JsonResponse(user_dic,json_dumps_params={'ensure_ascii':False})
更改JsonResponse序列化的数据类型:
def index(request):
l = [1,2,3,4,5,6,7,]
# JsonResponse默认只序列化字典 如果你想序列化其他数据类型(json模块能够序列化的) 你需要加一个safe参数
return JsonResponse(l,safe=False)
1.4FBV与CBV
Django的视图层由两种形式构成:FBV基于函数的视图(Function base view)和CBV基于类的视图(Class base view)
1.4.1FBV
我们前面使用的视图函数就是FBV。
路由的书写方法:url(r'^name/',views.name)
1.4.2CBV
CBV引入面向对象的思想对数据进行更高程度的封装。如下例所示:
class MyLogin(View):
def get(self,request):
print('我是MyLogin里面的get方法')
return render(request,'login.html')
def post(self,request):
print('我是MyLogin里面的post方法')
return HttpResponse('post')
路由的书写方法:url(r'^login/',views.MyLogin.as_view())
从路由的书写可以看出这里执行的是类的方法,而方法的本质还是函数所以CBV在路由匹配上的本质还是FBV。
1.5CBV源码
为什么CBV能够根据不同的请求方式自动执行不同的代码呢?下面我们一起看一下CBV的源码
class View(object):
"""
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 six.iteritems(kwargs):
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)#cls是我们自己定义的类Mylogin,self是我们自定义的类实例化的对象。
if hasattr(self, 'get') and not hasattr(self, 'head'):#这里的get是干啥使的?
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.
#判断当前的请求方式在不在默认的八个方法内,以get请求为例
if request.method.lower() in self.http_method_names:
#这里利用反射去我们自己定义的类实例化的对象中查找get属性或方法getattr(obj,'get')
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)#加括号调用该方法
给CBV内部的方法加装饰器:
方法1:使用内置模块
from django.utils.decorators import method_decorator
#先导入 method_decorator
#可以指定给哪个方法装(outter是我们定义的装饰器名称,这里略去装饰器的定义代码)
# @method_decorator(outter,name='post')
# @method_decorator(outter,name='dispatch')
class MyLogin(View):
@method_decorator(outter)
def dispatch(self, request, *args, **kwargs): # 如果你想在视图函数执行之前做一些操作,你可以在你的CBV中定义dispatch方法来拦截
return super().dispatch(request,*args,**kwargs)
# @method_decorator(outter) # 1.推荐写法
def get(self,request):
print('我是MyLogin里面的get方法')
return render(request,'login.html')
def post(self,request):
print('我是MyLogin里面的post方法')
time.sleep(1)
return HttpResponse('post')
方法2:把类的方法当成普通函数,直接在对应的方法上添加。
class MyLogin(View):
@outter
def get(self,request):
print('我是MyLogin里面的get方法')
return render(request,'login.html')
@outter
def post(self,request):
print('我是MyLogin里面的post方法')
time.sleep(1)
return HttpResponse('post')
二、模板层
2.1模板语法
2.1.1模板语法的取值
模板语法的取值方式只有一种:统一采用句点符取值(点的方式取值)
如:
#python代码
user_obj = {'name':'zgh','pwd':123,'hoppy':['book','music','movie']}
#模板语法取值
{{ user_obj.hobby.0}}#book
#句点符取值,如果从字典取值则点key值,如果从列表取值则点索引号
模板语法有两种书写格式:
{{}}#变量相关
{% %}#逻辑相关
2.1.2模板传值
模板支持的数据类型
模板支持的数据类型:整型、浮点型、字符串、字典、列表、元组、集合、bool,也就是支持python基本的数据类型全都支持。
模板传值
1.传函数名:{{ 函数名 }}
给HTML传函数名的时候,模板语法会自动加括号调用该函数,并将函数的返回值当做页面展示的依据,注意模板语法不支持函数传参,也就是说只能给页面传无参函数。
2.传类名:{{ 类名 }}
给HTML传类名的时候会自动加括号实例化产生对象,在HTML页面可以进行如下对对象的使用。
<p>传对象:{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_cls }}</p>
<p>{{ obj.get_func }}</p>
模板传值特点:只要能够加括号调用的类函数等传到HTML页面都会自动加上括号调用。
2.2过滤器
过滤器类似于python的内置函数,用来把视图函数传入的变量值加以修饰以后再显示
语法结构:{{ 变量名 | 过滤器名 : 传给过滤器的参数 }}
注意:过滤器最多只能有两个参数
常用的内置过滤器:
#1、default
#作用:如果一个变量值是False或者为空,使用default后指定的默认值,否则,使用变量本身的值,如果
value=’‘则输出“nothing”
{{ value|default:"nothing" }}
#2、length
#作用:返回值的长度。它对字符串、列表、字典等容器类型都起作用,如果value是 ['a', 'b', 'c', 'd'],那
么输出是4
{{ value|length }}
#3、filesizeformat
#作用:将值的格式化为一个"人类可读的"文件尺寸(如13KB、4.1 MB、102bytes等等),如果 value 是12312312321,输出将会是 11.5 GB
{{ value|filesizeformat }}
#4、date
#作用:将日期按照指定的格式输出,如果value=datetime.datetime.now(),按照格式Y-m-d则输出2019-02-02
{{ value|date:"Y-m-d" }}
#5、slice
#作用:对输出的字符串进行切片操作,顾头不顾尾,如果value=“ylpb“,则输出"yl"
{{ value|slice:"0:2" }}
#6、truncatechars
#作用:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾,
如果value="hello world abc def ",则输出"hello...",注意8个字符也包含末尾的3个点
{{ value|truncatechars:8 }}
#7、truncatewords
#作用:同truncatechars,但truncatewords是按照单词截断,注意末尾的3个点不算作单词,如果value="
hello world abc def" ,则输出"hello world ..."
{{ value|truncatewords:2 }}
#8、safe
#作用:出于安全考虑,Django的模板会对HTML标签、JS等语法标签进行自动转义,例如value="
<script>alert(123)</script>",模板变量{{ value }}会被渲染成
<script>alert(123)</script>交给浏览器后会被解析成普通字符”<script>alert(123)
</script>“,失去了js代码的语法意义,但如果我们就想让模板变量{{ value }}被渲染的结果又语法意义,那么就
用到了过滤器safe,比如value='<a href="https://www.baidu.com">点我啊</a>',在被safe过滤器处理后
就成为了真正的超链接,不加safe过滤器则会当做普通字符显示’<a href="https://www.baidu.com">点我啊
</a>‘
{{ value|safe }}
最为常用的过滤器有:统计长度、自动转文件大小格式、展示带有标签的文本。
2.3标签
标签(逻辑相关)是为了在模板中完成一些特殊的功能,语法为{% %},下面介绍几个常用的标签。
2.3.1for标签
'''语法:{% for user in 容器类数据类型 %}
for循环体
{% endfor %}'''
#如下面代码循环循环出列表中的每一个元素并展示元素的属性
{% for user in user_list %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.auth_name }}</td>
<td>{{ user.detail }}</td>
<td>
<a href="{% url 'app01_edit_author'%}?edit_id={{ user.id }} " class="btn btn-primary btn-xs">编辑</a>
<a href="{% url 'app01_delete_author' %}?delete_id={{ user.id }}" class="btn btn-danger btn-xs">删除</a>
</td>
</tr>
{% endfor %}
2.3.2if标签
{% if 条件1 %}
执行内容1
{% elif 条件2%}
执行内容2
{% else %}
执行内容3
{% endif %}
#if 标签长和for标签联合使用如:
{% for foo in l %}#l是一个列表
{% if forloop.first %}
<p>first</p>
{% elif forloop.last %}
<p>last</p>
{% else %}
<p>{{ foo }}</p>
{% endif %}
{% empty %}
<p>当for循环的对象是空的时候会走</p>
{% endfor %}
2.3.3with标签
with标签用来给一个复杂的变量名起别名,如果变量的值来自于数据库,在起别名后只需要使用别名即可,无需每次都向数据库发送请求重新获取变量的值,这里需要说明的是别名只能在with标签内部使用,如果在外部还是要用原名的。
{% with 原变量名 as 别名 %}
<p>{{ 别名 }}</p>
{% endwith %}
2.3.4csrf_token标签
# 当用form表单提交POST请求时必须加上标签{% csrf_token%},该标签用于防止跨站伪造请求
<form action="" method="POST">
{% csrf_token %}
<p>用户名:<input type="text" name="name"></p>
<p>密码:<input type="password" name="pwd"></p>
<p><input type="submit" value="提交"></p>
</form>
# 具体工作原理为:
# 1、在GET请求到form表单时,标签{% csrf_token%}会被渲染成一个隐藏的input标签,该标签包含了由服务端
生成的一串随机字符串,如<input type="hidden" name="csrfmiddlewaretoken"
value="dmje28mFo...OvnZ5">
# 2、在使用form表单提交POST请求时,会提交上述随机字符串,服务端在接收到该POST请求时会对比该随机字符
串,对比成功则处理该POST请求,否则拒绝,以此来确定客户端的身份
2.4自定义过滤器和标签
当内置的过滤器或标签无法满足我们的需求时,我们可以自定义标签和过滤器。
2.4.1自定义前的准备
django支持用户自定义过滤器和标签但前提必须要先执行以下三步:
1.在应用名下新建一个名为templatetags(必须是这个名字)的文件夹
2.在该文件夹内新建一个任意名称的py文件
3.在该py文件中先写下面两行代码(必须)
from django.template import Library
register = Library()
完成上面的步骤就可以利用register来自定义过滤器和标签了。
2.4.2自定义过滤器
@register.filter(name='test')
def index(a,b):
return a + b
#name为给过滤器起的名字,可以不写
自定义的过滤器最多只能有两个参数。
2.4.3自定义标签
# 自定义标签,可以接受任意多个参数
@register.simple_tag(name='mytag')
def mytag(a,b,c,d):
return '%s?%s?%s?%s'%(a,b,c,d)
2.4.4自定义inclusion_tag
inclusion_tag是一个函数,能够接受外界传入的参数,然后传递给一个HTML页面,页面获取数据,渲染完成后将渲染好的页面放到调用inclusion_tag的地方。
@register.inclusion_tag('mytag.html',name='xxx')
def index666(n):
l = []
for i in range(n):
l.append('第%s项'%i)
return locals() # 将l直接传递给mytag.html页面
# 给html页面传值的两种方式
# 第一种,指名道姓当需要传递的变量名特别多的情况下 有点麻烦
# return render(request,'test.html',{'n':n})
# 第二种,使用locals()会将当前所在名称空间中所有的名字全部传递给html页面
2.5模板的继承和导入
在实际开发中,模板文件彼此之间可能会有大量的冗余代码,为此Django提供了专门的语法来解决这一问题,即模板的继承和导入。
2.5.1继承
如果你想使用某个已有的页面,首先你需要先在你想使用的页面上划定区域,在继承这个区域之后,你就可以使用划定的这个区域。
block标签
划定区域使用block标签,只需将你想要修改的区域放在block内部即可:
{% block content %}
划定的区域
{% endblock %}
extends标签
在新的页面通过extends标签继承前面划定的区域:
{% extends 'XXX.html' %} {#XXX.html是之前的页面#}
{% block content %}
修改模板中content区域内容
{% endblock %}
建议一个模板页面至少划分为三个区域:css区、html代码区、JS区,这样方便每一个页面都有自己独立的css和JS代码。
2.5.2模板的导入
include标签
作用:在一个模板文件中引入另一个模板文件的内容,与继承不同的是include引用了目标模板的整个文件。
{% include 'xxx.html' %}