python2.0_day18_django_form

时间:2022-03-28 14:47:31
Django form
Django admin 为什么要讲form,Django里的form能做什么.
前面day16节 简单学习了Django admin,我们知道当我们的models在admin.py文件里注册后,就可以通过admin后台管理注册的数据表了.
可以通过页面上对表进行记录的添加.
需要注意的是默认在admin后台页面上对某一个表,比如Book类,进行添加记录的时候.我们在input标签里必须都输入内容.否则就提示报错.
这个是怎样实现的呢?我们首先想到的是通过编辑模版文件,限制表单不为空.那问题又来了,如果有些字段可以为空呢.你就说在html中不做js的验证.
对能实现.假如总共就10个字段,也就是在10个input标签进行设置.没觉得麻烦.那如果是100个字段,你还要手动设置岂不是很麻烦.且不说每一个input的属性要求不一样了.
就算一样全部要求需要做验证 ,js不是要写很多标签的验证(因为每一个标签提示的报错内容都不一样).那么就会想到,能不能做程公共的插件,然后在用的时候调用.
可以bootstrap validate 就是这么一个插件.当然你也可以自己写,但是自己写的不够漂亮.不管怎样,是能实现的,这里的实现是 --- 前端验证 能够使用js在前端进行验证,也可以在后端验证,前端不做任何验证,form表单最终以post或者get形式提交.我们就通过判断get和post的值来做判断.
而Django就可以很方便的实现在后端验证,它就在后台里提供Form模块,使用Form模块创建自己form类.然后再在form类中像定义数据库字段一样定义每一个input标签的属性.这样,当视图views.py里的函数收到request.post或者get请求后,可以讲请求的内容传入我们创建的类,然后调用验证方法完成验证.
  你以为这就完成了,Django的form类远远不只有这个功能.我们看在form类中创建的字段是用来做验证的,那后端怎么保证接收到的post请求数据就能和我创建的form类中进行一一对应呢?
当然是在创建前端html模版的时候就要定义好字段的名称,以便和后台字段一一对应.那么怎样做呢?我们知道靠程序员来根据自己之前定义的类,小心谨慎的做一一对应,是可以,但是这效率不高.这时就引出form类的另一大功能:
  form类根据定义的字段属性,自动生成html前端页面代码.这样views在return时,把这个类作为参数传到前端,那么传过去的既有html代码也有数据。
总结
django中的Form一般有两种功能:1.输入html 2.验证用户输入 Django form 使用实例:
1.创建一个新的URL,我们应该按照一个请求的顺序.比如Django框架中,先经过urls.py文件
所以我们在app01/urls.py文件,添加一个URL
 urlpatterns = [
...
url(r'^book_form/$',views.book_form),
]
2.然后通过URL后,就会调用views.py文件下的book_form,
所以我们在views.py文件里添加一个book_form视图,我们知道一个URL都有可能有get和post请求.一般默认get请求都是读数据.所以一般返回的都是页面.
那么页面返回就得通过render 将 模版文件返回.
所以按照常规,创建一个views视图和创建一个模版应该属于同一个级别的.其次再是对视图和模版进行加工的工作
2.1 创建模版文件templates/app01/book_form.html
2.2 创建视图
 def book_form(request):
if request.method == 'POST':
pass
return render(request,'app01/book_form.html')
3.进入视图后,我们这里要使用到Django里的form模块进行创建form类,所以理论上应该在views.py文件里创建一个form类,但是我们在平时的开发中,有N个页面都需要用到form表单提交.每一个form表单的字段都不一样.所以理论上来说每一个需要提交的form表单就是一个单独的类.那么类多了,最好是单独写一个文件.于是我们在app01目录下创建一个forms.py文件.
最简单的一个form表单:
    from django import forms
