【Django视图层】
视图层的主要工作是衔接HTTP请求,Python程序和HTML模板,使他们能够有机互相合作从模型层lou到数据并且反馈。说到视图层的工作就有以下几个方面要说
■ URL映射
对于一般的,通过django.conf.urls.url设置url路径,并且关联视图函数,甚至把url方法的参数写成正则表达式从而可以给视图函数传递多个参数的事情就不多说了。比如:
url(r‘^single/([0-9]{4})/([0-9]{2})/([0-9]+)/$‘,views.single)
这个url映射就可以方便地把日期给映射到视图函数中,让其根据日期数据得到数据库中存储的数据,然后进行界面的渲染。
更高级一点的映射方式是,可以给正则表达式中的子模式添加名字。就像是format函数的{0}和{var_name}一样,在这里的正则表达式中可以写类似于r‘^test/?P<year>([0-9]{4})/?P<month>([0-9]{2})/$‘这样在视图函数中就可以加上year=xxxx,month=yy这样来指定变量名了(当然request参数还是放在第一个且没有参数名)
include说得好听一点其实是Django支持分布式URL映射的一种体现。即URL映射规则不一定要写在一起,可以写在不同的模块中,通过include方法来进行统一的输出。
反向解析也是一个比较重要的点。我们知道在Django的后台Python代码中可以调用reverse(url_name)的方法来进行反向解析,然而在模板中我们也常常要使用反向解析。模板中应该写{% url ‘url_name‘ %}来体现反向解析。反向解析通常还涉及到如何进行带参数的反向解析,前端可以: {% url ‘url_name‘, args1,args2 %},而后台可以reverse(‘url_name‘, args=(args1,))这样。需要注意的是这里说的参数并不是像flask中url_for函数调用出来的GET请求参数,而是之前URL映射规则正则表达式中的子模式,把它看成一个参数的。
■ 视图函数
视图函数一定要有返回,一般可以有三种返回方式,分别是直接构造HTTP的BODY内容、用数据渲染HTML模板、返回错误或重定向。
返回HTTP内容用HttpResponse,渲染用render,重定向用redirect或者HttpResponseRedirect,而返回错误一般是HttpResponse(status=404)这样的。为了方便,Django也预定义了一些特别的类让开发者直接返回,包括:
HttpResponseNotFound 404
HttpResponseRedirect 302
HttpResponseBadRequest 400
HttpResponseForbidden 403
■ 模板语法
Django的模板和Jinja2很类似但不完全一致。
{{}} 和Jinja2一样都是变量替换,在其中写上变量名或者方法名
{{|}} 过滤器,将过滤器放到|符号的右边,在模板层面上对传递过来的值进行进一步的修正。
说到过滤器有必要讲讲Jinja2和Django模板语法的一个很大的差别。Jinja2把模板语法调教得和Python很接近,比如方法都是直接用括号来调用的,但是Django中一般调用方法都不加括号,直接写个方法名即可。比如前端验证用户是否登录的话用{% if request.user.is_authenticated %}不用加括号。那如果一些方法比如default这个过滤器方法需要值怎么办?就这样: {% value | default:"(N/A)" %}。
过滤器多种多样,这里也不想写了,可以看下书的214页和官方文档以了解用法。
流程控制和模板继承和Jinja2类似的,都是{% for xx xxxx %}{% endfor %}、{% if xxx %}{% endif %}、{% extends "xxx.html" %}这样。
■ 表单编程
Django自带了对表单的处理工具使得我们可以方便地构建表单。下面将进一步地说明Django的表单是如何作用的。
Django为所有继承自Form的类维护了一个bound状态,当一个表单对象在实例化的时候被赋予过数据则这个表单对象就是被绑定过的,其bound属性是True。反之当没有绑定过数据的自然就是unbound状态的表单。只有unbound状态的表单可以被赋予数据而只有bound状态的表单可以来验证数据。
● 表单数据验证
狭义上来说,表单数据验证就是指在服务器端用Python代码来验证输入数据的合法性(不包括前端的表单验证)。这种意义上的表单验证分成两种,分别是字段属性验证和自定义逻辑验证。
字段属性验证是指在创建表单类的时候就指定的字段的一些固有属性,比如CharField会指定max_length属性等等。这些验证实际上是HTML对输入的约束,不涉及到js或者django代码,而是HTML自带的一种机制。比如max_length实际上是在<input>标签中添加了maxlength属性,其表现是输入达到最大值之后再敲键盘也输入不进新内容了。不同的浏览器对这些约束的体现也不尽相同,总体而言一般简单的约束就是可以通过字段自带的一些属性来进行。
对于一些较复杂的验证逻辑(比如看输入中含不含有某些值等等),可以通过自定义逻辑来实现,这需要重载表单类的clean方法,比如:
class Moment(forms.Form): class Meta: model = Moment fields = ‘__all__‘ def clean(self): cleaned_data = super(Moment,self).clean() #先调用一次父类clean方法 content = cleaned_data.get(‘content‘) if content is None: raise ValidationError(‘输入不能为空‘) elif content.find(‘敏感词‘) >= 0: raise ValidationError(‘输入中含有敏感词‘) return cleaned_data #最终返回验证完后的数据
从中也可以看到,在验证的时候对于判断为验证通过的不用做什么特殊处理,而对于验证不通过的我们要raise forms.ValidationError来提示。如此通过raise一个错误来提示确实比较方便,不过有些丑,其效果就是在表单上方添加一个ul.errorlist里面写上我们raise出的错误信息。而且不带任何CSS,直接是一个原生的li的风格。。
如果想要改变这种丑丑的,那么可以考虑1.修改CSS,2.自己加上一些JS来处理。
其实说到自定义表单验证,其实表单类中重载clean方法究其原理还是把表单数据通过GET方法传送到后台让后台判断的。因为这个clean方法的代码一般是写在forms.py文件中的,姑且称是从表单类角度出发的验证手段吧,另外一种在views.py中也可以写验证啊,只不过这些验证代码放在这里的话会让views.py代码略显臃肿一点。但是放在views.py中有一个好处就是我们可以使用django.contrib.messages了。这个组件是类似于flask中的flash消息那样的东西。比如:
def user_login(request): form = LoginForm() if request.POST: form = loginForm(request.POST) #处理表单数据,然而再此之前可以先做验证 if form.is_valid(): username = request.POST.get(‘username‘) if ‘frank‘ in username: messages.error(request,‘frank你被禁止登录了‘) return redirect(reverse(‘psw_login‘)) #user = authenticated(xxxx)一些登录用户之类的操作 ctx = {} ctx.update(csrf(request)) ctx[‘form‘] = form return render(request,‘login.html‘,ctx)
顺便一提在前端该写的东西:
{% if messages %} {% for message in messages %} <div class="alert alert-warning"> <button class="close" type="button" data-dismiss="alert">×</button> <h4>{{ message }}</h4> </div> {% endfor %} {% endif %}
这只是个参考,精髓在{{ message }}上面。想咋用咋用。
需要注意的是,在messges.error向前端发送了警告信息之后,后面应该尽量跟return redirect到当前页面,因为messages和flash消息类似,是一个消息队列的形式,加入这次pop出去的消息没有第一时间渲染到页面上而后来又新加了几个消息的话,搞不好会在某次渲染的时候渲染出多个消息(当前队列中的所有消息)。另外把这个return换成raise ValidationError也是不行的,因为这是在views里面,函数不能没有返回值。关于messages的更多用法我想有机会在下面继续详细地说说。
■ 个性化管理员站点
Django项目都默认添加了/admin这个路径的管理台。如果认为管理台的功能不能满足当前的需求的话那么可以通过继承Django定义的管理员数据模型,模板,站点类来开发出个性化的数据模型管理功能。这部分功能其实就是我们之前那篇文章中提到过的记性类属性fields,fieldset等的设置。
● ModelAdmin模型
通过继承django.contrib.admin.ModelAdmin类可以自定义一个数据管理模型,在其中修改一些属性值来体现出和默认情况下管理台的一些不同。这部分操作一般在模块下的models.py文件中进行,ModelAdmin类可以改变的常用属性有以下这些:
empty_value_display 设置成一个字符串以改变空值的显示方式
field和exclude 上篇中提到过,通过白/黑名单方式设置一部分供编辑的字段
fieldsets 上篇中提到过,配置字段的分组以美化界面
list_editable 设置字段列表,规定模型中的哪些部分的字段在展示界面就可以编辑,没有被设置的字段将不能被编辑
list_per_page 整数,指定每页展示的实例数量最大值,默认值是100
search_fields 设置字段列表,在搜索界面中管理员可以按照这些字段的值来搜索
ordering 设置字段列表,定义管理页面中模型的排序方式
为了能够正确地显示出管理台界面,这里还需要提两点。1.正如之前所说的那样,在得到重载完成的ModelAdmin之后,需要在模块的admin.py中进行注册。不过这里的注册和一般models.py中的模型注册还不太一样。一般模型就是admin.site.register(模型名)即可。这里需要admin.site.register(关联模型名,重载ModelAdmin名)这样来做,否则会报MediaDefiningClass is not iterable之类的错误。 2. 如果启动应用还是报错的话请注意留意报错信息。比如有可能是重载后的ModelAdmin类是一定需要list_display属性等等。。
● 页面模板
如果说上面通过重载ModelAdmin类进行的修改只是小打小闹的话,直接修改页面的模板可以说是比较彻底的做法了。Django管理台相关的模板都放在了Django的模块目录(Windows的话通常在$PYTHON_HOME\site-packages\django)下的contrib/admin/templates/admin目录下。这些模板也都是按照Django的模板语法来的,所以可以根据这些模板的结构来重新定制化自己的页面。自己的页面模板可以放在templates/admin下,这个admin需要自己创建。
下面以修改login.html为示例进行演示:在项目目录下创建templates/admin/login.html模板:
{% extends ‘admin/login.html‘ %} {#content_title这个block是自带的login.html模板中定义的#} {% block content_title %} 欢迎来到Django管理界面 {% endblock %}
此时再访问admin页面时,在用户名输入框的上方就有一个中文的提示语了。
● 其他修改
和ModelAdmin类似的,在django.contrib.admin中还有一个AdminSite类。这个类代表的是整个管理的模块,可以进行一些设置如set_header等,需要注意的是注册的时候就不再是简单的admin.site.register了,而是要像下面这样:
from django.contrib.admin import AdminSite class MyAdminSite(AdminSite): site_header = ‘我的Django‘ admin_site = MyAdminSite() admin_site.register(xxx,xxx) #site变成了我们自定义的站点类实例,用它来注册各种模型
另外嘛就是可以把项目的urls.py中的^admin/的admin改成其他的内容来改变管理台的url。