一、视图层
视图函数(类)简称为视图,就是一个普通的函数(类),它的功能是接收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\r\nContent-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=“egon“,则输出"eg" {{ value|slice:"0:2" }} #6、truncatechars #作用:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾, 如果value=”hello world egon 嘎嘎“,则输出"hello...",注意8个字符也包含末尾的3个点 {{ value|truncatechars:8 }} #7、truncatewords #作用:同truncatechars,但truncatewords是按照单词截断,注意末尾的3个点不算作单词,如果value=” hello world egon 嘎嘎“,则输出"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' %}