魔力python---django---form和modelform组件,formset和modelformset组件
1.简介
form和modelform组件,formset和modelformset组件使用在校验数据方面。
考虑情景,在HTML页面中用form表单向后端提交数据时,HTML中都有让用户输入的标签并且用form标签包起来(考虑ajax异步提交是否可行)。 这时候,前端可以进行初步验证,然后后段再次进行验证。
总结form组件的功能:
- 生成页面可用的HTML标签
- 对用户提交的数据进行校验
- 保留上次输入内容
小结:
- 重写__init__的效果:
- 实时刷新选择字段;
- 批量添加样式。
- cleaned_data 是通过校验的数据,不需要设置,浑然天成.
- form,modelform,formset,modelformset区别:
- form适用于对单个表单的操作,并且需要对每个字段的验证规则自定义。
- modelform:适用于对用户提交的单个表单操作,字段可以用model中的表的字段来作为验证规则,适用于快速的进行增加、修改。
- formset:适用于对多个表单进行操作,字段需要也可以用model中的表的字段来作为验证规则。
- modelfoemset:适用于对多个表单进行操作,字段需要也可以用model中的表的字段来作为验证规则,速度可能快一些.
2.form组件
2.1 form组件使用原理
实际上form组件仅仅是用来判断输入内容是否符合form类字段的要求,符合的通过,否则不予通过。
2.2 form组件使用步骤
- 设置form类(无论在什么地方,建议重新建立form.py单独写),可以对字段进行设置(包括样式);
- 写完form类后在想使用的views视图函数里进行实例化,并且把数据传到前端页面(form表单是get方法时);
- 前端页面全靠渲染,input不用自己写(自己写的用不了),可以写label(具体详情参见实例);
- form表单是post时,写
form_obj = RegForm(data = request.POST)RegForm是设定的form类,data=可写可不写; - 接下来对form_obj进行判定,写
form_obj.is_valid()如果是True,做某事,否则做另一件事.
2.3 form组件常用字段与插件
字段:用于对用户请求数据的验证
插件:用于自动生成HTML
2.3.1 常用字段里面的属性
max_length 最大长度
min_length 最小长度
label 设置标签名
initial 默认值
error_messages 重写错误信息
widget 设置属性和样式 举例: widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) render_value是在前端保留用户输入的数据
choices 应用在选择框
validators 用于正则判断
2.3.2 常用field字段
常用:
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密码")
password:
class LoginForm(forms.Form):
...
pwd = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True) #这个密码字段和其他字段不一样,默认在前端输入数据错误的时候,点击提交之后,默认是不保存的原来数据的,但是可以通过这个render_value=True让这个字段在前端保留用户输入的数据
)
radioSelect 单选框
class LoginForm(forms.Form):
username = forms.CharField( #其他选择框或者输入框,基本都是在这个CharField的基础上通过插件来搞的
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密码")
gender = forms.fields.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
单选select
class LoginForm(forms.Form):
...
hobby = forms.fields.ChoiceField( #注意,单选框用的是ChoiceField,并且里面的插件是Select,不然验证的时候会报错, Select a valid choice的错误。
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
多选select
class LoginForm(forms.Form):
...
hobby = forms.fields.MultipleChoiceField( #多选框的时候用MultipleChoiceField,并且里面的插件用的是SelectMultiple,不然验证的时候会报错。
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
单选checkbox
class LoginForm(forms.Form):
...
keep = forms.fields.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
多选checkbox
class LoginForm(forms.Form):
...
hobby = forms.fields.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
date类型
from django import forms
from django.forms import widgets
class BookForm(forms.Form):
date = forms.DateField(widget=widgets.TextInput(attrs={'type':'date'})) #必须指定type,不然不能渲染成选择时间的input框
单选checkbox示例
#单选的checkbox
class TestForm2(forms.Form):
keep = forms.ChoiceField(
choices=(
('True',1),
('False',0),
),
label="是否7天内自动登录",
initial="1",
widget=forms.widgets.CheckboxInput(),
)
选中:'True' #form只是帮我们做校验,校验选择内容的时候,就是看在没在我们的choices里面,里面有这个值,表示合法,没有就不合法
没选中:'False'
---保存到数据库里面 keep:'True'
if keep == 'True':
session 设置有效期7天
else:
pass
注意
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
示例:(重新构造init函数即可)
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)
注意重写init方法的时候,*args和**kwargs一定要给人家写上,不然会出问题,并且验证总是不能通过,还不显示报错信息
# self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
# 或
self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
更多方法参见链接:https://www.cnblogs.com/clschao/articles/10486468.html
2.3.3 全部字段
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类型
复制代码
内置字段
2. 4 form组件实例
2.4.1 urls.py
"""foemtest URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^register/', views.register,name='register'),
url(r'^login/', views.login,name='login'),
url(r'^main/', views.main,name='main'),
]
2.4.2 views.py
from django import forms
from django.shortcuts import render, redirect, HttpResponse
# Create your views here.
# form组件写法
class RegForm(forms.Form):
username = forms.CharField(
# 最大长度
max_length=32,
# 最小长度
min_length=4,
# 提示语句
help_text='用户名长度大于4,小于32',
# 标签名(渲染label标签)
label='用户名',
#设置默认值
initial = '001';
# 重写错误信息
error_messages={'required': '非空'},
# widget是确定HTML的样式
widget=forms.widgets.TextInput(attrs={
'class': 'form-control'
})
)
# form字段的label标签些什么,前端生成input标签的时候input的name属性就是什么
password = forms.CharField(label='密码')
confirmpassword = forms.CharField(label='确认密码')
def register(request):
form_obj = RegForm()
if request.method == 'POST':
# 实例化form对象的时候,把post提交过来的数据直接传进去
# print(request.POST)
# 数据格式:<QueryDict:
# {'csrfmiddlewaretoken': ['IDNrmeoussAtWMeTp6Im3o1mEqnZUJYDpHi54XUSLDa15ZTYhapyfbFkr1qTWNi1'], 'id_username':
# ['1'], 'id_password': ['2'], 'id_confirmpassword': ['3']}>
print(request.POST)
form_obj = RegForm(request.POST)
# print(form_obj)
# 由于传过来的input标签的name属性值和form类对应的字段名是一样的,所以传过来之后,form就可以取出对应的form字段名相同的数据进行form校验
# 调用form_obj校验数据的方法
print(form_obj.is_valid())
# False
if form_obj.is_valid():
# 看传过来的值是否符合格式
return redirect('login')
return render(request, 'register.html', {'form_obj': form_obj})
def login(request):
if request.method == 'GET':
return render(request, 'base.html')
else:
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'smithpath' and password == '123':
return redirect('main')
else:
return HttpResponse('输错啦')
def main(request):
return render(request, 'main.html')
2.4.3 register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
<!--novalidate表名form表单不对输入的内容进行校验-->
{% csrf_token %}
{# 直接写{{ form_obj.as_p }},会自动生成页面,但是没办法调整#}
<div>
<label for="{{ form_obj.username.id_for_label }}">
{{ form_obj.username.label }}
</label>
<!--注意,是自动渲染标签,不要自己打上去-->
{{ form_obj.username }}
</div>
{# <div>#}
{# <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label><input type="text" id="{{ form_obj.username.id_for_label }}" name="{{ form_obj.username.id_for_label }}"><span>{{ form_obj.errors.0 }}</span>#}
{# </div>#}
<div>
<label for="{{ form_obj.password.id_for_label }}">
{{ form_obj.password.label }}
</label>
{{ form_obj.password }}
<span>{{ form_obj.errors.0 }}</span>
</div>
<div>
<label for="{{ form_obj.confirmpassword.id_for_label }}">
{{ form_obj.confirmpassword.label }}</label>
{{ form_obj.confirmpassword }}
<span>{{ form_obj.errors.0 }}</span>
</div>
<div>
<input type="submit" value="注册">
</div>
</form>
</body>
</html>
2.5 字段校验
这里主要定义form类对输入字段的校验,符合则通过.
2.5.1 RegexValidator验证器
RegexValidator验证器:利用正则匹配进行校验.
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
2.5.2 自定义验证函数
自定义验证函数:自己定义验证函数,再套用到form类里面.
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
# 自定义验证规则
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手机号码格式错误') #自定义验证规则的时候,如果不符合你的规则,需要自己发起错误
class PublishForm(Form):
title = fields.CharField(
max_length=20,
min_length=5,
error_messages={'required': '标题不能为空','min_length': '标题最少为5个字符','max_length': '标题最多为20个字符'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': '标题5-20个字符'}))
# 使用自定义验证规则
phone = fields.CharField(
validators=[mobile_validate, ],
error_messages={'required': '手机不能为空'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'手机号码'}))
email = fields.EmailField(
required=False,
error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
2.6 hook钩子方法
2.6.1 局部钩子
局部钩子是默认的cleaned_某字段,只针对当前定义的字段,
cleaned_data是通过验证的数据,然后如果符合这个问题就会跑异常.异常通过渲染在HTML文件中显示出来,但要确认一点的是要写上{{ form_obj.username.errors.0 }}是什么字段就写什么.
2.6.1.1 views.py
class RegForm(forms.Form):
username = forms.CharField(
# 最大长度
max_length=32,
# 最小长度
min_length=4,
# 提示语句
help_text='用户名长度大于4,小于32',
# 标签名(渲染label标签)
label='用户名',
# 提示报错信息
error_messages={'required': '非空'},
# widget是确定HTML的样式
widget=forms.widgets.TextInput(attrs={
'class': 'form-control'
})
)
# form字段的label标签些什么,前端生成input标签的时候input的name属性就是什么
password = forms.CharField(label='密码')
confirmpassword = forms.CharField(label='确认密码')
#定义局部钩子,用来校验username字段,之前的校验正则依然在,现在是添加了一个校验功能的钩子
#格式是clean_某字段,cleaned_data是通过验证的数据
def clean_username(self):
value = self.cleaned_data.get('username')
if '123' in value:
raise ValidationError('可以可以进来了')
else:
return value
def register(request):
form_obj = RegForm()
if request.method == 'POST':
form_obj = RegForm(request.POST)
if form_obj.is_valid():
return redirect('login')
return render(request, 'register.html', {'form_obj': form_obj})
2.6.1.2 register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
<!--novalidate表名form表单不对输入的内容进行校验-->
{% csrf_token %}
{# 直接写{{ form_obj.as_p }},会自动生成页面,但是没办法调整#}
<div>
<label for="{{ form_obj.username.id_for_label }}">
{{ form_obj.username.label }}
</label>
<!--注意,是自动渲染标签,不要自己打上去-->
{{ form_obj.username }}{{ form_obj.username.errors.0 }}
</div>
<div>
<input type="submit" value="注册">
</div>
</form>
</body>
</html>
注意,上面的py文件和HTML文件为了方便查阅,全都是简化了的
2.6.2 全局钩子
全局钩子是clean()方法,这个可以对多个字段进行比较检验,也可以对单个字段检验,所以是对任意字段进行检验.
2.6.2.1 views.py
from django import forms
from django.shortcuts import render, redirect, HttpResponse
from django.core.exceptions import ValidationError
# Create your views here.
# form组件写法
class RegForm(forms.Form):
username = forms.CharField(
# 最大长度
max_length=32,
# 最小长度
min_length=4,
# 提示语句
help_text='用户名长度大于4,小于32',
# 标签名(渲染label标签)
label='用户名',
# 提示报错信息
error_messages={'required': '非空'},
# widget是确定HTML的样式
widget=forms.widgets.TextInput(attrs={
'class': 'form-control'
})
)
# form字段的label标签些什么,前端生成input标签的时候input的name属性就是什么
password = forms.CharField(label='密码',widget=forms.widgets.PasswordInput())
confirmpassword = forms.CharField(
label='确认密码',
widget=forms.widgets.PasswordInput())
#定义局部钩子,用来校验username字段,之前的校验正则依然在,现在是添加了一个校验功能的钩子
def clean(self):
password = self.cleaned_data.get('password')
confirmpassword = self.cleaned_data.get('confirmpassword')
if password == confirmpassword:
return self.cleaned_data
else:
self.add_error('confirmpassword','两次密码不一致')
return ValidationError('两次密码不一致')
def register(request):
form_obj = RegForm()
if request.method == 'POST':
form_obj = RegForm(request.POST)
if form_obj.is_valid():
return redirect('login')
return render(request, 'register.html', {'form_obj': form_obj})
2.6.2.2 register.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
<!--novalidate表名form表单不对输入的内容进行校验-->
{% csrf_token %}
{# 直接写{{ form_obj.as_p }},会自动生成页面,但是没办法调整#}
<div>
<label for="{{ form_obj.username.id_for_label }}">
{{ form_obj.username.label }}
</label>
<!--注意,是自动渲染标签,不要自己打上去-->
{{ form_obj.username }}{{ form_obj.username.errors.0 }}
</div>
{# <div>#}
{# <label for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</label><input type="text" id="{{ form_obj.username.id_for_label }}" name="{{ form_obj.username.id_for_label }}"><span>{{ form_obj.errors.0 }}</span>#}
{# </div>#}
<div>
<label for="{{ form_obj.password.id_for_label }}">
{{ form_obj.password.label }}
</label>
{{ form_obj.password }}
<span>{{ form_obj.password.errors.0 }}</span>
</div>
<div>
<label for="{{ form_obj.confirmpassword.id_for_label }}">
{{ form_obj.confirmpassword.label }}</label>
{{ form_obj.confirmpassword }}
<span>{{ form_obj.confirmpassword.errors.0 }}</span>
</div>
<div>
<input type="submit" value="注册">
</div>
</form>
</body>
</html>
2.7 进阶补充
2.7.1 bootstrap应用
需要时看链接(讲bootstrap应用进去的)https://www.cnblogs.com/clschao/articles/10486468.html
2.7.2 批量添加样式
通过重写init方法实现
class RegForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
...
def __init__(self, *args, **kwargs):
super(RegForm, self).__init__(*args, **kwargs)
for field in iter(self.fields):
self.fields[field].widget.attrs.update({
'class': 'form-control'
})
3. modelform
基于form的基础上进行进一步简化.
我们在models表中已经定义了字段,没必要自己再写一个form类规定,可以直接引用models表的,基于这个想法产生了modelform.
modelform是model和form的结合,使用时会把model中的字段转换成对应的form字段,并且进行标签生成等操作.
3.1 modelform固定写法
class BookForm(forms.ModelForm):
class Meta:
model = models.Book
fields = "__all__"
labels = {
"title": "书名",
"price": "价格"
}
widgets = {
"password": forms.widgets.PasswordInput(attrs={"class": "c1"}),
"publishDate": forms.widgets.DateInput(attrs={"type": "date"}),
}
3.2 class Meta下面常用参数
model = models.Book # 对应的Model中的类
fields = "all" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
error_messages = {
'title':{'required':'不能为空',...} #每个字段的所有的错误都可以写,...是省略的意思,复制黏贴我代码的时候别忘了删了...
}
3.3 批量添加样式
和form一样
实例:
class BookForm(forms.ModelForm):
r_password = forms.CharField() #想多验证一些字段可以单独拿出来写,按照form的写法,写在Meta的上面或者下面都可以
class Meta:
model = models.Book
# fields = ['title','price']
fields = "__all__" #['title,'price'] 指定字段生成form
# exclude=['title',] #排除字段
labels = {
"title": "书名",
"price": "价格"
}
error_messages = {
'title':{'required':'不能为空',} #每个字段的错误都可以写
}
#如果models中的字段和咱们需要验证的字段对不齐的是,比如注册时,咱们需要验证密码和确认密码两个字段数据,但是后端数据库就保存一个数据就行,那么验证是两个,数据保存是一个,就可以再接着写form字段
r_password = forms.CharField()。
#同样的,如果想做一些特殊的验证定制,那么和form一昂,也是那两个钩子(全局和局部),写法也是form那个的写法,直接在咱们的类里面写:
#局部钩子:
def clean_title(self):
pass
#全局钩子
def clean(self):
pass
def __init__(self,*args,**kwargs): #批量操作(批量添加样式)
super().__init__(*args,**kwargs)
#或者写super(CustomerForm,self).__init__(*args,**kwargs)
for field in self.fields:
#field.error_messages = {'required':'不能为空'} #批量添加错误信息,这是都一样的错误,不一样的还是要单独写。
self.fields[field].widget.attrs.update({'class':'form-control'})
3.4 modelform验证
与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid()或者访问errors属性时会隐式调用.(调用is_valid()的时候是进行匹配的)
也可以像Form类一样自定义局部钩子方法和全局钩子来实现自定义的校验规则.
若不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的.(validators具体参见正则验证)
3.5 save()方法
该方法是根据表单绑定的数据创建并保存数据库对象.
ModelForm的子类可以接受现有的模型实例作为关键字参数instance;
若提供instance,则save()更新该实例;
若不提供,save()将创建模型的一个新实例.
关于save()的一点疑问:
因为我们再Meta中指定了是哪张表,所以它会自动识别,不管是外键还是多对多等,都会自行处理保存,它完成的就是上面三句话做的事情,并且还有就是如果你验证的数据比你后端数据表中的字段多,那么他自会自动剔除多余的不需要保存的字段,比如那个重复确认密码就不要保存
3.5.1 关于save()的使用
3.5.1.1 views.py
from django import forms
from django.shortcuts import render, redirect, HttpResponse
from django.core.exceptions import ValidationError
from app01 import models
# Create your views here.
# modelform组件写法
#
class CustomerForm(forms.ModelForm):
class Meta:
model = models.Customer
fields = '__all__'
labels = {
'id':'序号',
'goodsname':'书名',
'price':'价格',
'number':'购买数量'
}
widgets = {
'password':forms.widgets.PasswordInput()
}
def register(request):
form_obj = CustomerForm()
if request.method == 'POST':
cust_obj = models.Customer.objects.filter(id=1)
form_obj = CustomerForm(request.POST,instance=cust_obj)
#instance是默认值
if form_obj.is_valid():
form_obj.save()
# 因为我们再Meta中指定了是哪张表,所以它会自动识别,不管是外键还是多对多等,都会自行处理保存,它完成的就是上面三句话做的事情,并且还有就是如果你验证的数据比你后端数据表中的字段多,那么他自会自动剔除多余的不需要保存的字段,比如那个重复确认密码就不要保存
return redirect('login')
return render(request, 'register.html', {'form_obj': form_obj})
3.5.1.2 HTML文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post" novalidate autocomplete="off">
{% csrf_token %}
<div>
<label for="{{ form_obj.username.id_for_label }}">
{{ form_obj.goodsname.label }}
</label>
<!--注意,是自动渲染标签,不要自己打上去-->
{{ form_obj.goodsname }}{{ form_obj.goodsname.errors.0 }}
</div>
<div>
<input type="submit" value="注册">
</div>
</form>
</body>
</html>
3.5.2 补充
modelform让我们避免了一个一个写form的麻烦和尴尬,前端直接就可以把字段传进去进行渲染.使python变得更加"优雅".
3.5.3 modelform实例:
3.5.3.1 form.py
#首先导入ModelForm
from django.forms import ModelForm
#在视图函数中,定义一个类,比如就叫StudentList,这个类要继承ModelForm,在这个类中再写一个原类Meta(规定写法,并注意首字母是大写的)
#在这个原类中,有以下属性(部分):
class StudentList(ModelForm):
class Meta:
model =Student #对应的Model中的类
fields = "__all__" #字段,如果是__all__,就是表示列出所有的字段
exclude = None #排除的字段
#error_messages用法:
error_messages = {
'name':{'required':"用户名不能为空",},
'age':{'required':"年龄不能为空",},
}
#widgets用法,比如把输入用户名的input框给为Textarea
#首先得导入模块
from django.forms import widgets as wid #因为重名,所以起个别名
widgets = {
"name":wid.Textarea(attrs={"class":"c1"}) #还可以自定义属性
}
#labels,自定义在前端显示的名字
labels= {
"name":"用户名"
}
3.5.3.2 urls.py
写上url分发器
3.5.3.3 views.py
def student(request):
if request.method == 'GET':
student_list = StudentList()
return render(request,'student.html',{'student_list':student_list})
3.5.3.4 前端:
{{student_list.as_p}} <!--简单写法,详细的话就是for循环拿到student_list,然后慢慢拼-->
其他:
链接:https://www.cnblogs.com/clschao/articles/10486468.html
4 formset
适合对多个表单进行操作,也可用于验证.
实际上对于form类的设置formset和form相同,只不过formset使用了更加简洁的封装"工厂模式"(应该是这个),所以才能大批量操作数据. 因此有变化的地方也仅仅是视图函数.
django在不使用formset的时候若想拿到两张表的内容,写法很简单,但实际上接收数据很麻烦,多个form表单的话用formset和modelformset相对容易.
<form >
{{ form1.as_p }}
{{ form2.as_p }}
</form>
4.1 导包
from django.forms.formsets import formset_factory
4.2 属性
max_num 表示最大数量
extra 在原数据表示多添加几个
若max_num 为3,而extra为4,则最多显示3个,注意前者优先级高.
4.3 formset实例
4.3.1 views函数
#(非原创)
class MultiPermissionForm(forms.Form):
id = forms.IntegerField(
widget=forms.HiddenInput(),
required=False
)
title = forms.CharField(
widget=forms.TextInput(attrs={'class': "form-control"})
)
url = forms.CharField(
widget=forms.TextInput(attrs={'class': "form-control"})
)
name = forms.CharField(
widget=forms.TextInput(attrs={'class': "form-control"})
)
menu_id = forms.ChoiceField(
choices=[(None, '-----')],
widget=forms.Select(attrs={'class': "form-control"}),
required=False,
)
pid_id = forms.ChoiceField(
choices=[(None, '-----')],
widget=forms.Select(attrs={'class': "form-control"}),
required=False,
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['menu_id'].choices += models.Menu.objects.values_list('id', 'title')
self.fields['pid_id'].choices += models.Permission.objects.filter(pid__isnull=True).exclude(
menu__isnull=True).values_list('id', 'title')
def clean_pid_id(self):
menu = self.cleaned_data.get('menu_id')
pid = self.cleaned_data.get('pid_id')
if menu and pid:
raise forms.ValidationError('菜单和根权限同时只能选择一个')
return pid
def add(request):
"""
增加
:param request:
:return:
"""
MultiPermissionFormSet = formset_factory(MultiPermissionForm, extra=0)
#此处extra = 0 是指不额外添加,extra会额外添加一个
if request.method == 'GET':
form = MultiPermissionFormSet()
return render(request,'list.html',{'form':form}
form = MultiPermissionFormSet(request.POST)
if form.is_valid():
return redirect('url')
else:
.........
def edit(request):
"""
修改
:param request:
:return:
"""
MultiPermissionFormSet = formset_factory(MultiPermissionForm, extra=0)
if request.method == 'GET':
form = MultiPermissionFormSet('数据')
# formset是支持批量修改的所以 这里传入的数据一定要是可迭代对象,并且里面的
数据类型是字典或者对象
return render(request,'list.html',{'form':form}
form = MultiPermissionFormSet(request.post)
if form.is_valid():
return redirect('url')
else:
.........
4.3.2 HTML文件
<div class="luffy-container">
<form method="post" action="?type=generate">
{% csrf_token %}
{{ form.management_form }}
# 这里一定要记得写这一步
<div class="panel panel-default">
<!-- Default panel contents -->
<div class="panel-heading">
<i class="fa fa-binoculars" aria-hidden="true"></i> 待新建权限列表
<button class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;">
<i class="fa fa-save" aria-hidden="true"></i>
新建
</button>
</div>
<div class="panel-body" style="color: #9d9d9d;">
注意:路由系统中自动发现且数据库中不存在的路由。
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>URL</th>
<th>别名</th>
<th>所属菜单</th>
<th>根权限</th>
</tr>
</thead>
<tbody>
{% for form in generate_formset %}
<tr>
<td style="vertical-align: middle;">{{ forloop.counter }}</td>
{% for field in form %} # 这里在渲染标签的时候一定要渲染 id这个标签 这个很重要不然会报错 这里是写了两个fou循环 所以自动渲染了id标签
{% if forloop.first %}
<td class="hide">
{% else %}
<td>
{% endif %}
{{ field }}<span style="color: firebrick;">{{ field.errors.0 }}</span>
</td>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
</form>
5 modelformset
同formset,modelformset在定义字段时同modelform,只有视图函数中有变化.
对于modelformset,建议先创建modelform,而不是直接创建formset.
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)这个就是由modelform创建formset的做法.
补充:
惰性查询---queryset是查询集,就是传到服务器上的url里面的查询内容。Django会对查询返回的结果集QuerySet进行缓存,这是为了提高查询效率。也就是说,在你创建一个QuerySet对象的时候,Django并不会立即向数据库发出查询命令,只有在你需要用到这个QuerySet的时候才会这样做。
model实例---Objects是django实现的mvc中的m,Django中的模型类都有一个objects对象,它是一个Django中定义的QuerySet类型的对象,它包含了模型对象的实例。
5.1 导包
from django.forms.models import modelformset_factory
5.2 实例
5.2.1 form类
#首先导入包
class StudyRecordModelForm(forms.ModelForm):
class Meta:
model = models.StudyRecord
fields = ['student','record','score','homework_note']
def changelist_view(self,request):
ccid = request.GET.get('ccid')
model_formset_cls = modelformset_factory(models.StudyRecord,StudyRecordModelForm,extra=0)
queryset = models.StudyRecord.objects.filter(course_record_id=ccid)
if request.method == "GET":
formset = model_formset_cls(queryset=queryset)
#queryset是查询集,查询符合条件的数据
# 这里UI定是个可迭代对象,因为modelformset是操作多表的,里面的数据类型可以为字典或者对象
return render(request,'study_record.html',{'formset':formset})
formset = model_formset_cls(data=request.POST)
print(request.POST)
if formset.is_valid():
formset.save()
return redirect('/stark/crm/studyrecord/list/?ccid=%s' %ccid )
return render(request, 'study_record.html', {'formset': formset})
5.2.2 HTML文件
<div class="panel panel-default">
<div class="panel-heading">学习记录</div>
<div class="panel-body">
<div style="width: 680px;margin: 0 auto;">
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
# 这里一定要加这句代码
<table class="table table-bordered">
<thead>
<tr>
<th>姓名</th>
<th>考勤</th>
<th>作业成绩</th>
<th>作业评语</th>
</tr>
</thead>
<tbody>
{% for form in formset %}
<tr>
{{ form.id }}
# 这里只写了一层for循环,所以手动写字段,必须把id字段写上
<td>{{ form.instance.student }}</td>
<td>{{ form.record }} {{ form.record.errors.0 }}</td>
<td>{{ form.score }} {{ form.score.errors.0 }}</td>
<td>{{ form.homework_note }} {{ form.homework_note.errors.0 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<input type="submit" value="保存">
</form>
</div>
</div>
</div>
5.2.3 CBV用法(modelformset写到class里)
class StudyRecordDeialView(View):
def get(self, request, class_record_id):
class_record_obj = models.ClassStudyRecord.objects.get(pk=class_record_id)
all_study_recored = models.StudentStudyRecord.objects.filter(
classstudyrecord=class_record_obj,
)
form_set_obj = modelformset_factory(model=models.StudentStudyRecord,form=StudyRecordDeialModelForm,extra=0)
formset = form_set_obj(queryset=all_study_recored)
return render(request, 'student/study_record_detail.html',{'formset': formset})
def post(self, request, class_record_id):
class_record_obj = models.ClassStudyRecord.objects.get(pk=class_record_id)
all_study_recored = models.StudentStudyRecord.objects.filter(
classstudyrecord=class_record_obj,
)
form_set_obj = modelformset_factory(model=models.StudentStudyRecord, form=StudyRecordDeialModelForm, extra=0)
formset = form_set_obj(request.POST)
if formset.is_valid():
formset.save()
else:
print(formset.errors)
return redirect(reverse('study_decord',args=(class_record_id,)))
浙公网安备 33010602011771号