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

 

未完待續...

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM