• Django组件ModelForm


    模型表单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参数来提供表单中数据的初始值。
    ModelForm书写
    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})
    ModelForm验证
    #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和ModelForm

    四、字段类型

    生成的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')
    forms
    {% 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,
            }
        )
    Views
    #!/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
    service/routes.py
    #!/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
    old
  • 相关阅读:
    函数
    函数知识点 --- 函数的认知,组成,格式 --------------- #10
    打包app
    vue ie
    css position
    awesome vue
    20110636乐建18588529432
    vue2.0-基于elementui换肤[自定义主题]
    三目运算符,多条件判断
    微信二次开发准备工作
  • 原文地址:https://www.cnblogs.com/bubu99/p/10347432.html
Copyright © 2020-2023  润新知