官方文档
- 使用 Django 的验证系统(重要)
- Django中的密码管理
- Django 中的自定义验证(重要)
验证和授权概述
Django有一个内置的授权系统。他用来处理用户、分组、权限以及基于cookie的会话系统。Django的授权系统包括验证和授权两个部分。验证是验证这个用户是否是他声称的人(比如用户名和密码验证,角色验证),授权是给与他相应的权限。Django内置的权限系统包括以下方面:
- 用户。
- 权限。
- 分组。
- 一个可以配置的密码哈希系统。
- 为登录用户或限制内容提供表单和视图工具。
- 一个可插拔的后台管理系统。
使用授权系统:
默认中创建完一个django项目后,其实就已经集成了授权系统。那哪些部分是跟授权系统相关的配置呢。以下做一个简单列表:
INSTALLED_APPS:
django.contrib.auth:包含了一个核心授权框架,以及大部分的模型定义。django.contrib.contenttypes:Content Type系统,可以用来关联模型和权限。
中间件:
SessionMiddleware:用来管理session。AuthenticationMiddleware:用来处理和当前session相关联的用户。
User模型
User模型是这个框架的核心部分。他的完整的路径是在django.contrib.auth.models.User。以下对这个User对象做一个简单了解:
字段:
内置的User模型拥有以下的字段:
username: 用户名。150个字符以内。可以包含数字和英文字符,以及_、@、+、.和-字符。不能为空,且必须唯一!此处需要注意的是,国内唯一性验证往往是手机号码或者邮箱,而非用户名。后面会介绍更改方式。first_name:歪果仁的first_name,在30个字符以内。可以为空。last_name:歪果仁的last_name,在150个字符以内。可以为空。email:邮箱。可以为空。password:密码。经过哈希过后的密码。groups:分组。一个用户可以属于多个分组,一个分组可以拥有多个用户。groups这个字段是跟Group的一个多对多的关系。user_permissions:权限。一个用户可以拥有多个权限,一个权限可以被多个用户所有用。和Permission属于一种多对多的关系。is_staff:是否可以进入到admin的站点。代表是否是员工。is_active:是否是可用的。对于一些想要删除账号的数据,我们设置这个值为False就可以了,而不是真正的从数据库中删除。is_superuser:是否是超级管理员。如果是超级管理员,那么拥有整个网站的所有权限。last_login:上次登录的时间。date_joined:账号创建的时间。
User模型的基本用法:
创建用户:
通过User.objects.create_user()方法可以快速的创建用户。这个方法必须要传递username、email、password。示例代码如下:
from django.contrib.auth.models import Userdef create_user(request):user = User.objects.create_user(username='zhiliao', email='hynever@zhiliao.com', password='111111')# 此时user对象已经存储到数据库中了。当然你还可以继续使用user对象进行一些修改user.last_name = 'abc' # 如果修改了字段,一定要记得再save()一下!user.save()return HttpResponse("success")
一定要使用create_user而非create来创建用户,因为后者不会对密码进行加密。如果非要用create,可先不设置密码,而是用user.set_password(raw_password),然后再save()。
创建超级用户:
创建超级用户有两种方式。第一种是使用代码的方式。用代码创建超级用户跟创建普通用户非常的类似,只不过是使用User.objects.create_superuser()。示例代码如下:
# views.pyfrom django.contrib.auth.models import Userdef create_superuser(request):User.objects.create_superuser('admin', 'admin@163.com', '111111')return HttpResponse("success")
也可以通过命令行的方式。命令如下:
python manage.py createsuperuser
后面就会提示你输入用户名、邮箱以及密码。
修改密码:
因为密码是需要经过加密后才能存储进去的。所以如果想要修改密码,不能直接修改password字段,而需要通过调用set_password来达到修改密码的目的,因为密码是经过加密后存储的。示例代码如下:
from django.contrib.auth.models import Useruser = User.objects.get(pk=1)user.set_password('新的密码')user.save()
登录验证(重要!)
Django的验证系统已经帮我们实现了登录验证的功能。通过django.contrib.auth.authenticate()即可实现。这个方法只能通过username和password来进行验证。示例代码如下:
from django.contrib.auth import authenticateuser = authenticate(username='zhiliao', password='111111')# 如果验证通过了,那么就会返回一个user对象。如果验证失败,则返回Noneif user:# 执行验证通过后的代码print("登陆成功: ", user.username)else:# 执行验证没有通过的代码。print("用户名或密码错误!")
这只是通过用户名和密码来验证,对于国人来说,用手机号和密码验证比较符合习惯,我们会在【扩展用户模型:一对一外键】中讲解。
扩展用户模型:
Django内置的User模型虽然已经足够强大了。但是有时候还是不能满足我们的需求。比如在验证用户登录的时候,他用的是用户名作为验证,而我们有时候需要通过手机号码或者邮箱来进行验证。还有比如我们想要增加一些新的字段。那么这时候我们就需要扩展用户模型了。扩展用户模型有多种方式。这里我们来一一讨论下。
1. 使用代理(常用!):
如果你对Django提供的字段,以及验证的方法都比较满意,没有什么需要改的。但是只是需要在他原有的基础之上增加一些操作的方法,那么强烈建议使用这种方式,因为它最简单、无污染、可插拔。其缺点是可扩展能力比较有限。示例代码如下:
# models.pyclass Person(User):class Meta:proxy = True@classmethoddef get_blacklist(cls):return cls.objects.filter(is_active=False)# views.pyblack_list = Person.get_blacklist() # 一条代码即可提取黑名单!
在以上,我们定义了一个Person类,让他继承自User,并且在Meta中设置proxy=True,说明这个只是User的一个代理模型,此时这个模型的字段和方法就不会影响原来User模型在数据库中表的结构。以后如果你想方便的获取所有黑名单的人,那么你就可以通过Person.get_blacklist()就可以获取到。并且由于Person是代理模型,它实际上指向的是User,因此User.objects.all()和Person.objects.all()其实是等价的。
我们可以在代理类中添加方法,也可以添加属性,但是!不能添加models下的任意字段!所以代理模式最大的问题就是:不能添加新的字段。
2. 一对一外键(常用!最保险!):
如果你对用户验证方法authenticate没有其他要求,就是使用username和password即可完成。但是想要在原来模型的基础之上添加新的字段,那么可以使用一对一外键的方式。示例代码如下:
# models.pyfrom django.contrib.auth.models import Userfrom django.db import modelsfrom django.dispatch import receiver # receiver是一个装饰器,可接受某个信号,做出处理from django.db.models.signals import post_save # 保存对象时发出的信号class UserExtension(models.Model):# related_name用来指定将来通过User对象的哪个字段来访问UserExtension中的字段user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='extension')# 接下来就可以扩展字段了telephone = models.CharField(max_length=11)school = models.CharField(max_length=100)@receiver(post_save, sender=User)def create_user_extension(sender, instance, created, **kwargs):if created:# 如果是创建新用户,就创建UserExtensionUserExtension.objects.create(user=instance)else:instance.extension.save() # 注意此处:如果不是新创建的用户,就执行UserExtension的保存def create_user_and_extension(username, telephone, password, email=None, school=None, **extra_fields):user = User.objects.create_user(username=username, email=email, password=password, **extra_fields)user.extension.telephone = telephoneuser.extension.school = schooluser.save()
以上定义一个UserExtension的模型,并且让她和User模型进行一对一的绑定,以后我们新增的字段,就添加到UserExtension上。并且还写了一个接受保存模型的信号处理方法,只要是User调用了save()方法,那么就会自动创建一个UserExtension和User进行绑定。
# views.pyfrom django.http import HttpResponsefrom .models import UserExtension, User, create_user_and_extensiondef create_one_user(request):create_user_and_extension(username='yyy', telephone='17788889999',password='123456', email='yyy@qq.com',school='Peking University')return HttpResponse("success")
一对一外键也是比较常用的,对原User模型污染只有一个字段,不会带来潜在的危险:Django过于强大,你不知道哪些模块会用到User模型的哪些属性或方法,所以对于原生的User,能少动就少动。
之前讲登陆验证的时候,说到通过手机号和密码进行验证,现在来填坑:
def my_authenticate(telephone, password):user = User.objects.filter(extension__telephone=telephone).first()if user:is_correct = user.check_password(password)if is_correct:return userelse:return Noneelse:return None# views.pydef login(request):telephone = request.POST.get('telephone')password = request.POST.get('password')user = my_authenticate(telephone, password)if user:print("验证成功:%s" % user.username)else:print("验证失败:%s" % user.username)return HttpResponse("一对一扩展模型的验证")
3. 继承自AbstractUser(不建议用):
对于authenticate不满意,并且不想要修改原来User对象上的一些字段,但是想要增加一些字段,那么这时候可以直接继承自django.contrib.auth.models.AbstractUser,其实这个类也是django.contrib.auth.models.User的父类。比如我们想要在原来User模型的基础之上添加一个telephone和school字段。示例代码如下:
# models.pyfrom django.contrib.auth.models import AbstractUser, BaseUserManagerfrom django.db import modelsclass UserManager(BaseUserManager):use_in_migrations = Truedef _create_user(self, username, telephone, password, **extra_fields):if not telephone:raise ValueError("创建用户必须要传入手机号码!")if not password:raise ValueError("创建用户必须要传入密码!")user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型user.set_password(password)user.save()return userdef create_user(self, username, telephone, password, **extra_fields):extra_fields.setdefault('is_superuser', False)return self._create_user(username, telephone, password, **extra_fields)def create_superuser(self, username, telephone, password, **extra_fields):extra_fields['is_superuser'] = Truereturn self._create_user(username, telephone, password, **extra_fields)class User(AbstractUser):telephone = models.CharField(max_length=11, unique=True) # 如果将该字段作为“用户名”,一定要设置unique=True哦!# 指定telephone作为USERNAME_FIELD,以后使用authenticate# 函数验证的时候,就可以根据telephone来验证# 而不是原来的usernameUSERNAME_FIELD = 'telephone'REQUIRED_FIELDS = []# 重新定义Manager对象,在创建user的时候使用telephone和# password,而不是使用username和passwordobjects = UserManager()
关键一步:在settings中配置好AUTH_USER_MODEL=yourapp.User。(当做了此步修改后,authenticate()的使用就会有些语义上的“歧义”,请看下方第二个代码)
然后就可以写视图函数测试啦:
def register(request):telephone = '17788889999'username = 'yyy'password = '123456'user = User.objects.create_superuser(username, telephone, password)return HttpResponse("注册成功!")
from django.contrib.auth import authenticatedef authen(request):# 注意!下方电话号码依然传给username关键字!user = authenticate(request, username='17788889999', password='123456')if user:msg = '验证成功!'else:msg = '验证失败!'return HttpResponse(msg)
这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate前就先定义好。
4. 继承自AbstractBaseUser模型(灵活!但要小心!):
如果你想修改默认的验证方式,并且对于原来User模型上的一些字段不想要,比如默认的first_name,last_name,这是歪果仁的风格,中国人不玩这个。
我们可以自定义一个模型,然后继承自AbstractBaseUser,再添加你想要的字段。这个类比前面用的AbstractUser要干净许多!只有两个字段:
- password
- last_login
这种方式需要从零开始,完完全全把之前的模型重构掉!会比较麻烦,最好是确定自己对Django比较了解才推荐使用。步骤如下:
- 重新定义
UserManager:我们需要定义自己的UserManager,因为默认的UserManager在创建用户的时候使用的是username和password,那么我们要替换成telephone。示例代码如下: ```pythonmodels.py
from django.contrib.auth.models import BaseUserManager
class UserManager(BaseUserManager): use_in_migrations = True
def _create_user(self, username, telephone, password, **extra_fields):if not telephone:raise ValueError("创建用户必须要传入手机号码!")if not password:raise ValueError("创建用户必须要传入密码!")user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型user.set_password(password)user.save()return userdef create_user(self, username, telephone, password, **extra_fields):extra_fields.setdefault('is_superuser', False)return self._create_user(username, telephone, password, **extra_fields)def create_superuser(self, username, telephone, password, **extra_fields):extra_fields['is_superuser'] = Truereturn self._create_user(username, telephone, password, **extra_fields)
2. 创建模型。示例代码如下:```python# models.py(包含了上面的代码)from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, AbstractUserfrom django.contrib.auth.models import PermissionsMixinfrom django.db import modelsclass UserManager(BaseUserManager):use_in_migrations = Truedef _create_user(self, username, telephone, password, **extra_fields):if not telephone:raise ValueError("创建用户必须要传入手机号码!")if not password:raise ValueError("创建用户必须要传入密码!")user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型user.set_password(password)user.save()return userdef create_user(self, username, telephone, password, **extra_fields):extra_fields.setdefault('is_superuser', False)return self._create_user(username, telephone, password, **extra_fields)def create_superuser(self, username, telephone, password, **extra_fields):extra_fields['is_superuser'] = Truereturn self._create_user(username, telephone, password, **extra_fields)class User(AbstractBaseUser, PermissionsMixin):telephone = models.CharField(max_length=11, unique=True)username = models.CharField(max_length=150) # AbstractUser的username默认是unique=True的,如果继承自AbstractBaseUser,就可以自定义了is_active = models.BooleanField(default=True)USERNAME_FIELD = 'telephone'REQUIRED_FIELDS = [] # 在命令行创建超级管理员时需要提供的参数列表。如果为空,则只需要USERNAME_FIELD字段(在本例中是telephone)和passwordobjects = UserManager()# 我怎么知道需要完善哪些方法呢?可以参考AbstractUser的方法列表def get_full_name(self):return self.usernamedef get_short_name(self):return self.username
其中password和last_login是在AbstractBaseUser中已经添加好了的,我们直接继承就可以了。然后我们再添加我们想要的字段。比如email、username、telephone等。这样就可以实现自己想要的字段了。但是因为我们重写了User,所以应该尽可能的模拟User模型:
USERNAME_FIELD:用来描述User模型名字字段的字符串,作为唯一的标识。如果没有修改,那么会使用USERNAME来作为唯一字段。REQUIRED_FIELDS:一个字段名列表,用于当通过createsuperuser管理命令创建一个用户时的提示。is_active:一个布尔值,用于标识用户当前是否可用。get_full_name():获取完整的名字。get_short_name():一个比较简短的用户名。
- 在创建了新的
User模型后,还需要在settings中配置好。配置AUTH_USER_MODEL='appname.User'。 此时就可以迁移了。设计阶段到此结束,下面就开始使用啦~ - 接下来尝试基本功能:用户的注册和验证,这次加入了异常处理,程序也更加健壮。
```python
views.py
from django.http import HttpResponse from django.contrib.auth import authenticate from django.contrib.auth import get_user_model
def register(request):
# 下面三个字段模拟用户注册时提交的表单数据telephone = '17788889999'username = 'yyy'password = '123456'try:get_user_model().objects.create_superuser(username, telephone, password)return HttpResponse("注册成功!")except Exception as e:return HttpResponse("注册失败!原因:%s" % e)
def login(request):
# 下面两个数据模拟用户登陆时提交的表单数据telephone = '17788889999'password = '123456'# 注意!下方telephone依然传给username关键字!user = authenticate(request, username=telephone, password=password)if user:msg = '%s:登录成功!' % user.usernameelse:msg = '%s:登录验证失败!' % telephonereturn HttpResponse(msg)
值得一提的是,这次我们并没有直接导入我们自定义的User模型,而是使用了`django.contrib.auth.get_user_model()`函数,它将`User`抽象出来,使用`settings.AUTH_USER_MODEL`来表示。使程序具有更好的通用性。5. 如何将我们定义的用户模型作为外键?比如以后我们有一个`Article`模型,需要通过外键引用这个`User`模型,那么可以通过以下两种方式引用。示例代码如下:```pythonfrom django.db import modelsfrom django.contrib.auth import get_user_modelclass Article(models.Model):title = models.CharField(max_length=100)content = models.TextField()author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate前就先定义好。
权限和分组
登录、注销和登录限制:
登录:
在前面的代码中,我们已经模拟过登录过程,但是登录过程并非这么简单,在学习cookie和session的时候我们已经知道,http连接是无状态的,要想记住用户的登录状态,我们必须得设置session!强大的Django早已为我们准备好了工具:login()函数。
在使用authenticate进行验证后,如果验证通过了。那么会返回一个user对象,拿到user对象后,可以使用django.contrib.auth.login进行登录。示例代码如下:
from django.contrib.auth import loginuser = authenticate(username=username, password=password)if user is not None:if user.is_active:login(request, user)
实战-登录小项目
现在,我们要做一个像样点的demo了,这样才具实战意味~
1、创建一个名为front的app,即前台管理。
2、在上一小节中,我们创建了一个这样一个用户:
telephone = '17788889999'username = 'yyy'password = '123456'
我们将用该用户做登录测试。
3、front/models.py文件使用上一小节的models.py
# models.py(包含了上面的代码)from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, AbstractUserfrom django.contrib.auth.models import PermissionsMixinfrom django.db import modelsclass UserManager(BaseUserManager):use_in_migrations = Truedef _create_user(self, username, telephone, password, **extra_fields):if not telephone:raise ValueError("创建用户必须要传入手机号码!")if not password:raise ValueError("创建用户必须要传入密码!")user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型user.set_password(password)user.save()return userdef create_user(self, username, telephone, password, **extra_fields):extra_fields.setdefault('is_superuser', False)return self._create_user(username, telephone, password, **extra_fields)def create_superuser(self, username, telephone, password, **extra_fields):extra_fields['is_superuser'] = Truereturn self._create_user(username, telephone, password, **extra_fields)class User(AbstractBaseUser, PermissionsMixin):telephone = models.CharField(max_length=11, unique=True)username = models.CharField(max_length=150) # AbstractUser的username默认是unique=True的,如果继承自AbstractBaseUser,就可以自定义了is_active = models.BooleanField(default=True)USERNAME_FIELD = 'telephone'REQUIRED_FIELDS = [] # 在命令行创建超级管理员时需要提供的参数列表。如果为空,则只需要USERNAME_FIELD字段(在本例中是telephone)和passwordobjects = UserManager()# 我怎么知道需要完善哪些方法呢?可以参考AbstractUser的方法列表def get_full_name(self):return self.usernamedef get_short_name(self):return self.username
4、配置settings文件
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','front', # 这里加入front app]AUTH_USER_MODEL = 'front.User' # 设置全局用户模型,方便后期使用get_user_model()函数
5、front/views.py展示了如何使用login函数,定制我们自己的login函数
from django.http import HttpResponsefrom django.shortcuts import render, redirect, reversefrom django.contrib.auth import authenticatefrom django.contrib.auth import get_user_modelfrom django.contrib.auth import loginfrom .forms import LoginFormdef my_login(request):if request.method == 'GET':return render(request, 'login.html')else:form = LoginForm(request.POST)if form.is_valid():telephone = form.cleaned_data.get('telephone')password = form.cleaned_data.get('password')remember = form.cleaned_data.get('remember')# 注意!下方telephone依然传给username关键字!user = authenticate(request, username=telephone, password=password)if user and user.is_active:login(request, user) # 这个login做的事情:设置session。if remember:request.session.set_expiry(None) # 使用全局过期时间else:request.session.set_expiry(0) # 浏览器结束会话后过期return HttpResponse("登录成功!")else:return HttpResponse("手机号或密码错误!")else:print(form.errors)return redirect(reverse('login'))
上面用到的login.html文件和LoginForm分别在下面两步中。
6、模板文件
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>登录</title></head><body><form action="" method="POST">{% csrf_token %}<table><tbody><tr><td>手机:</td><td><input type="text" name="telephone"></td></tr><tr><td>密码:</td><td><input type="password" name="password"></td></tr><tr><td><label for=""><input type="checkbox" name="remember" value="1">记住我</label></td><td></td></tr><tr><td></td><td><input type="submit" value="登录"></td></tr></tbody></table></form></body></html>
7、表单文件
from django import formsfrom django.contrib.auth import get_user_modelclass LoginForm(forms.ModelForm):remember = forms.IntegerField(required=False)telephone = forms.CharField(max_length=11) # 为何把telephone单独拎出来?ModelForm的验证机制问题。看下方注释class Meta:model = get_user_model()fields = ['password']# 为何不把telephone放在这里,却拿出来放在上面?因为Django的ModelForm会对每一个unique=True的字段做唯一性验证,# 如果和数据库中的数据重复,就报错!像这样:User with this Telephone already exists.# 这在用户注册时无疑符合逻辑,但登录时就不对了呀!所以要单独拎出来。
8、url映射
from django.urls import pathfrom front import viewsurlpatterns = [path('login/', views.my_login, name='login'),]
大功告成!
启动项目,输入我们在上一小节创建的用户名和密码,登录成功!
可以看到,一切数据均符合预期!爽!
注销:
注销,或者说退出登录。我们可以通过django.contrib.auth.logout来实现。他会清理掉这个用户的session数据。
from django.contrib.auth import logoutdef my_logout(request):logout(request)return redirect(reverse('index'))
执行过上面的视图函数后再查看cookie信息,就会发现session的到期时间和创建时间一样了,也就是过期了,也就达成了退出的目的。
登录限制(重要):
有时候,某个视图函数是需要经过登录后才能访问的。那么我们可以通过django.contrib.auth.decorators.login_required(官网说明:login_required)装饰器来实现。示例代码如下:
在上面的登录小项目实战中,修改front/views.py代码:
from django.http import HttpResponsefrom django.shortcuts import render, redirect, reversefrom django.contrib.auth import authenticatefrom django.contrib.auth import login, logoutfrom .forms import LoginFormfrom django.contrib.auth.decorators import login_requireddef my_login(request):if request.method == 'GET':return render(request, 'login.html')else:form = LoginForm(request.POST)if form.is_valid():telephone = form.cleaned_data.get('telephone')password = form.cleaned_data.get('password')remember = form.cleaned_data.get('remember')user = authenticate(request, username=telephone, password=password)if user and user.is_active:login(request, user)if remember:request.session.set_expiry(None)else:request.session.set_expiry(0)next_url = request.GET.get('next')if next_url: # 如果能获得next_url,就重定向到它return redirect(next_url)return HttpResponse("登录成功!")else:return HttpResponse("手机号或密码错误!")else:print(form.errors)return redirect(reverse('login'))def my_logout(request):logout(request)return HttpResponse("成功退出登录!")@login_required(login_url='/login/') # 必须要指定登录的URLdef profile(request):return HttpResponse("这是个人中心,只有登录了以后才能查看!")
from django.urls import pathfrom front import viewsurlpatterns = [path('login/', views.my_login, name='login'),path('logout/', views.my_logout, name='logout'),path('profile/', views.profile, name='profile'),]
运行发现:如果已经登录,可以在浏览器中直接访问:http://127.0.0.1:8000/profile/,如果执行过http://127.0.0.1:8000/logout/后再访问profile,就职转向登录界面,登录完成后,再自动跳转回profile页面。一切结果均符合预期~
权限:
Django中内置了权限的功能。他的权限都是针对表或者说是模型级别的。比如对某个模型上的数据是否可以进行增删改查操作。他不能针对数据级别的,比如对某个表中的某条数据能否进行增删改查操作(如果要实现数据级别的,考虑使用django-guardian)。创建完一个模型后,针对这个模型默认就有四种权限,分别是增/删/改/查。可以在执行完migrate命令后,查看数据库中的auth_permission表中的所有权限。
后两个字段很好理解,其中的codename表示的是权限的名字。name表示的是这个权限的作用。
那么conten_type_id是干嘛的呢?
很显然,他连接了另一张表,一张叫content_type的表,这个content_type是干嘛的来?它专门表明某个模型是属于哪个app下面的,长下面这样:
如果想看权限模型的源代码,可以导入 from django.contrib.auth.models import Permission导入查看。
添加权限:
通过定义模型添加权限:
如果我们想要增加新的权限,比如查看某个模型的权限,那么我们可以在定义模型的时候在Meta中定义好。示例代码如下:
class Article(models.Model):title = models.CharField(max_length=100)content = models.TextField()author = models.ForeignKey(get_user_model(),on_delete=models.CASCADE)class Meta:permissions = (('black_article','拉黑文章'), # (codename, name))
通过代码添加权限:
权限都是django.contrib.auth.Permission的实例。所以添加权限的本质是创建Permission实例,即在auth_permission表中添加一条数据。这个模型包含三个字段,name、codename以及content_type,其中的content_type表示这个permission是属于哪个app下的哪个models。用Permission模型创建权限的代码如下:
from django.contrib.auth.models import Permission,ContentTypefrom .models import Articledef add_permission(request):content_type = ContentType.objects.get_for_model(Article)try:Permission.objects.create(codename='black_article', name='拉黑文章', content_type=content_type)return HttpResponse("添加权限成功!")except Exception as e:return HttpResponse("添加权限失败!原因:%s" % e)
运行上述代码,发现auth_permission表中多了一条数据,证明我们的操作成功!
用户与权限管理:
权限本身只是一个数据,必须和用户进行绑定,才能起到作用。用户与权限的关系是多对多的关系。既然是多对多的关系,那必然存在一张中间表来作桥梁,果不其然,初次迁移后,我们会发现下面这样一张表:
如果我们没有修改默认的用户模型,其表名是auth_user_user_permissions。可以发现,front_user_user_permissions中空空如也,说明我们唯一的超级用户竟然没有任何权限,是不是很奇怪?不用奇怪,超级用户默认拥有所有权限,所有在不在表中添加无所谓,干脆就没添加。所以说,如果你对超级用户执行了下面user.user_permissions.set(permission_list)或者user.user_permissions.add(permission,permission,...),虽然在front_user_user_permissions表中添加了数据,但没有实际影响。
如果对于非超级用户,执行上面的方法影响可就大喽~
User模型和权限之间的管理,可以通过以下几种方式来管理:
user.user_permissions.set(permission_list):直接给定一个权限的列表。user.user_permissions.add(permission,permission,...):一个个添加权限。user.user_permissions.remove(permission,permission,...):一个个删除权限。user.user_permissions.clear():清除当前用户的所有权限。user.has_perm('<app_name>.<codename>'):判断是否拥有某个权限。权限参数是一个字符串,格式是app_name.codename。user.get_all_permissons():获取所有的权限。 ```python from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission, ContentType from .models import Article
def operate_permission(request): user = get_user_model().objects.first() content_type = ContentType.objects.get_for_model(Article) permissions = Permission.objects.filter(content_type=content_type) user.user_permissions.set(permissions) return HttpResponse(“一次添加多条权限”)
<a name="hxIzM"></a>#### 权限缓存[ModelBackend](https://docs.djangoproject.com/zh-hans/3.2/ref/contrib/auth/#django.contrib.auth.backends.ModelBackend) 在首次为权限检查提取用户对象的权限之后**缓存**它们。这对于请求-响应周期来说通常是好的,因为通常在添加权限之后不会立即检查它们(例如,在管理员中)。如果您正在添加权限,并在之后立即检查它们,例如在测试或视图中,最简单的解决方案是从数据库中**重新获取用户**。```pythonfrom django.contrib.auth.models import Permission, Userfrom django.contrib.contenttypes.models import ContentTypefrom django.shortcuts import get_object_or_404from myapp.models import BlogPostdef user_gains_perms(request, user_id):user = get_object_or_404(User, pk=user_id)# any permission check will cache the current set of permissionsuser.has_perm('myapp.change_blogpost')content_type = ContentType.objects.get_for_model(BlogPost)permission = Permission.objects.get(codename='change_blogpost',content_type=content_type,)user.user_permissions.add(permission)# Checking the cached permission setuser.has_perm('myapp.change_blogpost') # False# Request new instance of User# Be aware that user.refresh_from_db() won't clear the cache.user = get_object_or_404(User, pk=user_id)# Permission cache is repopulated from the databaseuser.has_perm('myapp.change_blogpost') # True...
上面的代码中用到了get_object_or_404())这个神器函数,值得学习!
权限限定装饰器:permission_required()
以我们先用的知识,如果想要判断一个用户是否拥有添加文章的权限,需要这样:
from django.http import HttpResponsefrom django.shortcuts import redirect, reversedef add_article(request):if request.user.is_authenticated: # 神器函数:判断是否已经登录if request.user.has_perm('front.add_article'): # 再判断是否有权限return HttpResponse("这是添加文章的页面!")else:return HttpResponse("您没有访问该页面的权限!", status=403)else:return redirect(reverse('login')) # 如果没有权限,就重定向到登录
可以看到代码还是相当臃肿的。其实使用django.contrib.auth.decorators.permission_required可以非常方便的检查用户是否拥有这个权限,如果拥有,那么就可以进入到指定的视图函数中,如果不拥有,那么就会报一个400错误。示例代码如下:
from django.contrib.auth.decorators import permission_required@permission_required('front.view_article')def my_view(request):...
分组:
权限有很多,一个模型就有最少四个权限,如果一些用户拥有相同的权限,那么每次都要重复添加。这时候分组就可以帮我们解决这种问题了,我们可以把一些权限归类,然后添加到某个分组中,之后再把和把需要赋予这些权限的用户添加到这个分组中,就比较好管理了。分组我们使用的是django.contrib.auth.models.Group模型, 每个用户组拥有id和name两个字段,该模型在数据库被映射为auth_group数据表。
分组操作:
Group.object.create(group_name):创建分组。group.permissions:某个分组上的权限。多对多的关系。group.permissions.add:添加权限。group.permissions.remove:移除权限。group.permissions.clear:清除所有权限。user.get_group_permissions():获取用户所属组的权限。
user.groups:某个用户上的所有分组。多对多的关系。
from django.http import HttpResponsefrom django.contrib.auth.models import Permission, ContentType, Groupfrom .models import Articledef operate_group(request):group = Group.objects.create(name='运营组')content_type = ContentType.objects.get_for_model(Article)permissions = Permission.objects.filter(content_type=content_type)group.permissions.set(permissions)return HttpResponse("操作分组界面")

has_perm()判断顺序:
在settings.TEMPLATES.OPTIONS.context_processors下,因为添加了django.contrib.auth.context_processors.auth上下文处理器,因此在模板中可以直接通过perms来获取用户的所有权限。示例代码如下:
{% if perms.front_add_article %}<a href='article/add/'>添加文章</a>{% endif %}
