Django Forms组件
目录
多对多表关系三种创建方式
ORM自动创建
使用 ManyToManyField
方法
class Book(models.Model):
title = models.CharField(max_length=64)
# 多对多关系字段
authors = models.ManyToManyField(to='Authors')
class Authors(models.Model):
name = models.CharField(max_length=64)
好处:自动创建了第三张关系表,内置了四个操作第三张关系表的方法:
- add
- remove
- set
- clear
不足:自动创建的第三张表无法满足我们的扩展需求,可扩展性差。
自己实现第三张表
class Book(models.Model):
title = models.CharField(max_length=64)
class Authors(models.Model):
name = models.CharField(max_length=64)
# 自己实现第三张表
class Book_Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Authors')
# 可以扩展其他的字段:比如
create_time = models.DateField(auto_now_add=True)
好处:可扩展性高,字段个数和字段名称都可以自己定义。
不足:不支持ORM跨表查询,不再有正反向的概念。
半自动化(以后推荐使用)
- to:关联的表
- through:自己指定第三张关系表
- through_fields:自己制定第三张关系表中的关联字段,有先后顺序:在哪张表中创建的 外键关联字段要放在前面
class Book(models.Model):
title = models.CharField(max_length=64)
# 多对多关系字段
authors = models.ManyToManyField(to='Authors',
through='Book_Author',
through_fields=('book','authors'))
'''
当时用ManyToManyField方法只有一个参数to的时候,
ORM会自动创建第三张关系表出来
如果指定了through和through_fileds参数,orm就不会自动帮你创建第三张关系表
但是会在内部帮你维护关系,能够继续支持ORM跨表查询。
through :自己指定第三张关系表
through_fields :自己制定第三张关系表中的关联字段,有先后顺序:
在哪张表中创建的 外键关联字段要放在前面
'''
class Authors(models.Model):
name = models.CharField(max_length=64)
# authors = models.ManyToManyField(to='Authors',
# through='Book_Author',
# through_fields=('authors','book'))
# 自己实现第三张表
class Book_Author(models.Model):
book = models.ForeignKey(to='Book')
author = models.ForeignKey(to='Authors')
# 可以扩展其他的字段:比如
create_time = models.DateField(auto_now_add=True)
好处:支持ORM的跨表查询,可以任意扩展第三张关系表的字段。
不足:不支持ORM操作第三张关系表的一些方法
- add
- remove
- set
- clear
Forms组件
先来提个需求:
写一个注册页面,获取用户输入的用户名和密码,提交到后端之后,后端需要对用户名和密码做校验:
比如说:用户名不能含有xxx,密码不能少于三位,如果不符合条件,展示对应的错误信息。
这时候我们可以怎么做呢?
路由层urls.py:
url(r'^register/',views.register),
视图层views.py:
def register(request):
errors = {'username':'','password':''}
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
if 'xxx' in username:
errors['username'] = '用户名不能含有xxx'
if len(password) < 4:
errors['password'] = '密码不能少于三位'
# errors = errors = {'username':'用户名不能含有xxx','password':'密码不能少于三位'}
return render(request,'register.html',locals())
前端模板层register.html:
<form action="" method="post">
<p>username:
<input type="text" name="username">
# 我们可以顶一个span标签,展示错误信息
# 后端如果校验通过了,那么这个标签就是空的。
# 如果校验不通过,标签则显示对应的错误信息。
<span style="color: red">{{ errors.username }}</span>
</p>
<p>password:
<input type="text" name="password">
<span style="color: red">{{ errors.password }}</span>
</p>
<input type="submit">
</form>
这样也可以实现,但是这种方式实在是太LOW了!!!,如果需要校验的数据非常多,那就会很麻烦了,并且在前端页面提交数据之后,页面会刷新,用户输入的信息都会消失,这对用户的体验非常不好
这时候我们可以利用django中自带的组件Forms来完成以上所有事情。
使用Forms组件的前提:需要提前写一个类。
from django import forms
class MyForm(forms.Form):
username = forms.CharField(max_length=8,min_length=3)
password = forms.CharField(max_length=8,min_length=3)
# 定义email字段,必须是邮箱格式,
email = forms.EmailField()
渲染标签
<p>forms组件渲染标签方式1</p>
{{ form_obj.as_p }} 自动渲染所有inpu框,并用p标签包起来
{{ form_obj.as_ul }} 自动渲染所有inpu框,并用ul标签包起来
{{ form_obj.as_table }} 自动渲染所有inpu框,并用table标签包起来
<hr>
<p>forms组件渲染标签方式2</p>
{{ form_obj.username.label }}:{{ form_obj.username }}
{{ form_obj.password.label }}:{{ form_obj.password }}
{{ form_obj.email.label }}:{{ form_obj.email }}
<hr>
<p>forms组件渲染标签方式3</p>
<form action="" method="post">
{% for forms in form_obj %}
<p>
{{ forms.label }}:{{ forms }}
</p>
{% endfor %}
<input type="submit">
</form>
数据校验通常前后端都必须有,
这里有一个前端校验数据的方式:novalidate,
但是前端的防御弱不禁风,我们前端校验和后端校验一起用,告诉浏览器不在前端校验,可以在form表单中添加参数:novalidate
展示信息
使用forms组件 和form表单展示信息
<form action="" method="post">
{% for forms in form_obj %}
<p>
{{ forms.label }}:{{ forms }}
<span>{{ forms.errors.0 }}</span> <!--重点,展示错误信息-->
</p>
{% endfor %}
<input type="submit">
</form>
校验数据
我们可以通过pycharm提供了一个工具来校验我们的数据是否合法:
Python Console:
from app01.utils import myforms
# 实例化自己写好的类,传字典数据,待校验的数据
form_obj = myforms.MyForm({'username':'qinyj','password':'123','email':'123@qq.com'})
# 判断传入的数据是否合法:
form_obj.is_valid()
# 如果你的数据全部都符合校验规则的时候,是True,否则为False
Out[4]: True
# 查看不符合规则的字段及错误信息:如果没有错误,返回一个空字典
form_obj.errors
Out[5]: {}
form_obj = myforms.MyForm({'username':'xxxaxa','password':'123','email':'123'})
form_obj.is_valid()
Out[9]: False
# Forms组件中,定义的字段默认都是必须传值的,不能少传值,否则也是False
# 如果传入多余的字段参数,对判断结果不会有任何影响,依然是True。
后端校验数据-内置校验器
from django.core.validators import RegexValidator
phone = forms.IntegerField(label='手机号',validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头'),
])
后端校验数据-钩子函数
如果说上面的校验机制不够全面,可以考虑使用钩子函数来校验我们的数据:
钩子函数本质上还是一个函数,函数体内可以写任意的校验代码。
# 局部钩子 校验用户名中不能含有666
# 一般局部钩子来校验单个字段是否满足条件
def clean_username(self):
username = self.cleaned_data.get('username')
if '666' in username:
# 给username所对应的的输入框展示自定义错误信息
self.add_error('username','用户名不能包含"666"的字样')
return username
# 全局钩子 校验两次密码输入是否一致
# 一般全局钩子来校验两个字段是否满足条件。
def clean(self):
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
完整Forms组件代码示例
# coding=utf-8
# File : myforms.py
# Author: Jack秦
# Date : 2019/12/3
from django import forms
from django.core.validators import RegexValidator
from django.forms import widgets
class MyForm(forms.Form):
username = forms.CharField(max_length=8,min_length=3,label='用户名',initial='xxx',
error_messages={
'max_length':'用户名最长八位',
'min_length':'用户名最短三位',
'required':'用户名不能为空',
},required=False,
widget=forms.widgets.TextInput({'class':'form-control'}))
password = forms.CharField(max_length=8,min_length=3,label='密码',initial='11',
error_messages={
'max_length':'密码最长八位',
'min_length':'密码最短三位',
'required':'密码不能为空',
},widget=forms.widgets.PasswordInput({'class':'form-control'}))
confirm_password = forms.CharField(max_length=8,min_length=3,label='确认密码',initial='11',
error_messages={
'max_length':'确认密码最长八位',
'min_length':'确认密码最短三位',
'required':'确认密码不能为空',
},widget=forms.widgets.PasswordInput({'class':'form-control'}))
# 定义email字段,必须是邮箱格式,
email = forms.EmailField(label='邮箱',initial='example@xxx.com',
error_messages={
'required':'邮箱不能为空',
'invalid':'邮箱格式错误'
})
phone = forms.CharField(label='手机号',validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头'),
],max_length=11,min_length=10)
# radioSelect
# 单选 radio值为字符串
gender = forms.fields.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
#
multiple_hobby_select = 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
multiple_hobby_choices = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
# 局部钩子 校验用户名中不能含有666
def clean_username(self):
username = self.cleaned_data.get('username')
if '666' in username:
# 给username所对应的的输入框展示自定义错误信息
self.add_error('username','用户名不能包含"666"的字样')
return username
# 全局钩子 校验两次密码输入是否一致
def clean(self):
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
视图层views.py:
from app01.utils.myforms import MyForm
def register(request):
# 渲染标签:
# 需要先生成一个空的forms类的对象
form_obj = MyForm()
#
if request.method == "POST":
# 获取用户的数据,就是一个大字典。传入自己写的forms组件的类,
# 参数就是request.POST
form_obj = MyForm(request.POST)
# 判断是否有效
if form_obj.is_valid():
print(form_obj.cleaned_data)
return HttpResponse("数据全部校验成功")
# 将生成的对象传递给前端页面。
return render(request,'register.html',locals())
常用Forms组件字段参数
参数 | 描述 |
---|---|
label | input框对应的提示信息 |
initial | input框默认值 |
required | 默认为True,控制字段是否必填 |
widget | 可以给input框设置样式及属性 |
error_messages | 重写错误信息,默认是英文 |
max_length | 最大位数 |
min_length | 最小位数 |
Django Froms组件中所有内置字段
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类型