from django.shortcuts import render,HttpResponse from app01 import models from django import forms from django.forms import fields as Ffields from django.forms import widgets as Fwidgets#插件 as 设置别名 class UserInfoModelForm(forms.ModelForm): #自定义字段可以在前端展示 ,不与数据库关联 is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput()) class Meta: model = models.UserInfo#指定到一个类(表)中的字段 fields = '__all__'#所有字段 (列) # fields = ['username','email']#可以指定的列 # exclude = ['username']#排除指定项 #定义标签 labels = { 'username': '用户名', 'email': '邮箱', } help_texts = {#帮助提示信息 'username': '...' } widgets = {#自定义插件 使用 form的插件 'username': Fwidgets.Textarea(attrs={'class': 'c1'}) } error_messages = { '__all__':{ #整体的错误信息 }, 'email': {#指定的错误信息 'required': '邮箱不能为空', 'invalid': '邮箱格式错误..', } } field_classes = {#字段的类 # 'email': Ffields.URLField } # localized_fields=('ctime',) def clean_username(self): old = self.cleaned_data['username'] return old class UserInfoForm(forms.Form): username = Ffields.CharField(max_length=32) email = Ffields.EmailField() user_type = Ffields.ChoiceField( #从数据库取值 choices=models.UserType.objects.values_list('id','caption')#取选 项列表 ) #重写父类 def __init__(self, *args, **kwargs): super(UserInfoForm,self).__init__(*args, **kwargs) self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')#更新数据 def index(request): if request.method == "GET": obj = UserInfoModelForm() return render(request,'index.html',{'obj': obj}) elif request.method == "POST": obj = UserInfoModelForm(request.POST) if obj.is_valid(): # obj.save()#完成所有对应关系的保存 instance = obj.save(False)#手动对应关系进行保存 instance.save()#只保存表的数据 obj.save_m2m()#保存对应表的关系的表 # print(obj.is_valid()) # print(obj.cleaned_data) # print(obj.errors.as_json()) return render(request,'index.html',{'obj': obj}) #用户列表 def user_list(request):# 所有 跨表条件 li = models.UserInfo.objects.all().select_related('user_type') return render(request,'user_list.html',{'li': li}) #用户信息编辑 def user_edit(request, nid): # 获取当前id对象的用户信息 # 显示用户已经存在数据 if request.method == "GET": user_obj = models.UserInfo.objects.filter(id=nid).first() #调用ModelForm 默认值 mf = UserInfoModelForm(instance=user_obj) return render(request,'user_edit.html',{'mf': mf, 'nid': nid}) elif request.method == 'POST': user_obj = models.UserInfo.objects.filter(id=nid).first() mf = UserInfoModelForm(request.POST,instance=user_obj) if mf.is_valid(): mf.save() else: print(mf.errors.as_json()) return render(request,'user_edit.html',{'mf': mf, 'nid': nid})
limit_choice_to
from django.db import models from rbac.models import UserInfo as RbacUserInfo class School(models.Model): ''' 校区表 ''' title = models.CharField(verbose_name='校区名称', max_length=32) def __str__(self): return self.title class Department(models.Model): ''' 课程表 ''' title = models.CharField(verbose_name='部门名称', max_length=32) def __str__(self): return self.title class UserInfo(RbacUserInfo): ''' 用户表 ''' nickname = models.CharField(verbose_name='姓名', max_length=32) gender_choices = ( (1, '男'), (2, '女'),) gender = models.IntegerField(verbose_name='性别', choices=gender_choices) phone = models.CharField(verbose_name='手机号', max_length=32) email = models.EmailField(verbose_name='邮箱', ) depart = models.ForeignKey(verbose_name='所属部门', to='Department', on_delete=models.CASCADE) def __str__(self): return self.nickname class Course(models.Model): ''' 课程表 ''' name = models.CharField(verbose_name='课程名称', max_length=32) def __str__(self): return self.name class ClassTb(models.Model): ''' 班级表 ''' school = models.ForeignKey(verbose_name='所属校区', to='School', on_delete=models.CASCADE) course = models.ForeignKey(verbose_name='课程', to='Course', on_delete=models.CASCADE) semester = models.PositiveIntegerField(verbose_name='班级(期)') price = models.PositiveIntegerField(verbose_name='学费') start_date = models.DateField(verbose_name='开班日期') graduate_date = models.DateField(verbose_name='结业日期', null=True, blank=True) manage_teacher = models.ForeignKey(verbose_name='班主任', to='UserInfo', related_name='classes', on_delete=models.CASCADE, limit_choices_to={'depart__title': '教质部'}) tech_teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo', related_name='tech_classes', blank=True, limit_choices_to={'depart_id__lt': 4}) memo = models.TextField(verbose_name='说明', null=True, blank=True) def __str__(self): return '%s-%d期' % (self.course, self.semester)
保存前修改form的值:
form=MyModelForm(..)
form.instance.depart__id=2
form.save()
from django.forms.fields import DateField, TimeField, DateTimeField class BootstrapForm(forms.ModelForm): ''' 作用是给每个widget添加class:form-control样式 ''' def __init__(self, *args, **kwargs): super(BootstrapForm, self).__init__(*args, **kwargs) for name, field in self.fields.items(): if isinstance(field, DateField): field.widget.input_type = 'date' elif isinstance(field, DateTimeField): field.widget.input_type = 'datetime-local' elif isinstance(field, TimeField): field.widget.input_type = 'time' # input_type = field.widget.input_type # if input_type in ['text', 'email', 'number', 'password', 'url', 'select']: # field.widget.attrs['class'] = 'form-control' if 'password' in name or 'pwd' in name: field.widget.input_type = 'password' field.widget.input_type = 'password' field.widget.attrs['class'] = 'form-control'
一、forms组件--校验类的使用
二、form组件--校验类的参数
三、forms组件校验的局部钩子--自定义校验规则(要看源码理解)
四、forms组件校验的全局钩子--校验form表单两次密码输入是否一致
五、forms组件的渲染标签功能(推荐方式二)
六、formset 批量增加
6.1 初级讲解示例(form基本验证,唯一约束验证)
6.2 写在前面,如何进行重复字段验证?
6.3 自我提高(form基本验证,唯一约束验证,formset重复项验证)(方式一:还行,结构不太美观,展示不错,性能可以)
6.4 自我提高(form基本验证,唯一约束验证,formset重复项验证)(方式二:终极版,结构清晰,展示不错,性能可以)
七、formset 批量修改(带唯一验证)
0、基本用法:
forms.CharField(max_length=30) # 文本
forms.EmailField() # 邮箱
forms.IntegerField() # 验证是否整数
forms.FloatField() # 浮点数验证
forms.ChoiceField(choices=((1, 'a'), ),) # 单选下拉框
forms.MultipleChoiceField(choices=((1, 'a'), (2, 'b')), ) # 多选项列表框
forms.DateField() # 日期类型
forms.DateTimeField() # 日期时间类型
forms.TimeField() # 验证是否为时间格式
forms.RegexField(regex=r'^d+$') # 验证是否符合正则表达式
forms.FileField() # 验证是否为文件
forms.ImageField() # 验证是否图片文件
forms.BooleanField() # checkbox框
forms.DecimalField() # 精确浮点数?
forms.DurationField() # 时长验证
forms.FilePathField(path='c:\') # c:\路径下的文件下拉框
forms.GenericIPAddressField() # ipv4或ipv6地址验证
forms.NullBooleanField() # 单选下拉框,三个选项unknow/yes/no
forms.SlugField() # 验证是否连续的字母、数字、下划线或横线组成。
forms.URLField() # 验证是否为URL格式
forms.UUIDField() # 验证是否为UUID格式
=========
from django import forms
class FormControlForm(forms.Form):
'''
作用是给每个widget添加class:form-control样式
'''
def __init__(self, *args, **kwargs):
super(FormControlForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
input_type = field.widget.input_type
if input_type in ['text', 'email', 'number', 'password', 'url', 'select']:
field.widget.attrs['class'] = 'form-control'
===========
forms框架把每一个字段的显示逻辑分离到一组部件(widget)中。
每一个字段类型都拥有一个默认的部件,我们也可以容易地替换掉默认的部件,或者提供一个自定义的部件。
Field类表现校验逻辑,而widget表现显示逻辑。
模板渲染:{{form.as_ul() }} 或者{{form.as_p()}} 或者{{ form }}
查看出错信息:
>>> f['message'].errors
>>> f['subject'].errors
>>> f['email'].errors
渲染错误信息
{{ field.errors.0 }}
如果要加默认参数:示例
ContactForm(initial={'email':'122121@qq.com'})
class ContactForm(forms.Form):
# required: 是否必填
# max_length: 最大长度
# initial: 渲染时的初始值
# attrs: 添加class样式
# label: 自定义的input框名称
# error_messages: 自定义错误输出语句
subject = forms.CharField(required=False, max_length=20, initial='abc',
label="用户名", error_messages={"required": "不能为空", "invalid": "格式错误"})
email = forms.EmailField(required=False)
message = forms.MultipleChoiceField(choices=((1, 'a'), (2, 'b'), (3, 'c')),
widget=forms.SelectMultiple(attrs={'class': 'ass'}))
# 如果是date类型:
pay_time = forms.DateField(input_formats='%d/%m/%Y', required=False,
widget=forms.DateInput(attrs={'type': 'date', "class": "form-control"},
format='%d/%m/%Y'))
'''
message多重选择框渲染效果:
<select name="message" class="ass" required="" id="id_message" multiple="">
<option value="1">a</option>
<option value="2">b</option>
<option value="3">c</option>
</select>
'''
# form = ContactForm({'subject': '321', 'email': '2332@qq.com', 'message': [1, 3]})
# form.is_valid() 等于True,通过校验
==============================
# 多选框的渲染,用内置方法获取数据库数据:方式一
from django import forms
class FInfo(forms.Form): authors = forms.ModelMultipleChoiceField(queryset=models.Menu.objects.all())
#如果用这种方式渲染,直接写all(), 渲染的多选框的value是Menu表的主键,值是Menu的__str__()方法返回的值
#如果用这种方式渲染,forms里的外键一定要写对象名,而且初始化值的时候也要用对象,因为提交回来的是对象,必须用对象名来接收。
==============================
class MyForm(Form): user = fields.ChoiceField( # choices=((1, '上海'), (2, '北京'),), initial=2, widget=widgets.Select ) # 单选框的渲染,自己写init函数获取数据:方式二 def __init__(self, *args, **kwargs): super(MyForm,self).__init__(*args, **kwargs) # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),) # 或 self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')
#如果用这种方式渲染,forms里的外键要写对象ID,而且初始化值的时候也要用对象ID,因为提交回来的是对象ID,必须用对象ID来接收。
=========================================
def add(request):
if request.method == 'POST':
print(request.POST)
pf = PaymentForm()
pf.fields['pay_time'].disabled = True
return render(request, "web-add-edit.html", {"form": pf})
==============================
# 密码输入框的处理:解决用自带的PasswordInput“密码输入验证失败,返回表单无密码”问题
class UserForm(forms.Form):
name = forms.CharField(label='用户名', max_length=32, widget=forms.TextInput(attrs={"class": "form-control"}))
email = forms.EmailField(label='邮箱', max_length=32, widget=forms.EmailInput(attrs={"class": "form-control"}))
password = forms.CharField(label='密码', max_length=64, widget=forms.TextInput(attrs={"class": "form-control"}))
re_password = forms.CharField(label='重复密码', max_length=64, widget=forms.TextInput(attrs={"class": "form-control"}))
def __init__(self, *args, **kwargs):
super(UserForm, self).__init__(*args, **kwargs)
self.fields['password'].widget.input_type = 'password'
self.fields['re_password'].widget.input_type = 'password'
======================
一、forms组件--校验类的使用
1、校验字段功能(最核心功能)
示例1:
在服务端进行格式校验
-------这个写在一个新模块里-------------
from django import forms
class UserForm(forms.Form): #新建一个校验类
name = forms.CharField(min_length) #必须为字符串,最小4位
email = forms.EmailField() #邮箱校验
--------------------------------------------------
-------视图函数里的用户注册方法-------
def register(request):
# 字典的键必须跟校验类里的字段一样
form=UserForm({"name":"yuan","email":"123"})
# 开始校验,所有校验类里的值校验通过返回True,否则返回False
# 字典里有多少个无所谓,只要校验类里的字段通过就是True
bool = form.is_valid()
# cleaned_data 这里面放所有校验通过的键值,注意:必须在调用is_valid()方法之后才有cleaned_data
form.cleaned_data
# errors 这里面放所有未通过的键值 是仿字典类型
# 例如name没通过,要取errors里的错误信息:form.errors["name"][0]
form.errors
return HttpResponse(request, "ok")
------------------------------------------------------
完整示例:
def register(request):
#校验类字段要跟前端form表单一样
form=UserForm(request.POST)
if form.is_valid():
clean = form.cleaned_data
else:
clean = form.cleaned_data
errors = form.errors
-------------------------------------------------
完整示例一:注册页面。ajax提交post数据,返回json,局部刷新
def register(request):
if request.method=="POST":
form = UserForm(request.POST)
response = {"user":None,"msg":None}
if form.is_valid():
user = form.cleaned_data.get("user")
pwd = form.cleaned_data.get("pwd")
email = form.cleaned_data.get("email")
# 文件对象或者图片对象都按这样处理
avatar_obj = request.FILES.get("avatar")
extra_fields = {}
if avatar_obj:
extra_fields["avatar"] = avatar_obj
UserInfo.objects.create_user(username=user,password=pwd,email=email,**extra_fields)
response["user"] = user
response["msg"] = "ok"
else:
response["msg"] = form.errors
return JsonResponse(response)
form = UserForm()
return render(request,"register.html",{"form":form})
-----------------------------------
//提交注册 $("#reg-btn").click(function () { //formdata是上传文件专用 var formdata = new FormData(); var request_data = $("#form").serializeArray(); $.each(request_data,function (index, data) { formdata.append(data.name,data.value) }); formdata.append("avatar",$("#avatar")[0].files[0]); $.ajax({ url:'', type:'post', contentType:false, processData:false, data:formdata, success:function (data) { if(data.user){ //校验成功 location.href="/login/"; }else{ //校验失败 //先清空 $("span.error").text(""); $("span.error").parent().removeClass("has-error"); //再加上错误信息 $.each(data.msg,function (name, error_list) { if(name=="__all__"){ $("#id_re_pwd").next().text(error_list[0]); $("#id_re_pwd").parent().addClass("has-error"); } $("#id_"+name).next().text(error_list[0]); $("#id_"+name).parent().addClass("has-error"); }); } } }); });
----------
完整示例二:表单提交post请求,全局刷新,修改和新增共享同一个模板
def add(request):
'''
新增客户
:param request:
:return:
'''
cf = None
if request.method == 'POST':
cf = CustomerForm(request.POST)
if cf.is_valid():
# 通过校验:写入数据库,并重定向
clean = cf.cleaned_data
models.Customer.objects.create(name=clean.get('name'),
age=clean.get('age'),
email=clean.get('email'),
company=clean.get('company'))
return redirect(reverse('customer_list'))
if request.method == 'GET':
cf = CustomerForm(initial={})
# 1.GET请求:返回空的form渲染页面
# 2.未通过校验:form里包括用户上传的表单数据,和表单数据的错误信息
return render(request, "web-customer-add.html", {'form': cf})
-----------------
def edit(request, id):
'''
编辑客户
:param request:
:param id:
:return:
'''
obj_query_set = models.Customer.objects.filter(id=id)
obj = obj_query_set.first()
# 如果找不到该对象,返回404页面
if not obj:
return render(request, "web-404.html")
# 能找到该对象,判断请求方式
cf = None
if request.method == 'POST':
cf = CustomerForm(request.POST)
if cf.is_valid():
# 通过校验:更新记录,并重定向
clean = cf.cleaned_data
obj_query_set.update(name=clean.get('name'),
age=clean.get('age'),
email=clean.get('email'),
company=clean.get('company'))
return redirect(reverse('customer_list'))
if request.method == 'GET':
obj_dict ={'pay': obj.pay, 'pay_time': obj.pay_time, 'customer': obj.customer.id}
cf = CustomerForm(initial = obj_dict)
# 1.GET请求:用查询到的obj字典渲染页面
# 2.未通过校验:form里包括用户上传的表单数据,和表单数据的错误信息
return render(request, "web-customer-add.html", {'form': cf})
--------------------------------------------------
<form method="post"> {% csrf_token %} {% for field in form %} <div class="form-group"> <label for="{{ field.auto_id }}">{{ field.label }}</label> {{ field }} <span class="err">{{ field.errors.0 }}</span> </div> {% endfor %} <button type="submit" class="btn btn-primary">Submit</button> </form>
-------------------------------------------------
总结:
1.注意前端form表单内的需要校验的属性名称,要跟后端校验类的字段一致
2. 掌握is_valid() 、 cleaned_data、 errors各自含义
二、form组件--校验类的参数
如果想改渲染的标签类型为password:widgets.PasswordInput
如果想改错误提示信息:error_messages参数。提示信息支持中文:settings.py文件里-》LANGUAGE_CODE='zh-hans'
如果需要给渲染的标签加样式类, 就要加属性键值对,什么都行:widget=widgets.TextInput(attrs={"class":"form-control"})
from django.forms import widgets
class UserForm(forms.Form):
name = forms.CharField(min_length=4, label="用户名", error_messages={"required":"不能为空","invalid":"格式错误"}) #错误类型不能改其他名字
pwd = forms.CharField(min_length=4, label="密码",widget=widgets.PasswordInput(attrs={"class":"form-control"}))
r_pwd = forms.CharField(min_length=4, label="确认密码")
email = forms.EmailField(label="邮箱",widget=widgets.TextInput(attrs={"class":"form-control"})) # 给标签加属性
tel = forms.CharField(label="电话")
三、forms组件校验的局部钩子--自定义校验规则(要看源码理解)
第二层校验:clean_xx方法,校验通过返回该值,否则抛ValidationError异常
------------------------------------------------------------------------------------
from django.core.exceptions import ValidationError
class UserForm(forms.Form):
name = forms.CharField(min_length=4, label="用户名")
pwd = forms.CharField(min_length=4, label="密码")
r_pwd = forms.CharField(min_length=4, label="确认密码")
email = forms.EmailField(label="邮箱")
tel = forms.CharField(label="电话")
#该功能验证用户名是否已被注册
def clean_name(self):
name = self.cleaned_data.get("name")
ret = UserInfo.objects.filter(name=name)
if not ret:
return name
else:
#只能抛这个错误类型
raise ValidationError("该用户已注册")
----------------------------------------------------------------------------------------
四、forms组件校验的全局钩子--校验form表单两次密码输入是否一致
在校验类里,覆盖父类clean方法
获取全局钩子错误(在模板里渲染):form.errors.get("__all__")[0]
def clean():
# 先从cleaned_data中取要联合校验的字段
pwd=self.cleaned_data.get('pwd')
r_pwd=self.cleaned_data.get('r_pwd')
if pwd and r_pwd:
# pwd跟r_pwd之前的校验已通过,才验证全局钩子
if pwd==r_pwd:
# 这里面包括pwd和r_pwd
return self.cleaned_data
else:
raise ValidationError('两次输入密码不一致')
else:
# 这里面包括pwd和r_pwd两者之一,或者都不包括
return self.cleaned_data
五、forms组件的渲染标签功能(推荐方式二)
渲染方式三种、错误信息的渲染方式
1、渲染方式一
先写后端校验类,再在前端渲染,charFiled渲染成input标签:
# 校验类
class UserForm(forms.Form):
name = forms.CharField(min_length=4)
pwd = forms.CharField(min_length=4)
r_pwd = forms.CharField(min_length=4)
email = forms.EmailField()
tel = forms.CharField()
# 视图函数:用户注册
def reg(request):
form = UserForm(request.POST)
return render("reg_html",{"form":form})
为了方便,Django可以在前端按照后端写的校验类进行渲染
<form>
{% csrf_token %}
用户名:{{ form.name }}
密码:{{ form.pwd }}
确认密码:{{ form.r_pwd }}
邮箱:{{ form.email }}
电话:{{ form.tel }}
<input type="submit">
</form>
2、渲染方式二:如果字段很多
class UserForm(forms.Form):
name = forms.CharField(min_length=4, label="用户名")
pwd = forms.CharField(min_length=4, label="密码")
r_pwd = forms.CharField(min_length=4, label="确认密码")
email = forms.EmailField(label="邮箱")
tel = forms.CharField(label="电话")
def reg(request):
form = UserForm(request.POST)
return render("reg_html",{"form":form})
-------------------------------
<form method="post">
{% csrf_token %}
{% for field in form %}
<div>
<label for=" {{ field.auto_id }}">{{ field.label }}</label> {{ field }}
</div>
{% endfor %}
<input type="submit">
</form>
如果method不写会以get方式提交参数。写了post才以post方式提交
-------------------------------
3、渲染方式三,自己搞着玩可以
<form>
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
4、渲染错误信息
-------视图函数------
def register(request):
if request.method=="POST":
form=UserForm(request.POST)
if form.is_valid():
clean = form.cleaned_data
else:
clean = form.cleaned_data
errors = form.errors
# NOTES:这里返回的页面可以保存用户上次已经填过的数据,还包括错误信息
return render("reg_html",{"form":form})
#如果是get请求,渲染空form页面
form=UserForm()
return render(request,"reg_html",{"form":form})
------------------------------
<form>
{% csrf_token %}
{% for field in form %}
<div>
<label for="">{{ field.label }}</label>
{{ field }}
<span>{{ field.errors.0 }}</span>
</div>
{% endfor %}
<input type="submit">
</form>
--------------------------------
六、formset 批量增加
6.1 初级讲解示例(form基本验证,唯一约束验证)
Form或ModelForm来做一个表单的验证,对应数据库表中的一行数据,而formset是做多个表单验证的组件
应用场景:批量操作
用法:
==================视图函数================
from django import forms
from rbac import models
from django.forms import formset_factory
from django.shortcuts import render,HttpResponse
class MultiPermissionForm(forms.Form):
'''单个表单验证代码
标题,URL,NAME,菜单,父权限
'''
pass
def multi_permission_add(request):
# extra=2表示在前端生成2个表单,
# 如果一个表单都不填可以提交,不报错,数据库不增加
formset_class = formset_factory(MultiPermissionForm, extra=2)
if request.method == 'GET':
formset=formset_class()
return render(request, 'multi_add.html', {'formset': formset})
formset = formset_class(data=request.POST)
if formset.is_valid():
'''
cleaned_data必须放上面,因为如果errors方法执行,
会导致cleaned_data中没有数据,必须先保存cleaned_data
原因:能进到这里,数据就是验证通过的。cleaned_data每次取之前要
检查formset里是否有错误信息,没有才取的出来,如果cleaned_data
放里面,会检查很多次,直到写入错误信息之后,就检查出错误,取不出来了
'''
batch_list = [] # 待批量入库的对象
flag = True
post_row_list = formset.cleaned_data
for i in range(0, formset.total_form_count()):
row = post_row_list[i]
if not row:
continue
try:
# models.Permission.objects.create(**row)不要用这种方式,不能进行唯一性检查
obj = models.Permission(**row)
obj.validate_unique() #检查当前对象在数据库是否存在唯一
batch_list.append(obj)
except Exception as e:
formset.erros[i].update(e)
flag = False
if flag:
models.Permission.objects.bulk_create(batch_list, batch_size=50) #批量入库每次50条
return HttpResponse('当次提交通过验证')
else:
return render(request,'multi_add.html',{'formset':formset})
return render(request,'multi_add.html',{'formset':formset})
================模板渲染===================
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
<table>
<thead>
<tr>
<th>标题</th>
<th>URL</th>
<th>NAME</th>
<th>菜单</th>
<th>父权限</th>
</tr>
</thead>
<tbody>
{% for form in formset %}
<tr>
{% for field in form %}
<td>{{ field }} <span>{{ field.errors.0 }}</span></td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<input type="submit">
</form>
6.2 写在前面,如何进行重复字段验证?
6.3 自我提高(form基本验证,唯一约束验证,formset重复项验证)(方式一:还行,结构不太美观,展示不错,性能可以)
特点:唯一约束在视图函数中校验,
models:
from django.db import models class PersonInfo(models.Model): name = models.CharField(verbose_name='姓名', max_length=32, unique=True, ) email = models.EmailField(verbose_name='邮箱')
views:
from django.shortcuts import render, HttpResponse from django import forms from django.forms import Form from django.forms import BaseFormSet from django.forms import formset_factory from app01 import models #####################Form######################################## class AddForm(Form): name = forms.CharField() email = forms.EmailField() def __eq__(self, other): ''' 重写该方法为了进行index找索引时按name进行比较查找 ''' if self.cleaned_data['name'] == other.cleaned_data['name']: return True class NameDistinctFormSet(BaseFormSet): def clean(self): if any(self.errors): return temp_names = [] # 进行比较使用的临时列表 group_dict = {} # 要返回的结果{'重复项',[行数1,行数2,行数3],...} offset = 1 # 索引偏移量 index = 0 # 索引从0开始 for form in self.forms: if not form.cleaned_data: continue name = form.cleaned_data['name'] if name in temp_names: if name in group_dict: # 不是第一次了 group_dict[name].append(index + offset) else: # 第一次找到相同的,需要重写Form类的__eq__方法 group_dict[name] = [self.forms.index(form) + offset, ] group_dict[name].append(index + offset) temp_names.append(name) index += 1 if group_dict: # 全局错误通过formset.non_form_errors()取 raise forms.ValidationError("重复项及行号> " + group_dict.__str__()) #############################View########################################## def index(request): # extra=5表示产生6个空白Form, min_num=6表示数据集减去标记为删除后的最小表单数, # 此处表示每次必须提交6个有效表单 # 此处如果没有写,表示入库的是通过验证且不为空的行 add_formset_class = formset_factory(AddForm, formset=NameDistinctFormSet, extra=5, min_num=6, validate_min=True) add_formset = None # 批量添加 if request.method == 'POST': add_formset = add_formset_class(data=request.POST) if add_formset.is_valid(): batch_list = [] # 待写入数据库的数据 flag = True post_row_list = add_formset.cleaned_data for i in range(0, add_formset.total_form_count()): row = post_row_list[i] if not row: # 为空的行就算通过验证也不会入库 continue try: obj = models.PersonInfo(**row) obj.validate_unique() # 检查当前对象在数据库是否存在唯一 batch_list.append(obj) except Exception as e: add_formset.errors[i].update(e) flag = False if flag: models.PersonInfo.objects.bulk_create(batch_list, batch_size=20) return HttpResponse('成功入库>>%d条' % len(batch_list)) if request.method == 'GET': add_formset = add_formset_class() return render(request, 'index.html', {'add_formset': add_formset})
templates:
<form method="post"> {% csrf_token %} {{ add_formset.management_form }} <table class="table"> <thead> <tr> <th class="text-center">姓名(不能重复)</th> <th class="text-center">邮箱</th> </tr> </thead> <tbody> {% for form in add_formset %} <tr> {% for field in form %} <td class="text-center"> {{ field }}<br> <span style="color: red;">{{ field.errors.0 }}</span> </td> {% endfor %} </tr> {% endfor %} </tbody> </table> <div> <span style="color: red;">{{ add_formset.non_form_errors.0 }}</span> <input type="submit"> </div> </form>
6.4 自我提高(form基本验证,唯一约束验证,formset重复项验证)(方式二:终极版,结构清晰,展示不错,性能可以)
跟6.3相比就只是BaseFormSet和视图函数变了。
from django.shortcuts import render, HttpResponse from django import forms from django.forms import Form from django.forms import BaseFormSet from django.forms import formset_factory from app01 import models #####################Form######################################## class AddForm(Form): name = forms.CharField() email = forms.EmailField() def __eq__(self, other): ''' 重写该方法为了进行index找索引时按name进行比较查找 ''' if self.cleaned_data['name'] == other.cleaned_data['name']: return True class NameDistinctFormSet(BaseFormSet): def clean(self): if any(self.errors): return temp_names = [] # 进行比较使用的临时列表 group_dict = {} # 要返回的结果{'重复项',[行数1,行数2,行数3],...} offset = 1 # 索引偏移量 index = 0 # 索引从0开始 for form in self.forms: if not form.cleaned_data: continue name = form.cleaned_data['name'] if name in temp_names: if name in group_dict: # 不是第一次了 group_dict[name].append(index + offset) else: # 第一次找到相同的,需要重写Form类的__eq__方法 group_dict[name] = [self.forms.index(form) + offset, ] group_dict[name].append(index + offset) temp_names.append(name) index += 1 if group_dict: # 全局错误通过formset.non_form_errors()取 raise forms.ValidationError("重复项及行号> " + group_dict.__str__()) post_row_list = self.cleaned_data for i in range(0, self.total_form_count()): row = post_row_list[i] if not row: # 为空的行就算通过验证也不会入库 continue try: obj = models.PersonInfo(**row) obj.validate_unique() # 检查当前对象在数据库是否存在唯一 except Exception as e: self.errors[i].update(e) #############################View########################################## def index(request): # extra=5表示产生6个空白Form, min_num=6表示数据集减去标记为删除后的最小表单数, # 此处表示每次必须提交6个有效表单 # 此处如果没有写,表示入库的是通过验证且不为空的行 add_formset_class = formset_factory(AddForm, formset=NameDistinctFormSet, extra=4) add_formset = None # 批量添加 if request.method == 'POST': add_formset = add_formset_class(data=request.POST) # is_valid为真:通过了重复验证和数据库唯一约束验证 if add_formset.is_valid(): batch_list = [] # 待写入数据库的数据 for data in add_formset.cleaned_data: if not data: # 为空的不入库 continue obj = models.PersonInfo(**data) batch_list.append(obj) try: models.PersonInfo.objects.bulk_create(batch_list, batch_size=20) return HttpResponse('成功入库>>%d条' % len(batch_list)) except Exception as e: return HttpResponse('入库错误(绝对不是字段唯一性导致的):%s' % e.__str__()) if request.method == 'GET': add_formset = add_formset_class() return render(request, 'index.html', {'add_formset': add_formset})
七、formset 批量修改(带唯一验证)
======================视图函数=======================
class MultiUpdatePermissionForm(forms.Form):
'''修改要增加隐藏的ID字段
隐藏ID,标题,URL,NAME,菜单,父权限
'''
id = forms.IntegerField(widget=forms.HiddenInput())
pass
def multi_permission_edit(request):
# extra=0表示不额外增加列,与数据库一致
formset_class = formset_factory(MultiPermissionForm, extra=0)
if request.method == 'GET':
# formset = formset_class(initial=[{'id': 1, 'title': 'a', },
# {'id': 2, 'title': 'b', }])
formset = formset_class(
initial=models.Permission.objects.all().values('id', 'title', 'name', 'url', 'menu_id', 'pid_id')
)
return render(request, 'multi_edit.html', {'formset': formset})
formset = formset_class(data=request.POST)
if formset.is_valid():
post_row_list = formset.cleaned_data
flag = True
for i in range(0, formset.total_form_count()):
row = post_row_list[i]
if not row:
continue
permission_id = row.pop('id')
try:
permission_obj = models.Permission.objects.filter(id=permission_id).first
for key,value in row.items():
setattr(permission_obj,key,value)
permission_obj.validate_unique()
permission_obj.save()
except Exception as e:
formset.erros[i].update(e)
flag = False
if flag:
return HttpResponse('当次提交通过验证')
else:
return render(request, 'multi_edit.html', {'formset': formset})
return render(request, 'multi_edit.html', {'formset': formset})
==================模板渲染======================
<form method="post">
{% csrf_token %}
{{ formset.management_form }}
<table>
<thead>
<tr>
<th>标题</th>
<th>URL</th>
<th>NAME</th>
<th>菜单</th>
<th>父权限</th>
</tr>
</thead>
<tbody>
{% for form in formset %}
<tr>
{% for field in form %}
{% if forloop.first %}
{{ field }}
{% else %}
<td>{{ field }} <span>{{ field.errors.0 }}</span></td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<input type="submit">
</form>
sdf