示例效果:
form组件或ModelForm组件用于做一个表单验证,formset是用于做多个表单验证的组件。
formset/settings.py
LANGUAGE_CODE = 'zh-hans' # 把报错信息改为中文
formset/urls.py
from django.contrib import admin from django.urls import path, re_path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), re_path(r'^multi_add/$', views.multi_add, name="multi_add"), re_path(r'^multi_edit/$', views.multi_edit, name="multi_edit"), re_path(r'', views.multi_add), ]
app01/models.py
from django.db import models # Create your models here. class Menu(models.Model): """一级菜单表""" title = models.CharField( verbose_name="一级菜单名称", max_length=32, ) icon = models.CharField( verbose_name="图标", max_length=64, # null=True, # 表示数据库中可以为空 # blank=True, # admin后台管理中可以输入为空 ) 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( to="Menu", on_delete=models.CASCADE, verbose_name="所属二级菜单", null=True, # 并不是所有的url都可以做二级菜单,所以这里需要设置null=True blank=True, help_text="null表示不是二级菜单,非null表示是二级菜单", ) pid = models.ForeignKey( verbose_name="关联的权限", to="Permission", null=True, # 如果是二级菜单,那么此字段为空,非二级菜单此字段不为空 blank=True, on_delete=models.CASCADE, help_text="对于非菜单权限需要选择一个可以成为菜单的权限,用于做默认展开和选中菜单", related_name="parents", ) def __str__(self): return self.title
app01/views.py
from django.shortcuts import render, HttpResponse from django import forms from app01 import models from django.forms import formset_factory # Create your views here. 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") def multi_add(request): """ 批量添加 :param request: :return: """ # extra代表实例化了2个MultiPermissionForm 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) # [form(字段, 错误), form(字段, 错误), form(字段, 错误)] # 如果表单数据都为空,那么不会做任何校验 if formset.is_valid(): # print(formset.cleaned_data) # [{}, {}] flag = True # 将unique的错误添加到指定字段 post_row_list = formset.cleaned_data # 检查formset中有没有错误信息,将用户提交的数据获取到 """ formset.cleaned_data = [ {'title': '123', 'url': '123', 'name': '123', 'menu_id': '1', 'pid_id': ''}, {'title': '321', 'url': '321', 'name': '321', 'menu_id': '2', 'pid_id': ''} ] """ 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}) # for row in formset.cleaned_data: # # [ {}, {}, {}] # # models.Permission.objects.create(**row) # 这种方式不推荐,如果是unique错误此种方式捕捉不到 # try: # obj = models.Permission(**row) # obj.validate_unique() # 检查当前对象在数据库是否存在唯一的异常 # obj.save() # except Exception as e: # pass return render(request, "multi_add.html", {"formset": formset}) def multi_edit(request): """ 批量编辑 :param request: :return: """ formset_class = formset_factory(MultiUpdatePermissionForm, extra=0) if request.method == "GET": # formset = formset_class( # initial=[ # {"id": 1, "title": "x1", "url": "xxx"}, # {"id": 2, "title": "x2", "url": "ooo"} # ] # ) formset = formset_class( initial=models.Permission.objects.all().values("id", "name", "title", "url", "menu_id", "pid_id") ) return render(request, "multi_edit.html", {"formset": formset}) formset = formset_class(data=request.POST) if formset.is_valid(): 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 permission_id = row.pop("id") # 拿到表单的id # models.Permission.objects.filter(id=permission_id).update(**row) # 和上面的一样,unique错误无法捕捉 try: permission_object = models.Permission.objects.filter(id=permission_id).first() for key, value in row.items(): setattr(permission_object, key, value) permission_object.validate_unique() permission_object.save() except Exception as e: formset.errors[i].update(e) if flag: return HttpResponse("提交成功") else: return render(request, "multi_edit.html", {"formset": formset}) return render(request, "multi_edit.html", {"formset": formset})
templates/multi_add.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>批量添加</h1> <form method="post"> {% 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> {% for form in formset %} <tr> {% for field in form %} <td>{{ field }} <span style="color: red">{{ field.errors.0 }}</span></td> {% endfor %} </tr> {% endfor %} </tbody> </table> <p><input type="submit" value="提交"></p> <a href="{% url 'multi_edit' %}">批量编辑</a> </form> </body> </html>
templates/multi_edit.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>批量编辑</h1> <form method="post"> {% 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> {% 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> <p><input type="submit" value="提交"></p> <a href="{% url 'multi_add' %}">批量添加</a> </form> </body> </html>