饮冰三年-人工智能-Python-35权限管理(万能的权限通用模块)
我们分为两大块,第一块是自定义权限认证。可参考https://docs.djangoproject.com/en/2.2/topics/auth/customizing/ 第二块是一个通用的模板
自定义权限认证
1:修改model.py类。主要是添加两个class
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.db import models from django.contrib.auth.models import User # Create your models here. from django.db import models from django.contrib.auth.models import ( BaseUserManager, AbstractBaseUser,PermissionsMixin ) class UserProfileManager(BaseUserManager): def create_user(self, email, name, password=None): """ Creates and saves a User with the given email, date of birth and password. """ if not email: raise ValueError('Users must have an email address') user = self.model( email=self.normalize_email(email), name=name, ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, name, password): """ Creates and saves a superuser with the given email, date of birth and password. """ user = self.create_user( email, password=password, name=name, ) user.is_superuser = True user.save(using=self._db) return user class UserProfile(AbstractBaseUser, PermissionsMixin): email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) name = models.CharField(max_length=64, verbose_name="姓名") is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=True) #is_admin = models.BooleanField(default=False) role = models.ManyToManyField("Role", blank=True, null=True) objects = UserProfileManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['name'] def get_full_name(self): # The user is identified by their email address return self.email def get_short_name(self): # The user is identified by their email address return self.email def __str__(self): # __unicode__ on Python 2 return self.email class Meta: permissions = ( ('crm_table_list', '可以查看kingadmin每张表里所有的数据'), ('crm_table_list_view', '可以访问kingadmin表里每条数据的修改页'), ('crm_table_list_change', '可以对kingadmin表里的每条数据进行修改'), ('crm_table_obj_add_view', '可以访问kingadmin每张表的数据增加页'), ('crm_table_obj_add', '可以对kingadmin每张表进行数据添加'), )
2:修改admin.py 类
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.contrib import admin from CRM import models # Register your models here. from django import forms from django.contrib import admin from django.contrib.auth.models import Group from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.forms import ReadOnlyPasswordHashField from CRM.models import UserProfile class UserCreationForm(forms.ModelForm): """A form for creating new users. Includes all the required fields, plus a repeated password.""" password1 = forms.CharField(label='Password', widget=forms.PasswordInput) password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) class Meta: model = UserProfile fields = ('email', 'name') def clean_password2(self): # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: raise forms.ValidationError("Passwords don't match") return password2 def save(self, commit=True): # Save the provided password in hashed format user = super(UserCreationForm, self).save(commit=False) user.set_password(self.cleaned_data["password1"]) #把明文 根据算法改成密文 if commit: user.save() return user class UserChangeForm(forms.ModelForm): """A form for updating users. Includes all the fields on the user, but replaces the password field with admin's password hash display field. """ password = ReadOnlyPasswordHashField() class Meta: model = UserProfile fields = ('email', 'password', 'name', 'is_active', 'is_superuser') def clean_password(self): # Regardless of what the user provides, return the initial value. # This is done here, rather than on the field, because the # field does not have access to the initial value return self.initial["password"] class UserProfileAdmin(BaseUserAdmin): # The forms to add and change user instances form = UserChangeForm add_form = UserCreationForm # The fields to be used in displaying the User model. # These override the definitions on the base UserAdmin # that reference specific fields on auth.User. list_display = ('email', 'name', 'is_superuser') list_filter = ('is_superuser',) fieldsets = ( (None, {'fields': ('email', 'password')}), ('Personal info', {'fields': ('name',)}), ('Permissions', {'fields': ('is_active','is_staff','is_superuser','role','user_permissions','groups')}), ) # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin # overrides get_fieldsets to use this attribute when creating a user. add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('email', 'name', 'password1', 'password2')} ), ) search_fields = ('email',) ordering = ('email',) filter_horizontal = ("role",'user_permissions','groups') class CustomerAdmin(admin.ModelAdmin): list_display = ['id','name','source','contact_type','contact','consultant','consult_content','status','date'] list_filter = ['source','consultant','status','date'] search_fields = ['contact','consultant__name'] #readonly_fields = ['status','contact'] filter_horizontal = ['consult_courses',] actions = ['change_status',] def change_status(self,request,querysets): querysets.update(status=1) class CourseRecordAdmin(admin.ModelAdmin): list_display = ['class_grade','day_num','has_homework'] #list_per_page = 2 list_editable = ['has_homework',] admin.site.register(models.CustomerInfo,CustomerAdmin) admin.site.register(models.CustomerFollowUp) admin.site.register(models.ClassList) admin.site.register(models.Course) admin.site.register(models.Role) admin.site.register(models.Menus) admin.site.register(models.CourseRecord,CourseRecordAdmin) admin.site.register(models.StudyRecord) admin.site.register(models.Student) admin.site.register(models.UserProfile,UserProfileAdmin) admin.site.register(models.Branch) admin.site.register(models.ContractTemplate) admin.site.register(models.StudentEnrollment) admin.site.register(models.PaymentRecord)
3:在setting.py文件中添加
AUTH_USER_MODEL = 'CRM.UserProfile'
权限通用的模板
权限 = 一个url + 一种请求方法(get/post/put...) + 若干个请求参数
一、权限组件与代码的实现
我们把权限组件的实现分3步,权限条目的定义, 权限条目与用户的关联,权限组件与应用的结合
1:权限条目的定义
perm_dic={ 'crm_table_index':['table_index','GET',[],{},], #可以查看CRM APP里所有数据库表 'crm_table_list':['table_list','GET',[],{}], #可以查看每张表里所有的数据 'crm_table_list_view':['table_change','GET',[],{}],#可以访问表里每条数据的修改页 'crm_table_list_change':['table_change','POST',[],{}], #可以对表里的每条数据进行修改 }
字典里的key是权限名, 一会我们需要用过这些权限名来跟用户进行关联
- 后面values列表里第一个值如'table_index'是django中的url name,在这里必须相对的url name, 而不是绝对url路径,因为考虑到django url正则匹配的问题,搞绝对路径,不好控制。
- values里第2个值是http请求方法
- values里第3个[]是要求这个请求中必须带有某些参数,但不限定对数的值是什么
- values里的第4个{}是要求这个请求中必须带有某些参数,并且限定所带的参数必须等于特定的值
我现在的需求是,只允许 用户访问客户来源为qq群且 已报名的 客户,你怎么控制 ?
通过分析我们得出,这个动作的url为
http://127.0.0.1:9000/kingadmin/crm/customer/?source=qq&status=signed
客户来源参数是source,报名状态为status,那我的权限条目就可以配置成
'crm_table_list':['table_list','GET',[],{'source':'qq', 'status':'signed'}]
2:权限条目与用户的关联
我们可以直接借用django自带的权限系统,借用它的 权限 与用户的关联 逻辑!将我们自定义权限条目添加到系统中
通过model.py中的UserProfile的Meta
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Meta: permissions = ( ('crm_table_list', '可以查看kingadmin每张表里所有的数据'), ('crm_table_list_view', '可以访问kingadmin表里每条数据的修改页'), ('crm_table_list_change', '可以对kingadmin表里的每条数据进行修改'), ('crm_table_obj_add_view', '可以访问kingadmin每张表的数据增加页'), ('crm_table_obj_add', '可以对kingadmin每张表进行数据添加'), )
3:权限组件与应用的结合
在KingAdmin中添加相应的权限组件permissions(权限判断:获--转--判--调)permission_list(权限列表) permission_hook(钩子函数,可以自定义过滤条件)
在KingAdmin/views.py 中需要做权限判断的方法中,添加装饰器@permissions.check_permission
部分关键代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.urls import resolve from django.shortcuts import render,redirect,HttpResponse from KingAdmin.permission_list import perm_dic from django.conf import settings def perm_check(*args,**kwargs): request = args[0] resolve_url_obj = resolve(request.path) current_url_name = resolve_url_obj.url_name # 当前url的url_name print('---perm:',request.user,request.user.is_authenticated,current_url_name) #match_flag = False match_results = [None,] match_key = None if request.user.is_authenticated is False: return redirect(settings.LOGIN_URL) for permission_key,permission_val in perm_dic.items(): per_url_name = permission_val[0] per_method = permission_val[1] perm_args = permission_val[2] perm_kwargs = permission_val[3] perm_hook_func = permission_val[4] if len(permission_val)>4 else None if per_url_name == current_url_name: #matches current request url if per_method == request.method: #matches request method # if not perm_args: #if no args defined in perm dic, then set this request to passed perm #逐个匹配参数,看每个参数时候都能对应的上。 args_matched = False #for args only for item in perm_args: request_method_func = getattr(request,per_method) #request.GET/POST if request_method_func.get(item,None):# request字典中有此参数 args_matched = True else: print("arg not match......") args_matched = False break # 有一个参数不能匹配成功,则判定为假,退出该循环。 else:#当列表为空的时候才走这里 args_matched = True #匹配有特定值的参数 kwargs_matched = False for k,v in perm_kwargs.items(): request_method_func = getattr(request, per_method) arg_val = request_method_func.get(k, None) # request字典中有此参数 print("perm kwargs check:",arg_val,type(arg_val),v,type(v)) if arg_val == str(v): #匹配上了特定的参数 及对应的 参数值, 比如,需要request 对象里必须有一个叫 user_id=3的参数 kwargs_matched = True else: kwargs_matched = False break # 有一个参数不能匹配成功,则判定为假,退出该循环。 else: kwargs_matched = True #开始匹配自定义权限钩子函数 perm_hook_matched = True if perm_hook_func: perm_hook_matched = perm_hook_func(request) match_results = [args_matched,kwargs_matched,perm_hook_matched] print("--->match_results ", match_results) if all(match_results): #都匹配上了 match_key = permission_key break if all(match_results): app_name, *per_name = match_key.split('_') print("--->matched ",match_results,match_key) print(app_name, *per_name) perm_obj = '%s.%s' % (app_name,match_key) print("perm str:",perm_obj) if request.user.has_perm(perm_obj): print('当前用户有此权限') return True else: print('当前用户没有该权限') return False else: print("未匹配到权限项,当前用户无权限") def check_permission(func): def inner(*args,**kwargs): if not perm_check(*args,**kwargs): request = args[0] return render(request,'kingadmin/page_403.html') return func(*args,**kwargs) return inner
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from KingAdmin import permission_hook perm_dic= { #'crm_table_index': ['table_index', 'GET', [], {'source':'qq'}, ], # 可以查看CRM APP里所有数据库表 'CRM_table_list': ['table_obj_list', 'GET', [], {}], # 可以查看每张表里所有的数据 'CRM_table_list_view': ['table_obj_change', 'GET', [], {}], # 可以访问表里每条数据的修改页 'CRM_table_list_change': ['table_obj_change', 'POST', [], {}], # 可以对表里的每条数据进行修改 'CRM_table_obj_add_view': ['table_obj_add', 'GET', [], {}], # 可以访问数据增加页 'CRM_table_obj_add': ['table_obj_add', 'POST', [], {}], # 可以创建表里的数据 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def view_my_own_customers(request): print("running permisionn hook check.....") if str(request.user.id) == request.GET.get('consultant'): print("访问自己创建的用户,允许") return True else: return False
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.db import models from django.contrib.auth.models import User # Create your models here. from django.db import models from django.contrib.auth.models import ( BaseUserManager, AbstractBaseUser,PermissionsMixin ) class UserProfileManager(BaseUserManager): def create_user(self, email, name, password=None): """ Creates and saves a User with the given email, date of birth and password. """ if not email: raise ValueError('Users must have an email address') user = self.model( email=self.normalize_email(email), name=name, ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, name, password): """ Creates and saves a superuser with the given email, date of birth and password. """ user = self.create_user( email, password=password, name=name, ) user.is_superuser = True user.save(using=self._db) return user class UserProfile(AbstractBaseUser, PermissionsMixin): email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) name = models.CharField(max_length=64, verbose_name="姓名") is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=True) #is_admin = models.BooleanField(default=False) role = models.ManyToManyField("Role", blank=True, null=True) objects = UserProfileManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['name'] def get_full_name(self): # The user is identified by their email address return self.email def get_short_name(self): # The user is identified by their email address return self.email def __str__(self): # __unicode__ on Python 2 return self.email class Meta: permissions = ( ('CRM_table_list', '可以查看kingadmin每张表里所有的数据'), ('CRM_table_list_view', '可以访问kingadmin表里每条数据的修改页'), ('CRM_table_list_change', '可以对kingadmin表里的每条数据进行修改'), ('CRM_table_obj_add_view', '可以访问kingadmin每张表的数据增加页'), ('CRM_table_obj_add', '可以对kingadmin每张表进行数据添加'), ) class Role(models.Model): """角色表""" name = models.CharField(max_length=64, unique=True) menus = models.ManyToManyField("Menus", blank=True) def __str__(self): return self.name class CustomerInfo(models.Model): """客户信息表""" name = models.CharField(max_length=64, default=None) contact_type_choices = ((0, 'qq'), (1, '微信'), (2, '手机')) contact_type = models.SmallIntegerField(choices=contact_type_choices, default=0) contact = models.CharField(max_length=64, unique=True) source_choices = ((0, 'QQ群'), (1, '51CTO'), (2, '百度推广'), (3, '知乎'), (4, '转介绍'), (5, '其它'), ) source = models.SmallIntegerField(choices=source_choices) referral_from = models.ForeignKey("self", blank=True, null=True, verbose_name="转介绍",on_delete=models.CASCADE) consult_courses = models.ManyToManyField("Course", verbose_name="咨询课程") consult_content = models.TextField(verbose_name="咨询内容") status_choices = ((0, '未报名'), (1, '已报名'), (2, '已退学')) status = models.SmallIntegerField(choices=status_choices) consultant = models.ForeignKey("UserProfile", verbose_name="课程顾问",on_delete=None) id_num = models.CharField(max_length=128, blank=True, null=True) emergency_contact = models.PositiveIntegerField(blank=True, null=True) sex_choices = ((0, '男'), (1, '女')) sex = models.PositiveSmallIntegerField(choices=sex_choices, blank=True, null=True) date = models.DateField(auto_now_add=True) def __str__(self): return self.name class Meta: verbose_name = "客户信息" verbose_name_plural = "客户信息" class CustomerFollowUp(models.Model): """客户跟踪记录表""" customer = models.ForeignKey("CustomerInfo",on_delete=None) content = models.TextField(verbose_name="跟踪内容") user = models.ForeignKey("UserProfile", verbose_name="跟进人",on_delete=None) status_choices = ((0, '近期无报名计划'), (1, '一个月内报名'), (2, '2周内内报名'), (3, '已报名'), ) status = models.SmallIntegerField(choices=status_choices) date = models.DateField(auto_now_add=True) def __str__(self): return self.content class Student(models.Model): """学员表""" customer = models.ForeignKey("CustomerInfo",on_delete=None) class_grades = models.ManyToManyField("ClassList") def __str__(self): return "%s" % self.customer class ClassList(models.Model): """班级列表""" branch = models.ForeignKey("Branch",on_delete=None) course = models.ForeignKey("Course",on_delete=None) class_type_choices = ((0, '脱产'), (1, '周末'), (2, '网络班')) class_type = models.SmallIntegerField(choices=class_type_choices, default=0) semester = models.SmallIntegerField(verbose_name="学期") contract_template = models.ForeignKey("ContractTemplate", blank=True, null=True,on_delete=None) teachers = models.ManyToManyField("UserProfile", verbose_name="讲师") start_date = models.DateField("开班日期") graduate_date = models.DateField("毕业日期", blank=True, null=True) def __str__(self): return "%s(%s)期" % (self.course.name, self.semester) class Meta: unique_together = ('branch', 'class_type', 'course', 'semester') class Branch(models.Model): """校区""" name = models.CharField(max_length=64,unique=True) addr = models.CharField(max_length=128,blank=True,null=True) def __str__(self): return self.name class Course(models.Model): """课程表""" name = models.CharField(verbose_name='课程名称', max_length=64, unique=True) price = models.PositiveSmallIntegerField() period = models.PositiveSmallIntegerField(verbose_name="课程周期(月)", default=5) outline = models.TextField(verbose_name="大纲") def __str__(self): return self.name class CourseRecord(models.Model): """上课记录""" class_grade = models.ForeignKey("ClassList",verbose_name="上课班级",on_delete=None) day_num = models.PositiveSmallIntegerField(verbose_name="课程节次") teacher = models.ForeignKey("UserProfile",on_delete=None) title = models.CharField("本节主题",max_length=64) content = models.TextField("本节内容") has_homework = models.BooleanField("本节有作业",default=True) homework = models.TextField("作业需求",blank=True,null=True) date = models.DateTimeField(auto_now_add=True) def __str__(self): return "%s第(%s)节" %(self.class_grade,self.day_num) class Meta: unique_together = ('class_grade','day_num') class StudyRecord(models.Model): """学习记录表""" course_record = models.ForeignKey("CourseRecord",on_delete=None) student = models.ForeignKey("Student",on_delete=None) score_choices = ((100,"A+"), (90,"A"), (85,"B+"), (80,"B"), (75,"B-"), (70,"C+"), (60,"C"), (40,"C-"), (-50,"D"), (0,"N/A"), #not avaliable (-100,"COPY"), #not avaliable ) score = models.SmallIntegerField(choices=score_choices,default=0) show_choices = ((0,'缺勤'), (1,'已签到'), (2,'迟到'), (3,'早退'), ) show_status = models.SmallIntegerField(choices=show_choices,default=1) note = models.TextField("成绩备注",blank=True,null=True) date = models.DateTimeField(auto_now_add=True) def __str__(self): return "%s %s %s" %(self.course_record,self.student,self.score) class Menus(models.Model): """动态菜单""" name = models.CharField(max_length=64) url_type_choices = ((0,'absolute'),(1,'dynamic')) url_type = models.SmallIntegerField(choices=url_type_choices,default=0) url_name = models.CharField(max_length=128) def __str__(self): return self.name class Meta: unique_together = ('name','url_name') class ContractTemplate(models.Model): """存储合同模板""" name = models.CharField(max_length=64) content = models.TextField() date = models.DateField(auto_now_add=True) class StudentEnrollment(models.Model): """学员报名表""" customer = models.ForeignKey("CustomerInfo",on_delete=None) class_grade = models.ForeignKey("ClassList",on_delete=None) consultant = models.ForeignKey("UserProfile",on_delete=None) contract_agreed = models.BooleanField(default=False) contract_signed_date = models.DateTimeField(blank=True,null=True) contract_approved = models.BooleanField(default=False) contract_approved_date = models.DateTimeField(verbose_name="合同审核时间", blank=True,null=True) class Meta: unique_together = ('customer','class_grade') def __str__(self): return "%s" % self.customer class PaymentRecord(models.Model): """存储学员缴费记录""" enrollment = models.ForeignKey(StudentEnrollment,on_delete=None) payment_type_choices = ((0,'报名费'),(1,'学费'),(2,'退费')) payment_type = models.SmallIntegerField(choices=payment_type_choices,default=0) amount = models.IntegerField("费用",default=500) consultant = models.ForeignKey("UserProfile",on_delete=None) date = models.DateTimeField(auto_now_add=True) def __str__(self): return "%s" % self.enrollment
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.shortcuts import render,redirect from django.contrib.auth import authenticate,login,logout # Create your views here. def acc_login(request): error_msg = '' if request.method == "POST": username = request.POST.get('username') password = request.POST.get('password') user = authenticate(username=username,password=password) if user: print("passed authencation",user) login(request,user) #request.user = user return redirect( request.GET.get('next','/') ) else: error_msg = "Wrong username or password!" return render(request, 'login.html', {'error_msg':error_msg}) def acc_logout(request): logout(request) return redirect("/login/")