formset进行批量操作
项目的目录结构
效果如下
添加界面
编辑界面
models中生成数据库表的类
定义了两张表:按钮表与权限表:
from django.db import models class Menu(models.Model): """ 菜单表 """ title = models.CharField(verbose_name='菜单名称', max_length=32) icon = models.CharField(verbose_name='图标', max_length=32) def __str__(self): return self.title class Permission(models.Model): """ 权限表 """ title = models.CharField(verbose_name='标题', max_length=32) url = models.CharField(verbose_name='含正则的URL', max_length=128) name = models.CharField(verbose_name='URL别名', max_length=32, unique=True)
menu = models.ForeignKey(verbose_name='所属菜单', to='Menu', null=True, blank=True, help_text='null表示不是菜单;非null表示是二级菜单') pid = models.ForeignKey(verbose_name='关联的权限', to='Permission', null=True, blank=True, related_name='parents', help_text='对于非菜单权限需要选择一个可以成为菜单的权限,用户做默认展开和选中菜单') def __str__(self): return self.title
forms中用于校验的类
用于校验的类我写在了app01应用下forms.py文件中了~
forms中的校验类的写法跟form表单进行校验时的写法一模一样!
但是需要注意“菜单”与“父权限”是需要用下拉框checkbox多选一的~需要在初始化方法(__init__方法)中定制一下~
注意:编辑比删除要多一个id字段~~在做更新的时候,是按照id作为条件的!
# -*- coding:utf-8 -*- from django import forms from app01 import models # 添加 class MultiPermissionForm(forms.Form): 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') # 编辑 class MultiUpdatePermissionForm(forms.Form): # 编辑需要多加一个id字段~ id = forms.IntegerField( widget=forms.HiddenInput() ) 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')
视图函数的写法——注意添加与编辑的逻辑-注释!
视图函数的写法固定~~注意get请求与post请求不一样~post请求需要参数~
添加与编辑中“唯一性”的判断;编辑用反射setattr方法!
from django.forms import formset_factory from django.shortcuts import render, HttpResponse from app01 import models from app01.forms import MultiPermissionForm,MultiUpdatePermissionForm # 批量添加 def multi_add(request): # 1~利用前面定义的“Form子类”得到一个forset类 formset_class = formset_factory(MultiPermissionForm, extra=2) # get请求~ if request.method == 'GET': # 2~实例化一个formset队形~不加参数 formset = formset_class() return render(request, 'multi_add.html', {'formset': formset}) # 后台用post方法提交了数据的话~ #~~用提交过来的数据作为参数 formset = formset_class(data=request.POST) if formset.is_valid(): flag = True # 往数据库中添加数据~~用这种方法!~需要捕获一个“联合唯一”的异常~ # 重要的小细节:先把cleaned_data传入一个列表~因为errors与cleaned_data是互斥的!! # 检查formset中没有错误信息,则讲用户提交的数据获取到。 post_row_list = formset.cleaned_data # “总共有多少个form”~将错误信息放在对应的位置,需要这个索引i的值! for i in range(0, formset.total_form_count()): row = post_row_list[i] # 用户没有提交数据~~数据库不增加! # 特别注意!不加这个判断的话,用户提交一个空数据数据库还会增加~ if not row: continue # 有一个异常:如果添加的数据一样的话~会有一个“联合唯一”错误的异常 try: # 字典打散的方式~ obj = models.Permission(**row) # # 检查当前对象在数据库是否存在唯一的异常! obj.validate_unique() obj.save() except Exception as e: # 将错误信息放在对应的位置! formset.errors[i].update(e) flag = False if flag: return HttpResponse('提交成功') else: return render(request, 'multi_add.html', {'formset': formset}) return render(request, 'multi_add.html', {'formset': formset}) # 批量编辑 def multi_edit(request): # 1 得到一个formset的类 formset_class = formset_factory(MultiUpdatePermissionForm, extra=0) if request.method == 'GET': # 2 用这个类实例化一个formset对象~注意参数的写法 formset = formset_class( # 将数据库中的数据传给initial~页面可以显示原有的数据! initial=models.Permission.objects.all().values('id', 'title', 'name', 'url', 'menu_id', 'pid_id')) return render(request, 'multi_edit.html', {'formset': formset}) # post方法提交的话~ #~~用提交过来的数据作为参数 formset = formset_class(data=request.POST) if formset.is_valid(): post_row_list = formset.cleaned_data # 检查formset中没有错误信息,则讲用户提交的数据获取到。 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对象去做校验~ permission_object = models.Permission.objects.filter(id=permission_id).first() # *** 通过“反射”来做! for key, value in row.items(): # *** setattr~ 属性的名字是一样的~ setattr(permission_object, key, value) permission_object.validate_unique() permission_object.save() except Exception as e: formset.errors[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})
模板渲染
模板渲染需要注意3点:
1、用form表单~post方式提交
2、form表单中不仅要加csrf_token,还要加formset的一个变量:{{ formset.management_form }}
3、遍历后台传过来的formset对象的时候,注意是两层:第一次取出form对象,第二次用这个form对象生成table中的一行数据。
添加的模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> {# 记得写csrf_token #} {% csrf_token %} {# 用formn_set记得前端页面必须写这个 #} {{ formset.management_form }} <table border="1"> <thead> <tr> <th>标题</th> <th>URL</th> <th>NAME</th> <th>菜单</th> <th>父权限</th> </tr> </thead> <tbody> {# 两层循环~~外层循环的是formset #} {% for form in formset %} <tr> {# 内层循环的是form #} {% for field in form %} <td>{{ field }} <span style="color: red;">{{ field.errors.0 }}</span></td> {% endfor %} </tr> {% endfor %} </tbody> </table> <input type="submit" value="提交"> </form> </body> </html>
编辑的模板
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> {# 前端既要写csrftoken 也要写formset的设置! #} {% csrf_token %} {{ formset.management_form }} <table border="1"> <thead> <tr> <th>标题</th> <th>URL</th> <th>NAME</th> <th>菜单</th> <th>父权限</th> </tr> </thead> <tbody> {# 两层循环-外层遍历formset~第二层遍历form #} {% for form in formset %} <tr> {% for field in form %} {% if forloop.first %} {{ field }} {% else %} <td>{{ field }} <span style="color: red;">{{ field.errors.0 }}</span></td> {% endif %} {% endfor %} </tr> {% endfor %} </tbody> </table> <input type="submit" value="提交"> </form> </body> </html>
~~~