Form表单
⼀、概要
通常情况下,我们需要自己手动在HTML页面中,编写form标签和其内的其它元素。但这费时费力,而且有可能写得不太恰当,数据验证也比较麻烦。有鉴于此,Django在内部集成了一个表单模块,专门帮助我们快速处理表单相关的内容。Django的表单模块给我们提供了下面三个主要功能
- 准备和重构数据用于页面渲染
- 为数据创建HTML表单元素
- 接收和处理用户从表单发送过来的数据
⼆、Form相关的对象包括
- Widget:用来渲染成HTML元素的部件,如:forms.Textarea对应HTML中的textarea标签
- Form对象中的一个字段,如:EmailField表示ema字段,如果这个字段不是有效的Emd地址格式,就会产生错误。
- Form:一系列Fed对象的集合,负责验证和显示HTML元素
- Form media:用来渲染表单的CSS和 avaScrip资源。
三、基本使用
1、说明
Form对象封装了⼀系列Field和验证规则,Form类都必须直接或间接继承自
django.forms.Form,定义Form有两种方式:
- 直接继承Form
class XXXForm(forms.Form):
pass
- 结合Model,继承django.forms.ModelForm
class XXX(models.Model):
字段 = models.CharField(max_length=30)
字段 = models.CharField(max_length=20)
class XXXForm(ModelForm):
class Meta:
model = XXX
field = ('字段', '字段') # 只显示model中指定的字段,显示所有是用'__all__
2、示例代码
-
models
class Shop(models.Model): title = models.CharField('标题' , max_length=30) content = models.CharField('内容' , max_length=20) class Meta: db_table = 'T_SHOP'
-
ModelForm
class ShopForm(ModelForm): class Meta: model = models.Shop fields = ('title', 'content') # 只显示model中指定的字段
-
views
from django.shortcuts import render # Create your views here. from .forms import ShopForm def add_shop(request): if request.method == "POST": form = ShopForm(request.POST) if form.is_valid(): # 所有验证都通过 # 处理表单数据 title = form.cleaned_data['title'] print(title) # content = form.cleaned_data['content'] # 保存数据 form.save() return render(request, 'shop/add_shop.html', {"shop_form": form}) else: form = ShopForm() return render(request, 'shop/add_shop.html', {"shop_form": form})
-
模板中使用
<form action= "{% url 'add' %}" method="post"> {% csrf_token %} {{ shop_form }} <input type="submit" value= "提交"/> </form>
四、常用的field类
- 核心字段参数
参数名 | 说明 |
---|---|
required | 给字段添加必填属性,不能空着,若要表示⼀个字段不是 必需的,设置required=False |
label | label参数用来给字段添加‘⼈类友好’的提示信息。如果没 有设置这个参数,那么就用字段的首字母大写名字 |
label_suffix | Django默认为上面的label参数后面加个冒号后缀,如果 想⾃定义,可以使用 label_suffix 参数 |
initial | 为HTML页面中表单元素定义初始值。也就是input元素的 value参数的值 |
widget | 指定渲染Widget时使用的widget类,也就是这个form字 段在HTML页面中是显示为文本输⼊框?密码输⼊框?单 选按钮?多选框?还是别的 |
help_text | 该参数用于设置字段的辅助描述文本 |
error_messages | 该参数允许你覆盖字段引发异常时的默认信息。 传递的是 ⼀个字典,其值为你想覆盖的错误信息 |
validators | 指定⼀个列表,其中包含了为字段进⾏验证的函数 |
localize | localize参数帮助实现表单数据输⼊的本地化。 |
disabled | 设置有该属性的字段在前端页面中将显示为不可编辑状 态。 |
- 核心字段
字段名 | 参数 |
---|---|
BooleanField | 默认的Widget:CheckboxInput 空值:False 规范:Python 的True 或 False。 错误信息的键:required |
IntegerField | 默认的Widget:当Field.localize是False时为NumberInput,否则 为TextInput。 空值:None 规范化为:Python 整数或⻓整数。 验证给定值是⼀个整数。 允许前导和尾随空格,类似Python的int()函数。 错误信息的键:max_value, invalid, required, min_value |
CharField | 默认的Widget:TextInput 空值:''(⼀个空字符串) 规范化为:⼀个Unicode 对象。 如果提供,验证max_length 或 min_length。 否则,所有的输⼊都是合法的。 错误信息的键:required, max_length, min_length |
ChoiceField | 默认的Widget:Select 空值:' '(⼀个空字符串) 规范化为:⼀个Unicode 对象。 验证给定的值在选项列表中存在。 错误信息的键:required, invalid_choice invalid_choice:错误消息可能包含%(value)s,它将被选择的选项 替换掉。接收⼀个额外的必选参数:choices⽤来作为该字段选项的 ⼀个⼆元组组成的可迭代对象(例如,列表或元组)或者⼀个可调 ⽤对 |
DateField | 默认的Widget:DateInput 空值:None 规范化为:⼀个Python datetime.date 对象。 错误信息的键:required, invalid input_formats:⼀个格式的列表,验证给出的值是⼀个 datetime.date、datetime.datetime 或指定⽇期格式的字符串。如 果不提供,默认的⽇期格式:['%Y-%m- %d','%m/%d/%Y','%m/%d/%y'] |
DateTimeField | 默认的Widget:DateInput 空值:None 规范化为:⼀个Python datetime.datetime对象。 错误信息的键:required, invalid input_formats:默认的格式['%Y-%m-%d %H:%M:%S', '%Y-%m- %d %H:%M', '%Y-%m-%d', '%m/%d/%Y %H:%M:%S','%m/%d/%Y %H:%M','%m/%d/%Y', '%m/%d/%y %H:%M:%S', '%m/%d/%y %H:%M', '%m/%d/%y'] |
DecimalField | 默认的Widget:当Field.localize是False时为NumberInput,否则 为TextInput。 空值:None 规范化为:Python decimal对象。 错误信息的键: max_whole_digits , max_digits , max_decimal_places , max_value , invalid, required, min_value 可选参数:max_value,min_value:允许的值的范围,需要赋值 decimal.Decimal对象,不能直接给个整数类型。 max_digits:值允许的最⼤位数(⼩数点之前和之后的数字总共的位数,前导的零将被删除)。 decimal_places:允许的最⼤⼩数位。 |
FloatField | 默认的Widget:当Field.localize是False时为NumberInput,否则 为TextInput。 空值:None 规范化为:Float 对象。 验证给定的值是⼀个浮点数。 错误信息的键:max_value, invalid, required, min_value 可选的参数:max_value和min_value |
EmailField | 默认的Widget:EmailInput 空值:''(⼀个空字符串) 规范化为:Unicode 对象。 使⽤正则表达式验证给出的值是⼀个合 法的邮件地址。 错误信息的键:required, invalid 两个可选的参数⽤于验证,max_length 和min_length。 |
ImageField | 默认⼩部件: ClearableFileInput 空值: None 规范化为: UploadedFile 将⽂件内容和⽂件名称封装到单个对象 中的对象。 验证⽂件数据是否已绑定到表单,并且该⽂件是Pillow可以理解的 图像格式。 错误信息键: required , invalid , missing , empty , invalid_image |
FileField | 默认⼩部件: ClearableFileInput 空值: None 规范化为: UploadedFile 将⽂件内容和⽂件名称封装到单个对象 中的对象。 可以验证⾮空⽂件数据已被绑定到表单。 错误信息键: required , invalid , missing , empty , max_length |
ModelChoiceField | 默认的Widget:Select 空值:None 规范化为:⼀个模型实例。 验证给定的id存在于查询集中。 错误信息的键:required, invalid_choice 可以选择⼀个单独的模型 对像,适⽤于表示⼀个外键字段。 ModelChoiceField默认widet不 适⽤选择数量很⼤的情况,在⼤于100项时应该避免使⽤它。 可选参数: empty_label:默认情况下,ModelChoiceField使⽤的上下点击样式 |
五、Form常用属性和方法
名称 | 说明 | 示例 |
---|---|---|
cleaned_data(字典) | 表单中验证通过的⼲净数 据 |
form.cleaned_data form.cleaned_data.get('username') |
changed_data | 有变化的字段的列表 | form.changed_data |
fields(字典) | 表单中的字段属性 | form.fiedls['username'] |
is_bound | 表单是否绑定数据 | form.is_bound |
errors(字典) | 错误信息 | form.errors |
is_valid() | 表单中数据是否验证通 过,通过返回True,否 则返回False |
form.is_valid() |
has_changed() | 检查表单数据是否已从初 始数据更改 |
form.has_changed() |
errors.as_json(escape_html=False) | 返回JSON序列化后的错 误信息字典 |
form.errors.as_json() |
六、表单渲染的选项
1、概要
对于 label,input 对,还有几个输出选项:
- {{ form.as_table }} 以表格的形式将它们渲染在 tr 标签中
- {{ form.as_p }} 将它们渲染在 p 标签中
- {{ form.as_ul }} 将它们渲染在 li 标签中
注意,你必须自己提供 table 或 ul 元素。
-
常用渲染项:
有⽤的属性包括:{{ field }} {{ field.label }} 该领域的标签,例如。Email address {{ field.label_tag }} 字段的标签包含在适当的HTML <label>标记中。这包括表格label_suffix。例 如,默认label_suffix值为冒号: <label for="id_email">Email address:</label> {{ field.id_for_label }} 将⽤于此字段的ID(id_email在上⾯的示例中)。如果您⼿动构建标签,则可能 需要使⽤此代替label_tag。例如,如果你有⼀些内联JavaScript并且想要避免 硬编码字段的ID,它也很有⽤。 {{ field.value }} 该字段的值。例如someone@example.com。 {{ field.html_name }} 将在输⼊元素的名称字段中使⽤的字段的名称。这会将表单前缀考虑在内,如果已 设置的话。 {{ field.help_text }} 与该字段关联的任何帮助⽂本。 {{ field.errors }} 输出包含与此字段对应的任何验证错误的a 。您可以使⽤循环⾃定义错误的表示。 在这种情况下,循环中的每个对象都是包含错误消息的简单字符串。<ul class="errorlist">{% for error in field.errors %} {{ field.is_hidden }} True如果表单字段是隐藏字段, False则此属性。它作为模板变量并不是特别有 ⽤,但在条件测试中可能很有⽤,例如: {% if field.is_hidden %} {# Do something special #} {% endif %} {{ field.field }} Field来⾃此BoundField包装的表单类的实例。您可以使⽤它来访问 Field属 性,例如 。{{ char_field.field.max_length }}
2、渲染
-
form.as_ul
<form action="/add/" method="post"> {% csrf_token %} <ul> {{ form.as_ul }} </ul> <input type="submit" value= "注册⼀个学⽣"> </form>
-
手动渲染
<form action="/add/" method="post"> {% csrf_token %} <div> <label for="{{ form.name.id_for_label }}">姓名:</label> {{ form.name }} {{ form.name.errors }} </div> <div> <label for="{{ form.sex.id_for_label }}">性别:</label> {{ form.sex }} {{ form.sex.errors }} </div> <div> <label for="{{ form.age.id_for_label }}">年龄:</label> {{ form.age }} {{ form.age.errors }} </div> <input type="submit"> </form>
-
循环渲染
{% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.help_text %} <p class="help">{{ field.help_text|safe }}</p> {% endif %} </div> {% endfor %}
-
循环隐藏和可见字段
{# Include the hidden fields #} {% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %} {# Include the visible fields #} {% for field in form.visible_fields %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div> {% endfor %}
-
可重用的表单模板
# In your form template: {% include "form_snippet.html" %} # In form_snippet.html: {% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div> {% endfor %}
七、重写验证
-
说明
-
举个栗子
-
注意事项
- 函数名就必须为clean_字段名
- 必须有返回值
- 只能拿自己当前字段值
- raise ValidationError('xxx')
class UserFrom(forms.Form): # ⾃定义⽅法(局部钩⼦),密码必须包含字⺟和数字 def clean_password(self): if self.cleaned_data.get('password').isdigit() or self.cleaned_data.get('password').isalpha(): raise ValidationError('密码必须包含数字和字⺟') else: return self.cleaned_data['password'] def clean_valid_code(self): # 检验验证码正确;之前⽣成的验证码保存在了了session中 if self.cleaned_data.get('valid_code').upper() == self.request.session.get('valid_code'): return self.cleaned_data['valid_code'] else: raise ValidationError('验证码不正确') # ⾃定义⽅法(全局钩⼦, 检验两个字段),检验两次密码⼀致; def clean(self): if self.cleaned_data.get('password') != self.cleaned_data.get('password2'): raise ValidationError('密码不⼀致') else: return self.cleaned_data # 注意,上⾯的字典取值⽤get, 因为假如在clean_password中判断失败,那么没有返回值,最下⾯的clean⽅法直接取值就会失败
完整代码
-
示例代码
class UserFrom(forms.Form): name = forms.BooleanField(label='⽤户名', required=True,error_messages={'required': u'必选'}) password = forms.CharField(label= '密码',widget=forms.PasswordInput(attrs={'placeholder': '请输⼊密码'})) confirm_password = forms.CharField(label='密码',widget=forms.PasswordInput(attrs={'placeholder': '请再次输⼊密 码'})) # 下拉框 city = forms.ChoiceField(choices=[(1, '上海'), (2, '北京',), (3, '⼴州')]) create_date = forms.DateField(label= '选择时间', input_formats=['%Y-%m-%d']) price = forms.DecimalField(label='价格' , max_digits=10,decimal_places=2) head = forms.FileField(allow_empty_file=True) img = forms.ImageField(allow_empty_file=True) email = forms.EmailField(required=False, error_messages={'required': u'邮箱不能为空' ,'invalid': u'邮箱格式错误'}, widget=forms.TextInput( attrs={'class': "form-control",'placeholder': u'邮箱'})) address=forms.ModelChoiceField(queryset=models.Address.objects.filter(uid =2), empty_label=None , to_field_name=None) def clean_password(self): if self.cleaned_data.get('password').isdigit() or self.cleaned_data.get('password').isalpha(): raise ValidationError('密码必须包含数字和字⺟') else: return self.cleaned_data['password'] def clean_valid_code(self): if self.cleaned_data.get('valid_code').upper() == self.request.session.get('valid_code'): return self.cleaned_data['valid_code'] else: raise ValidationError('验证码不正确') def clean(self): if self.cleaned_data.get('password') != self.cleaned_data.get('confirm_password'): raise ValidationError('密码不⼀致') else: return self.cleaned_data
⼋、综合
#-----------------------------------------models.py
from django.db import models
class Info(models.Model):
name = models.CharField(max_length=64)
sex = models.CharField(max_length=64)
birthday = models.CharField(max_length=64)
age=models.CharField(max_length=64)
qualification=models.CharField(max_length=64)
job=models.CharField(max_length=64)
email=models.CharField(max_length=64,default='')
class Hobby(models.Model):
item=models.CharField(max_length=64)
#-----------------------------------------form.py
from django import forms
from app01 import models
from django.core.exceptions import ValidationError
class InfoForm(forms.Form):
def validate_name(value):
try:
models.Info.objects.get(name=value)
raise ValidationError('%s 的信息已经存在!'%value)
except models.Info.DoesNotExist:
pass
sex_choice=((0,'男'),
(1,'⼥'))#select的数据可以像这样写,也可以在另外⼀张表中动
态去拿
name = forms.CharField(validators=[validate_name],label='姓
名',error_messages={'required':'必填'})
age = forms.CharField(label='年龄',error_messages={'required':'必
填'})
# sex = forms.CharField(label='性别',error_messages=
{'required':'必填',},)
sex=forms.IntegerField(widget=forms.widgets.Select(choices=sex_choice
,
attrs={'class':'setform2'} ))
birthday = forms.CharField(label='⽣⽇',error_messages=
{'required':'必填'})
qualification = forms.CharField(label='学历',error_messages=
{'required':'必填'},widget=forms.TextInput(attrs=
{'class':'formset','placeholder':'本科' }))
email=forms.EmailField(max_length=100,min_length=10)
job = forms.CharField(label='⼯作',error_messages={'required':'必
填'})
def __init__(self,*args,**kwargs):
super(Info_form,self).__init__(*args,**kwargs)
self.fields['hobby']=forms.CharField(widget=forms.widgets.Select(choi
ces=models.Hobby.objects.values_list('id','item')))
#-----------views.py
from django.shortcuts import render,HttpResponse
def add_info(req):
if req.method=='POST':
Info_form_obj=Info_form(req.POST)
if Info_form_obj.is_valid():
Info.objects.create(name=Info_form_obj.cleaned_data['name'],
age=Info_form_obj.cleaned_data['age'],
sex=Info_form_obj.cleaned_data['sex'],
birthday=Info_form_obj.cleaned_data['birthday'],
qualification=Info_form_obj.cleaned_data['qualification'],
job=Info_form_obj.cleaned_data['job']
)
return HttpResponse('添加成功!')
else:
error_obj=Info_form_obj.errors
print('***************')
print(type(error_obj))#<class
'django.forms.utils.ErrorDict'>
print(error_obj['name'][0])#必填
print(error_obj.get('age'))#<ul class="errorlist"><li>必
填</li></ul>
return render(req,'add_info.html',
{'form_obj':Info_form_obj,'error_obj':error_obj})
Info_form_obj=Info_form()
return render(req,'add_info.html',{'form_obj':Info_form_obj})
#------add_info.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加个⼈信息</title>
<style>
.formset {
color: rebeccapurple;
border: dashed cadetblue;
}
</style>
</head>
<body>
<form action=
"{% url 'add_info' %}" method="post">
<p>姓名{{ form_obj.name }}{{ error_obj.name.0 }}</p>
<p>年龄{{ form_obj.age }}{{ error_obj.age.0 }}</p>
<p>⽣⽇{{ form_obj.birthday }}{{ error_obj.birthday.0 }}</p>
<p>⼯作{{ form_obj.job }}<span>{{ error_obj.job }}</span>
</p>
<p>学历{{ form_obj.qualification }}<span>{{
error_obj.qualification }}</span></p>
<p>性别{{ form_obj.sex }}<span>{{ error_obj.sex }}</span>
</p>
<p>邮箱{{ form_obj.email }}<span>{{ error_obj.email }}
</span></p>
<p>爱好{{ form_obj.hobby }}<span>{{ error_obj.hobby }}
</span></p>
{{ form_obj.as_p }}
<input type="submit" value=
"提交"><br>
{% csrf_token %}
</form>
</body>
</html>