Django组件--forms组件(注册用)

 
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})
ModelForm

 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)
limit_choice_to

 保存前修改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

原文地址:https://www.cnblogs.com/staff/p/10735347.html