python2.0_day18_django_form
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
1 urlpatterns = [ 2 ... 3 url(r'^book_form/$',views.book_form), 4 ]
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 创建视图
1 def book_form(request): 2 if request.method == 'POST': 3 pass 4 return render(request,'app01/book_form.html')
3.进入视图后,我们这里要使用到Django里的form模块进行创建form类,所以理论上应该在views.py文件里创建一个form类,但是我们在平时的开发中,有N个页面都需要用到form表单提交.每一个form表单的字段都不一样.所以理论上来说每一个需要提交的form表单就是一个单独的类.那么类多了,最好是单独写一个文件.于是我们在app01目录下创建一个forms.py文件.
最简单的一个form表单:
1 from django import forms 2 class BookForm(forms.Form): 3 # 定义表单字段的时候要注意一定要和这个form表单将要提交到的ORM类保持一致 4 title = forms.CharField(max_length=10) 5 publisher_id = forms.IntegerField(widget=forms.Select) 6 # 这里models.py文件里的Book类是外键,外键对应的是一个类,而form类中无外键类型.所以这里不能用外键 7 # 同时models.py文件里虽然是外键,但实际写到数据库中的是外键的id,所以这里用publisher_id ,代表publisher.id 8 # widget=forms.Select 这个是数据类型的一个属性.主要是定义你在form表单代码内用什么样的标签类型. 9 publish_date = forms.DateField() 10 # 时间类型 11 # 当然这里少了一个many_to_many 的作者字段.咱们先不管,因为这个多对多关系并不在Book表中体现,而是在app01_book_authors 12 # 我们稍后在来处理这个多对多关系的填报方法
4. 我们在form表单中写了3个字段,就需要在前端显示3个字段.怎么返回给前端?就通过views视图,返回给前端,于是再次更改视图和模版文件.
总结Django中更改视图和模版经常会一起,所以当你更改时,要多关注对方
视图文件更改成:
1 def book_form(request): 2 form = forms.BookForm() 3 if request.method == 'POST': 4 pass 5 return render(request,'app01/book_form.html',{'book_form':form}) #这里传给模版html
模版html更改成:
1 <body> 2 <h1>222222</h1> 3 {{ book_form }} 4 </body>
这时候在访问
http://127.0.0.1:8000/payment/book_form/
返回结果如下图:
我们看到的结果是在form中我们写的字段,都拿出来做提示了:
title 显示了Title:
publisher_id 显示了Publisher id
publish_date 显示了Publish date
这也算是一个知识点把.
优化前端代码,添加 input的commit类型标签,实现提交功能
这时候我们看下前端页面中,form被传入到模版中到底以什么样的前端代码转换的?
1 <body> 2 <h1>222222</h1> 3 4 <label for="id_title">Title:</label> 5 <input id="id_title" maxlength="10" name="title" type="text"> 6 <label for="id_publisher_id">Publisher id:</label> 7 <select id="id_publisher_id" name="publisher_id"> 8 </select> 9 <label for="id_publish_date">Publish date:</label> 10 <input id="id_publish_date" name="publish_date" type="text"> 11 12 </body>
我们看到,虽然显示成输入标签,并且Django中也称之为form表单,但是实际中它并没有真正的做在form 标签中,而是转换成应该写在form标签内的标签代码.
所以我们应该在html模版中加上form标签,用来包含被转换的前端代码,于是更改模版html为:
1 <body> 2 <form action="/payment/book_form" method="post"> {% csrf_token %} 3 {{ book_form }} 4 <input type="submit" value="创建图书"> 5 </form> 6 </body>
这里post方式提交,我们前面总结了修改 模版html 和 视图views一般都是连动的,既然这里有post,我们就应该在视图里,做出对post的数据的处理.
所以视图更改如下:
1 def book_form(request): 2 form = forms.BookForm() 3 if request.method == 'POST': 4 print(request.POST) 5 return render(request,'app01/book_form.html',{'book_form':form}) #这里传给模版html
到这里位置,我们已经把后台创建的form表单传到了前端.并且在前端打开页面也已经看到了
这时候只是看到Django form表单的功能一:1.输入html
那功能二:2.验证用户输入 如何实现呢?
更改views.py视图函数,实现form类验证功能
要想进行验证:
1.需要把前端页面输入的内容传入到form表单模块创建的类中生成实例.
2.调用form类的验证方法.
代码如下:
1 def book_form(request): 2 form = forms.BookForm() 3 if request.method == 'POST': 4 print(request.POST) 5 form = forms.BookForm(request.POST) #将前端输入的数据,传入forms.BookForm类生成实例 6 if form.is_valid(): # 调用form 的验证方法 7 print("form is ok") 8 else: 9 print(form.errors) 10 return render(request,'app01/book_form.html',{'book_form':form}) #这里传给模版html
此时我们就可以在前端页面输入,查看验证效果:
紧接着,我们会发现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文件改成:
1 class BookForm(forms.Form): 2 title = forms.CharField(max_length=10) 3 publish_date = forms.DateField()
views.py 文件改成
1 def book_form(request): 2 form = forms.BookForm() 3 if request.method == 'POST': 4 print(request.POST) 5 form = forms.BookForm(request.POST) #将前端输入的数据,传入forms.BookForm类生成实例 6 if form.is_valid(): 7 print("form is ok") 8 else: 9 print(form.errors) 10 publisher_list = models.Publisher.objects.all() 11 return render(request,'app01/book_form.html',{'book_form':form},{'publishers':publisher_list}) #这里单独传入publishers作为参数
模版的app01/book_form.html文件改成
1 <body> 2 3 <form action="/payment/book_form/" method="post">{% csrf_token %} 4 {{ book_form }} 5 <select name="publisher_id"> 6 {% for publisher in publishers %} //单独的添加publisher_id的选择框 7 <option value="{{ publisher.id }}"> {{ publisher.name }}</option> 8 {% endfor %} 9 </select> 10 <input type="submit" value="创建图书"> 11 </form> 12 </body>
上面改动完成后,我们在浏览器中访问测试,结果如下
我们看到虽然是能够动态显示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来的数据保存到数据库
按照上面的思路,后台保存数据库的视图应改成如下:
1 def book_form(request): 2 form = forms.BookForm() 3 publisher_list = models.Publisher.objects.all() 4 if request.method == 'POST': 5 print(request.POST) 6 form = forms.BookForm(request.POST) #将前端输入的数据,传入forms.BookForm类生成实例 7 if form.is_valid(): 8 print("form is ok") 9 form_data = form.cleaned_data 10 form_data['publisher_id'] = request.POST.get('publisher_id') 11 book_obj = models.Book(**form_data) 12 book_obj.save 13 else: 14 print(form.errors) 15 return render(request,'app01/book_form.html',{'book_form':form,'publishers':publisher_list}) #这里传给模版html
表单定制
上面我们定义了最简单的django form类表单.要知道每一个字段都是可以定义很多属性.下面是老师给的代码实例,不要求记住,只要求在用的时候能够根据这个实例里代码套用.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import re 4 from django import forms 5 from django.core.exceptions import ValidationError 6 7 #定义一个验证的方法 8 def mobile_validate(value): 9 mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') 10 if not mobile_re.match(value): 11 raise ValidationError('手机号码格式错误') 12 13 14 class PublishForm(forms.Form): 15 # 定义一个供select标签作为option的元祖 16 user_type_choice = ( 17 (0, u'普通用户'), 18 (1, u'高级用户'), 19 ) 20 # 引入上面定义的choice方法,widget定义前端页面的一些属性. attrs={'class': "form-control"}指的是此字段转换的html应用这个样式.前提是在你的html模版中有这个样式 21 user_type = forms.IntegerField(widget=forms.widgets.Select(choices=user_type_choice, 22 attrs={'class': "form-control"})) 23 # error_messages属性定义提示语,以及前面条件限制的报警语言 24 # 在widget里'placeholder': u'标题5-20个字符'是前端提示符 25 title = forms.CharField(max_length=20, 26 min_length=5, 27 error_messages={'required': u'标题不能为空', 28 'min_length': u'标题最少为5个字符', 29 'max_length': u'标题最多为20个字符'}, 30 widget=forms.TextInput(attrs={'class': "form-control", 31 'placeholder': u'标题5-20个字符'})) 32 # required=False 表示不是必填项.并且我们看能在标签里定义 33 memo = forms.CharField(required=False, 34 max_length=256, 35 widget=forms.widgets.Textarea(attrs={'class': "form-control no-radius", 'placeholder': u'详细描述', 'rows': 3})) 36 # validators=[mobile_validate, ] 这里使用了上面定义的验证手机号的函数,我们看这里是一个列表,所以可以引入多个验证函数 37 phone = forms.CharField(validators=[mobile_validate, ], 38 error_messages={'required': u'手机不能为空'}, 39 widget=forms.TextInput(attrs={'class': "form-control", 40 'placeholder': u'手机号码'})) 41 # error_messages里设置当格式错误时提示的信息. 42 email = forms.EmailField(required=False, 43 error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, 44 widget=forms.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'})) 45 46 ''' 47 def __init__(self, *args, **kwargs): 48 super(SampleImportForm, self).__init__(*args, **kwargs) 49 50 self.fields['idc'].widget.choices = models.IDC.objects.all().order_by('id').values_list('id','display') 51 self.fields['business_unit'].widget.choices = models.BusinessUnit.objects.all().order_by('id').values_list('id','name') 52 53 Forms 54 '''
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 def test_form_view(request): 2 if request.method == 'POST': 3 request_form = PublishForm(request.POST) 4 if request_form.is_valid(): 5 request_dict = request_form.clean() 6 print(request_dict) 7 return render(request,'test.html', {'pub_form':request_form}) 8 else: 9 pub_form = PublishForm() 10 return render(request,'test.html',{'pub_form':pub_form})
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <div> 2 <form method="post" action="{% url 'test_form' %}">{% csrf_token %} 3 4 <div>{{ pub_form.user_type }} {{ pub_form.errors.title }}</div> 5 <div>{{ pub_form.title }}</div> 6 <div>{{ pub_form.email }}</div> 7 <div>{{ pub_form.phone }}</div> 8 <div>{{ pub_form.memo }}</div> 9 10 11 {% if pub_form.errors %} 12 {{ pub_form.errors }} 13 {% endif %} 14 <input type="submit" value="提交"> 15 </form> 16 17 </div>
接下来我们来解决上面对于Django form表单中对于外键需要动态显示选择框的需求.
解决方案:不在使用forms.Form作为基类,而是使用forms.ModelForm作为基类.
PS:forms.ModelForm模块有很多功能是forms.Form所不能实现的.比如我们现在只是通过form类写入到数据库.能不能对之前的记录进行查看,然后在修改呢.
这就需要使用forms.ModelForm模块来实现了.
首先我们用这个模块实现一个新的URL
1.编辑app01/urls.py
1 from django.conf.urls import url 2 from app01 import views 3 urlpatterns = [ 4 url(r'^$', views.index), # 因为这里已经是二级匹配了,所以前面的url路径就不需要了.$匹配的根目录比如http://127.0.0.1/payment/ 5 url(r'^book_modelform/$',views.book_modelform), 6 ]
2.1 编辑app01/views.py
1 def book_modelform(request): 2 if request.method == 'POST': 3 pass 4 return render(request,'app01/book_modelform.html')
2.2 编辑templates/app01/book_modelform.py
1 <body> 2 <h2>book_modelform_page</h2> 3 </body>
3.编辑app01/forms.py文件
1 class BookModelForm(forms.ModelForm): 2 # 此字段和models.py里的字段一一对应 3 class Meta: 4 model = models.Book # 指定要对照的ORM类 5 # fields = ('title','authors','publisher','publication_date') # 指定关联哪些字段 6 exclude = () # 也可以指定不关联哪些字段. 7 8 widgets = { 9 'title':forms.TextInput(attrs={'class':'form-control'}), 10 } 11 # 之前在字段里定义的属性,这里就可以单独拿出来定义了 12 error_messages = { 13 'title':{'required': u'书名不能为空','invalid': u'书名书写错误'}, 14 }
4.修改app01/views.py文件和templates/app01/book_modelform.html文件,引入新创建的modelform类
app01/views.py
1 def book_modelform(request): 2 modelform = forms.BookModelForm() 3 if request.method == 'POST': 4 pass 5 return render(request,'app01/book_modelform.html',{'book_modelform':modelform,})
templates/app01/book_modelform.html
1 <body> 2 <form action="/payment/book_modelform/" method="post"> {% csrf_token %} 3 {{book_modelform}} 4 <input type="submit" value="创建书本"> 5 </form> 6 </body>
访问http://127.0.0.1:8000/payment/book_modelform/,结果如下图
5.修改views.py文件进行验证
1 def book_modelform(request): 2 modelform = forms.BookModelForm() 3 if request.method == 'POST': 4 print(request.POST) 5 modelform = forms.BookModelForm(request.POST) #将前端输入的数据,传入forms.BookModelForm类生成实例 6 if modelform.is_valid(): # 调用验证方法 7 print("modelform is ok") 8 else: 9 print(modelform.errors) 10 return render(request,'app01/book_modelform.html',{'book_modelform':modelform,})
验证效果图片:
6.然后就是保存提交的内容到数据库,这时候我们的保存就不需要像forms.Form那样先使用form.cleaned_data,然后添加,最后保存了.
这里就可以直接保存了.modelform.save()即可
1 def book_modelform(request): 2 modelform = forms.BookModelForm() 3 if request.method == 'POST': 4 print(request.POST) 5 modelform = forms.BookModelForm(request.POST) #将前端输入的数据,传入forms.BookModelForm类生成实例 6 if modelform.is_valid(): # 调用验证方法 7 print("modelform is ok") 8 modelform.save() 9 else: 10 print(modelform.errors) 11 return render(request,'app01/book_modelform.html',{'book_modelform':modelform,})
前端页面就可以填数据进行提交了.没报错,就一定保存到数据库了,进入数据库查看是否有提交的数据或者通过admin后台查询.
7.我们看验证效果图,发现这些input标签的排版很乱.原因是modelform转换程前端的代码不是块级标签,如下:
1 <body> 2 <form action="/payment/book_modelform/" method="post"> <input type="hidden" name="csrfmiddlewaretoken" value="f9CRr2eHNzU3U43KJUV7D1YTHnhxhLyo"> 3 <label for="id_title">Title:</label> 4 <input class="form-control" id="id_title" maxlength="100" name="title" type="text"> 5 <label for="id_authors">Authors:</label> 6 <select multiple="multiple" id="id_authors" name="authors"> 7 <option value="1"><alex li></option> 8 <option value="2"><nanhai old></option> 9 </select> 10 <label for="id_publisher">Publisher:</label> 11 <select id="id_publisher" name="publisher"> 12 <option value="" selected="selected">---------</option> 13 <option value="1"><北大出版社></option> 14 <option value="2"><上海出版社></option> 15 </select> 16 <label for="id_publication_date">Publication date:</label> 17 <input id="id_publication_date" name="publication_date" type="text"> 18 <input type="submit" value="创建书本"> 19 </form> 20 </body>
想让这些标签排版好看,那我们就要在html模版文件中加上div标签,自己来写.
1 <body> 2 <form action="/payment/book_modelform/" method="post"> {% csrf_token %} 3 <div class="form-confirm"> 4 {% for ele in book_modelform %} 5 <div class="form-ele">{{ele.name}}:{{ele}} {{ele.errors}}</div> 6 #{{ele.name}}显示的名称 7 # 查出来后错误信息不会默认返回,需要后面加上{{ele.errors}},这样在出错的时候就可以返回错误提示了 8 {% endfor %} 9 </div> 10 <input type="submit" value="创建书本"> 11 </form> 12 </body>
码农小明