一、构建一个form表单
一)不用Django创建表单
假设你想在网站上创建一个简单的表单,以获取用户的名字。你需要如下模板
1 <form action="/your-name/" method="post">
2 <label for="your_name">Your name: </label>
3 <input id="your_name" type="text" name="your_name">
4 <input type="submit" value="OK">
5 </form>
实际应用中,一个表单可能包含几十上百个字段,其中大部分需要预填充,而且我们预料到用户将来会编辑-提交几次才能完成操作。
我们可能需要在提交表单之前,在浏览器做一些验证;可能想使用非常复杂的字段,以允许用户做类似从日历中挑选日期,等等。再用这种方式会很繁琐。
二)使用Django的form表单类
使用Django完成上面大部分需求是很容易的
1、使用Django突出特点
- form表单提交时,数据出现错误,返回的页面中仍保留之前输入的数据
- 方便地限制字段条件
2、form表单的功能
- 自动生成HTML表单元素
- 检查表单数据的合法性
- 若验证错误,重新显示表单(数据不会重置)
- 数据类型转换(字符类型的数据转换成相应的Python类型)
3、form相关的对象概述
- Widget:用来渲染成HTML元素的工具,如:forms.Textarea对应HTML中的<textarea>标签
- Field:Form对象中的一个字段,如:EmailField表示email字段,如果这个字段不是有效的email格式,就会产生错误。
- Form:一系列Field对象的集合,负责验证和显示HTML元素
- Form Media:用来渲染表单的CSS和JavaScript资源。
二、在Django中构建一个表单
一)form类
1、继承方式
form对象封装了一系类field和验证规则,form类必须直接或者间接继承自django.forms.Form,定义form有两种方式
1、方法一:直接继承Form
#form.py
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100,label='主题')
message = form.CharField(widget=forms.TextArea)
sender = form.EmailField()
cc_myself = forms.BooleanField(required=False)
字段允许的最大长度通过max_length
定义。它完成两件事情。首先,它在HTML 的<input>
上放置一个maxlength="100"
(这样浏览器将在第一时间阻止用户输入多于这个数目的字符)。它还意味着当Django 收到浏览器发送过来的表单时,它将验证数据的长度。
2、方法二:结合Model,继承django.form.ModelForm
#models.py
class Contact(models.Model):
title = models.CharField(max_length=30)
content = models.CharField(max_length=20)
#form.py
class ConotactForm(ModelForm):
class Meta:
model = Contact
field = ('title','content') #只显示model中指定的字段
Form
的实例具有一个is_valid()
方法,它为所有的字段运行验证的程序。当调用这个方法时,如果所有的字段都包含合法的数据,它将:
- 返回
True
- 将表单的数据放到
cleaned_data
属性中。
完整的表单,第一次渲染时,看上去将像:
<label for="your_name">Your name: </label>
<input id="your_name" type="text" name="your_name" maxlength="100">
** 注意它不包含 <form>
标签和提交按钮。我们必须自己在模板中提供它们。**
二)在视图(view)中使用form
1、在view函数中使用form的一般情景
form django.shortcuts import render
form django.http import HttpResponseRedirect
def contact(request):
if request.method=="POST":
form = ContactForm(request.POST)
if form.is_valid(): #所有验证都通过
#do something处理业务
return HttpResponseRedirect('/')
else:
form = ContactForm()
return render(request,'contact.html',{'form':form})
contact.html
<form action='/contact/' method='POST'>
{% for field in form %}
<div class = 'fieldWrapper'>
{{field.label_tag}}:{{field}}
{{field.errors}}
</div>
{% endfor %}
<div class='fieldWrapper'> <p><input type='submit' value='留言'></p></div>
</form>
如果访问视图的是一个GET
请求,它将创建一个空的表单实例并将它放置到要渲染的模板的上下文中。这是我们在第一个访问该URL 时预期发生的情况。
如果表单的提交使用POST
请求,那么视图将再次创建一个表单实例并使用请求中的数据填充它:form = NameForm(request.POST)
。这叫做"绑定数据至表单"(它现在是一个绑定的表单)。
我们调用表单的is_valid()
方法;如果它不为True
,我们将带着这个表单返回到模板。这时表单不再为空(未绑定),所以HTML 表单将用之前提交的数据填充,然后可以根据要求编辑并改正它。
如果is_valid()
为True
,我们将能够在cleaned_data
属性中找到所有合法的表单数据。在发送HTTP 重定向给浏览器告诉它下一步的去向之前,我们可以用这个数据来更新数据库或者做其它处理。
2、处理表单数据
form.is_valid()返回true后,表单数据都被存储在form.cleaned_data对象中(字典类型,意为经过清洗的数据),而且数据会被自动转换为Python对象,如:在form中定义了DateTimeField,那么该字段将被转换为datetime类型,还有诸如:IntegerField、FloatField
if form.is_valid():
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
sender = form.cleaned_data['sender']
cc_myself = form.cleaned_data['cc_myself']
recipients = ['info@example.com']
if cc_myself:
recipients.append(sender)
from django.core.mail import send_mail
send_mail(subject, message, sender, recipients)
return HttpResponseRedirect('/thanks/') # Redirect after POST
三)Django Form类详解
1、绑定的和未绑定的表单实例
绑定和未绑定的表单之间的区别
- 未绑定的表单没有关联的数据。当渲染给用户时,它将为空或包含默认的值。
- 绑定的表单具有提交的数据,因此可以用来检验数据是否合法。如果渲染一个不合法的绑定的表单,它将包含内联的错误信息,告诉用户如何纠正数据。
2、字段详解
#forms.py
from django import forms
class RegisterForm(forms.Form):
username = forms.CharField(max_length=100,
error_messages={"min_length":"最短为5个字符","required":"该字段不能为空"},
)
password = forms.CharField(max_length=100,
widget=widgets.PasswordInput(attrs={"placeholder":"password"})
)
telephone=forms.IntegerField(
error_messages={
"invalid":"格式错误"
}
)
gender=forms.CharField(
initial=2,
widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
)
email = forms.EmailField()
is_married = forms.BooleanField(required=False)
3、widgets
每个表单字段都有一个对应的Widget
类,它对应一个HTML 表单Widget
,例如<input type="text">
。
在大部分情况下,字段都具有一个合理的默认Widget。例如,默认情况下,CharField
具有一个TextInput Widget
,它在HTML 中生成一个<input type="text">
。
4、字段的数据
不管表单提交的是什么数据,一旦通过调用is_valid()
成功验证(is_valid()
返回True
),验证后的表单数据将位于form.cleaned_data
字典中。这些数据已经为你转换好为Python 的类型。
注:此时,你依然可以从request.POST
中直接访问到未验证的数据,但是访问验证后的数据更好一些。
在上面的联系表单示例中,is_married将是一个布尔值。类似地,IntegerField
和FloatField
字段分别将值转换为Python 的int
和float
。
四)使用表单模板
1、表单渲染的选项
对于<label>/<input>
对,还有几个输出选项:
{``{ form.as_table }}
以表格的形式将它们渲染在<tr>
标签中{``{ form.as_p }}
将它们渲染在<p>
标签中{``{ form.as_ul }}
将它们渲染在<li>
标签中
注意,你必须自己提供<table>
或<ul>
元素。
2、显示form找template中的方法多种多样
1、方式一
<form action="/contact/" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
根据{``{ form }}
,所有的表单字段和它们的属性将通过Django 的模板语言拆分成HTML 标记
注:Django 原生支持一个简单易用的跨站请求伪造的防护。当提交一个启用CSRF 防护的POST
表单时,你必须使用上面例子中的csrf_token
模板标签。
现在我们有了一个可以工作的网页表单,它通过Django Form 描述、通过视图处理并渲染成一个HTML <form>
。
2、方式二:使用form.as_table、form.as_ul
分别表示用<p>标签,<table>标签和<ul>表示显示表单。如果要自定义,你只要获取到每个元素的值就行:
<form action="/contact/" method="post">
{{ form.non_field_errors }}
<div class="fieldWrapper">
{{ form.subject.errors }}
<label for="id_subject">Email subject:</label>
{{ form.subject }}
</div>
<div class="fieldWrapper">
{{ form.message.errors }}
<label for="id_message">Your message:</label>
{{ form.message }}
</div>
<div class="fieldWrapper">
{{ form.sender.errors }}
<label for="id_sender">Your email address:</label>
{{ form.sender }}
</div>
<div class="fieldWrapper">
{{ form.cc_myself.errors }}
<label for="id_cc_myself">CC yourself?</label>
{{ form.cc_myself }}
</div>
<p><input type="submit" value="Send message" /></p>
</form>
每个form字段都可以使用 {{form.name_of_field}}得到。也可以通过迭代form,每个迭代元素对应的是form里面的field
<form action="/contact/" method="post">
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}: {{ field }}
</div>
{% endfor %}
<p><input type="submit" value="Send message" /></p>
</form>
{{field}}有如下属性:
{{field.label}},如:Email address
{{field.label_tag}},如: <label for=id_email>Email address</label>
{{field.value}} 如:someone.@gmail.com
{{field.errors}}
3、手工渲染字段
我们没有必要非要让Django 来分拆表单的字段;如果我们喜欢,我们可以手工来做(例如,这样允许重新对字段排序)。每个字段都是表单的一个属性,可以使用{``{ form.name_of_field }}
访问,并将在Django 模板中正确地渲染。例如:
<div class="fieldWrapper">
{{ form.Username.errors }}
{{ form.Username.label_tag }}
{{ form.Username }}
</div>
4、渲染表单的错误信息
registerForm=RegisterForm(request.POST)
print(type(registerForm.errors)) #<class 'django.forms.utils.ErrorDict'>
print(type(registerForm.errors["username"])) #<class 'django.forms.utils.ErrorList'>
使用{``{ form.name_of_field.errors }}
显示表单错误的一个清单,并渲染成一个ul
。看上去可能像:
<ul class="errorlist">
<li>Sender is required.</li>
</ul>
五)form组件的钩子
def foo(request):
if request.method=="POST":
regForm=RegForm(request.POST)
if regForm.is_valid():
pass
# 可用数据: regForm.cleaned_data,
# 将数据插入数据库表中
else:
pass
# 可用数据: regForm.errors
# 可以利用模板渲染讲errors嵌套到页面中返回
# 也可以打包到一个字典中,用于ajax返回
else:
regForm=RegForm()
return render(request,"register.html",{"regForm":regForm})
'''
实例化时:
self.fields={
"username":"字段规则对象",
"password":"字段规则对象",
}
is_valid时:
self._errors = {}
self.cleaned_data = {}
#局部钩子:
for name, field in self.fields.items():
try:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
# 全局钩子:
self.clean() # def self.clean():return self.cleaned_data
return not self.errors # True或者False
'''
六)form组件补充
https://www.cnblogs.com/wupeiqi/articles/6144178.html
1、Django内置字段如下
Django内置字段
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀
CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
BaseTemporalField(Field)
input_formats=None 时间格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允许空文件
ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换
empty_value= '' 空值的默认值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值
ComboField(Field)
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
SlugField(CharField) 数字,字母,下划线,减号(连字符)
...
UUIDField(CharField) uuid类型
...
2、Django内置插件
Django内置插件
TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
3、常用选择插件
常用选择插件
# 单radio,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 单radio,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 单select,值为字符串
# user = fields.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 单select,值为字符串
# user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多选select,值为列表
# user = fields.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 单checkbox
# user = fields.CharField(
# widget=widgets.CheckboxInput()
# )
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
七)实战
1、第一步:首先在models.py中定义一个表单模型
class RemarkForm(forms.Form):
subject = forms.CharField(max_length=100 ,label='留言标题')
mail = forms.EmailField(label='电子邮件')
topic = forms.ChoiceField(choices=TOPIC_CHOICES,label='选择评分')
message = forms.CharField(label='留言内容',widget=forms.Textarea)
cc_myself = forms.BooleanField(required=False ,label='订阅该贴')
那个topic中的choices需要在models.py中定义一个数组
TOPIC_CHOICES = (
('leve1', '差评'),
('leve2', '中评'),
('leve3', '好评'),
)
这样,之后在html显示的表单就采用这个模型的数据了。
另外还有一种定义表单模型的方式,那就是直接继承另一个models。如果我们在models里设计数据库时,已经设计好了一个类(就是数据库的表)之后想复用这个类的信息来作为表单的模型,那么很简单,同样是在models中的一个类
class Advertisement(models.Model):
#订单编号
OrderID = models.ForeignKey(OrderInfo)
#//广告标题#
Title = models.CharField(max_length = 36)
#//广告内容#
Content = models.CharField(max_length = 600)
注意,他的类型是models.Model,是做数据库ORM用的。
然后怎么关联呢?
需要导入一个新的类 (ModelForm)
from django.forms import ModelForm
class ContactForm(ModelForm):
class Meta:
model = Advertisement
fields = ('Title', 'CustomerID')
这里的Advertisement就是之前那个ORM的模型。
2、第二步:OK,继续我们的表单,下一步需要做什么呢?
开始在views.py里写对表单的调用
def remark(request):
if request.method == 'POST': # 如果表单被提交
form = ContactForm(request.POST) # 获取Post表单数据
if form.is_valid(): # 验证表单
return HttpResponseRedirect('/') # 跳转
else:
form = ContactForm() #获得表单对象
return render_to_response('message.html', {
'form': form,
})
3、第三步:我们知道django里任何访问都是通过urls.py来管理的。所以下面我们需要配置一个路径。
(r'^message', 'iring.views.remark'),
4、第四步:最后应该是建立一个模板的时候了,因为我们最终是输出到html上的.注意views的remark函数最后一行
return render_to_response('message.html', {
'form': form,
})
就是讲当前的表单对象输出到message.html里,自动生成一个表单。
<form action="/message/" method="POST">
{% for field in form %}
<div class="fieldWrapper">
{{ field.label_tag }}:{{ field }}
{{ field.errors }}
div>
{% endfor %}
<div class="fieldWrapper"><p><input type="submit" value="留言" />p>div>
form>
用于对表单对象里的元素进行遍历,然后通过
{{ field.label_tag }}
{{ field }}
{{ field.errors }}
这三个标签来输出,注意{{ field.errors }}默认情况下是不会输出的,只有当验证表单正确性时才会输出内容。