class BookForm(forms.Form):
# 定义表单字段的时候要注意一定要和这个form表单将要提交到的ORM类保持一致
title = forms.CharField(max_length=)
publisher_id = forms.IntegerField(widget=forms.Select)
# 这里models.py文件里的Book类是外键,外键对应的是一个类,而form类中无外键类型.所以这里不能用外键
# 同时models.py文件里虽然是外键,但实际写到数据库中的是外键的id,所以这里用publisher_id ,代表publisher.id
# widget=forms.Select 这个是数据类型的一个属性.主要是定义你在form表单代码内用什么样的标签类型.
publish_date = forms.DateField()
# 时间类型
# 当然这里少了一个many_to_many 的作者字段.咱们先不管,因为这个多对多关系并不在Book表中体现,而是在app01_book_authors
# 我们稍后在来处理这个多对多关系的填报方法
4. 我们在form表单中写了3个字段,就需要在前端显示3个字段.怎么返回给前端?就通过views视图,返回给前端,于是再次更改视图和模版文件.
总结Django中更改视图和模版经常会一起,所以当你更改时,要多关注对方
视图文件更改成:
    def book_form(request):
form = forms.BookForm()
if request.method == 'POST':
pass
return render(request,'app01/book_form.html',{'book_form':form}) #这里传给模版html
模版html更改成:
     <body>
<h1></h1>
{{ book_form }}
</body>
这时候在访问
http://127.0.0.1:8000/payment/book_form/
返回结果如下图:

python2.0_day18_django_form

我们看到的结果是在form中我们写的字段,都拿出来做提示了:
title 显示了Title:
publisher_id 显示了Publisher id
publish_date 显示了Publish date
这也算是一个知识点把. 优化前端代码,添加 input的commit类型标签,实现提交功能
这时候我们看下前端页面中,form被传入到模版中到底以什么样的前端代码转换的?
 <body>
<h1></h1> <label for="id_title">Title:</label>
<input id="id_title" maxlength="" name="title" type="text">
<label for="id_publisher_id">Publisher id:</label>
<select id="id_publisher_id" name="publisher_id">
</select>
<label for="id_publish_date">Publish date:</label>
<input id="id_publish_date" name="publish_date" type="text"> </body>
我们看到,虽然显示成输入标签,并且Django中也称之为form表单,但是实际中它并没有真正的做在form 标签中,而是转换成应该写在form标签内的标签代码.
所以我们应该在html模版中加上form标签,用来包含被转换的前端代码,于是更改模版html为:
 <body>
<form action="/payment/book_form" method="post"> {% csrf_token %}
{{ book_form }}
<input type="submit" value="创建图书">
</form>
</body>
这里post方式提交,我们前面总结了修改 模版html 和 视图views一般都是连动的,既然这里有post,我们就应该在视图里,做出对post的数据的处理.
所以视图更改如下:
 def book_form(request):
form = forms.BookForm()
if request.method == 'POST':
print(request.POST)
return render(request,'app01/book_form.html',{'book_form':form}) #这里传给模版html
到这里位置,我们已经把后台创建的form表单传到了前端.并且在前端打开页面也已经看到了

python2.0_day18_django_form

这时候只是看到Django form表单的功能一:1.输入html
那功能二:2.验证用户输入 如何实现呢?
更改views.py视图函数,实现form类验证功能
要想进行验证:
1.需要把前端页面输入的内容传入到form表单模块创建的类中生成实例.
2.调用form类的验证方法.
代码如下:
 def book_form(request):
form = forms.BookForm()
if request.method == 'POST':
print(request.POST)
form = forms.BookForm(request.POST) #将前端输入的数据,传入forms.BookForm类生成实例
if form.is_valid(): # 调用form 的验证方法
print("form is ok")
else:
print(form.errors)
return render(request,'app01/book_form.html',{'book_form':form}) #这里传给模版html
此时我们就可以在前端页面输入,查看验证效果:

python2.0_day18_django_form

