rbac 权限分配, 基于formset实现,批量增加
這里需要兩個知識點:
- formset
- 自動發現項目中的URL
1. 什么是formset:
Django中 form組件 或 ModelForm組件,用于做一個表單的驗證。 接收前端form表單中的數據,并進行驗證。 并且還可以用于表單的渲染工作。 (就是直接循環form對象,每一個字段都會被渲染成一個標簽。并放在form標簽中。)
注: 我提到了,是 “一個” 表單的驗證。? 就是說前端的數據過來, 不管是增刪改查,都是對數據庫中的 “一行” 記錄,一條數據進行的處理。??? 都是單一的。
ok, 既然如此,但我想要進行批量操作的時候,對“多個” 表單進行驗證的時候。? 就使用 formset 啦! 可以理解為他是把 一堆的form標簽, 套嵌進了一個 formset中, 然后一起進行驗證。
2. 那么怎么使用的呢?
先搞一個,新的項目。 先用一下, 然后再 集成到,我的rbac項目里面去。
先遷移兩張表,用一下。 因為我不想寫了!所以直接復制,項目的。
Menu 表, 先手動錄入兩條信息。
Permission 表, 還是空的, 所以試試 formset 給這張表,批量的添加。
編寫form類:
class MultiPermissionForm(forms.Form):title = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}))url = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}))name = forms.CharField(widget=forms.TextInput(attrs={"class": "form-control"}))menu_id = forms.ChoiceField( # ChoiceField 和 choices 就表示數據源choices=[(None, "---------")],widget=forms.Select(attrs={"class": "form-control"}),required=False)pid_id = forms.ChoiceField(choices=[(None, "---------")],widget=forms.Select(attrs={"class": "form-control"}),required=False)# 因為 menu_id 和 pid_id ,應該是一個可以選擇的下拉框形式, 所以重寫了 __init__初始化函數。# 讓fields對象中的這兩個字段, 與數據庫中查詢出的結果,進行拼接def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)self.fields["menu_id"].choices += models.Menu.objects.values_list("mid", "title")self.fields["pid_id"].choices += models.Permission.objects.filter(pid__isnull=True).exclude(menu__isnull=True).values_list("id", "title")# 過濾出 pid==Null的 可以做二級菜單的, exclude排除 menu==Null,不屬于任何一個一級菜單的。所有的權限記錄 注意繼承的是 forms.form 不再是 forms.ModelForm 這其中menu_id 和 pid_id是外鍵,一對多的關系。 所以我們需要讓頁面,展示的是一個 select 下拉框的樣式。所以用的是 forms.ChoiceField? 參數 choices=[(None, "---------")] 這是一個默認值,用戶什么都沒選的時候, 頁面展示為"---------"數據庫存儲為?None. ?
不過最終,要是要,將外鍵關聯的表中的數據, 給展示到這里的,所以, 重寫了 __init__ 初始化方法。從數據庫中取出數據并進行拼接。
?因為重寫的是? 父類的方法。 所以是 super。? 每個對象自己都有一個? fields 對象。 這個對象里面包含了我們類中寫的所有的字段。 所以在這里 對? menu_id 字段 和 pid_id 字段。進行拼接。
因為我們拼接的是? choices 這個參數。他 接收的是,列表格式,所以使用 values_list 取出數據庫中的值。 并不是所有的都取出來, 因為我們需要的是, 1.保存到數據庫中的外鍵關系,有個主鍵id就好。 2.然后是需要進行頁面展示的 title 字段。
至于需要哪些,根據需求來: 我這里pid_id 需要的是。 Permission表中 pid字段為Null的, 這些字段是可以做二級菜單使用的。
OK 到此為止, 一個表單的 form 算是弄好了!
然后就是想要進行批量的,添加。 需要借助一個Django內置的模塊:
? from django.forms import formset_factory
formset_factory 接收兩個參數, 一個是我們剛剛寫好的 MultiPermissionForm 然后是 extra 。
第一個參數是,我要渲染的form。 第二個參數是要渲染幾次。
看代碼:
然后是,前端頁面的渲染:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="x-ua-compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Title</title> </head> <body> <form method="post">{% csrf_token %}{{ formset.management_form }}<table border="1"><thead><tr><th>標題</th><th>url</th><th>Code</th><th>Menu</th><th>權限</th></tr></thead><tbody>{% for form in formset %}<tr>{% for field in form %}<td>{{ field }} <span>{{ field.errors.0 }}</span></td>{% endfor %}</tr>{% endfor %}</tbody></table><input type="submit" value="提交"> </form></body> </html> templates除了Django必須要的? {%csre_token%}? 還有一個也是必須要寫的{{ formset.management_form }}。如過不寫的話, 可能會遇到上傳數據,丟失的錯誤。
然后看一看,前端頁面的效果吧:
low的一筆, 不過湊合看吧!
so 我們看一看,運行的效果怎么樣?
1. 第一次我什么都不寫,直接提交。
臥槽, 竟然成功了? 什么情況, 說好的驗證呢?? 看一看 print(formset.cleaned_data) 的結果:
[{}, {}] ?
emmmm ok, 雖然經過了, formset.is_valid()? 但是返回, 一個空列表是什么意思?
解釋一下: 如果我提交的時候,什么都不寫的話, form 組件就不幫我進行驗證了。 但是入過我寫了東西,才會進行驗證。
還是看一張圖解釋比較清晰:
這就很清楚了, 第一行數據, 我寫了一個標題。 他幫我進行了驗證, 然后沒通過的地方,也返回了相應的錯誤信息。
?但是 第二行,數據。 我什么都沒寫, form組件, 就沒有給我進行驗證。所以沒有驗證,也就不存在錯誤了, 對吧:
看一看 print(formset.cleaned_data) 的結果:
這次他沒有執行, 因為? if formset.is_valid(): 沒有通過驗證。
如果你只寫滿一行 和啥樣呢?? [{'title': '這是一個標題', 'url': '/costomer/list/', 'name': 'costomer_list', 'menu_id': '2', 'pid_id': ''}, {}]
第二個還是空的。
OK 明白了,返回的是一個列表, 列表中是字典格式的每一行數據 看看怎么保存到數據庫吧:
肯定是 for 循環了。
?這里有兩種,保存的方式。
1. models.Permission.objects.create(**row)? 直接使用。 ORM 的方法。 將row 這個字典。 傳給 create() 可以進行保存。
2. 也可以 先生成一個 model 對象。 然后這個對象執行 save()? 方法。 都可以進行保存。
?OK 看效果:
然后 最后還有一個問題就是, 關于唯一性的問題的? 就是數據庫中, 添加了 unipue=Ture 的這個字段:
所以我們就得 捕獲到這個錯誤,然后進行處理:
然后 這里有個大大大大大的問題,怎樣才能, 讓這個字段出的這個唯一的錯誤, 相應的顯示到這個字段上呢?
1. 我們知道 每一個form對象, 都有兩個屬性 他的字段 和他的錯誤信息: formset是啥樣的呢?
[ form(字段,錯誤), form(字段,錯誤), form(字段,錯誤), form(字段,錯誤) ]? 大概就是這個樣子。
2. 然后從前端返回的數據呢? 是什么樣的呢?
[ {},? {},? {}, {} ]?? 就是這個樣子。
他們唯一有練習的地方就是,? 第一個form對象, 對應的就是? 第一個 {}? 的 索引。
所以我接下來 進行循環的 時候, 就不能按照上面的方法,再去循環了, 而是應該這樣。
formset.cleaned_data[i] # cleaned_data就是前端返回來的 [ {},? {},? {}, {} ] formset.errors[i] # errors 就是所有的錯誤信息,
取出的時候, 就按照索引進行,取出數據。 中間進行判斷錯誤。 如果有錯誤,就加到當前索引下的 errors 列表中。 然后返回前端進行渲染。
?這里有一個小細節,? formset.cleaned_data[i]? 和 formset.errors[i]? 這兩句是 互斥的, 也就是如果發生錯誤 cleaned_data 你就拿不到數據了。
formset.cleaned_data 是這樣工作的: 檢查如果formset中沒有錯誤信息,則將用戶的提交的數據獲取到。他不是一次性把每一個form對象中的數據都獲取到。而是執行一次就獲取一次 form 對象中的數據。
什么意思呢? 就是說, 如果循環的第一次就發生了錯誤, 那后面的數據, 你也得不到了。 but way?
因為formset.errors[i]? 這個就相當于,人為的給form中添加上了一個錯誤;formset.cleaned_data去檢查的時候,就會檢測到這個錯誤,然后后面的數據你就別想了
SO 看代碼吧
if not row:
continue # 如果是空的數據, 下面就不用走了。try:obj = models.Permission(**row)obj.validate_unique()# 檢測當前對象在數據庫是否存在 唯一的異常。如果有他就會拋出一個異常obj.save()except Exception as e:# 捕獲到這個異常, e就是錯誤信息的提示
# 為什么是 update(e) 因為 errors是這樣子的 [{"title":[]}] 在這里就是獲取到其中一個字典, 然后給字典 update()formset.errors[i].update(e)flag = Falseif flag:return HttpResponse("提交成功")else:return render(request, "multi_add.html", {"formset": formset})return render(request, "multi_add.html", {"formset": formset})formset = formset_class() # 然后將formset_class 進行實例化return render(request, "multi_add.html", {"formset": formset})
?ok? 搞定了。 批量增加的時候, 就沒啥別的問題了。
再就是批量修改了:? 煩煩煩。。。。。。。。。。。。
?
轉載于:https://www.cnblogs.com/chengege/p/10718076.html
總結
以上是生活随笔為你收集整理的rbac 权限分配, 基于formset实现,批量增加的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 流畅的Python(Fluent Pyt
- 下一篇: 每周一题 扫雷问题