Django中的Form & ModelForm
在web开发中,处理表单是家常便饭,我们当然可以直接在模板中编写原生的html表单,然后在后端进行表单验证,这常常是一项繁琐的工作。
我们可以通过Django提供了表单功能,来方便表单的处理。下面看一下如何使用:
自定义表单类
from django import forms # 导入表单模块
from django.core.exceptions import ValidationError
class RegisterForm(forms.Form): # 自定义表单类,并继承forms.Form
email = forms.EmailField(widget=forms.EmailInput(
attrs={"class": "form-control"}))
username = forms.CharField(min_length=4, max_length=12, widget=forms.TextInput(
attrs={"class": "form-control"}))
password = forms.CharField(min_length=6, widget=forms.PasswordInput(
attrs={"class": "form-control"}))
password2 = forms.CharField(min_length=6, widget=forms.PasswordInput(
attrs={"class": "form-control"}))
valid_code = forms.CharField(widget=forms.TextInput(
attrs={"class": "form-control"}))
def __init__(self, request, *args, **kwargs):
# 如果需要额外接收参数,要重写构造器函数
# 这里额外接收一个参数,用于从request.sesssion中提取之前保存的验证码
super(RegisterForm,self).__init__(*args, **kwargs)
self.request = request
# 自定义方法(局部钩子),密码必须包含字母和数字
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方法直接取值就会失败
说明:
min_length=4, max_length=12
定义字段字符长度error_messages={"required":["请输入有效的......"]}
自定义错误信息clean_field
用于验证单个字段,全局钩子clean
用于验证两个字段;实例化表单对象,传入模板渲染
# 实例化表单对象; 因为重写了init方法,这里要额外接收request
form_obj = RegisterForm(request)
<form action="{% url 'register' %}" method="post" id="register">
{% csrf_token %}
<div class="form-group">
<label for="id_email">邮箱 label><span class="error-info">span>
{{ form_obj.email }}
div>
......
<p class="reg-button">
<button type="submit" class="btn btn-primary btn-block">
注册
button>
p>
form>
说明:
也可以通过
as_p
方法直接生成整个表单,但是自定义性比较差。
验证提交的表单
# 传入request.POST,实例化表单对象用于户验证和提取数据
form_obj = RegisterForm(request, request.POST)
# 方法:
form_obj.is_valid() # 根据字段属性和验证函数进行校验
form.cleaned_data.get(field) # 校验OK,提取数据
form.errors # 错误信息:{field:[error_info], field:[],...}
根据用户提交,保存或修改数据库
略……
用过Django Form功能的可能会觉得用起来很繁琐,要写一堆验证方法,验证通过后还是要一一提取数据,最终的操作都是数据库模型中进行保存或修改操作。不难发现Form表单的字段和对应的数据库Model几乎是一样的,于是Django提供了ModelForm,极大的简化了表单的处理和验证。我们看下如何使用。
自定义表单,继承ModelForm
from django.forms import ModelForm # 导入ModelForm
from django.forms import fields
from .models import UserInfo, Role, Permission, Menu # 导入Model
class UserInfoModelForm(ModelForm):
username = fields.CharField(required=True)
"""
可以在这里单独定义字段的具体类型和属性,同Form
如果定义的字段在model中存在,则会覆盖;
"""
class Meta:
model = UserInfo
fields = '__all__'
labels = {
'username': '用户名',
'password': '密码',
'nickname': '昵称',
'email': '邮箱',
}
说明:
model: 对应的数据库model
fields: 选择字段列表;’all’是选择所有字段
exclude: 排除字段列表
widgets: 插件字典
labels: 前端显示字段名
error_messages: 自定义错误提示
localized_fields: 本地化,如:根据不同时区显示数据
数据库中
2016-12-27 04:10:57
setting中的配置
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
则显示:
2016-12-27 12:10:57
实例化表单对象,传入模板,同Form
model_form = UserInfoModelForm()
验证提交的表单
model_form = UserInfoModelForm(request.POST)
if model_form.is_valid():
model_form.save()
is_valid()
方法会根据model字段的类型以及自定义验证方法来验证提交的数据;如果is_valid()
验证通过,直接save()
,就可以完成向数据库中插入一条记录。
如果是修改记录,需要传入记录对象
# 实例化表单对象,传入模板
model_form = UserInfoModelForm(instance=user_obj)
# 验证提交
model_form = UserInfoModelForm(request.POST, instance=user_obj)
if model_form.is_valid():
model_form.save()
例1:将外键字段定义为ChoiceField字段,这里以Form表单为例来说明:
class UserInfoForm(Form):
name = fields.CharField(required=True, error_messages={'required': '用户名不能为空'})
password = fields.CharField(required=True, error_messages={'required': '密码不能为空'})
email = fields.EmailField(required=True)
# depart为外键字段,那么我们可以将其变为下拉选项(非外键字段也可以,只要给它提供[(m, n), (m, n)]这种结构的数据源就可以)
depart = fields.ChoiceField(choices=models.Department.objects.values_list('id', 'title'))
depart字段的模板渲染结果:
<select name="depart" id="id_depart">
<option value="1">销售option>
<option value="2">公关option>
<option value="3">技术option>
<option value="4">后勤option>
select>
例2:这里以ModelForm为例来进行说明:
def func():
return [
(1, '北京'),
(2, '上海'),
(3, '武汉'),
]
class UserInfoModelForm(ModelForm):
city = fields.ChoiceField(choices=func())
"""
可以在这里单独定义字段的具体类型和属性,同Form
如果定义的字段在model中存在,则会覆盖;
假设,model中的city字段是CharField类型,默认用ModelForm会生成input框,但是我们又不希望每次都手动填入数据。
那么,就可以单独定义该字段为ChoiceField类型。只需要为其提供[(m, n), (m, n)]这种结构的数据源就可以,比如这里选择通过一个函数来实现。
"""
class Meta:
model = UserInfo
fields = '__all__'
labels = {
'username': '用户名',
'password': '密码',
'email': '邮箱',
}
city字段的模板渲染结果:
<select name="city" id="id_city">
<option value="1">北京option>
<option value="2">上海option>
<option value="3">武汉option>
select>
以上两个栗子,分别用了外键的关联表和函数返回值作为表单中ChoiceField字段的下拉选项数据。在实际使用中,常常在model中定义一个静态字段,来作为数据来源,如下:
from django.db import models
class Department(models.Model):
pass
class UserInfo(models.Model):
city_choices = [
(1, '北京'),
(2, '上海'),
(3, '武汉'),
]
username = models.CharField(verbose_name='用户名', max_length=32, null=True, blank=True)
password = models.CharField(verbose_name='密码', max_length=64, null=True, blank=True)
email = models.EmailField(verbose_name='邮箱', max_length=64, null=True, blank=True)
# city以静态字段city_choices作为数据来源
city = models.IntegerField(verbose_name='城市', choices=city_choices, null=True, blank=True)
depart = models.ForeignKey(verbose_name='部门', to="Department", null=True, blank=True)
不论数据是来自静态字段还是外键关联表,ChoiceField字段的值都是是从数据库获取的。但是一旦数据源更新了,模板在渲染时无法显示最新的数据;除非重启程序。这是因为,定义了Form或ModelForm后,ChoiceField会从数据库取一次值,后面再使用时这个值就不更新了。因此,要动态生成ChoiceField字段的数据。
解决办法:不在定义阶段获取数据,在每次实例化表单时获取数据,重写__init__
方法:
class UserInfoForm(Form):
name = fields.CharField(required=True, error_messages={'required': '用户名不能为空'})
password = fields.CharField(required=True, error_messages={'required': '密码不能为空'})
email = fields.EmailField(required=True)
# depart这里只定义字段类型
depart = fields.ChoiceField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 实例化时才动态获取数据
self.fields['depart'].choices = models.Department.objects.values_list('id', 'title')
除了上面这种手动的方式,我们也可以利用ModelChoiceField
字段,达到动态获取数据的目的:
class UserInfoForm(Form):
name = fields.CharField(required=True, error_messages={'required': '用户名不能为空'})
password = fields.CharField(required=True, error_messages={'required': '密码不能为空'})
email = fields.EmailField(required=True)
# ModelChoiceField字段动态获取数据
depart = ModelChoiceField(queryset=models.Department.objects.all())
depart模板渲染结果:
<select name="depart" required="" id="id_depart">
<option value="" selected="">---------option>
<option value="1">销售option>
<option value="2">公关option>
<option value="3">技术option>
<option value="4">后勤option>
select>
不过这里还是推荐通过第一种,重写__init__
的方式,定制性更强;
第二种ModelChoiceField
自动获取数据,它接收QuerySet对象,因此,如果没有在model中定义__str__
方法,渲染出来的结果将是这样子的。。。
<select name="depart" required="" id="id_depart">
<option value="" selected="">---------option>
<option value="1">Department objectoption>
select>