昨日內容回顧

1. 權限系統的流程? 2. 權限的表有幾個? 3. 技術點 中間件 session orm - 去重 - 去空 inclusion_tag filter 有序字典 settings配置 引入靜態文件 url別名 namespace 路由分發 構造數據結構 ModelForm 組件應用 admin icon爬蟲 mark_safe 下載文件
一、客戶管理之 編輯權限(二)
下載代碼:
鏈接: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'), ]
進入目錄 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')
訪問測試頁面: http://127.0.0.1:8000/rbac/permission/test/,效果如下:
查看Pycharm控制台輸出:

None 1 1 1 1 None 7 7 7 None
再篩選不能做菜單的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')
刷新頁面,查看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+)/
點擊菜單里面的超鏈接,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'])
修改 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 %}
訪問頁面: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")
這段代碼看似並不復雜,然而當表單數量很多或不確定時,這個代碼會非常冗長。我們希望能控制表單的數量,這是我們就可以用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)
在視圖文件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})
模板里可以這樣使用formset
<form action=”.” method=”POST”>
{{ formset }}
</form>
也可以這樣使用

<form method="post"> {{ formset.management_form }} <table> {% for form in formset %} {{ form }} {% endfor %} </table> </form>
關於其他更多信息,請參考鏈接:
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), ]
修改views.py

from django.shortcuts import render # Create your views here. def index(request): return render(request,"index.html")
在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>
啟動項目,訪問頁面: 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})
修改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>
刷新頁面,效果如下:
假設不知道有多少條數據呢?得使用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})
修改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>
關閉網頁,重新訪問,效果如下:
它有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
使用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})
注意:**表示將字典擴展為關鍵字參數
問題來了,為什么要用**呢?
先來看一下,使用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), ]
修改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})
在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>
訪問頁面: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])
執行輸出:
字典更改前 {'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
執行之后,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)
執行之后,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": [] } }
上面的結構,是用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))
執行輸出:
注意:這個是使用網頁版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": "寫的不錯" }]
關於客戶權限管理系統,詳細步驟沒有時間寫了,附上終極版本
鏈接:https://pan.baidu.com/s/1uVM5iAl4lCqAhdPZSSyd_A 密碼:yj4z
未完待續...