Django 內(nèi)置權(quán)限擴(kuò)展案例詳解
當(dāng)Django的內(nèi)置權(quán)限無法滿足需求的時(shí)候就自己擴(kuò)展吧~
背景介紹
overmind項(xiàng)目使用了Django內(nèi)置的權(quán)限系統(tǒng),Django內(nèi)置權(quán)限系統(tǒng)基于model層做控制,新的model創(chuàng)建后會(huì)默認(rèn)新建三個(gè)權(quán)限,分別為:add、change、delete,如果給用戶或組賦予delete的權(quán)限,那么用戶將可以刪除這個(gè)model下的所有數(shù)據(jù)。
原本overmind只管理了我們自己部門的數(shù)據(jù)庫,權(quán)限設(shè)置只針對具體的功能不針對細(xì)粒度的數(shù)據(jù)庫實(shí)例,例如用戶A 有審核的權(quán)限,那么用戶A 可以審核所有的DB,此時(shí)使用內(nèi)置的權(quán)限系統(tǒng)就可以滿足需求了,但隨著系統(tǒng)的不斷完善要接入其他部門的數(shù)據(jù)庫管理,這就要求針對不同用戶開放不同DB的權(quán)限了,例如A部門的用戶只能操作A部門的DB,Django內(nèi)置基于model的權(quán)限無法滿足需求了。
實(shí)現(xiàn)過程
先來確定下需求:
1. 保持原本的基于功能的權(quán)限控制不變,例如用戶A有查詢權(quán)限,B有審核權(quán)限
2. 增加針對DB實(shí)例的權(quán)限控制,例如用戶A只能查詢特定的DB,B只能審核特定的DB
對于上邊需求1用內(nèi)置的權(quán)限系統(tǒng)已經(jīng)可以實(shí)現(xiàn),這里不贅述,重點(diǎn)看下需求2,DB信息都存放在同一個(gè)表里,不同用戶能操作不同的DB,也就是需要把每一條DB信息與有權(quán)限操作的用戶進(jìn)行關(guān)聯(lián),為了方便操作,我們考慮把DB跟用戶組關(guān)聯(lián),在用戶組里的用戶都有權(quán)限,而操作類型經(jīng)過分析主要有兩類讀和寫,那么需要給每個(gè)MySQL實(shí)例添加兩個(gè)字段分別記錄對此實(shí)例有讀和寫權(quán)限的用戶組
如下代碼在原來的model基礎(chǔ)上添加 read_groups
和 write_groups
字段,DB實(shí)例跟用戶組應(yīng)是ManyToManyField多對多關(guān)系,一個(gè)實(shí)例可以關(guān)聯(lián)多個(gè)用戶組,一個(gè)用戶組也可以屬于多個(gè)實(shí)例
class Mysql(models.Model): Env = ( (1, 'Dev'), (2, 'Qa'), (3, 'Prod'), ) create_time = models.DateTimeField(auto_now_add=True, verbose_name='創(chuàng)建時(shí)間') update_time = models.DateTimeField(auto_now=True, verbose_name='更新時(shí)間') project_id = models.IntegerField(verbose_name='項(xiàng)目') project_tmp = models.CharField(max_length=128, default='') environment = models.IntegerField(choices=Env, verbose_name='環(huán)境') master_host = models.GenericIPAddressField(verbose_name='master主機(jī)') master_port = models.IntegerField(default=3306, verbose_name='master端口') slave_host = models.GenericIPAddressField(null=True, verbose_name='slave主機(jī)') slave_port = models.IntegerField(null=True, default=3306, verbose_name='slave端口') database = models.CharField(max_length=64, verbose_name='數(shù)據(jù)庫') read_groups = models.ManyToManyField(Group, related_name='read', verbose_name='讀權(quán)限') write_groups = models.ManyToManyField(Group, related_name='write', verbose_name='寫權(quán)限') description = models.TextField(null=True, verbose_name='備注')
model確定了,接下來我們分三部分詳細(xì)介紹下權(quán)限驗(yàn)證的具體實(shí)現(xiàn)
列表頁權(quán)限控制
如上圖列表頁,每個(gè)用戶進(jìn)入系統(tǒng)后只能查看自己有讀權(quán)限的MySQL實(shí)例列表,管理員能查看所有,代碼如下:
def mysql(request): if request.method == 'GET': if request.user.is_superuser: _lists = Mysql.objects.all().order_by('id') else: # 獲取登錄用戶的所有組 _user_groups = request.user.groups.all() # 構(gòu)造一個(gè)空的QuerySet然后合并 _lists = Mysql.objects.none() for group in _user_groups: _lists = _lists | group.read.all() return render(request, 'overmind/mysql.index.html', {'request': request, 'lPage': _lists})
實(shí)現(xiàn)的思路是:獲取登錄用戶的所有組,然后循環(huán)查詢每個(gè)組有讀取權(quán)限的數(shù)據(jù)庫實(shí)例,最后把每個(gè)組有權(quán)限讀的數(shù)據(jù)庫實(shí)例進(jìn)行合并返回
獲取登錄用戶的所有組用到了ManyToMany的查詢方法: request.user.groups.all()
最終返回的一個(gè)結(jié)果是QuerySet,所以我們需要先構(gòu)造一個(gè)空的Queryset: Mysql.objects.none()
QuerySet合并不能用簡單的相加,應(yīng)為: QuerySet-1 | QuerySet-2
查詢接口權(quán)限控制
如上圖系統(tǒng)中有很多功能是需要根據(jù)項(xiàng)目、環(huán)境查詢對應(yīng)的DB信息的,對于此類接口也需要控制用戶只能查詢自己有權(quán)限讀的DB實(shí)例,管理員能查看所有,代碼如下:
def get_project_database(request, project, environment): if request.method == 'GET': _jsondata = {} if request.user.is_superuser: # 返回所有項(xiàng)目和環(huán)境匹配的DB _lists = Mysql.objects.filter( project_id=int(project), environment=int(environment) ) _jsondata = {i.id: i.database for i in _lists} else: # 只返回用戶有權(quán)限查詢的DB _user_groups = request.user.groups.all() for group in _user_groups: # 循環(huán)mysql表中有read_groups權(quán)限的所有組 for mysql in group.read.all(): if mysql.project_id == int(project) and mysql.environment == int(environment): _jsondata[mysql.id] = mysql.database return JsonResponse(_jsondata)
實(shí)現(xiàn)思路與上邊類似,只是多了一步根據(jù)項(xiàng)目和環(huán)境再進(jìn)行判斷
需要根據(jù)group去反查都有哪些DB實(shí)例包含了該組,這里用到了M2M的related_name屬性: group.read.all()
更多關(guān)于Django ORM查詢的內(nèi)容可以看這篇文章 Django model select的各種用法詳解 有詳細(xì)的總結(jié)
執(zhí)行操作權(quán)限控制
除了上邊的兩個(gè)場景之外我們還需要在執(zhí)行具體的操作之前去判斷是否有權(quán)限,例如執(zhí)行審核操作前判斷用戶是否對此DB有寫權(quán)限
有很多地方都需要做這個(gè)判斷,所以把這個(gè)權(quán)限判斷單獨(dú)寫個(gè)方法來處理,代碼如下:
def check_permission(perm, mysql, user): # 如果用戶是超級管理員則有權(quán)限 if user.is_superuser: return True # 取出用戶所屬的所有組 _user_groups = user.groups.all() # 取出Mysql對應(yīng)權(quán)限的所有組 if perm == 'read': _mysql_groups = mysql.read_groups.all() if perm == 'write': _mysql_groups = mysql.write_groups.all() # 用戶組和DB權(quán)限組取交集,有則表示有權(quán)限,否則沒有權(quán)限 group_list = list(set(_user_groups).intersection(set(_mysql_groups))) return False if len(group_list) == 0 else True
實(shí)現(xiàn)思路是:根據(jù)傳入的第三個(gè)用戶參數(shù),來獲取到用戶所有的組,然后根據(jù)傳入的第一個(gè)參數(shù)類型讀取或?qū)懭牒偷诙€(gè)參數(shù)DB實(shí)例來獲取到有權(quán)限的所有組,然后對兩個(gè)組取交集,交集不為空則表示有權(quán)限,為空則沒有
M2M的 .all()
取出來的結(jié)果是個(gè)list,兩個(gè)list取交集的方法為: list(set(list-A).intersection(set(list-B)))
view中使用就很簡單了,如下:
def query(request): if request.method == 'POST': postdata = request.body.decode('utf-8') _host = get_object_or_404(Mysql, id=int(postdata.get('database'))) # 檢查用戶是否有DB的查詢權(quán)限 if check_permission('read', _host, request.user) == False: return JsonResponse({'state': 0, 'message': '當(dāng)前用戶沒有查詢此DB的權(quán)限'})
寫在最后
1. Django有第三方的基于object的權(quán)限管理模塊Django-guardian,本項(xiàng)目沒有使用主要是因?yàn)橐粊頇?quán)限需求并不復(fù)雜,自己實(shí)現(xiàn)也很方便,二來個(gè)人在非必要的情況下并不喜歡引用過多第三方的包,后續(xù)升級維護(hù)都是負(fù)擔(dān)
2. 方案和代碼不盡完美,各位有更好的方案建議或更優(yōu)雅的代碼寫法歡迎與我交流
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- django 實(shí)現(xiàn)編寫控制登錄和訪問權(quán)限控制的中間件方法
- Django Rest framework之權(quán)限的實(shí)現(xiàn)示例
- Django-Rest-Framework 權(quán)限管理源碼淺析(小結(jié))
- 使用django-guardian實(shí)現(xiàn)django-admin的行級權(quán)限控制的方法
- 詳解python如何在django中為用戶模型添加自定義權(quán)限
- django認(rèn)證系統(tǒng)實(shí)現(xiàn)自定義權(quán)限管理的方法
- 利用django-suit模板添加自定義的菜單、頁面及設(shè)置訪問權(quán)限
- Django權(quán)限機(jī)制實(shí)現(xiàn)代碼詳解
- 詳解Django中的權(quán)限和組以及消息
相關(guān)文章
allure結(jié)合python生成測試報(bào)告教程
這篇文章主要介紹了allure結(jié)合python生成測試報(bào)告教程,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06python 中Arduino串口傳輸數(shù)據(jù)到電腦并保存至excel表格
這篇文章主要介紹了python Arduino串口傳輸數(shù)據(jù)到電腦并保存至excel表格,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10python樹莓派通過隊(duì)列實(shí)現(xiàn)進(jìn)程交互的程序分析
這篇博客就結(jié)合實(shí)際的python程序通過隊(duì)列實(shí)現(xiàn)進(jìn)程交互,通過程序分析需要的庫函數(shù),對python樹莓派進(jìn)程交互相關(guān)知識(shí)感興趣的朋友一起看看吧2021-07-07python CMD命令行傳參實(shí)現(xiàn)方法(argparse、click、fire)
這篇文章主要介紹了python CMD命令行傳參實(shí)現(xiàn)方法(argparse、click、fire),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-07-07在python中利用GDAL對tif文件進(jìn)行讀寫的方法
今天小編就為大家分享一篇在python中利用GDAL對tif文件進(jìn)行讀寫的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-11-11Python3.5面向?qū)ο笈c繼承圖文實(shí)例詳解
這篇文章主要介紹了Python3.5面向?qū)ο笈c繼承,結(jié)合圖文與實(shí)例形式詳細(xì)分析了Python3.5面向?qū)ο笈c繼承的相關(guān)概念、原理、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下2019-04-04python+opencv像素的加減和加權(quán)操作的實(shí)現(xiàn)
這篇文章主要介紹了python+opencv像素的加減和加權(quán)操作的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07