您现在的位置是: 网站首页 >Django Django

Django权限机制以及控制组和用户访问权限

user2018年10月6日 18:38 Django 938人已围观

# Django权限机制以及控制组和用户访问权限 知识要点: - 模型中自定义权限 - 用户和组权限查看 - 判断登录用户是否有权限访问视图和url - 如果无权限情况处理,默认跳转到登录,可以进行403提示 - 创建权限和权限列表查看 - 创建用户组,给组授权 - 给用户单独授权 ## 权限机制 Django 用 user, group 和 permission (三者构成多对多的关系)完成了权限机制,这个权限机制是将属于 model 的某个 permission 赋予 user 或 group,可以理解为全局权限,如果用户A对模型B有`can change`权限,那么A能修改模型B的所有实例,如果用户组C对模型B有`can change`权限,那么输入用户组C的所有用户都具有对模型B所有实例的权限。也就是说用户属于某些用户组,他都拥有这些用户组的权限。 Django使用`from django.contrib.auth.models import Permission`中的`Permission`对象存储权限项,每个新创建的模型如果Meta中没有申明`default_permissions`,则默认都会生成三个权限,即 `add model`, `change model` 和 `delete model`。所以说在创建好模型后,且在同步数据库前,如果我们不需要默认的权限,在`Meta`中加入`default_permissions = ()`来阻止生成默认权限 需要注意的是,permission总是与model对应的,如果一个object不是model的实例,我们无法为它创建/分配权限。 自定义的 permission 可以在我们定义 model 时手动添加,如下面的`permissions`字段 ## 使用方法 ### 定义权限 ```python class UserPermission(models.Model): class Meta: # 权限信息,这里定义的权限的名字,后面是描述信息,描述信息是在django admin中显示权限用的 permissions = ( ('permission_blog_manage', '博客管理'), ('permission_notice_manage', '公告管理'), ('permission_category_manage', '分类管理'), ('permission_feature_manage', '专题管理'), ('permission_tag_manage', '标签管理'), ('permission_comment_manage', '评论管理'), ('permission_user_manage', '用户管理'), ) verbose_name_plural = verbose_name = '自定义用户权限' default_permissions = () # 不给自己增加add、delete、change权限在模型被 migrate 命令创建之前,这个属性必须被指定,以防一些遗漏的属性被创建。 ``` 每个 permission 都是 `from django.contrib.auth.models import Permission` 类型的实例,该类型包含三个字段 `name`, `codename` 和 `content_type` - content_type 反应了 permission 属于哪个 model, - codename 如上面的 view_server,代码逻辑中检查权限时要用, - name 是 permission 的描述,将 permission 打印到屏幕或页面时默认显示的就是 name ### 增删清空权限 用户权限增加、查看、删除和清空 ```python >>> from usercenter.models import UserProfile >>> user = UserProfile.objects.get(username='user') >>> user <UserProfile: user> >>> from django.contrib.auth.models import Permission # 导入权限模块 >>> user.user_permissions.all() <QuerySet []> >>> Permission.objects.get(id=162) <Permission: usercenter | 自定义用户权限 | 博客管理> >>> Permission.objects.filter(id=162) <QuerySet [<Permission: usercenter | 自定义用户权限 | 博客管理>]> >>> user.user_permissions = Permission.objects.filter(id=162) >>> perm_a = Permission.objects.get(id=162) >>> perm_b = Permission.objects.get(id=163) >>> perm_a <Permission: usercenter | 自定义用户权限 | 博客管理> >>> perm_b <Permission: usercenter | 自定义用户权限 | 公告管理> >>> user.user_permissions.add(perm_a, perm_b) # 添加权限,多对多管理,添加不需保存,权限必须是一个Permission实例 >>> user.user_permissions.all() # 查看用户当前权限 <QuerySet [<Permission: usercenter | 自定义用户权限 | 博客管理>, <Permission: usercenter | 自定义用户权限 | 公告管理>]> >>> user.user_permissions.remove(perm_b) # 删除权限 >>> user.user_permissions.all() <QuerySet [<Permission: usercenter | 自定义用户权限 | 博客管理>]> >>> user.user_permissions.clear() # 清空权限 >>> user.user_permissions.all() <QuerySet []> ``` ### 获取用户权限列表 能获取到用户独有的权限,以及所属组的权限 ```python >>> user.user_permissions.add(perm_a, perm_b) >>> user.user_permissions.all() <QuerySet [<Permission: usercenter | 自定义用户权限 | 博客管理>, <Permission: usercenter | 自定义用户权限 | 公告管理>]> >>> user.has_perm('usercenter.permission_blog_manage') True >>> perm_list1 = ['usercenter.permission_blog_manage', 'usercenter.permission_notice_manage'] >>> perm_list2 = ['usercenter.permission_blog_manage', 'usercenter.permission_tag_manage'] >>> user.has_perms(perm_list1) True >>> user.has_perms(perm_list2) # 如果判断是否有多个权限,只有没有其中一项,就会返回False False ``` `has_perm()` 方法的参数,即 permission 的 `codename`,但传递参数时需要加上 model 所属 app 的前缀,格式为`<app label>.<permission codename>`,无论 permission 赋予 user 还是 group,`has_perm()`方法均适用 ```python >>> user.get_all_permissions() # 出用户的所有权限,返回值是permission name的list {'usercenter.permission_blog_manage', 'usercenter.permission_notice_manage'} >>> user.get_group_permissions() # 列出用户所属group的权限,返回值是permission name set() ``` ### 组权限操作 group permission 管理逻辑与 user permission 管理一致,group 中使用permissions 字段做权限管理: ```python group.permissions.add(permission, permission, …) group.permissions.remove(permission, permission, …) group.permissions.clear() ``` ## 权限验证 ### 基于类的视图检查权限 **方法一** 直接添加视图装饰器 ```python from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.contrib.auth.decorators import permission_required # 分类管理 # @method_decorator(login_required, name='dispatch') # 如果是要求权限后,这个要求登录可以不要 @method_decorator(permission_required('usercenter.permission_category_manage'), name='dispatch') class CategoryManage(View): # ... def get(self, request): # ... def post(self, request): # ... @method_decorator(permission_required('usercenter.permission_category_manage'), name='dispatch') class BlogUpdate(UpdateView): # ... ``` 如果是继承`View`视图,可以分别给get和post授权 ```python class CategoryManage(View): # ... @method_decorator(permission_required('usercenter.permission_category_manage')) def get(self, request): # ... @method_decorator(permission_required('usercenter.permission_category_manage')) def post(self, request): # ... ``` **方法二** 添加urls装饰器 ```python from django.contrib.auth.decorators import permission_required urlpatterns = [ # ... path('manage/feature/', permission_required('usercenter.permission_feature_manage')(FeatureManage.as_view()), name='manage_feature'), # 博客专题管理 # ... ] ``` ### 基于函数的视图 ```python from django.contrib.auth.decorators import permission_required @permission_required('usercenter.permission_category_manage') def my_view(request): ... ``` ### 模板中判断是否有权限 ```html {% if perms.usercenter.permission_blog_manage %} 显示一些功能 {% else %} 无权操作或者不提示 {% endif %} ``` ### 功能逻辑中判断是否有权限 ```python if not request.user.has_perm('usercenter.permission_category_manage'): print('无权限操作') ``` ## 如果无权限进行提示 `def permission_required(perm, login_url=None, raise_exception=False):` 如果用户没有权限,则默认会跳转到登录页。可以设置在settings.py中 ```python from django.urls import reverse, reverse_lazy LOGIN_URL = reverse_lazy('usercenter:login') # 设置登录地址,当视图里login_requird使用 ``` 也可以自定义要跳转的登录页 ```python path('manage/category/', permission_required('usercenter.permission_category_manage', login_url='/my_login/')(CategoryManage.as_view()), name='manage_category'), # 博客分类管理 ``` 如果`raise_exception`参数设置为`True`,当用户没有权限时,则会在页面提示`403 Forbidden`,以下在url中判断权限,在视图中也是也想,只需添加`raise_exception=True`即可。 ```python path('manage/category/', permission_required('usercenter.permission_category_manage', raise_exception=True)(CategoryManage.as_view()), name='manage_category'), # 博客分类管理 ``` ### 自定义403无权限错误页面 创建html文件 ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>403-禁止访问</title> </head> <body> HTTP 403 - 禁止访问 </body> </html> ``` 创建视图 ```python def permission_denied(request): return render(request, '403.html') ``` 创建url ```python from .views import permission_denied # 定义错误跳转页面 handler403 = permission_denied ``` 尝试使用无权限用户访问,看是否会显示该页面 如果不对,修改settings.py中的DEBUG的值 `DEBUG = False` > 注:若是DEBUG=True,则不会生效,也有可能控制台显示`django.core.exceptions.PermissionDenied`异常,但页面是403提示。 ## 作者不是自己不能进行更新删除 以博客系统为例,博客系统的用户可分为『管理员』、『编辑』、『作者』和『读者』四个用户组;博客系统管理员和编辑具有查看、修改和删除所有的文章的权限,**作者只能修改和删除自己写的文章**,而读者则只有阅读权限。管理员、编辑和读者的权限,我们可以用全局权限做控制,而对于作者,全局权限无法满足需求,仅通过全局权限,要么允许作者编辑不属于自己的文章,要么让作者连自己的文章都无法修改。 博客系统中作者的权限控制迎刃而解:系统全局上不允许作者编辑文章,而对于属于作者的具体文章,赋予编辑权限即可。 ### 修改视图逻辑 修改视图,覆盖`dispatch`方法 ```python @method_decorator(login_required, name='dispatch') class BlogUpdate(UpdateView): model = Article template_name = 'rearend/blog-edit.html' form_class = ArticleForm context_object_name = 'blog_article' # 传递给前端的博客对象 pk_url_kwarg = 'article_id' # success_url = 'xxx' # 可以设置创建成功后跳转地址,也可以使用下面的函数 def get_success_url(self): # 获取url中的参数 return reverse('blog_admin:blog_detail', args=[self.kwargs[self.pk_url_kwarg]]) # ...... def dispatch(self, request, *args, **kwargs): """ 重写该方法,用户只能更新自己写的博客 :param request: :param args: :param kwargs: :return: """ # article = Article.objects.get(id=self.kwargs[self.pk_url_kwarg]) article = get_object_or_404(Article, id=self.kwargs[self.pk_url_kwarg]) if article.author != request.user and not request.user.has_perm('usercenter.permission_blog_manage'): # print('不是自己写的,且用户无管理博客权限,不能编辑') from django.core.exceptions import PermissionDenied raise PermissionDenied return super(BlogUpdate, self).dispatch(request, *args, **kwargs) ``` **删除** ```python # 文章删除 @method_decorator(login_required, name='dispatch') class BlogDelete(DeleteView): model = Article pk_url_kwarg = 'article_id' # 如果没有则使用默认的pk在url里面 context_object_name = 'blog_article' template_name = 'rearend/blog-confirm-delete.html' # 如果不定义则会使用article_confirm_delete.html,模板中需要定义post确认删除 success_url = reverse_lazy('blog_admin:blog_list') # ...... def dispatch(self, request, *args, **kwargs): # article = Article.objects.get(id=self.kwargs[self.pk_url_kwarg]) article = get_object_or_404(Article, id=self.kwargs[self.pk_url_kwarg]) if article.author != request.user and not request.user.has_perm('usercenter.permission_blog_manage'): # print('不是自己写的,且用户无管理博客权限,不能删除') from django.core.exceptions import PermissionDenied raise PermissionDenied return super(BlogDelete, self).dispatch(request, *args, **kwargs) ``` ### 修改前端展示 当前登录用户有权限,或者作者是当前用户的才显示更新删除按钮 **博客列表页** ```html {# if user.is_superuser#} {% if perms.usercenter.permission_blog_manage or request.user == blog.author %} <!--删除按钮,超级管理员才显示--> <div class="row" id="id_delete_button_{{ blog.id }}" style="display: none"> <div class="col-md-4 col-md-offset-8"> <div class="row text-right"> <div class="col-sm-6"> <a href="{% url 'blog_admin:blog_update' blog.id %}" title="编辑"><i class="fa fa-edit"></i></a> </div> <div class="col-sm-6"> <a href="{% url 'blog_admin:blog_delete' blog.id %}" title="删除"><i class="fa fa-trash-o"></i></a> </div> </div> </div> </div> <!--是否显示删除按钮js--> <script> document.getElementById("id_blog_content_{{ blog.id }}").onmouseout=function(){//当鼠标滑出 document.getElementById("id_delete_button_{{ blog.id }}").style.display="none"; //改变div1的display属性 }; document.getElementById("id_blog_content_{{ blog.id }}").onmouseover=function(){ //当鼠标经过 document.getElementById("id_delete_button_{{ blog.id }}").style.display="block"; } </script> {% endif %} ``` **博客详情页** ```html {% if perms.usercenter.permission_blog_manage or request.user == blog_article.author %} <span title="编辑博客"><a href="{% url 'blog_admin:blog_update' blog_article.id %}"><i class="fa fa-edit"></i> 编辑</a> &nbsp;&nbsp; </span> <span title="删除博客"><a href="{% url 'blog_admin:blog_delete' blog_article.id %}"><i class="fa fa-trash-o"></i> 删除</a> </span> {% endif %} ``` ## 系统运行后创建自定义权限 在model中创建自定义权限,从系统开发的角度,可理解为创建系统的内置权限,如果需求中涉及到用户使用系统时创建自定义权限,则要通过下面方法: ```python from usercenter.models import UserProfile from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType content_type = ContentType.objects.get_for_model(UserProfile) permission = Permission.objects.create(codename='permission_check_user', name='检查用户', content_type=content_type) ``` ```python >>> a = Permission.objects.get(id=169) >>> a <Permission: casual | 购买订单 | 工作室> >>> a.content_type_id 27 >>> >>> >>> a.content_type <ContentType: 购买订单> >>> type(a.content_type) <class 'django.contrib.contenttypes.models.ContentType'> >>> from django.contrib.contenttypes.models import ContentType >>> b = ContentType.objects.get(id=27) >>> b <ContentType: 购买订单> >>> type(b) <class 'django.contrib.contenttypes.models.ContentType'> ``` ### 表单 ```python # 权限编辑 class PermissionEditForm(forms.ModelForm): name = forms.CharField(max_length=20, label='权限名称', required=True, widget=forms.TextInput(attrs={'class': 'form-control'})) codename = forms.CharField(max_length=100, label='权限代码', required=True, widget=forms.TextInput(attrs={'class': 'form-control', 'placeholder': '参考格式:permission_xxx_manage'})) content_type = forms.ModelChoiceField(queryset=ContentType.objects.all(), label='内容类型', required=True, widget=forms.Select(attrs={'class': 'form-control', 'size': '10'}), initial=ContentType.objects.get(model='userpermission') # 初始化一个选择项 ) class Meta: model = Permission fields = '__all__' ``` ### 视图 ```python # 用户权限创建,会自动验证内容类型 和 代码名称单一性 class PermissionCreate(CreateView): model = Permission form_class = PermissionEditForm template_name = 'permission-edit.html' success_url = reverse_lazy('usercenter:permission_list') # 用户权限更新 class PermissionUpdate(UpdateView): model = Permission form_class = PermissionEditForm template_name = 'permission-edit.html' pk_url_kwarg = 'permission_id' success_url = reverse_lazy('usercenter:permission_list') ``` ### 模板 ```html <div class="row wrapper wrapper-content animated fadeInRight"> <div class="col-sm-12"> <div class="ibox float-e-margins"> <div class="ibox-content"> <form action="." method="post" class="form-horizontal" enctype="multipart/form-data"> {{ form.as_p }} {% csrf_token %} <div class="form-group"> <div class="col-sm-4"> <button class="btn btn-primary" type="submit">保存</button> </div> </div> </form> </div> </div> </div> </div> ``` ### 权限展示视图 ```python # 用户权限显示 class PermissionList(ListView): model = Permission template_name = 'permissions.html' context_object_name = 'permissions' def get_queryset(self): queryset = Permission.objects.all() queryset = queryset.exclude(name__contains='Can') # 排除系统默认创建的权限 return queryset ``` ### 权限展示模板 ```html <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>权限名称</th> <th>权限代码<small>(已使用的权限代码不能修改)</small></th> <th>拥有该权限的组</th> <th>拥有该权限的用户</th> <th>内容类型</th> <th>权限</th> <th>操作</th> </tr> </thead> <tbody> {% for permission in permissions %} <tr> <td>{{ permission.id }}</td> <td>{{ permission.name }}</td> <td>{{ permission.codename }}</td> <td> {% for group in permission.group_set.all %} {{ group.name }}{% if not forloop.last %}<br>{% endif %} {% endfor %} </td> <td> {% for user in permission.user_set.all %} {{ user.nick_name|default_if_none:user.username }}{% if not forloop.last %}、{% endif %} {% endfor %} </td> <td>{{ permission.content_type }}</td> <td>{{ permission }}</td> <td><a href="{% url 'usercenter:permission_update' permission.id %}">更新</a></td> </tr> {% endfor %} </tbody> </table> ``` ![BLOG_20181007_105331_23](/media/blog/images/2018/10/BLOG_20181007_105331_23.png "博客图集BLOG_20181007_105331_23.png") ## 创建用户组和授权 ### 创建更新表单 ```python from django.contrib.auth.models import Permission, Group # 组表单 class GroupEditForm(forms.ModelForm): name = forms.CharField(max_length=20, label='组名称', required=True, widget=forms.TextInput(attrs={'class': 'form-control'})) permissions = forms.ModelMultipleChoiceField(queryset=Permission.objects.exclude(name__contains='Can'), label='组权限', required=False, widget=forms.SelectMultiple(attrs={'class': 'form-control', 'size': '10'}) ) class Meta: model = Group fields = '__all__' ``` ### 创建更新视图 ```python # 组创建 class GroupCreate(CreateView): model = Group template_name = 'group-edit.html' form_class = GroupEditForm success_url = reverse_lazy('usercenter:group_list') # 组更新 class GroupUpdate(UpdateView): model = Group template_name = 'group-edit.html' form_class = GroupEditForm pk_url_kwarg = 'group_id' success_url = reverse_lazy('usercenter:group_list') ``` ### 创建更新模板 ```html <form action="." method="post" class="form-horizontal" enctype="multipart/form-data"> {{ form.as_p }} {% csrf_token %} <div class="form-group"> <div class="col-sm-4"> <button class="btn btn-primary" type="submit">保存</button> </div> </div> </form> ``` ![BLOG_20181007_105809_69](/media/blog/images/2018/10/BLOG_20181007_105809_69.png "博客图集BLOG_20181007_105809_69.png") ### 组权限展示视图 ```python # 组创建 class GroupCreate(CreateView): model = Group template_name = 'group-edit.html' form_class = GroupEditForm success_url = reverse_lazy('usercenter:group_list') ``` ### 组权限展示模板 ```html <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>组名称</th> <th>成员</th> <th>组权限</th> <th>操作</th> </tr> </thead> <tbody> {% for group in groups %} <tr> <td>{{ group.id }}</td> <td>{{ group.name }}</td> <td> {% for user in group.user_set.all %} {{ user.nick_name|default_if_none:user.username }}{% if not forloop.last %}、{% endif %} {% endfor %} </td> <td> {% if group.permissions.count != 0 %} {% for permission in group.permissions.all %} {{ permission.name }}{% if not forloop.last %}、{% endif %} {% endfor %} {% endif %} </td> <td><a href="{% url 'usercenter:group_update' group.id %}">更新</a></td> </tr> {% endfor %} </tbody> </table> ``` ![BLOG_20181007_105400_86](/media/blog/images/2018/10/BLOG_20181007_105400_86.png "博客图集BLOG_20181007_105400_86.png") ## 用户权限管理 ### 更新表单 排除系统创建的以`Can`开头的默认权限 ```python # 用户权限表单 class UserPermissionEditForm(forms.ModelForm): groups = forms.ModelMultipleChoiceField(queryset=Group.objects.all(), label='用户组', required=False, widget=forms.SelectMultiple(attrs={'class': 'form-control', 'size': '10'}) ) user_permissions = forms.ModelMultipleChoiceField(queryset=Permission.objects.exclude(name__contains='Can'), label='组权限', required=False, widget=forms.SelectMultiple(attrs={'class': 'form-control', 'size': '10'}) ) class Meta: model = UserProfile fields = ['groups', 'user_permissions', 'is_active', 'is_staff', 'is_superuser'] ``` ### 更新视图 ```python # 用户权限更新 class UserPermissionUpdate(UpdateView): model = UserProfile template_name = 'user-edit.html' form_class = UserPermissionEditForm pk_url_kwarg = 'user_id' success_url = reverse_lazy('usercenter:user_permission_list') ``` ### 更新模板 ```html <form action="." method="post" class="form-horizontal" enctype="multipart/form-data"> {{ form.as_p }} {% csrf_token %} <div class="form-group"> <div class="col-sm-4"> <button class="btn btn-primary" type="submit">保存</button> </div> </div> </form> ``` ![BLOG_20181007_105554_32](/media/blog/images/2018/10/BLOG_20181007_105554_32.png "博客图集BLOG_20181007_105554_32.png") ### 用户权限展示视图 排除超级管理员 ```python # 用户显示 class UserPermissionList(ListView): model = UserProfile template_name = 'users.html' context_object_name = 'users' queryset = UserProfile.objects.all() def get_queryset(self): return self.queryset.exclude(is_superuser=True) ``` ### 用户权限展示模板 ```html <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>昵称</th> <th><a href="{% url 'usercenter:group_list' %}">用户组</a></th> <th>用户权限代码</th> <th>组权限</th> <th><a href="{% url 'usercenter:permission_list' %}">用户个人权限</a></th> <th>操作</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{ user.id }}</td> <td>{{ user.username }}</td> <td>{{ user.nick_name|default_if_none:'' }}</td> <td> {% if user.groups.count != 0 %} {% for group in user.groups.all %} {{ group.name }}{% if not forloop.last %}<br>{% endif %} {% endfor %} {% endif %} </td> <td> {% for permission in user.get_all_permissions %} {{ permission }}{% if permission in user.get_group_permissions %} (Group){% endif %}{% if not forloop.last %}<br>{% endif %} {% endfor %} </td> <td> {% get_groups_permissions user.groups.all %} </td> <td> {% if user.user_permissions.count != 0 %} {% for permission in user.user_permissions.all %} {{ permission.name }}{% if not forloop.last %}、{% endif %} {% endfor %} {% endif %} </td> <td><a href="{% url 'usercenter:user_permission_update' user.id %}">更新</a></td> </tr> {% endfor %} </tbody> </table> ``` ![BLOG_20181007_105430_99](/media/blog/images/2018/10/BLOG_20181007_105430_99.png "博客图集BLOG_20181007_105430_99.png")

很赞哦! (0)

文章交流

  • emoji
2人参与,2条评论
佩奇 2018年10月7日 11:17
权限管理写的好,配合实例,很容易看懂
吾星喵 2018年10月6日 20:52
学了总结一下,感觉又进步了一丢丢

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行1163天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:54 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站