紧接着,我们会发现publisher_id这个外键的select里没有任何内容.
我们怎么把这种外键关联的内容显示出来呢?
首先我们知道这种外键关联的字段,1.一般都是在数据库中存储外键关联对象的id值.2.在页面上展示的时候都是用这种选择框方式展现出来的.
3.并且选择框里的内容是动态的.
而我们通过form模块中的Form类创建的表单.实例化后,这个实例可就不会在更新了.
你即使在定义class BookForm(forms.Form): ... 里引入models模块,调用models.Publisher.objects.all()获得列表,也不行,因为一旦实例化后就不会在更新了.
所以使用forms.Form就无法实现这种动态显示后台数据的需求.
使用FormModel是可以实现的,这里先不说.
但如果我们在不使用FromModel方式情况下,如何实现这个需求呢.
可以把publisher_id中从form类中踢出来 .在视图中单独作为一个字典传入.
forms.py文件改成:
 class BookForm(forms.Form):
title = forms.CharField(max_length=)
publish_date = forms.DateField()
views.py 文件改成
 def book_form(request):
form = forms.BookForm()
if request.method == 'POST':
print(request.POST)
form = forms.BookForm(request.POST) #将前端输入的数据,传入forms.BookForm类生成实例
if form.is_valid():
print("form is ok")
else:
print(form.errors)
publisher_list = models.Publisher.objects.all()
return render(request,'app01/book_form.html',{'book_form':form},{'publishers':publisher_list}) #这里单独传入publishers作为参数
模版的app01/book_form.html文件改成
 <body>

     <form action="/payment/book_form/" method="post">{% csrf_token %}
{{ book_form }}
<select name="publisher_id">
{% for publisher in publishers %} //单独的添加publisher_id的选择框
<option value="{{ publisher.id }}"> {{ publisher.name }}</option>
{% endfor %}
</select>
<input type="submit" value="创建图书">
</form>
</body>

上面改动完成后,我们在浏览器中访问测试,结果如下

python2.0_day18_django_form

我们看到虽然是能够动态显示publisher_id的列表框了.但是却不能做验证了.对于这个需求根本的解决方案不是这个,这里只是提供一个解决暂时的解决方案.接下来会学习真正的解决方案

验证完数据,当然是把数据添加到后台数据库了.改如何添加呢?
这里有两个知识点:
1.form类提供两个功能: a.输入html. b.用户验证. 所以 form类应该包含html的部分和数据的部分.可以使用form.cleaned_date获得纯数据
2. 通过form.cleaned_date获得的纯数据.是字典的形式如:{'publish_date':datetime.date(2016,6,6),'name':'alex python'}
3. 前面我们把publisher_id提出form类了,所以这里需要单独获取并加到form.cleaned_date里
4. 使用book_obj= models.Book(**form.cleaned_date) ,book_obj.save()直接把前端post来的数据保存到数据库
按照上面的思路,后台保存数据库的视图应改成如下:
 def book_form(request):
form = forms.BookForm()
publisher_list = models.Publisher.objects.all()
if request.method == 'POST':
print(request.POST)
form = forms.BookForm(request.POST) #将前端输入的数据,传入forms.BookForm类生成实例
if form.is_valid():
print("form is ok")
form_data = form.cleaned_data
form_data['publisher_id'] = request.POST.get('publisher_id')
book_obj = models.Book(**form_data)
book_obj.save
else:
print(form.errors)
return render(request,'app01/book_form.html',{'book_form':form,'publishers':publisher_list}) #这里传给模版html
表单定制
上面我们定义了最简单的django form类表单.要知道每一个字段都是可以定义很多属性.下面是老师给的代码实例,不要求记住,只要求在用的时候能够根据这个实例里代码套用.
 #!/usr/bin/env python
