Form表单组件
form表单向后端提交数据时,需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示对应的错误信息
Django form组件就实现了上面所述的功能
- 渲染前端页面
- 校验数据是否合法
- 展示错误信息
普通方式写注册校验
views.py
def register(request):
errors_msg = {'username': '', 'password': ''}
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if '金梅' in username:
errors['username'] = '用户名不能包含金梅'
if len(password) < 3:
errors['password'] = '密码不能小于三位'
return render(request, 'register.html', locals())
register.html
<form action="" method="post">
<p>username:
<input type="text" name="username">
<span style="color: red">{{ errors_msg.username }}</span>
</p>
<p>password:
<input type="password" name="password">
<span style="color: red">{{ errors_msg.password }}</span>
</p>
<input type="submit">
</form>
form组件实现注册校验
views.py
from django import forms
class RegisterForm(forms.Form):
username = forms.CharField(max_length=8, min_length=3) # 用户名最长八位最短三位
password = forms.CharField(max_length=8, min_length=5) # 密码最长八位最短五位
email = forms.EmailField() # email必须符合邮箱格式
校验数据是否合法
测试:
方式一:test.py测试脚本
方式二:Python Console,类似于test.py测试脚本的环境
from app01 import views
# 将需要校验的数据以字典的方式传递给自定义的类实例化产生对象
form_obj = views.RegisterForm({'username': 'jason', 'password': '123', 'email': '123'})
# 查看数据是否全部合法,只有所有的数据都符合要求才会是True
form_obj.is_valid() # False
# 查看没有通过校验的数据及原因
form_obj.errors # {'password': ['Ensure this value has at least 5 characters (it has 3).'], 'email': ['Enter a valid email address.']}
# 查看通过校验的数据
form_obj.cleaned_data # {'username': 'jason'}
form_obj = views.RegisterForm({'username': 'jason', 'password': '12345', 'email': '[email protected]'})
form_obj.is_valid() # True
# 自定义类中的所有字段默认都是必须传值
form_obj = views.RegisterForm({'username': 'jason', 'password': '12345'})
form_obj.is_valid() # False
# 可以额外传入类中没有定义的字段,forms组件不会去校验
form_obj = views.RegisterForm({'username': 'jason', 'password': '12345', 'email': '[email protected]', 'hobby': 'read'})
form_obj.is_valid() # True
渲染前端页面的三种方式
views.py
def reg(request):
form_obj = RegisterForm() # 生成一个空的自定义类对象
return render(request, 'reg.html', locals()) # 将该对象传递给前端页面
reg.html
forms组件渲染页面时只会渲染获取用户输入的标签,提交按钮需要手动添加
渲染后的标签都会加上名为 id_字段名 的id
<form action="" method="post">
<p>渲染页面的三种方式</p>
<br>
<p>方式一:封装程度太高,通常不适用,一般只用于本地测试)</p>
{{ form_obj }}
{{ form_obj.as_p }}
{{ form_obj.as_ul }}
{{ form_obj.as_table }}
<br>
<p>方式二:可扩展性较高,书写麻烦</p>
<p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
<p>{{ form_obj.password.label }}:{{ form_obj.password }}</p>
<p>{{ form_obj.email.label }}:{{ form_obj.email }}</p>
<br>
<p>方式三:推荐 {{ foo.auto_id }}获取input框的id值</p>
{% for foo in form_obj %}
<p>
<label for="{{ foo.auto_id }}">{{ foo.label }}</label>:{{ foo }}
</p>
{% endfor %}
<input type="submit">
</form>
展示错误信息
views.py
def reg(request):
form_obj = RegisterForm() # 生成一个空的自定义类对象
if request.method == 'POST':
form_obj = RegisterForm(request.POST) # 由于request.POST其实就是一个字典,所以可以直接传给LoginForm
if form_obj.is_valid(): # forms组件校验数据
pass # 校验通过则写入数据库
# 校验不通过则向前端展示错误信息
return render(request, 'reg.html', locals()) # 将该对象传递给前端页面
reg.html
前端取消浏览器校验功能:form标签指定novalidate属性,<form action="" method='post' novalidate></form>
校验数据时可以前后端都校验,前端校验可有可无但后端校验必须要有,因为前端校验可以通过爬虫直接避开
<form action="" method='post' novalidate>
{% for foo in form_obj %}
<p>{{ foo.label }}:{{ foo }}
<span>{{ foo.errors.0 }}</span>
</p>
{% endfor %}
<input type="submit">
</form>
常见字段与插件
字段与插件
创建Form类时,主要涉及到 字段 和 插件,字段用于对用户请求数据的验证,插件用于自动生成HTML
from django import forms
from django.forms import widgets
class RegisterForm(forms.Form):
# label指定input框的label注释,默认是类的字段名首字母大写
# error_messages指定错误信息
# initial指定默认值
# widget指定input框type类型和指定input标签属性
username = forms.CharField(max_length=8, min_length=3, label='用户名', initial='jason', inerror_messages={
'max_length': '用户名最大八位',
'min_length': '用户名最小三位',
'required': '用户名不能为空'
}, widget=forms.widgets.TextInput(attrs={'class': 'form-control c1 c2'}))
# required=False指定字段可以为空
password = forms.CharField(max_length=8, min_length=5, label='密码', error_messages={
'max_length': '密码最大八位',
'min_length': '密码最小五位',
'required': '密码不能为空'
},required=False, widget=widgets.PasswordInput())
confirm_password = forms.CharField(max_length=8, min_length=5, label='确认密码', error_messages={
'max_length': '确认密码最大八位',
'min_length': '确认密码最小五位',
'required': '确认密码不能为空'
})
email = forms.EmailField(label='邮箱', error_messages={
'required': '邮箱不能为空',
'invalid': '邮箱格式不正确'
}, widget=forms.widgets.EmailInput(attrs={'class': 'form-control'}))
# 所有字段批量指定input标签属性
def __init__(self, *args, **kwargs):
super(RegisterForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({'class': 'form-control'})
# widget指定input框其他type类型
# 单选radio
gender = forms.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
# 单选select
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
# 多选select
hobby1 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
# 单选checkbox
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
# 多选checkbox
hobby2 = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
choice字段选项从数据库中获取
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
方式一:
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs)
# self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
# 或
self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二:
from django import forms
from django.forms import models as form_model
class FInfo(forms.Form):
authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选
# authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
Django Form所有内置字段
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
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类型
正则校验
RegexValidator验证器
from django import forms
from django.core.validators import RegexValidator
class RegisterForm(forms.Form):
# validators指定正则校验
password = forms.CharField(max_length=8, min_length=5, label='密码', error_messages={
'max_length': '密码最大八位',
'min_length': '密码最小五位',
'required': '密码不能为空'
},required=False,validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')])
自定义验证函数
import re
from django.forms import Form
from django.forms import fields
from django.core.exceptions import ValidationError
# 自定义验证规则
def mobile_validate(value):
mobile_re = re.compile(r'^159[0-9]+$')
if not mobile_re.match(value):
raise ValidationError('必须以159开头')
class PublishForm(Form):
# 使用自定义验证规则
phone = fields.CharField(validators=[mobile_validate,],
error_messages={'required': '手机不能为空'})
钩子函数HOOK
钩子函数(HOOK):forms组件暴露给用户的可以自定义的校验规则,写在自定义的form类中
局部钩子:针对某一个字段做额外的校验
全局钩子:针对多个字段做额外的校验
局部钩子
from django import forms
class RegisterForm(forms.Form):
...
# 局部钩子示例:校验用户名中不能包含666,否则展示错误信息
def clean_username(self): # 函数名必须是 clean_字段名
username = self.cleaned_data.get('username')
if '666' in username:
self.add_error('username', '光喊666是不行的') # 把第二个参数指定的报错信息放到第一个参数指定的input框后面
return username # 要返回字段
"""
# 方式二:
from django.core.exceptions import ValidationError
def clean_username(self):
username = self.cleaned_data.get("username")
if "666" in username:
raise ValidationError("光喊666是不行的")
else:
return username
"""
全局钩子
from django import forms
class RegisterForm(forms.Form):
...
# 局部钩子示例:校验用户输入的两次密码是否一致
def clean(self): # 函数名必须是 clean
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not password == confirm_password:
self.add_error('confirm_password', '两次密码不一致')
return self.cleaned_data # 返回cleaned_data
ModelForm
通常在Django项目中,编写的大部分都是与Django模型紧密映射的表单。 举个例子,有个Book模型,并且想创建一个form表单用来添加和编辑书籍信息到这个模型中,则在form表单中定义字段将是冗余的,因为已经在模型中定义了那些字段
Django提供一个辅助类可以从Django模型创建Form,这就是ModelForm
modelForm定义
class BookForm(forms.ModelForm):
class Meta:
model = models.Book
fields = "__all__"
labels = {
"title": "书名",
"price": "价格"
}
widgets = {
"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
}
class Meta常用参数
model = models.Book # 对应的Model中的类
fields = "__all__" # 字段,__all__表示列出所有字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
ModelForm验证
与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid()或访问errors属性时隐式调用。
可以像使用Form类一样自定义局部钩子和全局钩子方法来实现自定义的校验规则。
如果不重写具体字段并设置validators属性,ModelForm是按照模型中字段的validators来校验的。
save方法
每个ModelForm还具有一个save()方法,这个方法根据表单绑定的数据创建并保存数据库对象,ModelForm的子类可以接受现有的模型实例作为关键字参数instance,如果提供此功能则save()将更新该实例,如果没有提供此功能则save()将创建模型的一个新实例
from myapp.models import Book
from myapp.forms import BookForm
# 根据POST数据创建一个新的form对象
form_obj = BookForm(request.POST)
# 创建书籍对象
new_ book = form_obj.save()
# 基于一个书籍对象创建form对象
edit_obj = Book.objects.get(id=1)
# 使用POST提交的数据更新书籍对象
form_obj = BookForm(request.POST, instance=edit_obj)
form_obj.save()
form组件渲染后发送ajax请求
<body>
<form id="myform" novalidate> <!-- form表单的作用是使用form表单的serializeArray()方法 -->
{% csrf_token %}
{% for foo in form_obj %}
<div class="form-group">
<label for="{{ foo.auto_id }}">{{ foo.label }}</label>
{{ foo }}
</div>
{% endfor %}
<div class="form-group">
<label for="myfile">头像
<img src="/static/img/default.webp" alt="" height="80" style="margin-left: 20px;" id="img">
</label>
<input type="file" name="avatar" id="myfile" style="display: none">
</div>
<input type="button" class="btn btn-primary pull-right" value="注册" id="id_submit">
</form>
</body>
<script>
$('#id_submit').click(function () {
var formData = new FormData();
// console.log($('#myform').serializeArray()) // form标签.serializeArray() 获取form表单内所有普通字段键值,不能获取文件字段
// 0:{name: 'csrfmiddlewaretoken', value: '0Kk4ncbqm2Vj9wrMoXX0HwSJLTAHgSuFy5BvD8VYfxS3iTf8bhdvUDfpOUIVfzm3'}
// 1:{name: 'username', value: ''}
// 2:{name: 'password', value: ''}
$.each($('#myform').serializeArray(), function (index, obj) {
// console.log(index, obj) // index: 0, obj: {name: 'csrfmiddlewaretoken', value: 'WYKcC6IsamNlbCgXtTx3xy1EBl4kI9NUuj1DS2s03RK5kZ4jgdNyKFokEmcyHQFi'}#}
formData.append(obj.name, obj.value);
})
formData.append('myfile', $('#myfile')[0].files[0]);
$.ajax({
url: '',
type: 'post',
data: formData,
contentType: false,
processData: false,
success: function (data) {
alert(data)
}
})
})
</script>
标签:username,None,obj,Form,form,08,表单,forms,password
From: https://www.cnblogs.com/jlllog/p/17115182.html