需求描述
我们项目组开发的一些系统通常会用mysql数据库来存储一些配置,但是如果每次有配置修改的时候都去手动修改mysql数据的话,会挺麻烦的,同时也比较容易出错。django-admin能够根据定义的model自动的生成相应的页面,同时还能提供权限的管理,所以我们就把一些系统到的配置放到django中。但是到现在,随着系统的需求越来越多,该系统已经不止我们自己项目组的人员使用,也要开放给其他项目组的同事使用,所以就产生了一些更细粒度的权限需求。因此,我们要在现有的系统上支持行级的权限控制。
解决方案
当然可以自己写一套权限系统了,但是自己写的成本比较高,而且自己写的不一定比较好。所以我就先在网上找了一些现成的解决方案, https://djangopackages.org/grids/g/perms/ 该链接列出了现有的一些第三方的权限系统解决方案。从该页面来看,django-guardian是最受欢迎的第三方权限系统,而且支持行级的权限系统,同时还可以整合到django-admin里面,所以我就选择了django-guardian。
关键步骤
安装配置django-guardian
安装配置django-guardian比较简单,按照她项目提供的 文档 进行安装就可以了,安装完成后会在数据库里面创建两张权限相关的表。
pip install django-guardian
在项目setting.py中添加配置如下
... AUTH_USER_MODEL = "commonrestfulapi.UserInfo" # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'guardian', 'commonrestfulapi', ] ... AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend' ) ...
项目应用models.py如下
from django.db import models # Create your models here. from django.contrib.auth.models import AbstractUser class UserInfo(AbstractUser): r_pwd = models.CharField(max_length=32) def __str__(self): return self.username class Blog(models.Model): author = models.ForeignKey( "UserInfo", blank=True, null=True, on_delete=models.SET_DEFAULT, default=2, ) title = models.CharField(max_length=100) def __str__(self): return self.title class Company(models.Model): name = models.CharField(max_length=100, verbose_name="公司名称") def __str__(self): return self.name class Post(models.Model): title = models.CharField("title", max_length=64) slug = models.SlugField(max_length=64) content = models.TextField("content") created_at = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: permissions = (("hide_post", "Can hide post"),) get_latest_by = "created_at" def __str__(self): return self.title def get_absolute_url(self): return {"post_slug": self.slug}
执行数据库migrate相关:
python manager.py makemigrations python manager.py migrate
封装对象级别权限管理adminModel基类文件说明如下:
路径:restfulapi_project/commonrestfulapi/CommonGuardedModelAdminBase.py
其中restfulapi_project为项目名称,commonrestfulapi为应用名称,
CommonGuardedModelAdminBase.py 为基类所在文件名称。具体内容如下:
#! /usr/bin/python3 # -*- coding:utf-8 -*- # file: commonrestfulapi/CommonGuardedModelAdminBase.py # author: wangchenxi # mail: wongchenxi@icloud.com # brief: # version: 0.1.00 # Create Time:2020-09-17 16:36:48 # Last Update: 2020-09-17 05:04:46 PM from guardian.admin import GuardedModelAdmin from guardian.shortcuts import ( get_objects_for_user, assign_perm, remove_perm, get_users_with_perms, get_groups_with_perms, ) from functools import partial class GuardedModelAdminBase(GuardedModelAdmin): # app是否在主页面中显示的话由该函数决定 def has_module_permission(self, request): if request.user.is_superuser and request.user.is_active: return True has_view_perm = partial( request.user.has_perm, "view_%s" % self.opts.model_name ) query_set = self.model.objects.all() return any(map(has_view_perm, query_set)) def get_model_perms(self, request): return { "add": True, "change": True, "delete": True, } # 在显示数据列表额时候,哪些数据显示,哪些不显示,由该函数控制 def get_queryset(self, request): if request.user.is_superuser and request.user.is_active: return super().get_queryset(request) data = self.get_model_objs(request) return data # 内部用来获取某个用户有权限访问的数据行 def get_model_objs(self, request, action=None, klass=None): if request.user.is_superuser and request.user.is_active: return super().get_queryset(request) opts = self.opts actions = ["view", "add", "change", "delete"] klass = klass if klass else opts.model model_name = klass._meta.model_name return get_objects_for_user( user=request.user, perms=[f"{perm}_{model_name}" for perm in actions], klass=klass, any_perm=True, ) # 用来判断某个用户是否有某个数据行的权限 def has_perm(self, request, obj, action): if request.user.is_superuser: return True opts = self.opts codename = f"{action}_{opts.model_name}" if obj: return request.user.has_perm(f"{opts.app_label}.{codename}", obj) else: return self.get_model_objs(request, action).exists() # 是否有查看某个数据行的权限 def has_view_permission(self, request, obj=None): return self.has_perm(request, obj, "view") # 是否有修改某个数据行的权限 def has_change_permission(self, request, obj=None): return self.has_perm(request, obj, "change") # 是否有删除某个数据行的权限 def has_delete_permission(self, request, obj=None): return self.has_perm(request, obj, "delete") # 用户应该拥有他新增的数据行的所有权限 def save_model(self, request, obj, form, change): result = super().save_model(request, obj, form, change) if not request.user.is_superuser and not change: opts = self.opts actions = ["view", "add", "change", "delete"] [ assign_perm( f"{opts.app_label}.{action}_{opts.model_name}", request.user, obj ) for action in actions ] return result
编辑项目应用models关于django-admin相关配置admin.py,具体内容如下:
from django.contrib import admin #from django.contrib import admin from commonrestfulapi.models import UserInfo, Blog, Company, Post from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.contrib.admin.models import LogEntry from guardian.models import GroupObjectPermission, UserObjectPermission from .CommonGuardedModelAdminBase import GuardedModelAdminBase from django.contrib.auth.models import Group # 该表不需要进行对象级别(行级别)的权限控制,所以继承admin.ModelAdmin, # 不需要继承我们自己封装的GuardedModelAdminBase class LogEntryConfig(admin.ModelAdmin): list_display = [ "id", "action_time", "object_id", "object_repr", "change_message", "content_type", "user", "action_flag", ] list_per_page = 10 admin.site.register(LogEntry, LogEntryConfig) # 该表不需要进行对象级别(行级别)的权限控制,所以继承admin.ModelAdmin, # 不需要继承我们自己封装的GuardedModelAdminBase class ContentTypeConfig(admin.ModelAdmin): list_display = ["id", "app_label", "model"] list_per_page = 10 admin.site.register(ContentType, ContentTypeConfig) # 该表不需要进行对象级别(行级别)的权限控制,所以继承admin.ModelAdmin, # 不需要继承我们自己封装的GuardedModelAdminBase class PermissionConfig(admin.ModelAdmin): list_display = [ "id", "content_type", "codename", "name", ] list_filter = ("user",) list_per_page = 10 admin.site.register(Permission, PermissionConfig) # 该表不需要进行对象级别(行级别)的权限控制,所以继承admin.ModelAdmin, # 不需要继承我们自己封装的GuardedModelAdminBase class UserObjectPermissionConfig(admin.ModelAdmin): list_display = [ "id", "content_type", "object_info", "user", "permission", ] list_filter = ("object_pk", "content_type") list_per_page = 10 def object_info(self, obj): return f"object_id: {obj.object_pk} {obj.content_object}" admin.site.register(UserObjectPermission, UserObjectPermissionConfig) # 该表不需要进行对象级别(行级别)的权限控制,所以继承admin.ModelAdmin, # 不需要继承我们自己封装的GuardedModelAdminBase class GroupObjectPermissionConfig(admin.ModelAdmin): list_display = ["id", "content_type", "object_pk", "group", "permission"] list_per_page = 10 admin.site.register(GroupObjectPermission, GroupObjectPermissionConfig) # 该表需要我们进行对象级别的权限控制,所以继承我们自己封装的GuardedModelAdminBase class UserInfoConfig(GuardedModelAdminBase): list_display = [ "id", "password", "last_login", "is_superuser", "username", "first_name", "last_name", "email", "is_staff", "is_active", "date_joined", "r_pwd", ] list_filter = ( "groups", "user_permissions", ) filter_horizontal = ( "groups", "user_permissions", ) list_per_page = 10 # 让每页显示几条记录的设置 admin.site.register(UserInfo, UserInfoConfig) # 该表需要我们进行对象级别的权限控制,所以继承我们自己封装的GuardedModelAdminBase class BlogConfig(GuardedModelAdminBase): list_display = ["id", "author", "title"] admin.site.register(Blog, BlogConfig) # 该表需要我们进行对象级别的权限控制,所以继承我们自己封装的GuardedModelAdminBase class PostAdmin(GuardedModelAdminBase): prepopulated_fields = {"slug": ("title",)} list_display = ("id", "title", "slug", "created_at") search_fields = ("title", "content") ordering = ("-created_at",) date_hierarchy = "created_at" admin.site.register(Post, PostAdmin) # 该表需要我们进行对象级别的权限控制,所以继承我们自己封装的GuardedModelAdminBase class CompanyAdmin(GuardedModelAdminBase): pass admin.site.register(Company, CompanyAdmin)
如果需要将组Group也加入对象级别(行级别)权限控制,所以继承我们自己封装的GuardedModelAdminBase。
修改django中的Group注册admin.py
相关文件路径为: /your/project/env/lib/python3.6/site-packages/django/contrib/auth/admin.py
修改前如下:
from django.utils.html import escape from django.utils.translation import gettext, gettext_lazy as _ from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters csrf_protect_m = method_decorator(csrf_protect) sensitive_post_parameters_m = method_decorator(sensitive_post_parameters()) @admin.register(Group) class GroupAdmin(admin.ModelAdmin): list_display = [ "id", "name"] search_fields = ('name',) ordering = ('name',) filter_horizontal = ('permissions',) def formfield_for_manytomany(self, db_field, request=None, **kwargs): if db_field.name == 'permissions': qs = kwargs.get('queryset', db_field.remote_field.model.objects) # Avoid a major performance hit resolving permission names which # triggers a content_type load: kwargs['queryset'] = qs.select_related('content_type') return super().formfield_for_manytomany(db_field, request=request, **kwargs)
修改后如下:
from django.utils.html import escape from django.utils.translation import gettext, gettext_lazy as _ from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters csrf_protect_m = method_decorator(csrf_protect) sensitive_post_parameters_m = method_decorator(sensitive_post_parameters()) # 新添加 from commonrestfulapi.CommonGuardedModelAdminBase import GuardedModelAdminBase @admin.register(Group) # class GroupAdmin(admin.ModelAdmin): # 注释掉 class GroupAdmin(GuardedModelAdminBase): # 新添加 list_display = [ "id", "name"] search_fields = ('name',) ordering = ('name',) filter_horizontal = ('permissions',) def formfield_for_manytomany(self, db_field, request=None, **kwargs): if db_field.name == 'permissions': qs = kwargs.get('queryset', db_field.remote_field.model.objects) # Avoid a major performance hit resolving permission names which # triggers a content_type load: kwargs['queryset'] = qs.select_related('content_type') return super().formfield_for_manytomany(db_field, request=request, **kwargs)
实现说明:
首先把admin.py文件里面需要用到行级权限的类,由原来的继承admin.ModelAdmin,改成继承GuardedModelAdmin,这时候打开某个数据行的页面的时候,在该页面的右上角的历史旁边会显示编辑对象权限的按钮,点击该按钮进去相应的页面就可以编辑该行数据的具体权限。
配置完权限的时候,用一个新的用户测试的话,会发现该用户没有权限来访问任何的数据,这是因为GuardedModelAdmin还有很多事情没有帮我们做,我们还需要重写一些函数来实现admin后台页面的显示,故此有了我们自己封装在restfulapi_project/commonrestfulapi/CommonGuardedModelAdminBase.py 中的基类(GuardedModelAdminBase)。具体的信息请看上述文件(代码及注释)或者产看源码官方文档相关。
总结
通过上面的修改,django-admin中的模块就能够支持行级的权限,并能够正确的在后台页面中显示出来,当然如果有很多的模块需要支持行级的权限控制,则可以把上面的这些修改写到一个新的类中,然后其他想支持行级权限的模块再从该模块继承就可以了。
感觉django-guardian和django-admin整合,实现的不是很好。如果开发者对django内部的代码不怎么了解,那么用django-guardian来实现行级权限控制的话可能会麻烦点,个人认为django-guardian完全可以把和django-admin的整合做到开箱即用的效果,就像django自带的权限系统一样。
版权声明:除特别注明外,本站所有文章均为王晨曦个人站点原创
转载请注明:出处来自王晨曦个人站点 » 使用django-guardian实现django-admin的行级权限控制的方法
本篇文档为本人查看网络分享,经过个人实践,整理。
Online Generic Cialis Tadalafil
generic cialis soft
cialis viagra pharmacie
Generic Cialis Vs Cialis
Cafergot
Priligy Avis Blog
Viagra Pour Bander
Levitra Starting Dose
イヴィトン財布コピー激安代引き China 2mm Waterproofing Membrane 4 Head clip grow light 40W full spectrum with Dimmer
Adapter with Male Threaded End 2kg Gas Bottle ルイヴィトンコピー財布
Active Dynamic Filter ADF Tarnish Free Necklaces ルイヴィトン財布コピー
ganni feather trimmed dress red wing moc toe black sole nike air max excee famous footwear san francisco giants spring training hat for sale plus gingham dress atlanta braves green hat zionsville indiana theassetedge http://www.theassetedge.net/
Digital Display Temperature Controller ルイヴィトン財布コピー激安代引き Brazed Aluminum Heat Exchanger
ルイヴィトンコピー財布 1858885 SCANIA Tensioner Pulley V-ribbedbelt Wheel Bearing Repair Kit
Ice Platinum イヴィトン財布コピー激安代引き Sintra board
Electro Magnet ルイヴィトン財布コピー激安代引き RGB Led Strip Lights Led Light Strips Bluetooth Wifi 24 Keys Remote Control
Epdm Waterproof Membrane Stainless Steel 90 Degree Elbow ルイヴィトンコピー財布
Biodegradable Film Wrap 13 Inch Dildo-Purple ルイヴィトン財布コピー
xxs prom dresses for sale new era nrl fitted hats 2017 los angeles kings blank white third jersey plus size wine bridesmaid dresses chicago white sox hats for sale uk christmas dresses for toddlers boutique sugotogo http://www.sugotogo.com/
Electric Proportional Valve Spare Parts Extrusion Billet ルイヴィトンコピー財布
イヴィトン財布コピー激安代引き Standard Duplex Receptacle Double Head Lifting Anchor
Aerobic Vertical Fermentation Equipment ヴィトン財布コピー Alu Bottle Factory
nhl devils live stream mesh back 59fifty jordan retro 14 black noir blue mens moncler jacket couch charging outlet new balance m990 v2 osamusushi http://www.osamusushi.com/
Premade Pouch Packing Machine ルイヴィトン財布コピー激安代引き ASME SA213 TP316 / 316L stainless steel seamless pipe OF Pickled / Bright Annealed