# -*- coding:utf- -*-
import re
from django import forms
from django.core.exceptions import ValidationError #定义一个验证的方法
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手机号码格式错误') class PublishForm(forms.Form):
# 定义一个供select标签作为option的元祖
user_type_choice = (
(, u'普通用户'),
(, u'高级用户'),
)
# 引入上面定义的choice方法,widget定义前端页面的一些属性. attrs={'class': "form-control"}指的是此字段转换的html应用这个样式.前提是在你的html模版中有这个样式
user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice,
attrs={'class': "form-control"}))
# error_messages属性定义提示语,以及前面条件限制的报警语言
# 在widget里'placeholder': u'标题5-20个字符'是前端提示符
title = forms.CharField(max_length=,
min_length=,
error_messages={'required': u'标题不能为空',
'min_length': u'标题最少为5个字符',
'max_length': u'标题最多为20个字符'},
widget=forms.TextInput(attrs={'class': "form-control",
'placeholder': u'标题5-20个字符'}))
# required=False 表示不是必填项.并且我们看能在标签里定义
memo = forms.CharField(required=False,
max_length=,
widget=forms.widgets.Textarea(attrs={'class': "form-control no-radius", 'placeholder': u'详细描述', 'rows': }))
# validators=[mobile_validate, ] 这里使用了上面定义的验证手机号的函数,我们看这里是一个列表,所以可以引入多个验证函数
phone = forms.CharField(validators=[mobile_validate, ],
error_messages={'required': u'手机不能为空'},
widget=forms.TextInput(attrs={'class': "form-control",
'placeholder': u'手机号码'}))
# error_messages里设置当格式错误时提示的信息.
email = forms.EmailField(required=False,
error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'})) '''
def __init__(self, *args, **kwargs):
super(SampleImportForm, self).__init__(*args, **kwargs) self.fields['idc'].widget.choices = models.IDC.objects.all().order_by('id').values_list('id','display')
self.fields['business_unit'].widget.choices = models.BusinessUnit.objects.all().order_by('id').values_list('id','name') Forms
'''

先写好一个form

 def test_form_view(request):
if request.method == 'POST':
request_form = PublishForm(request.POST)
if request_form.is_valid():
request_dict = request_form.clean()
print(request_dict)
return render(request,'test.html', {'pub_form':request_form})
else:
pub_form = PublishForm()
return render(request,'test.html',{'pub_form':pub_form})

写好视图

 <div>
<form method="post" action="{% url 'test_form' %}">{% csrf_token %} <div>{{ pub_form.user_type }} {{ pub_form.errors.title }}</div>
<div>{{ pub_form.title }}</div>
<div>{{ pub_form.email }}</div>
<div>{{ pub_form.phone }}</div>
<div>{{ pub_form.memo }}</div> {% if pub_form.errors %}
{{ pub_form.errors }}
{% endif %}
<input type="submit" value="提交">
</form> </div>

模版文件

接下来我们来解决上面对于Django form表单中对于外键需要动态显示选择框的需求.
解决方案:不在使用forms.Form作为基类,而是使用forms.ModelForm作为基类.
PS:forms.ModelForm模块有很多功能是forms.Form所不能实现的.比如我们现在只是通过form类写入到数据库.能不能对之前的记录进行查看,然后在修改呢.
这就需要使用forms.ModelForm模块来实现了.
首先我们用这个模块实现一个新的URL
1.编辑app01/urls.py
 from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^$', views.index), # 因为这里已经是二级匹配了,所以前面的url路径就不需要了.$匹配的根目录比如http://127.0.0.1/payment/
url(r'^book_modelform/$',views.book_modelform),
]
2.1 编辑app01/views.py
     def book_modelform(request):
if request.method == 'POST':
pass
return render(request,'app01/book_modelform.html')
2.2 编辑templates/app01/book_modelform.py
     <body>
<h2>book_modelform_page</h2>
</body>
3.编辑app01/forms.py文件
     class BookModelForm(forms.ModelForm):
# 此字段和models.py里的字段一一对应
class Meta:
model = models.Book # 指定要对照的ORM类
# fields = ('title','authors','publisher','publication_date') # 指定关联哪些字段
exclude = () # 也可以指定不关联哪些字段. widgets = {
'title':forms.TextInput(attrs={'class':'form-control'}),
}
# 之前在字段里定义的属性,这里就可以单独拿出来定义了
error_messages = {
'title':{'required': u'书名不能为空','invalid': u'书名书写错误'},
}

4.修改app01/views.py文件和templates/app01/book_modelform.html文件,引入新创建的modelform类
app01/views.py
     def book_modelform(request):
modelform = forms.BookModelForm()
if request.method == 'POST':
pass
return render(request,'app01/book_modelform.html',{'book_modelform':modelform,})
templates/app01/book_modelform.html
 <body>
<form action="/payment/book_modelform/" method="post"> {% csrf_token %}
{{book_modelform}}
<input type="submit" value="创建书本">
</form>
</body>
访问http://127.0.0.1:8000/payment/book_modelform/,结果如下图

python2.0_day18_django_form

5.修改views.py文件进行验证
     def book_modelform(request):
modelform = forms.BookModelForm()
if request.method == 'POST':
print(request.POST)
modelform = forms.BookModelForm(request.POST) #将前端输入的数据,传入forms.BookModelForm类生成实例
if modelform.is_valid(): # 调用验证方法
print("modelform is ok")
else:
print(modelform.errors)
return render(request,'app01/book_modelform.html',{'book_modelform':modelform,})
验证效果图片:

python2.0_day18_django_form

6.然后就是保存提交的内容到数据库,这时候我们的保存就不需要像forms.Form那样先使用form.cleaned_data,然后添加,最后保存了.
这里就可以直接保存了.modelform.save()即可
     def book_modelform(request):
modelform = forms.BookModelForm()
if request.method == 'POST':
print(request.POST)
modelform = forms.BookModelForm(request.POST) #将前端输入的数据,传入forms.BookModelForm类生成实例
if modelform.is_valid(): # 调用验证方法
print("modelform is ok")
modelform.save()
else:
print(modelform.errors)
return render(request,'app01/book_modelform.html',{'book_modelform':modelform,})
前端页面就可以填数据进行提交了.没报错,就一定保存到数据库了,进入数据库查看是否有提交的数据或者通过admin后台查询.

7.我们看验证效果图,发现这些input标签的排版很乱.原因是modelform转换程前端的代码不是块级标签,如下:
 <body>
<form action="/payment/book_modelform/" method="post"> <input type="hidden" name="csrfmiddlewaretoken" value="f9CRr2eHNzU3U43KJUV7D1YTHnhxhLyo">
<label for="id_title">Title:</label>
<input class="form-control" id="id_title" maxlength="" name="title" type="text">
<label for="id_authors">Authors:</label>
<select multiple="multiple" id="id_authors" name="authors">
<option value="">&lt;alex li&gt;</option>
<option value="">&lt;nanhai old&gt;</option>
</select>
<label for="id_publisher">Publisher:</label>
<select id="id_publisher" name="publisher">
<option value="" selected="selected">---------</option>
<option value="">&lt;北大出版社&gt;</option>
<option value="">&lt;上海出版社&gt;</option>
</select>
<label for="id_publication_date">Publication date:</label>
<input id="id_publication_date" name="publication_date" type="text">
<input type="submit" value="创建书本">
</form>
</body>
想让这些标签排版好看,那我们就要在html模版文件中加上div标签,自己来写.
 <body>
<form action="/payment/book_modelform/" method="post"> {% csrf_token %}
<div class="form-confirm">
{% for ele in book_modelform %}
<div class="form-ele">{{ele.name}}:{{ele}} {{ele.errors}}</div>
#{{ele.name}}显示的名称
# 查出来后错误信息不会默认返回,需要后面加上{{ele.errors}},这样在出错的时候就可以返回错误提示了
{% endfor %}
</div>
<input type="submit" value="创建书本">
</form>
</body>