Django之视图层与模板层

做~自己de王妃 提交于 2019-12-05 17:48:17

一、视图层

视图函数(类)简称为视图,就是一个普通的函数(类),它的功能是接收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 }}会被渲染成
&lt;script&gt;alert(123)&lt;/script&gt;交给浏览器后会被解析成普通字符”<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' %}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!