zoukankan      html  css  js  c++  java
  • python 全栈开发,Day111(客户管理之 编辑权限(二),Django表单集合Formset,ORM之limit_choices_to,构造家族结构)

    昨日内容回顾

    1. 权限系统的流程?
            
    2. 权限的表有几个?
    
    3. 技术点 
        中间件
        session
        orm
            - 去重
            - 去空
        inclusion_tag
        filter
        有序字典
        settings配置
        引入静态文件
        url别名
        namespace 
        路由分发
        构造数据结构
        ModelForm
        组件应用
        admin 
        icon爬虫
        mark_safe
        下载文件
    View Code

    一、客户管理之 编辑权限(二)

    下载代码:

    链接:https://pan.baidu.com/s/1xYkyWFwmOZIFK4cqWWUizg 密码:zzs9

    效果如下:

    上面的权限管理,需要构造一个字典

    构造字典

    权限管理数据结构如下:

    "1": {
        "title": "客户列表",
        "url": "/customer/list/",
        "name": "customer_list",
        "children": [
            {"title": "添加客户","url": "/customer/add/","name": "customer_add"}
            {"title": "编辑客户","url": "/customer/edit/","name": "customer_edit"}
        ],
    },

    这里的1指的是权限id。也就Permission表的主键

    修改 rbac-->urls.py,增加路径test,用来做测试。

    from django.conf.urls import url
    from rbac.views import role,menu,permission
    
    urlpatterns = [
        url(r'^role/list/$', role.role_list,name='role_list'),
        url(r'^role/add/$', role.role_add,name='role_add'),
        url(r'^role/edit/(?P<rid>d+)/$', role.role_edit,name='role_edit'),
    
        url(r'^menu/list/$', menu.menu_list,name='menu_list'),
        url(r'^menu/add/$', menu.menu_add,name='menu_add'),
        url(r'^menu/edit/(?P<pk>d+)/$', menu.menu_edit,name='menu_edit'),
        url(r'^menu/del/(?P<pk>d+)/$', menu.menu_del,name='menu_del'),
    
        url(r'^permission/edit/(?P<pk>d+)/$', menu.permission_edit, name='permission_edit'),
        url(r'^permission/del/(?P<pk>d+)/$', menu.permission_del, name='permission_del'),
    
        url(r'^permission/test/$', permission.test,name='test'),
    ]
    View Code

    进入目录 rbac-->views,创建文件permission.py

    from django.shortcuts import render, redirect,HttpResponse
    from rbac import models
    
    def test(request):
        permission_queryset = models.Permission.objects.all().values("id","title","url","name","parent_id")
        root_permission_dict = {}
        for item in permission_queryset:
            print(item['parent_id'])
            # 判断parent_id不为空时,也就是能做菜单的url
            if not item['parent_id']:
                # 以id为key
                root_permission_dict[item['id']] = {
                    'title':item['title'],
                    'url': item['url'],
                    'name': item['name'],
                    'children':[]  # 子列表默认为空
                }
    
        return HttpResponse('ok')
    View Code

    访问测试页面: http://127.0.0.1:8000/rbac/permission/test/,效果如下:

    查看Pycharm控制台输出:

    None
    1
    1
    1
    1
    None
    7
    7
    7
    None
    View Code

     再筛选不能做菜单的url,也就是parent_id不能为空的

    from django.shortcuts import render, redirect,HttpResponse
    from rbac import models
    
    def test(request):
        permission_queryset = models.Permission.objects.all().values("id","title","url","name","parent_id")
        root_permission_dict = {}
    
        # 先添加二级菜单
        for item in permission_queryset:
            # print(item['parent_id'])
            # 判断parent_id不为空时,也就是能做菜单的url
            if not item['parent_id']:
                # 以id为key
                root_permission_dict[item['id']] = {
                    'title':item['title'],
                    'url': item['url'],
                    'name': item['name'],
                    'children':[]  # 子列表默认为空
                }
    
        # 再添加非菜单的url
        for item in permission_queryset:
            # 获取pid
            parent_id = item['parent_id']
            if parent_id:  # 判断pid是否存在
                # 追加到parent_id对应的children中
                root_permission_dict[parent_id]['children'].append({
                    'title': item['title'],
                    'url': item['url'],
                    'name': item['name'],
                })
    
        # 查看最终数据
        for root in root_permission_dict.values():
            print(root['title'],root['name'],root['url'])
            for node in root['children']:
                print('--->',node['title'],node['name'],node['url'])
    
        return HttpResponse('ok')
    View Code

    刷新页面,查看Pycharm控制台输出:

    客户列表 customer_list /customer/list/
    ---> 添加客户1 customer_add /customer/add/
    ---> 编辑客户 customer_edit /customer/edit/(?P<cid>d+)/
    ---> 删除客户 customer_del /customer/del/(?P<cid>d+)/
    ---> 批量导入客户 customer_import /customer/import/
    角色列表 role /role/
    账单列表 payment_list /payment/list/
    ---> 添加账单 payment_add /payment/add/
    ---> 编辑账单 payment_edit /payment/edit/(?P<pid>d+)/
    ---> 删除账单 payment_del /payment/del/(?P<pid>d+)/
    View Code

    点击菜单里面的超链接,url会有mid

    这个时候,需要修改ORM查询语句

    修改 rbac-->views-->menu.py

    from django.shortcuts import render, redirect,HttpResponse
    from django.urls import reverse
    from rbac import models
    from django import forms
    from django.utils.safestring import mark_safe
    
    ICON_LIST = [
        ['fa-address-book', '<i aria-hidden="true" class="fa fa-address-book"></i>'],
        ['fa-address-book-o', '<i aria-hidden="true" class="fa fa-address-book-o"></i>'],
        ['fa-address-card', '<i aria-hidden="true" class="fa fa-address-card"></i>'],
        ['fa-address-card-o', '<i aria-hidden="true" class="fa fa-address-card-o"></i>'],
        ['fa-adjust', '<i aria-hidden="true" class="fa fa-adjust"></i>'],
    ]
    for item in ICON_LIST:
        item[1] = mark_safe(item[1])
    
    
    def menu_list(request):
        """
        权限管理和分配
        :param request:
        :return:
        """
        menus = models.Menu.objects.all()
    
        mid = request.GET.get('mid')
        root_permission_list = []
        if mid:
            # 找到可以成为菜单的权限 + 某个菜单下的
            permissions = models.Permission.objects.filter(menu_id=mid).order_by('-id')
        else:
            # 找到可以成为菜单的权限
            permissions = models.Permission.objects.filter(menu__isnull=False).order_by('-id')
    
        root_permission_queryset = permissions.values('id', 'title', 'url', 'name', 'menu__title')
        root_permission_dict = {}
        for item in root_permission_queryset:
            item['children'] = []
            root_permission_list.append(item)
            root_permission_dict[item['id']] = item
    
        # 找到可以成为菜单的权限的所有子权限
        node_permission_list = models.Permission.objects.filter(parent_id__in=permissions).order_by('-id').values('id',
                                                                                                            'title',
                                                                                                            'url',
                                                                                                            'name',
                                                                                                            'parent_id')
        for node in node_permission_list:
            pid = node['parent_id']
            root_permission_dict[pid]['children'].append(node)
    
        return render(
            request,
            'rbac/menu_list.html',
            {
                'menu_list': menus,
                'root_permission_list': root_permission_list,
                'mid': mid
            }
        )
    
    
    class MenuModelForm(forms.ModelForm):
        class Meta:
            model = models.Menu
            fields = ['title','icon']
            widgets = {
                # title字段添加class
                'title': forms.TextInput(attrs={'class': 'form-control'}),
                'icon': forms.RadioSelect(
                    choices=ICON_LIST
                )
            }
            error_messages = {
                'title': {
                    'required': '菜单名称不能为空'
                },
                'icon': {
                    'required': '请选择图标'
                }
            }
    
    def menu_add(request):
        if request.method == "GET":
            form = MenuModelForm()
        else:
            form = MenuModelForm(request.POST)
            if form.is_valid():
                form.save()
                return redirect(reverse('rbac:menu_list'))
        return render(request,"rbac/menu_add.html",{'form':form})
    
    def menu_edit(request,pk):
        obj = models.Menu.objects.filter(id=pk).first()
        if request.method =='GET':
            # instance参数,如果存在那么save()方法会更新这个实例,否则会创建一个新的实例
            form = MenuModelForm(instance=obj)
            return render(request,'rbac/menu_edit.html',{'form':form})
        else:
            form = MenuModelForm(data=request.POST,instance=obj)
            if form.is_valid():
                form.save()
                return redirect(reverse('rbac:menu_list'))
            else:
                return render(request, 'rbac/menu_edit.html', {'form': form})
    
    
    def menu_del(request, pk):
        """
        删除菜单
        :param request:
        :param pk:
        :return:
        """
        obj = models.Menu.objects.filter(id=pk).delete()
        # print(obj)
        return redirect(reverse('rbac:menu_list'))
    
    
    class PermissionModelForm(forms.ModelForm):
        class Meta:
            model = models.Permission
            fields = '__all__'
            help_texts = {
                'pid': '父级权限,无法作为菜单的权限才需要选择。',
                'menu': "选中,表示该权限可以作为菜单;否则,不可做菜单。"
    
            }
            widgets = {
                'title': forms.TextInput(attrs={'class': "form-control", 'placeholder': '请输入权限名称'}),
                'url': forms.TextInput(attrs={'class': "form-control", 'placeholder': '请输入URL'}),
                'name': forms.TextInput(attrs={'class': "form-control", 'placeholder': '请输入URL别名'}),
                'pid': forms.Select(attrs={'class': "form-control", 'placeholder': '请选择父级权限'}),
                'menu': forms.Select(attrs={'class': "form-control", 'placeholder': '请选择菜单'}),
            }
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
        def clean(self):
            menu = self.cleaned_data.get('menu')
            pid = self.cleaned_data.get('pid')
            if menu and pid:
                self.add_error('menu', '菜单和根权限同时只能选择一个')
    
    def permission_edit(request, pk):
        """
        编辑权限
        :param request:
        :return:
        """
        obj = models.Permission.objects.filter(id=pk).first()
        if not obj:
            return HttpResponse('权限不存在')
    
        if request.method == 'GET':
            form = PermissionModelForm(instance=obj)
        else:
            form = PermissionModelForm(request.POST, instance=obj)
            if form.is_valid():
                form.save()
                return redirect(reverse('rbac:menu_list'))
        return render(request, 'rbac/permission_edit.html', {'form': form})
    
    
    def permission_del(request, pk):
        """
        删除权限
        :param request:
        :return:
        """
        models.Permission.objects.filter(id=pk).delete()
        return redirect(request.META['HTTP_REFERER'])
    View Code

    修改 rbac-->templates-->rbac-->menu_list.html

    {% extends 'layout.html' %}
    {% block css %}
        <style>
            tr.root {
                background-color: #f1f7fd;
            }
    
            .menu-area tr.active {
                background-color: #f1f7fd;
                border-left: 3px solid #fdc00f;
            }
    
            #menuBody td[mid], #permissionBody > .root .title {
                cursor: pointer;
            }
    
            #permissionBody tr.root {
                background-color: #f1f7fd;
            }
    
            td a {
                margin: 0 2px;
                cursor: pointer;
            }
    
            table {
                font-size: 12px;
            }
    
            .panel-body {
                font-size: 12px;
            }
    
            .panel-body .form-control {
                font-size: 12px;
            }
    
    
        </style>
    {% endblock %}
    {% block content %}
        <div style="padding: 10px">
            <h1>菜单管理</h1>
    
            <div class="col-sm-3 menu-area">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading">
                        <i class="fa fa-book" aria-hidden="true"></i>
                        菜单管理
                        <a href="{% url 'rbac:menu_add' %}" class="btn btn-xs btn-success right"
                           style="padding: 2px 8px;margin: -3px;">
                            <i class="fa fa-plus-circle" aria-hidden="true"></i>
                            新建
                        </a>
                    </div>
    
                    <!-- Table -->
                    <table class="table">
                        <th>名称</th>
                        <th>图标</th>
                        <th>选项</th>
                        <tbody id="menuBody">
                        {% for row in menu_list %}
                            {% if row.id|safe == mid %}
                                <tr class="active">
                                    {% else %}
                                <tr>
                            {% endif %}
                                <td>
                                    <a href="?mid={{ row.id }}">{{ row.title }}</a>
                                </td>
                                <td>
                                    <!-- 展示图标 -->
                                    <i class="fa {{ row.icon }}" aria-hidden="true"></i>
                                </td>
                                <td>
                                    <!-- 编辑和删除 -->
                                    <a href="{% url 'rbac:menu_edit' pk=row.id %}"><i class="fa fa-edit"
                                                                                      aria-hidden="true"></i></a>
                                    <span style="padding: 0 5px;display: inline-block">|</span>
                                    <a href="{% url 'rbac:menu_del' pk=row.id %}"><i class="fa fa-trash-o"
                                                                                     aria-hidden="true"></i></a>
                                </td>
                            </tr>
                        {% endfor %}
    
                        </tbody>
                    </table>
                </div>
            </div>
    
            <div class="col-sm-9">
                <div class="panel panel-default">
                    <!-- Default panel contents -->
                    <div class="panel-heading">
                        <i class="fa fa-cubes" aria-hidden="true"></i>
                        权限管理
                        <a href="{% url 'rbac:menu_add' %}" class="btn btn-xs btn-success right"
                           style="padding: 2px 8px;margin: -3px;">
                            <i class="fa fa-plus-circle" aria-hidden="true"></i>
                            新建
                        </a>
                    </div>
    
                    <!-- Table -->
                    <table class="table">
                        <th>名称</th>
                        <th>URL</th>
                        <th>CODE</th>
                        <th>菜单</th>
                        <th>所属菜单</th>
                        <th>选项</th>
                        <tbody>
                        {% for row in root_permission_list %}
                            {# 判断菜单id是否为空 #}
                            {% if row.menu__title %}
                                <tr class="root">
                                    {% else %}
                                <tr style="display: none">
                            {% endif %}
    
                        <td>
                            <i class="fa fa-caret-down" aria-hidden="true"></i>
                            {{ row.title }}
                        </td>
                        <td>{{ row.url }}</td>
                        <td>{{ row.name }}</td>
                        <td>
                            {# 判断菜单id是否为空 #}
                            {% if row.menu__title %}
                                是
                            {% endif %}
                        </td>
                        <td>
                            {# 判断菜单id是否为空 #}
                            {% if row.menu__title %}
                                {{ row.menu__title }}
                            {% endif %}
                        </td>
                        <td>
                            <!-- 编辑和删除 -->
                            <a href="{% url 'rbac:permission_edit' pk=row.id %}"><i class="fa fa-edit"
                                                                                    aria-hidden="true"></i></a>
                            <span style="padding: 0 5px;display: inline-block">|</span>
                            <a href="{% url 'rbac:permission_del' pk=row.id %}"><i class="fa fa-trash-o"
                                                                                   aria-hidden="true"></i></a>
                        </td>
                        </tr>
                            <tbody class="two">
                            {% for child in row.children %}
    
                                <tr pid="{{ row.id }}">
                                    <td class="title">{{ child.title }}</td>
                                    <td>{{ child.url }}</td>
                                    <td>{{ child.name }}</td>
                                    <td></td>
                                    <td>
                                        {% if child.menu__title %}
                                            {{ row.menu__title }}
                                        {% endif %}
                                    </td>
                                    <td>
                                        <a href="{% url 'rbac:permission_edit' pk=row.id %}" style="color:#333333;">
                                            <i class="fa fa-edit" aria-hidden="true"></i></a>
                                        <a href="{% url 'rbac:permission_del' pk=row.id %}" style="color:#d9534f;">
                                            <i class="fa fa-trash-o" aria-hidden="true"></i>
                                        </a>
                                    </td>
                                </tr>
    
                            {% endfor %}
                            </tbody>
                        {% endfor %}
                        </tbody>
                    </table>
                </div>
            </div>
    
    
        </div>
    
    
    {% endblock %}
    
    {% block js %}
        <script>
            //默认隐藏
            {#$('.aa').prev().next().hide();#}
            $('.two').prev().click(function () {
                console.log('点击了');
                //卷帘门切换
                $(this).next().slideToggle(300);
            })
        </script>
    {% endblock %}
    View Code

    访问页面:http://127.0.0.1:8000/rbac/menu/list,效果如下:

    点击左边的信息管理,效果如下:

    二、Django表单集合Formset

    什么是Formset

    Formset(表单集)是多个表单的集合。Formset在Web开发中应用很普遍,它可以让用户在同一个页面上提交多张表单,一键添加多个数据,比如一个页面上添加多个用户信息

    为什么要使用Django Formset

    我们先来下看下Django中不使用Formset情况下是如何在同一页面上一键提交2张或多张表单的。我们在模板中给每个表单取不同的名字,如form1和form2(如下面代码所示)。注: form1和form2分别对应forms.py里的Form1()和Form2()。

    <form >
        {{ form1.as_p }}
        {{ form2.as_p }}
    </form>

    用户点击提交后,我们就可以在视图里了对用户提交的数据分别处理。

    if request.method == 'POST':
            form1 = Form1( request.POST,prefix="form1")
            form2 = Form2( request.POST,prefix="form2")
            
            if form1.is_valid() or form2.is_valid(): 
                pass
    else:
            form1 = Form1(prefix="form1")
            form2 = Form2(prefix="form2")
    View Code

    这段代码看似并不复杂,然而当表单数量很多或不确定时,这个代码会非常冗长。我们希望能控制表单的数量,这是我们就可以用Formset了。

    Formset的分类

    Django针对不同的formset提供了3种方法: formset_factory, modelformset_factory和inlineformset_factory。我们接下来分别看下如何使用它们。

    如何使用formset_factory

    对于继承forms.Form的自定义表单,我们可以使用formset_factory。我们可以通过设置extra和max_num属性来确定我们想要展示的表单数量。注意: max_num优先级高于extra。比如下例中,我们想要显示3个空表单(extra=3),但最后只会显示2个空表单,因为max_num=2。

    from django import forms
    
    
    class BookForm(forms.Form):
        name = forms.CharField(max_length=100)
        title = forms.CharField()
        pub_date = forms.DateField(required=False)
    
    
    # forms.py - build a formset of books
    
    from django.forms import formset_factory
    from .forms import BookForm
    
    # extra: 想要显示空表单的数量
    # max_num: 表单显示最大数量,可选,默认1000
    
    BookFormSet = formset_factory(BookForm, extra=3, max_num=2)
    View Code

    在视图文件views.py里,我们可以像使用form一样使用formset。

    # views.py - formsets example.
    from .forms import BookFormSet
    from django.shortcuts import render
    
    def manage_books(request):
        if request.method == 'POST':
            formset = BookFormSet(request.POST, request.FILES)
            if formset.is_valid():
                # do something with the formset.cleaned_data
                pass
        else:
            formset = BookFormSet()
        return render(request, 'manage_books.html', {'formset': formset})
    View Code

    模板里可以这样使用formset

    <form action=”.” method=”POST”>
    {{ formset }}
    </form>

    也可以这样使用

    <form method="post">
        {{ formset.management_form }}
        <table>
            {% for form in formset %}
            {{ form }}
            {% endfor %}
        </table>
    </form>
    View Code

    关于其他更多信息,请参考链接:

    https://blog.csdn.net/weixin_42134789/article/details/81505983

    举例

    新建一个django项目,注意:django版本为1.11

    修改urls.py

    from django.conf.urls import url
    from django.contrib import admin
    
    from app01 import views
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^$', views.index),
        url(r'^index/', views.index),
    ]
    View Code

    修改views.py

    from django.shortcuts import render
    
    # Create your views here.
    def index(request):
        return render(request,"index.html")
    View Code

    在templates目录下创建文件index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .hide{
                display: none;
            }
        </style>
    </head>
    <body>
        <form method="post">
            {{ formset.management_form }}
            {% csrf_token %}
            <table border="1">
                <tr>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>邮箱</th>
                </tr>
                <tr>
                    <td><input type="text" name="user" placeholder="请输入用户名"></td>
                    <td><input type="text" name="pwd" placeholder="请输入密码"></td>
                    <td><input type="text" name="email" placeholder="请输入邮箱"></td>
                </tr>
                <tr>
                    <td><input type="text" name="user" placeholder="请输入用户名"></td>
                    <td><input type="text" name="pwd" placeholder="请输入密码"></td>
                    <td><input type="text" name="email" placeholder="请输入邮箱"></td>
                </tr>
                <tr>
                    <td><input type="text" name="user" placeholder="请输入用户名"></td>
                    <td><input type="text" name="pwd" placeholder="请输入密码"></td>
                    <td><input type="text" name="email" placeholder="请输入邮箱"></td>
                </tr>
            </table>
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    View Code

    启动项目,访问页面: http://127.0.0.1:8000/

    点击提交按钮,需要一次性写入3条信息到数据库中

    如果使用post提交,那么后台需要做form表单验证。之前学习的form组件,一次验证一行数据。

    使用form

    修改views.py

    from django.shortcuts import render
    from django import forms
    
    # Create your views here.
    class UserForm(forms.Form):
        user = forms.CharField()
        pwd = forms.CharField()
        email = forms.CharField()
    
    def index(request):
        if request.method == 'GET':
            form = UserForm()
            return render(request,"index.html",{'form':form})
    View Code

    修改index.html

    由于一个form对象,只能生成一行。那么3行,得需要复制3次

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .hide{
                display: none;
            }
        </style>
    </head>
    <body>
        <form method="post">
            {{ formset.management_form }}
            {% csrf_token %}
            <table border="1">
                <tr>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>邮箱</th>
                </tr>
                <tr>
                    {% for field in form %}
                        <td>{{ field }}</td>
                    {% endfor %}
                </tr>
                <tr>
                    {% for field in form %}
                        <td>{{ field }}</td>
                    {% endfor %}
                </tr>
                <tr>
                    {% for field in form %}
                        <td>{{ field }}</td>
                    {% endfor %}
                </tr>
            </table>
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    View Code

    刷新页面,效果如下:

    假设不知道有多少条数据呢?得使用formset

    使用formset

    修改views.py

    extra设置展示的表单数量,如果是0,则不会生成!

    min_num=1,第一个form表单,必须是完整的。否则提示This field is required

    from django.shortcuts import render
    from django import forms
    
    # Create your views here.
    class UserForm(forms.Form):
        user = forms.CharField()
        pwd = forms.CharField()
        email = forms.CharField()
    
    def index(request):
        # 生成一个类,它是form集合。extra设置展示的表单数量
        # min_num至少提交一个完整的form表单
        UserFormSet = forms.formset_factory(UserForm,extra=2,min_num=1)
        if request.method == 'GET':
            formset = UserFormSet()
            return render(request,"index.html",{'formset':formset})
    
        formset = UserFormSet(request.POST)
        if formset.is_valid():
            print(formset.cleaned_data)  # 验证通过的数据
            print('验证成功')
    
        return render(request, "index.html", {'formset': formset})
    View Code

    修改index.html

    它必须使用2层for循环。第一次是fromset,它是一个集合,集合每一个元素都是form对象。

    第二次是form对象

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .hide{
                display: none;
            }
        </style>
    </head>
    <body>
        <form method="post">
            {{ formset.management_form }}
            {% csrf_token %}
            <table border="1">
                <tr>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>邮箱</th>
                </tr>
                {% for form in formset %}
                <tr>
                    {% for field in form %}
                        <td>{{ field }}{{ field.errors.0 }}</td>
                    {% endfor %}
                </tr>
                {% endfor %}
            </table>
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    View Code

    关闭网页,重新访问,效果如下:

    它有3个form对象,分别是extra和min_num设置的总和。min_num就是第一个!

    直接提交一个空的表单,它会提示This field is required

    提交数据

    查看Pycharm控制台输出:

    [{'pwd': '11', 'email': '11', 'user': '11'}, {'pwd': '22', 'email': '22', 'user': '22'}, {}]
    验证成功

    可以看出formset.cleaned_data是一个列表,列表的每一个元素,都是字典。

    由于第3行没有填写,它是一个空字典

    批量保存数据

    修改models.py

    from django.db import models
    
    # Create your models here.
    class User(models.Model):
        """
        用户表
        """
        username = models.CharField(verbose_name='用户名', max_length=32)
        password = models.CharField(verbose_name='密码', max_length=32)
        email = models.EmailField(verbose_name='邮箱', max_length=32)
        
        def __str__(self):
            return self.username
    View Code

    使用2个命令生成表

    python manage.py makemigrations
    python manage.py migrate

    修改views.py,使用ModelForm做添加,并且对数据做校验

    from django.shortcuts import render,HttpResponse
    from django import forms
    from app01 import models
    
    # Create your views here.
    class UserForm(forms.ModelForm):
        class Meta:
            model = models.User  # user表
            fields = '__all__'  # 所有字段
    
    def index(request):
        # 生成一个类,它是form集合。extra设置展示的表单数量
        # min_num至少提交一个完整的form表单
        UserFormSet = forms.formset_factory(UserForm,extra=2,min_num=1)
        if request.method == 'GET':
            formset = UserFormSet()
            return render(request,"index.html",{'formset':formset})
    
        formset = UserFormSet(request.POST)
        if formset.is_valid():
            # print(formset.cleaned_data)  # 验证通过的数据
            flag = False  # 标志位
            for row in formset.cleaned_data:
                if row:  # 判断数据不为空
                    # print(row)  # 它是一个字典
                    # **表示将字典扩展为关键字参数
                    res = models.User.objects.create(**row)
                    if res:  # 判断返回信息
                        flag = True
    
            if flag:
                return HttpResponse('添加成功')
            else:
                return HttpResponse('添加失败')
    
        return render(request, "index.html", {'formset': formset})
    View Code

    注意:**表示将字典扩展为关键字参数

    问题来了,为什么要用**呢?

    先来看一下,使用create的添加示例

    models.User.objects.create(username='xiao',password='123',email='123@qq.com')

    由于row是一个字典,而create需要关键字参数。那么**就可以完美解决这个问题!论Python基础的重要性!

    重启django,刷新页面。

    如果添加不合法的数据,会有提示!

    输入2条正确的值

    提示添加成功

    查看用户表,发现有2条数据了!

    批量修改数据

     修改urls.py,增加路径

    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^$', views.index),
        url(r'^index/', views.index),
        url(r'^batch_update/', views.batch_update),
    ]
    View Code

    修改views.py,增加视图函数

    from django.shortcuts import render,HttpResponse
    from django import forms
    from app01 import models
    
    # Create your views here.
    class UserForm(forms.ModelForm):
        id = forms.IntegerField()
        class Meta:
            model = models.User  # user表
            fields = '__all__'  # 所有字段
    
    def index(request):
        # 生成一个类,它是form集合。extra设置展示的表单数量
        # min_num至少提交一个完整的form表单
        UserFormSet = forms.formset_factory(UserForm,extra=2,min_num=1)
        if request.method == 'GET':
            formset = UserFormSet()
            return render(request,"index.html",{'formset':formset})
    
        formset = UserFormSet(request.POST)
        if formset.is_valid():
            # print(formset.cleaned_data)  # 验证通过的数据
            flag = False  # 标志位
            for row in formset.cleaned_data:
                if row:  # 判断数据不为空
                    # print(row)  # 它是一个字典
                    # **表示将字典扩展为关键字参数
                    res = models.User.objects.create(**row)
                    if res:  # 判断返回信息
                        flag = True
    
            if flag:
                return HttpResponse('添加成功')
            else:
                return HttpResponse('添加失败')
    
        return render(request, "index.html", {'formset': formset})
    
    def batch_update(request):
        """
        批量更新
        :param request:
        :return:
        """
        queryset = models.User.objects.all().values()  # 查询表的所有记录
        # extra=0表示不渲染form表单。如果指定为0,页面会多一个空的form表单。强迫症者表示不爽!
        UserFormSet = forms.formset_factory(UserForm, extra=0)
        if request.method =='GET':
            # initial 参数用来给 ModelForm 定义初始值。注意:同名 Field 会覆盖 instance 的值
            formset = UserFormSet(initial=queryset)
            # print(queryset)
            return render(request,'batch_update.html',{'formset':formset})
        else:
            formset = UserFormSet(request.POST)
            # print(request.POST)
            if formset.is_valid(): # 判断数据
                flag = False  # 标志位
                for row in formset.cleaned_data:
                    # pop() 方法删除字典给定键 key 及对应的值,返回值为被删除的值
                    id = row.pop('id')  # 获取id
                    if id:  # 判断数据不为空
                        # print(row)  # 它是一个字典
                        # **表示将字典扩展为关键字参数
                        res = models.User.objects.filter(id=id).update(**row)
                        if res:  # 判断返回信息
                            flag = True
    
                if flag:
                    return HttpResponse('修改成功')
                else:
                    return HttpResponse('修改失败')
            else:
                return render(request, "batch_update.html", {'formset': formset})
    View Code

    在templates目录新建文件batch_update.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .hide{
                display: none;
            }
        </style>
    </head>
    <body>
        <form method="post">
            {{ formset.management_form }}
            {% csrf_token %}
            <table border="1">
                <tr>
                    <th>用户名</th>
                    <th>密码</th>
                    <th>邮箱</th>
                </tr>
                {% for form in formset %}
                <tr>
                    {% for field in form %}
                        {#判断最后一个字段,也就是id#}
                        {% if forloop.last %}
                            {#隐藏id#}
                            <td class="hide">{{ field }}</td>
                        {% else %}
                            {#显示其他字段#}
                            <td>{{ field }}{{ field.errors.0 }}</td>
                        {% endif %}
                    {% endfor %}
                </tr>
                {% endfor %}
            </table>
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    View Code

    访问页面:http://127.0.0.1:8000/batch_update/

    修改密码

    点击提交,效果如下:

    查看表记录

     

    注释事项

    在更新的时候,需要有一个id。由于id字段是AutoField,该 Field 不会出现在 ModelForm 表单中。

    因此,在views.py里面的UserForm中,定义了一个id字段。

    那么在batch_update.html渲染时,就会显示这个id字段。但是更新时,这个id是主键,是不允许更改的。

    所以用了一个很巧妙的办法,使用css样式,来隐藏它。

    使用ModelForm渲染表单时,自定义的字段它是排在最后面的。所以使用forloop.last就可以定位到ID!

    最后点击提交时,request.POST就带有id数据。那么后端,就可以通过id进行更新了!

    关于其他更多的ModelForm信息,请参考链接:

    https://blog.csdn.net/Leo062701/article/details/80963625

    三、ORM之limit_choices_to

    limit_choices_to介绍

    它是在Admin或ModelForm中显示关联数据时,提供的条件

    比如:

    - limit_choices_to={'nid__gt': 5}
    - limit_choices_to=lambda : {'nid__gt': 5}
    
    from django.db.models import Q
    - limit_choices_to=Q(nid__gt=10)
    - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
    - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')

    举例

    打开权限管理项目,访问url: http://127.0.0.1:8000/rbac/menu/list/

    点击删除账单后面的编辑按钮,进入之后,点击父权限

    注意:这里的父权限,展示不对。为什么?因为它必须是能作为菜单权限的!

    而这里却展示了所有url。

    怎么解决呢?有2个办法

    第一:页面渲染时,对数据做判断。

    第二:使用limit_choices_to(推荐)

    修改 rbac-->models.py,找到字段

    parent = models.ForeignKey(verbose_name='父权限',to='Permission',null=True,blank=True)

    更改为

    parent = models.ForeignKey(verbose_name='父权限',to='Permission',null=True,blank=True,limit_choices_to={'menu__isnull': False})

    这个时候,不需要执行那2个命令

    直接刷新页面,效果如下:

    补充另外一个选项help_text,它是用来提示帮助信息

    比如:

    help_text="对于无法作为菜单的URL,可以为其选择一个可以作为菜单的权限,那么访问时,则默认选中此权限",

    parent 字段完整信息如下:

    parent = models.ForeignKey(verbose_name='父权限', to='Permission', null=True, blank=True,
                                   limit_choices_to={'menu__isnull': False},
                                   help_text="对于无法作为菜单的URL,可以为其选择一个可以作为菜单的权限,那么访问时,则默认选中此权限")

    在输入框的右侧,就可以显示信息

    在模板中,使用{{ field.help_text }}渲染,效果如下:

     由于父权限和菜单是二选一的,前端无法做验证,需要在后端做验证。

    使用全局钩子,来做判断!

    def clean(self):
        menu = self.cleaned_data.get('menu_id')
        pid = self.cleaned_data.get('pid_id')
        if menu and pid:
            self.add_error('menu','菜单和根权限同时只能选择一个')

    四、构造家族结构

    先来看一下Laravel官网的评论区,这是一个评论树形结构

    其中可以针对任何一个人进行回复.说白一点就是多叉树,类似的结构如下:

    文章的id就是根节点,每一个子节点都保存着上一级节点的id,同级节点之间使用根据创建时间进行先后排序。

    评论数型结构

    根评论

    comment_list = [
        {'id':1, 'title':'写的不错', 'pid':None}
    ]

    子评论

    comment_list = [
        {'id':1, 'title':'写的不错', 'pid':None},
        {'id':2, 'title':'还不错', 'pid':1}
    ]

    三级评论

    comment_list = [
        {'id':1, 'title':'写的不错', 'pid':None},
        {'id':2, 'title':'还不错', 'pid':1},
        {'id':3, 'title':'什么玩意', 'pid':2}
    ]

    后面的层级,依次类推。

    最终数据如下:

    comment_list = [
        {'id':1, 'title':'写的不错', 'pid':None},
        {'id':2, 'title':'还不错', 'pid':1},
        {'id':3, 'title':'什么玩意', 'pid':2},
        {'id':4, 'title':'q1', 'pid':2},
        {'id':5, 'title':'666', 'pid':1},
        {'id':6, 'title':'去你的吧', 'pid':3},
    ]

    层级关系应该是这个样子的

    {'id':1, 'title':'写的不错', 'pid':None},
        {'id':2, 'title':'还不错', 'pid':1},
            {'id':3, 'title':'什么玩意', 'pid':2}
                {'id':6, 'title':'去你的吧', 'pid':3},
                
            {'id':4, 'title':'q1', 'pid':2}
        
        {'id':5, 'title':'666', 'pid':1},

    如何构造

    转换字典

    先将数据转换成字典

    comment_dict = {}
    for item in comment_list:
        comment_dict[item['id']] = item

    执行之后,结果是一个大字典

    comment_dict = {
        1: {'title': '写的不错', 'id': 1, 'pid': None},
        2: {'title': '还不错', 'id': 2, 'pid': 1}, 
        3: {'title': '什么玩意', 'id': 3, 'pid': 2}, 
        4: {'title': 'q1', 'id': 4, 'pid': 2},
        5: {'title': '666', 'id': 5, 'pid': 1}, 
        6: {'title': '去你的吧', 'id': 6, 'pid': 3}
    }

    注意:这里面的每一个小字典,和comment_list里面的字典,用的是同一个内存地址

    证明一下,改个数据

    comment_list = [
        {'id':1, 'title':'写的不错', 'pid':None},
        {'id':2, 'title':'还不错', 'pid':1},
        {'id':3, 'title':'什么玩意', 'pid':2},
        {'id':4, 'title':'q1', 'pid':2},
        {'id':5, 'title':'666', 'pid':1},
        {'id':6, 'title':'去你的吧', 'pid':3},
    ]
    
    comment_dict = {}
    for item in comment_list:
        comment_dict[item['id']] = item
    
    print('字典更改前',comment_dict[1])
    comment_list[0]['title'] = '写的不错~~~'
    print('字典更改后',comment_dict[1])
    View Code

    执行输出:

    字典更改前 {'title': '写的不错', 'pid': None, 'id': 1}
    字典更改后 {'title': '写的不错~~~', 'pid': None, 'id': 1}

    发现了吧!明明更改的是大列表,但是大字典里面的数据,也随之变动!

    加children

    加children的目的,是为了存放除了根评论之外的,比如:二级和三级评论!

    comment_list = [
        {'id':1, 'title':'写的不错', 'pid':None},
        {'id':2, 'title':'还不错', 'pid':1},
        {'id':3, 'title':'什么玩意', 'pid':2},
        {'id':4, 'title':'q1', 'pid':2},
        {'id':5, 'title':'666', 'pid':1},
        {'id':6, 'title':'去你的吧', 'pid':3},
    ]
    
    comment_dict = {}
    for item in comment_list:
        # 增加新的key为children,值为空列表
        item['children'] = []
        comment_dict[item['id']] = item
    
    for row in comment_list:
        if not row['pid']:
            continue
    View Code

    执行之后,comment_dict结构如下:

    comment_dict = {
        1: {'title': '写的不错', 'id': 1, 'pid': None,'children':[]},
        2: {'title': '还不错', 'id': 2, 'pid': 1,'children':[]},
        3: {'title': '什么玩意', 'id': 3, 'pid': 2,'children':[]},
        4: {'title': 'q1', 'id': 4, 'pid': 2,'children':[]},
        5: {'title': '666', 'id': 5, 'pid': 1,'children':[]},
        6: {'title': '去你的吧', 'id': 6, 'pid': 3,'children':[]}
    }

    最加到children

    判断是根评论,跳过循环。否则最加到children

    comment_list = [
        {'id':1, 'title':'写的不错', 'pid':None},
        {'id':2, 'title':'还不错', 'pid':1},
        {'id':3, 'title':'什么玩意', 'pid':2},
        {'id':4, 'title':'q1', 'pid':2},
        {'id':5, 'title':'666', 'pid':1},
        {'id':6, 'title':'去你的吧', 'pid':3},
    ]
    
    comment_dict = {}
    for item in comment_list:
        # 增加新的key为children,值为空列表
        item['children'] = []
        comment_dict[item['id']] = item
    
    for row in comment_list:
        if not row['pid']:  # 判断根评论
            continue  # 跳过此次循环
        pid = row['pid']  # 获取pid
        # 最加到children中
        comment_dict[pid]['children'].append(row)
    View Code

    执行之后,comment_dict结构如下:

    {
        "1": {
            "pid": null,
            "id": 1,
            "title": "写的不错",
            "children": [{
                "pid": 1,
                "id": 2,
                "title": "还不错",
                "children": [{
                    "pid": 2,
                    "id": 3,
                    "title": "什么玩意",
                    "children": [{
                        "pid": 3,
                        "id": 6,
                        "title": "去你的吧",
                        "children": []
                    }]
                }, {
                    "pid": 2,
                    "id": 4,
                    "title": "q1",
                    "children": []
                }]
            }, {
                "pid": 1,
                "id": 5,
                "title": "666",
                "children": []
            }]
        },
        "2": {
            "pid": 1,
            "id": 2,
            "title": "还不错",
            "children": [{
                "pid": 2,
                "id": 3,
                "title": "什么玩意",
                "children": [{
                    "pid": 3,
                    "id": 6,
                    "title": "去你的吧",
                    "children": []
                }]
            }, {
                "pid": 2,
                "id": 4,
                "title": "q1",
                "children": []
            }]
        },
        "3": {
            "pid": 2,
            "id": 3,
            "title": "什么玩意",
            "children": [{
                "pid": 3,
                "id": 6,
                "title": "去你的吧",
                "children": []
            }]
        },
        "4": {
            "pid": 2,
            "id": 4,
            "title": "q1",
            "children": []
        },
        "5": {
            "pid": 1,
            "id": 5,
            "title": "666",
            "children": []
        },
        "6": {
            "pid": 3,
            "id": 6,
            "title": "去你的吧",
            "children": []
        }
    }
    View Code

    上面的结构,是用json格式化工具之后的!

    这里面的字典,虽然有重复的,但是它们是同一个内存地址!

    可以看到children的数据,都加上了

    终极版

    import json
    comment_list = [
        {'id': 1, 'title': '写的不错', 'pid': None},
        {'id': 2, 'title': '还不错', 'pid': 1},
        {'id': 3, 'title': '什么玩意', 'pid': 2},
        {'id': 4, 'title': 'q1', 'pid': 2},
        {'id': 5, 'title': '666', 'pid': 1},
        {'id': 6, 'title': '去你的吧', 'pid': 3},
    ]
    
    comment_dict = {}
    for item in comment_list:
        # 增加新的key为children,值为空列表
        item['children'] = []
        comment_dict[item['id']] = item
    
    result = []  # 空列表
    for row in comment_list:
        if not row['pid']:  # 判断根评论
            result.append(row)  # 添加到列表
        else:
            pid = row['pid']  # 获取pid
            # 最加到children中
            comment_dict[pid]['children'].append(row)
    
    print(json.dumps(result))
    View Code

    执行输出:

    注意:这个是使用网页版json工具,进行排版的!!!

    [{
        "id": 1,
        "children": [{
            "id": 2,
            "children": [{
                "id": 3,
                "children": [{
                    "id": 6,
                    "children": [],
                    "pid": 3,
                    "title": "去你的吧"
                }],
                "pid": 2,
                "title": "什么玩意"
            }, {
                "id": 4,
                "children": [],
                "pid": 2,
                "title": "q1"
            }],
            "pid": 1,
            "title": "还不错"
        }, {
            "id": 5,
            "children": [],
            "pid": 1,
            "title": "666"
        }],
        "pid": null,
        "title": "写的不错"
    }]
    View Code

     关于客户权限管理系统,详细步骤没有时间写了,附上终极版本

    链接:https://pan.baidu.com/s/1uVM5iAl4lCqAhdPZSSyd_A 密码:yj4z

    未完待续...

  • 相关阅读:
    323. Number of Connected Components in an Undirected Graph
    418. Sentence Screen Fitting
    417. Pacific Atlantic Water Flow
    416. Partition Equal Subset Sum
    415. Add Strings
    245. Shortest Word Distance III
    [AHOI2009]维护序列
    [洛谷P1439]排列LCS问题
    [Vijos P1369]难解的问题
    [codevs3657]括号序列
  • 原文地址:https://www.cnblogs.com/xiao987334176/p/9544831.html
Copyright © 2011-2022 走看看