模型表单ModelForm
一、基本用法
class BookForm(forms.ModelForm): class Meta: model = models.Book fields = "__all__" labels = { "title": "书名", "price": "价格" } widgets = { "password": forms.widgets.PasswordInput(attrs={"class": "c1"}), "password": forms.PasswordInput(attrs={"class": "c1"}), }
二、class Meta下常用参数
class XXXModelForm(ModelForm) a. class Meta: model, # 对应Model的 fields=None, # 字段,如果是__all__,就是表示列出所有的字段 exclude=None, # 排除字段 labels=None, # 提示信息 help_texts=None, # 帮助提示信息 widgets=None, # 自定义插件 error_messages=None, # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS) field_classes=None # 自定义字段类 (也可以自定义字段) localized_fields=('birth_date',) # 本地化,如:根据不同时区显示数据 如: 数据库中 2016-12-27 04:10:57 setting中的配置 TIME_ZONE = 'Asia/Shanghai' USE_TZ = True 则显示: 2016-12-27 12:10:57 b. 验证执行过程 is_valid -> full_clean -> 钩子 -> 整体错误 c. 字典字段验证 def clean_字段名(self): # 可以抛出异常 # from django.core.exceptions import ValidationError return "新值" d. 用于验证 model_form_obj = XXOOModelForm() model_form_obj.is_valid() model_form_obj.errors.as_json() model_form_obj.clean() model_form_obj.cleaned_data e. 用于创建 model_form_obj = XXOOModelForm(request.POST) #### 页面显示,并提交 ##### # 默认保存多对多 obj = form.save(commit=True) # 不做任何操作,内部定义 save_m2m(用于保存多对多) obj = form.save(commit=False) obj.save() # 保存单表信息 obj.save_m2m() # 保存关联多对多信息 f. 用于更新和初始化 obj = model.tb.objects.get(id=1) model_form_obj = XXOOModelForm(request.POST,instance=obj) ... PS: 单纯初始化 model_form_obj = XXOOModelForm(initial={...})
三、ModelForm的验证和save()方法
- 与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。
- 可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。
- 如果不重写具体字段并设置validators属性的化,ModelForm是按照模型中字段的validators来校验的。
- 每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。
- ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。
- 如果没有提供,save() 将创建模型的一个新实例:
from myapp.models import Book from myapp.forms import BookForm # 根据POST数据创建一个新的form对象 form_obj = BookForm(data=request.POST) # 创建书籍对象 new_ book = form_obj.save() # 基于一个书籍对象创建form对象 edit_obj = Book.objects.get(id=1) # 使用POST提交的数据更新书籍对象 form_obj = BookForm(data=request.POST, instance=edit_obj) form_obj.save() #添加书籍时给出版社设置默认值 initial form_obj = BookForm(initial={'publish_id': 1}) #添加数据时,pid不是从表单获取的而是默认就知道值时,在保存之前给该对象的pid赋值,一起保存。 if form_obj.is_valid(): form_obj.instance.pid = 123 form_obj.save()
from django import forms from django.utils.safestring import mark_safe from django.core.exceptions import ValidationError from rbac import models from django.utils.translation import ugettext_lazy ICON_LIST = [ ['fa-hand-scissors-o', '<i aria-hidden="true" class="fa fa-hand-scissors-o"></i>'], ['fa-hand-spock-o', '<i aria-hidden="true" class="fa fa-hand-spock-o"></i>'], ] for item in ICON_LIST: item[1] = mark_safe(item[1]) class BootStrapModelForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(BootStrapModelForm, self).__init__(*args, **kwargs) # 统一给ModelForm生成字段添加样式 for name, field in self.fields.items(): field.widget.attrs['class'] = 'form-control' """ 基本用法: 首先从django.forms导入ModelForm; 编写一个自己的类,继承ModelForm; 在新类里,设置元类Meta; 在Meta中,设置model属性为你要关联的ORM模型,这里是Menu; 在Meta中,设置fields属性为你要在表单中使用的字段列表;列表里的值,应该是ORM模型model中的字段名。 """ class UserModelForm(BootStrapModelForm): confirm_password = forms.CharField(label='确认密码') # class Meta: model = models.UserInfo fields = ['name', 'email', 'password', 'confirm_password', 'icon'] # fields = '__all__' #表示将映射的模型中的全部字段都添加到表单类中来 exclude = ['pid'] #表示将model中,除了exclude属性中列出的字段之外的所有字段,添加到表单类中作为表单字段。 widgets = { 'name': forms.TextInput(attrs={'class': 'form-control'}), 'icon': forms.RadioSelect( choices=ICON_LIST, attrs={'class': 'clearfix'} ) } labels = { 'name': ugettext_lazy('Writer'), } help_texts = { 'name': ugettext_lazy('Some useful help text.'), } error_messages = { 'name': { 'max_length': ugettext_lazy("This writer's name is too long."), }, } def clean_confirm_password(self): """ 检测密码是否一致 :return: """ password = self.cleaned_data['password'] confirm_password = self.cleaned_data['confirm_password'] if password != confirm_password: raise ValidationError('两次密码输入不一致') return confirm_password # 可以在实例化一个表单时通过指定initial参数来提供表单中数据的初始值。
def menu_list(request): """ 菜单和权限列表 :param request: :return: """ menus = models.Menu.objects.all() menu_id = request.GET.get('mid') # 用户选择的一级菜单 second_menu_id = request.GET.get('sid') # 用户选择的二级菜单 menu_exists = models.Menu.objects.filter(id=menu_id).exists() if not menu_exists: menu_id = None if menu_id: second_menus = models.Permission.objects.filter(menu_id=menu_id) else: second_menus = [] second_menu_exists = models.Permission.objects.filter(id=second_menu_id).exists() if not second_menu_exists: second_menu_id = None if second_menu_id: permissions = models.Permission.objects.filter(pid_id=second_menu_id) else: permissions = [] return render( request, 'rbac/menu_list.html', { 'menus': menus, 'second_menus': second_menus, 'permissions': permissions, 'menu_id': menu_id, 'second_menu_id': second_menu_id, } ) def menu_add(request): """ 添加一级菜单 :param request: :return: """ if request.method == 'GET': form = MenuModelForm() return render(request, 'rbac/change.html', {'form': form}) form = MenuModelForm(data=request.POST) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def menu_edit(request, pk): """ :param request: :param pk: :return: """ obj = models.Menu.objects.filter(id=pk).first() if not obj: return HttpResponse('菜单不存在') if request.method == 'GET': form = MenuModelForm(instance=obj) return render(request, 'rbac/change.html', {'form': form}) form = MenuModelForm(instance=obj, data=request.POST) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def menu_del(request, pk): """ :param request: :param pk: :return: """ url = memory_reverse(request, 'rbac:menu_list') if request.method == 'GET': return render(request, 'rbac/delete.html', {'cancel': url}) models.Menu.objects.filter(id=pk).delete() return redirect(url) #######initial def second_menu_add(request, menu_id): """ 添加二级菜单 :param request: :param menu_id: 已选择的一级菜单ID(用于设置默认值) :return: """ menu_object = models.Menu.objects.filter(id=menu_id).first() if request.method == 'GET': form = SecondMenuModelForm(initial={'menu': menu_object}) #######initial return render(request, 'rbac/change.html', {'form': form}) form = SecondMenuModelForm(data=request.POST) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) #######instance def permission_add(request, second_menu_id): """ 添加权限 :param request: :param second_menu_id: :return: """ if request.method == 'GET': form = PermissionModelForm() return render(request, 'rbac/change.html', {'form': form}) form = PermissionModelForm(data=request.POST) if form.is_valid(): second_menu_object = models.Permission.objects.filter(id=second_menu_id).first() if not second_menu_object: return HttpResponse('二级菜单不存在,请重新选择!') form.instance.pid = second_menu_object #################instance form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form})
#models.py from django.db import models TITLE_CHOICES = ( ('MR', 'Mr.'), ('MRS', 'Mrs.'), ('MS', 'Ms.'), ) class Author(models.Model): name = models.CharField(max_length=100) title = models.CharField(max_length=3, choices=TITLE_CHOICES) birth_date = models.DateField(blank=True, null=True) def __str__(self): # __unicode__ on Python 2 return self.name class Book(models.Model): name = models.CharField(max_length=100) authors = models.ManyToManyField(Author) #myforms.py from django import forms class AuthorForm(forms.ModelForm): class Meta: model = models.Author fields = ['name', 'title', 'birth_date'] class BookForm(forms.ModelForm): class Meta: model = models.Book fields = ['name', 'authors'] #上面的ModelForm子类基本等同于下面的定义方式(唯一的区别是save()方法): from django import forms class AuthorForm(forms.Form): name = forms.CharField(max_length=100) title = forms.CharField( max_length=3, widget=forms.Select(choices=TITLE_CHOICES), ) birth_date = forms.DateField(required=False) class BookForm(forms.Form): name = forms.CharField(max_length=100) authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
四、字段类型
生成的Form类中将具有和指定的模型字段对应的表单字段,顺序为fields属性列表中指定的顺序。
每个模型字段有一个对应的默认表单字段。比如,模型中的CharField表现成表单中的CharField。模型中的ManyToManyField字段会表现成MultipleChoiceField字段。下面是完整的映射列表:
-
ForeignKey被映射成为表单类的django.forms.ModelChoiceField,它的选项是一个模型的QuerySet,也就是可以选择的对象的列表,但是只能选择一个。
-
ManyToManyField被映射成为表单类的django.forms.ModelMultipleChoiceField,它的选项也是一个模型的QuerySet,也就是可以选择的对象的列表,但是可以同时选择多个,多对多嘛。
- 如果模型字段设置blank=True,那么表单字段的required设置为False。 否则,required=True。
- 表单字段的label属性根据模型字段的verbose_name属性设置,并将第一个字母大写。
- 如果模型的某个字段设置了editable=False属性,那么它表单类中将不会出现该字段。道理很简单,都不能编辑了,还放在表单里提交什么?
- 表单字段的
help_text
设置为模型字段的help_text
。 - 如果模型字段设置了choices参数,那么表单字段的widget属性将设置成Select框,其选项来自模型字段的choices。选单中通常会包含一个空选项,并且作为默认选择。如果该字段是必选的,它会强制用户选择一个选项。 如果模型字段具有default参数,则不会添加空选项到选单中。
模型字段 | 表单字段 |
---|---|
AutoField | 在Form类中无法使用 |
BigAutoField | 在Form类中无法使用 |
BigIntegerField | IntegerField,最小-9223372036854775808,最大9223372036854775807. |
BooleanField | BooleanField |
CharField | CharField,同样的最大长度限制。如果model设置了null=True,Form将使用empty_value |
CommaSeparatedIntegerField | CharField |
DateField | DateField |
DateTimeField | DateTimeField |
DecimalField | DecimalField |
EmailField | EmailField |
FileField | FileField |
FilePathField | FilePathField |
FloatField | FloatField |
ForeignKey | ModelChoiceField |
ImageField | ImageField |
IntegerField | IntegerField |
IPAddressField | IPAddressField |
GenericIPAddressField | GenericIPAddressField |
ManyToManyField | ModelMultipleChoiceField |
NullBooleanField | NullBooleanField |
PositiveIntegerField | IntegerField |
PositiveSmallIntegerField | IntegerField |
SlugField | SlugField |
SmallIntegerField | IntegerField |
TextField | CharField,并带有widget=forms.Textarea参数 |
TimeField | TimeField |
URLField | URLField |
五、formset
#!/usr/bin/env python # -*- coding:utf-8 -*- from django import forms class BootStrapModelForm(forms.ModelForm): def __init__(self, *args, **kwargs): super(BootStrapModelForm, self).__init__(*args, **kwargs) # 统一给ModelForm生成字段添加样式 for name, field in self.fields.items(): field.widget.attrs['class'] = 'form-control' ############################## from django import forms from django.utils.safestring import mark_safe from rbac import models from rbac.forms.base import BootStrapModelForm ICON_LIST = [ ['fa-hand-scissors-o', '<i aria-hidden="true" class="fa fa-hand-scissors-o"></i>'], ['fa-hand-spock-o', '<i aria-hidden="true" class="fa fa-hand-spock-o"></i>'], ] for item in ICON_LIST: item[1] = mark_safe(item[1]) class MenuModelForm(forms.ModelForm): class Meta: model = models.Menu fields = ['title', 'icon'] widgets = { 'title': forms.TextInput(attrs={'class': 'form-control'}), 'icon': forms.RadioSelect( choices=ICON_LIST, attrs={'class': 'clearfix'} ) } class SecondMenuModelForm(BootStrapModelForm): class Meta: model = models.Permission exclude = ['pid'] class PermissionModelForm(BootStrapModelForm): class Meta: model = models.Permission fields = ['title', 'name', 'url'] class MultiAddPermissionForm(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 MultiEditPermissionForm(forms.Form): 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')
{% extends 'layout.html' %} {% block content %} <div class="luffy-container"> <form method="post" action="?type=generate"> {% csrf_token %} {{ generate_formset.management_form }} <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true"></i> 待新建权限列表 <button href="#" class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;"> <i class="fa fa-save" aria-hidden="true"></i> 新建 </button> </div> <!-- Table --> <table class="table"> <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>{{ forloop.counter }}</td> {% for field in form %} <td>{{ field }}<span style="color: red;">{{ field.errors.0 }}</span></td> {% endfor %} </tr> {% endfor %} </tbody> </table> </div> </form> <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true"></i> 待删除权限列表 </div> <!-- Table --> <table class="table"> <thead> <tr> <th>序号</th> <th>名称</th> <th>URL</th> <th>别名</th> <th>删除</th> </tr> </thead> <tbody> {% for row in delete_row_list %} <tr> <td>{{ forloop.counter }}</td> <td>{{ row.title }}</td> <td>{{ row.url }}</td> <td>{{ row.name }}</td> <td> <a style="color: #d9534f;" href="{% url 'rbac:multi_permissions_del' pk=row.id %}"> <i class="fa fa-trash-o"></i> </a> </td> </tr> {% endfor %} </tbody> </table> </div> <form method="post" action="?type=update"> {% csrf_token %} {{ update_formset.management_form }} <div class="panel panel-default"> <!-- Default panel contents --> <div class="panel-heading"> <i class="fa fa-th-list" aria-hidden="true"></i> 待更新权限列表 <button href="#" class="right btn btn-primary btn-xs" style="padding: 2px 8px;margin: -3px;"> <i class="fa fa-save" aria-hidden="true"></i> 保存 </button> </div> <!-- Table --> <table class="table"> <thead> <tr> <th>序号</th> <th>名称</th> <th>URL</th> <th>别名</th> <th>菜单</th> <th>父权限</th> </tr> </thead> <tbody> {% for form in update_formset %} <tr> <td>{{ forloop.counter }}</td> {% 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> </div> </form> </div> {% endblock %}
#!/usr/bin/env python # -*- coding:utf-8 -*- from collections import OrderedDict from django.shortcuts import render, redirect, HttpResponse from django.forms import formset_factory from django.conf import settings from django.utils.module_loading import import_string from rbac import models from rbac.forms.menu import MenuModelForm, SecondMenuModelForm, PermissionModelForm, MultiAddPermissionForm, \ MultiEditPermissionForm from rbac.service.urls import memory_reverse from rbac.service.routes import get_all_url_dict def menu_list(request): """ 菜单和权限列表 :param request: :return: """ menus = models.Menu.objects.all() menu_id = request.GET.get('mid') # 用户选择的一级菜单 second_menu_id = request.GET.get('sid') # 用户选择的二级菜单 menu_exists = models.Menu.objects.filter(id=menu_id).exists() if not menu_exists: menu_id = None if menu_id: second_menus = models.Permission.objects.filter(menu_id=menu_id) else: second_menus = [] second_menu_exists = models.Permission.objects.filter(id=second_menu_id).exists() if not second_menu_exists: second_menu_id = None if second_menu_id: permissions = models.Permission.objects.filter(pid_id=second_menu_id) else: permissions = [] return render( request, 'rbac/menu_list.html', { 'menus': menus, 'second_menus': second_menus, 'permissions': permissions, 'menu_id': menu_id, 'second_menu_id': second_menu_id, } ) def menu_add(request): """ 添加一级菜单 :param request: :return: """ if request.method == 'GET': form = MenuModelForm() return render(request, 'rbac/change.html', {'form': form}) form = MenuModelForm(data=request.POST) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def menu_edit(request, pk): """ :param request: :param pk: :return: """ obj = models.Menu.objects.filter(id=pk).first() if not obj: return HttpResponse('菜单不存在') if request.method == 'GET': form = MenuModelForm(instance=obj) return render(request, 'rbac/change.html', {'form': form}) form = MenuModelForm(instance=obj, data=request.POST) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def menu_del(request, pk): """ :param request: :param pk: :return: """ url = memory_reverse(request, 'rbac:menu_list') if request.method == 'GET': return render(request, 'rbac/delete.html', {'cancel': url}) models.Menu.objects.filter(id=pk).delete() return redirect(url) def second_menu_add(request, menu_id): """ 添加二级菜单 :param request: :param menu_id: 已选择的一级菜单ID(用于设置默认值) :return: """ menu_object = models.Menu.objects.filter(id=menu_id).first() if request.method == 'GET': form = SecondMenuModelForm(initial={'menu': menu_object}) return render(request, 'rbac/change.html', {'form': form}) form = SecondMenuModelForm(data=request.POST) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def second_menu_edit(request, pk): """ 编辑二级菜单 :param request: :param pk: 当前要编辑的二级菜单 :return: """ permission_object = models.Permission.objects.filter(id=pk).first() if request.method == 'GET': form = SecondMenuModelForm(instance=permission_object) return render(request, 'rbac/change.html', {'form': form}) form = SecondMenuModelForm(data=request.POST, instance=permission_object) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def second_menu_del(request, pk): """ :param request: :param pk: :return: """ url = memory_reverse(request, 'rbac:menu_list') if request.method == 'GET': return render(request, 'rbac/delete.html', {'cancel': url}) models.Permission.objects.filter(id=pk).delete() return redirect(url) def permission_add(request, second_menu_id): """ 添加权限 :param request: :param second_menu_id: :return: """ if request.method == 'GET': form = PermissionModelForm() return render(request, 'rbac/change.html', {'form': form}) form = PermissionModelForm(data=request.POST) if form.is_valid(): second_menu_object = models.Permission.objects.filter(id=second_menu_id).first() if not second_menu_object: return HttpResponse('二级菜单不存在,请重新选择!') form.instance.pid = second_menu_object form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def permission_edit(request, pk): """ 编辑权限 :param request: :param pk: 当前要编辑的权限ID :return: """ permission_object = models.Permission.objects.filter(id=pk).first() if request.method == 'GET': form = PermissionModelForm(instance=permission_object) return render(request, 'rbac/change.html', {'form': form}) form = PermissionModelForm(data=request.POST, instance=permission_object) if form.is_valid(): form.save() return redirect(memory_reverse(request, 'rbac:menu_list')) return render(request, 'rbac/change.html', {'form': form}) def permission_del(request, pk): """ :param request: :param pk: :return: """ url = memory_reverse(request, 'rbac:menu_list') if request.method == 'GET': return render(request, 'rbac/delete.html', {'cancel': url}) models.Permission.objects.filter(id=pk).delete() return redirect(url) def multi_permissions(request): """ 批量操作权限 :param request: :return: """ post_type = request.GET.get('type') generate_formset_class = formset_factory(MultiAddPermissionForm, extra=0) update_formset_class = formset_factory(MultiEditPermissionForm, extra=0) generate_formset = None update_formset = None if request.method == 'POST' and post_type == 'generate': # pass # 批量添加 formset = generate_formset_class(data=request.POST) if formset.is_valid(): object_list = [] post_row_list = formset.cleaned_data has_error = False for i in range(0, formset.total_form_count()): row_dict = post_row_list[i] try: new_object = models.Permission(**row_dict) new_object.validate_unique() object_list.append(new_object) except Exception as e: formset.errors[i].update(e) generate_formset = formset has_error = True if not has_error: models.Permission.objects.bulk_create(object_list, batch_size=100) else: generate_formset = formset if request.method == 'POST' and post_type == 'update': # pass # 批量更新 formset = update_formset_class(data=request.POST) if formset.is_valid(): post_row_list = formset.cleaned_data for i in range(0, formset.total_form_count()): row_dict = post_row_list[i] permission_id = row_dict.pop('id') try: row_object = models.Permission.objects.filter(id=permission_id).first() for k, v in row_dict.items(): setattr(row_object, k, v) row_object.validate_unique() row_object.save() except Exception as e: formset.errors[i].update(e) update_formset = formset else: update_formset = formset # 1. 获取项目中所有的URL all_url_dict = get_all_url_dict() """ { 'rbac:role_list':{'name': 'rbac:role_list', 'url': '/rbac/role/list/'}, 'rbac:role_add':{'name': 'rbac:role_add', 'url': '/rbac/role/add/'}, .... } """ router_name_set = set(all_url_dict.keys()) # 2. 获取数据库中所有的URL permissions = models.Permission.objects.all().values('id', 'title', 'name', 'url', 'menu_id', 'pid_id') permission_dict = OrderedDict() permission_name_set = set() for row in permissions: permission_dict[row['name']] = row permission_name_set.add(row['name']) """ { 'rbac:role_list': {'id':1,'title':'角色列表',name:'rbac:role_list',url.....}, 'rbac:role_add': {'id':1,'title':'添加角色',name:'rbac:role_add',url.....}, ... } """ for name, value in permission_dict.items(): router_row_dict = all_url_dict.get(name) # {'name': 'rbac:role_list', 'url': '/rbac/role/list/'}, if not router_row_dict: continue if value['url'] != router_row_dict['url']: value['url'] = '路由和数据库中不一致' # 3. 应该添加、删除、修改的权限有哪些? # 3.1 计算出应该增加的name if not generate_formset: generate_name_list = router_name_set - permission_name_set generate_formset = generate_formset_class( initial=[row_dict for name, row_dict in all_url_dict.items() if name in generate_name_list]) # 3.2 计算出应该删除的name delete_name_list = permission_name_set - router_name_set delete_row_list = [row_dict for name, row_dict in permission_dict.items() if name in delete_name_list] # 3.3 计算出应该更新的name if not update_formset: update_name_list = permission_name_set & router_name_set update_formset = update_formset_class( initial=[row_dict for name, row_dict in permission_dict.items() if name in update_name_list]) return render( request, 'rbac/multi_permissions.html', { 'generate_formset': generate_formset, 'delete_row_list': delete_row_list, 'update_formset': update_formset, } ) def multi_permissions_del(request, pk): """ 批量页面的权限删除 :param request: :param pk: :return: """ url = memory_reverse(request, 'rbac:multi_permissions') if request.method == 'GET': return render(request, 'rbac/delete.html', {'cancel': url}) models.Permission.objects.filter(id=pk).delete() return redirect(url) def distribute_permissions(request): """ 权限分配 :param request: :return: """ user_id = request.GET.get('uid') # 业务中的用户表 "app01.models.UserInfo"" user_model_class = import_string(settings.RBAC_USER_MODLE_CLASS) user_object = user_model_class.objects.filter(id=user_id).first() if not user_object: user_id = None role_id = request.GET.get('rid') role_object = models.Role.objects.filter(id=role_id).first() if not role_object: role_id = None if request.method == 'POST' and request.POST.get('type') == 'role': role_id_list = request.POST.getlist('roles') # 用户和角色关系添加到第三张表(关系表) if not user_object: return HttpResponse('请选择用户,然后再分配角色!') user_object.roles.set(role_id_list) if request.method == 'POST' and request.POST.get('type') == 'permission': permission_id_list = request.POST.getlist('permissions') if not role_object: return HttpResponse('请选择角色,然后再分配权限!') role_object.permissions.set(permission_id_list) # 获取当前用户拥有的所有角色 if user_id: user_has_roles = user_object.roles.all() else: user_has_roles = [] user_has_roles_dict = {item.id: None for item in user_has_roles} # 获取当前用户用户用户的所有权限 # 如果选中的角色,优先显示选中角色所拥有的权限 # 如果没有选择角色,才显示用户所拥有的权限 if role_object: # 选择了角色 user_has_permissions = role_object.permissions.all() user_has_permissions_dict = {item.id: None for item in user_has_permissions} elif user_object: # 未选择角色,但选择了用户 user_has_permissions = user_object.roles.filter(permissions__id__isnull=False).values('id', 'permissions').distinct() user_has_permissions_dict = {item['permissions']: None for item in user_has_permissions} else: user_has_permissions_dict = {} all_user_list = user_model_class.objects.all() all_role_list = models.Role.objects.all() menu_permission_list = [] # 所有的菜单(一级菜单) all_menu_list = models.Menu.objects.values('id', 'title') """ [ {id:1,title:菜单1,children:[{id:1,title:x1, menu_id:1,'children':[{id:11,title:x2,pid:1},] },{id:2,title:x1, menu_id:1 },]}, {id:2,title:菜单2,children:[{id:3,title:x1, menu_id:2 },{id:5,title:x1, menu_id:2 },]}, {id:3,title:菜单3,children:[{id:4,title:x1, menu_id:3 },]}, ] """ all_menu_dict = {} """ { 1:{id:1,title:菜单1,children:[{id:1,title:x1, menu_id:1,children:[{id:11,title:x2,pid:1},] },{id:2,title:x1, menu_id:1,children:[] },]}, 2:{id:2,title:菜单2,children:[{id:3,title:x1, menu_id:2,children:[] },{id:5,title:x1, menu_id:2,children:[] },]}, 3:{id:3,title:菜单3,children:[{id:4,title:x1, menu_id:3,children:[] },]}, } """ for item in all_menu_list: item['children'] = [] all_menu_dict[item['id']] = item # 所有二级菜单 all_second_menu_list = models.Permission.objects.filter(menu__isnull=False).values('id', 'title', 'menu_id') """ [ {id:1,title:x1, menu_id:1,children:[{id:11,title:x2,pid:1},] }, {id:2,title:x1, menu_id:1,children:[] }, {id:3,title:x1, menu_id:2,children:[] }, {id:4,title:x1, menu_id:3,children:[] }, {id:5,title:x1, menu_id:2,children:[] }, ] """ all_second_menu_dict = {} """ { 1:{id:1,title:x1, menu_id:1,children:[{id:11,title:x2,pid:1},] }, 2:{id:2,title:x1, menu_id:1,children:[] }, 3:{id:3,title:x1, menu_id:2,children:[] }, 4:{id:4,title:x1, menu_id:3,children:[] }, 5:{id:5,title:x1, menu_id:2,children:[] }, } """ for row in all_second_menu_list: row['children'] = [] all_second_menu_dict[row['id']] = row menu_id = row['menu_id'] all_menu_dict[menu_id]['children'].append(row) # 所有三级菜单(不能做菜单的权限) all_permission_list = models.Permission.objects.filter(menu__isnull=True).values('id', 'title', 'pid_id') """ [ {id:11,title:x2,pid:1}, {id:12,title:x2,pid:1}, {id:13,title:x2,pid:2}, {id:14,title:x2,pid:3}, {id:15,title:x2,pid:4}, {id:16,title:x2,pid:5}, ] """ for row in all_permission_list: pid = row['pid_id'] if not pid: continue all_second_menu_dict[pid]['children'].append(row) """ [ { id:1, title:'业务管理' children:[ { 'id':11, title:'账单列表', children:[ {'id':12,title:'添加账单'} ] }, {'id':11, title:'客户列表'}, ] }, ] """ return render( request, 'rbac/distribute_permissions.html', { 'user_list': all_user_list, 'role_list': all_role_list, 'all_menu_list': all_menu_list, 'user_id': user_id, 'role_id': role_id, 'user_has_roles_dict': user_has_roles_dict, 'user_has_permissions_dict': user_has_permissions_dict, } )
#!/usr/bin/env python # -*- coding:utf-8 -*- import re from collections import OrderedDict from django.conf import settings from django.utils.module_loading import import_string from django.urls import URLResolver, URLPattern def check_url_exclude(url): """ 排除一些特定的URL :param url: :return: """ for regex in settings.AUTO_DISCOVER_EXCLUDE: if re.match(regex, url): return True def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict): """ 递归的去获取URL :param pre_namespace: namespace前缀,以后用户拼接name :param pre_url: url前缀,以后用于拼接url :param urlpatterns: 路由关系列表 :param url_ordered_dict: 用于保存递归中获取的所有路由 :return: """ for item in urlpatterns: if isinstance(item, URLPattern): # 非路由分发,讲路由添加到url_ordered_dict if not item.name: continue if pre_namespace: name = "%s:%s" % (pre_namespace, item.name) else: name = item.name url = pre_url + str(item.pattern) # /rbac/user/edit/(?P<pk>\d+)/ url = url.replace('^', '').replace('$', '') if check_url_exclude(url): continue url_ordered_dict[name] = {'name': name, 'url': url} elif isinstance(item, URLResolver): # 路由分发,递归操作 if pre_namespace: if item.namespace: namespace = "%s:%s" % (pre_namespace, item.namespace,) else: namespace = item.namespace else: if item.namespace: namespace = item.namespace else: namespace = None recursion_urls(namespace, pre_url + str(item.pattern), item.url_patterns, url_ordered_dict) def get_all_url_dict(): """ 获取项目中所有的URL(必须有name别名) :return: """ url_ordered_dict = OrderedDict() md = import_string(settings.ROOT_URLCONF) # from luff.. import urls recursion_urls(None, '/', md.urlpatterns, url_ordered_dict) # 递归去获取所有的路由 return url_ordered_dict
#!/usr/bin/env python # -*- coding:utf-8 -*- import re from collections import OrderedDict from django.conf import settings from django.utils.module_loading import import_string from django.urls import RegexURLResolver, RegexURLPattern def check_url_exclude(url): """ 排除一些特定的URL :param url: :return: """ for regex in settings.AUTO_DISCOVER_EXCLUDE: if re.match(regex, url): return True def recursion_urls(pre_namespace, pre_url, urlpatterns, url_ordered_dict): """ 递归的去获取URL :param pre_namespace: namespace前缀,以后用户拼接name :param pre_url: url前缀,以后用于拼接url :param urlpatterns: 路由关系列表 :param url_ordered_dict: 用于保存递归中获取的所有路由 :return: """ for item in urlpatterns: if isinstance(item, RegexURLPattern): # 非路由分发,讲路由添加到url_ordered_dict if not item.name: continue if pre_namespace: name = "%s:%s" % (pre_namespace, item.name) else: name = item.name url = pre_url + item._regex # /rbac/user/edit/(?P<pk>\d+)/ url = url.replace('^', '').replace('$', '') if check_url_exclude(url): continue url_ordered_dict[name] = {'name': name, 'url': url} elif isinstance(item, RegexURLResolver): # 路由分发,递归操作 if pre_namespace: if item.namespace: namespace = "%s:%s" % (pre_namespace, item.namespace,) else: namespace = item.namespace else: if item.namespace: namespace = item.namespace else: namespace = None recursion_urls(namespace, pre_url + item.regex.pattern, item.url_patterns, url_ordered_dict) def get_all_url_dict(): """ 获取项目中所有的URL(必须有name别名) :return: """ url_ordered_dict = OrderedDict() md = import_string(settings.ROOT_URLCONF) # from luff.. import urls recursion_urls(None, '/', md.urlpatterns, url_ordered_dict) # 递归去获取所有的路由 return url_ordered_dict