官方文档

  1. 使用 Django 的验证系统(重要)
  2. Django中的密码管理
  3. Django 中的自定义验证(重要)

    验证和授权概述

Django有一个内置的授权系统。他用来处理用户、分组、权限以及基于cookie的会话系统。Django的授权系统包括验证和授权两个部分。验证是验证这个用户是否是他声称的人(比如用户名和密码验证,角色验证),授权是给与他相应的权限。Django内置的权限系统包括以下方面:

  1. 用户。
  2. 权限。
  3. 分组。
  4. 一个可以配置的密码哈希系统。
  5. 为登录用户或限制内容提供表单和视图工具。
  6. 一个可插拔的后台管理系统。

使用授权系统:

默认中创建完一个django项目后,其实就已经集成了授权系统。那哪些部分是跟授权系统相关的配置呢。以下做一个简单列表:

INSTALLED_APPS

  1. django.contrib.auth:包含了一个核心授权框架,以及大部分的模型定义。
  2. django.contrib.contenttypesContent Type系统,可以用来关联模型和权限。

中间件:

  1. SessionMiddleware:用来管理session
  2. AuthenticationMiddleware:用来处理和当前session相关联的用户。

User模型

User模型是这个框架的核心部分。他的完整的路径是在django.contrib.auth.models.User。以下对这个User对象做一个简单了解:

字段:

内置的User模型拥有以下的字段:

  1. username: 用户名。150个字符以内。可以包含数字和英文字符,以及_@+.-字符。不能为空,且必须唯一!此处需要注意的是,国内唯一性验证往往是手机号码或者邮箱,而非用户名。后面会介绍更改方式。
  2. first_name:歪果仁的first_name,在30个字符以内。可以为空。
  3. last_name:歪果仁的last_name,在150个字符以内。可以为空。
  4. email:邮箱。可以为空。
  5. password:密码。经过哈希过后的密码。
  6. groups:分组。一个用户可以属于多个分组,一个分组可以拥有多个用户。groups这个字段是跟Group的一个多对多的关系。
  7. user_permissions:权限。一个用户可以拥有多个权限,一个权限可以被多个用户所有用。和Permission属于一种多对多的关系。
  8. is_staff:是否可以进入到admin的站点。代表是否是员工。
  9. is_active:是否是可用的。对于一些想要删除账号的数据,我们设置这个值为False就可以了,而不是真正的从数据库中删除。
  10. is_superuser:是否是超级管理员。如果是超级管理员,那么拥有整个网站的所有权限。
  11. last_login:上次登录的时间。
  12. date_joined:账号创建的时间。

和Linux的权限系统很类似有木有?🧐

User模型的基本用法:

创建用户:

通过User.objects.create_user()方法可以快速的创建用户。这个方法必须要传递usernameemailpassword。示例代码如下:

  1. from django.contrib.auth.models import User
  2. def create_user(request):
  3. user = User.objects.create_user(username='zhiliao', email='hynever@zhiliao.com', password='111111')
  4. # 此时user对象已经存储到数据库中了。当然你还可以继续使用user对象进行一些修改
  5. user.last_name = 'abc' # 如果修改了字段,一定要记得再save()一下!
  6. user.save()
  7. return HttpResponse("success")

一定要使用create_user而非create来创建用户,因为后者不会对密码进行加密。如果非要用create,可先不设置密码,而是用user.set_password(raw_password),然后再save()

创建超级用户:

创建超级用户有两种方式。第一种是使用代码的方式。用代码创建超级用户跟创建普通用户非常的类似,只不过是使用User.objects.create_superuser()。示例代码如下:

  1. # views.py
  2. from django.contrib.auth.models import User
  3. def create_superuser(request):
  4. User.objects.create_superuser('admin', 'admin@163.com', '111111')
  5. return HttpResponse("success")

也可以通过命令行的方式。命令如下:

  1. python manage.py createsuperuser

后面就会提示你输入用户名、邮箱以及密码。

修改密码:

因为密码是需要经过加密后才能存储进去的。所以如果想要修改密码,不能直接修改password字段,而需要通过调用set_password来达到修改密码的目的,因为密码是经过加密后存储的。示例代码如下:

  1. from django.contrib.auth.models import User
  2. user = User.objects.get(pk=1)
  3. user.set_password('新的密码')
  4. user.save()

登录验证(重要!)

Django的验证系统已经帮我们实现了登录验证的功能。通过django.contrib.auth.authenticate()即可实现。这个方法只能通过usernamepassword来进行验证。示例代码如下:

  1. from django.contrib.auth import authenticate
  2. user = authenticate(username='zhiliao', password='111111')
  3. # 如果验证通过了,那么就会返回一个user对象。如果验证失败,则返回None
  4. if user:
  5. # 执行验证通过后的代码
  6. print("登陆成功: ", user.username)
  7. else:
  8. # 执行验证没有通过的代码。
  9. print("用户名或密码错误!")

这只是通过用户名和密码来验证,对于国人来说,用手机号和密码验证比较符合习惯,我们会在【扩展用户模型:一对一外键】中讲解。

扩展用户模型:

Django内置的User模型虽然已经足够强大了。但是有时候还是不能满足我们的需求。比如在验证用户登录的时候,他用的是用户名作为验证,而我们有时候需要通过手机号码或者邮箱来进行验证。还有比如我们想要增加一些新的字段。那么这时候我们就需要扩展用户模型了。扩展用户模型有多种方式。这里我们来一一讨论下。

1. 使用代理(常用!):

如果你对Django提供的字段,以及验证的方法都比较满意,没有什么需要改的。但是只是需要在他原有的基础之上增加一些操作的方法,那么强烈建议使用这种方式,因为它最简单、无污染、可插拔。其缺点是可扩展能力比较有限。示例代码如下:

  1. # models.py
  2. class Person(User):
  3. class Meta:
  4. proxy = True
  5. @classmethod
  6. def get_blacklist(cls):
  7. return cls.objects.filter(is_active=False)
  8. # views.py
  9. black_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没有其他要求,就是使用usernamepassword即可完成。但是想要在原来模型的基础之上添加新的字段,那么可以使用一对一外键的方式。示例代码如下:

  1. # models.py
  2. from django.contrib.auth.models import User
  3. from django.db import models
  4. from django.dispatch import receiver # receiver是一个装饰器,可接受某个信号,做出处理
  5. from django.db.models.signals import post_save # 保存对象时发出的信号
  6. class UserExtension(models.Model):
  7. # related_name用来指定将来通过User对象的哪个字段来访问UserExtension中的字段
  8. user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='extension')
  9. # 接下来就可以扩展字段了
  10. telephone = models.CharField(max_length=11)
  11. school = models.CharField(max_length=100)
  12. @receiver(post_save, sender=User)
  13. def create_user_extension(sender, instance, created, **kwargs):
  14. if created:
  15. # 如果是创建新用户,就创建UserExtension
  16. UserExtension.objects.create(user=instance)
  17. else:
  18. instance.extension.save() # 注意此处:如果不是新创建的用户,就执行UserExtension的保存
  19. def create_user_and_extension(username, telephone, password, email=None, school=None, **extra_fields):
  20. user = User.objects.create_user(username=username, email=email, password=password, **extra_fields)
  21. user.extension.telephone = telephone
  22. user.extension.school = school
  23. user.save()

以上定义一个UserExtension的模型,并且让她和User模型进行一对一的绑定,以后我们新增的字段,就添加到UserExtension上。并且还写了一个接受保存模型的信号处理方法,只要是User调用了save()方法,那么就会自动创建一个UserExtensionUser进行绑定。

  1. # views.py
  2. from django.http import HttpResponse
  3. from .models import UserExtension, User, create_user_and_extension
  4. def create_one_user(request):
  5. create_user_and_extension(username='yyy', telephone='17788889999',
  6. password='123456', email='yyy@qq.com',
  7. school='Peking University')
  8. return HttpResponse("success")

一对一外键也是比较常用的,对原User模型污染只有一个字段,不会带来潜在的危险:Django过于强大,你不知道哪些模块会用到User模型的哪些属性或方法,所以对于原生的User,能少动就少动。

之前讲登陆验证的时候,说到通过手机号和密码进行验证,现在来填坑:

  1. def my_authenticate(telephone, password):
  2. user = User.objects.filter(extension__telephone=telephone).first()
  3. if user:
  4. is_correct = user.check_password(password)
  5. if is_correct:
  6. return user
  7. else:
  8. return None
  9. else:
  10. return None
  11. # views.py
  12. def login(request):
  13. telephone = request.POST.get('telephone')
  14. password = request.POST.get('password')
  15. user = my_authenticate(telephone, password)
  16. if user:
  17. print("验证成功:%s" % user.username)
  18. else:
  19. print("验证失败:%s" % user.username)
  20. return HttpResponse("一对一扩展模型的验证")

3. 继承自AbstractUser(不建议用):

对于authenticate不满意,并且不想要修改原来User对象上的一些字段,但是想要增加一些字段,那么这时候可以直接继承自django.contrib.auth.models.AbstractUser,其实这个类也是django.contrib.auth.models.User的父类。比如我们想要在原来User模型的基础之上添加一个telephoneschool字段。示例代码如下:

  1. # models.py
  2. from django.contrib.auth.models import AbstractUser, BaseUserManager
  3. from django.db import models
  4. class UserManager(BaseUserManager):
  5. use_in_migrations = True
  6. def _create_user(self, username, telephone, password, **extra_fields):
  7. if not telephone:
  8. raise ValueError("创建用户必须要传入手机号码!")
  9. if not password:
  10. raise ValueError("创建用户必须要传入密码!")
  11. user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型
  12. user.set_password(password)
  13. user.save()
  14. return user
  15. def create_user(self, username, telephone, password, **extra_fields):
  16. extra_fields.setdefault('is_superuser', False)
  17. return self._create_user(username, telephone, password, **extra_fields)
  18. def create_superuser(self, username, telephone, password, **extra_fields):
  19. extra_fields['is_superuser'] = True
  20. return self._create_user(username, telephone, password, **extra_fields)
  21. class User(AbstractUser):
  22. telephone = models.CharField(max_length=11, unique=True) # 如果将该字段作为“用户名”,一定要设置unique=True哦!
  23. # 指定telephone作为USERNAME_FIELD,以后使用authenticate
  24. # 函数验证的时候,就可以根据telephone来验证
  25. # 而不是原来的username
  26. USERNAME_FIELD = 'telephone'
  27. REQUIRED_FIELDS = []
  28. # 重新定义Manager对象,在创建user的时候使用telephone和
  29. # password,而不是使用username和password
  30. objects = UserManager()

关键一步:在settings中配置好AUTH_USER_MODEL=yourapp.User。(当做了此步修改后,authenticate()的使用就会有些语义上的“歧义”,请看下方第二个代码)

然后就可以写视图函数测试啦:

  1. def register(request):
  2. telephone = '17788889999'
  3. username = 'yyy'
  4. password = '123456'
  5. user = User.objects.create_superuser(username, telephone, password)
  6. return HttpResponse("注册成功!")
  1. from django.contrib.auth import authenticate
  2. def authen(request):
  3. # 注意!下方电话号码依然传给username关键字!
  4. user = authenticate(request, username='17788889999', password='123456')
  5. if user:
  6. msg = '验证成功!'
  7. else:
  8. msg = '验证失败!'
  9. return HttpResponse(msg)

这种方式因为破坏了原来User模型的表结构,所以必须要在第一次migrate前就先定义好。

4. 继承自AbstractBaseUser模型(灵活!但要小心!):

如果你想修改默认的验证方式,并且对于原来User模型上的一些字段不想要,比如默认的first_namelast_name,这是歪果仁的风格,中国人不玩这个。

我们可以自定义一个模型,然后继承自AbstractBaseUser,再添加你想要的字段。这个类比前面用的AbstractUser要干净许多!只有两个字段:

  • password
  • last_login

这种方式需要从零开始,完完全全把之前的模型重构掉!会比较麻烦,最好是确定自己对Django比较了解才推荐使用。步骤如下:

  1. 重新定义UserManager:我们需要定义自己的UserManager,因为默认的UserManager在创建用户的时候使用的是username和password,那么我们要替换成telephone。示例代码如下: ```python

    models.py

    from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager): use_in_migrations = True

  1. def _create_user(self, username, telephone, password, **extra_fields):
  2. if not telephone:
  3. raise ValueError("创建用户必须要传入手机号码!")
  4. if not password:
  5. raise ValueError("创建用户必须要传入密码!")
  6. user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型
  7. user.set_password(password)
  8. user.save()
  9. return user
  10. def create_user(self, username, telephone, password, **extra_fields):
  11. extra_fields.setdefault('is_superuser', False)
  12. return self._create_user(username, telephone, password, **extra_fields)
  13. def create_superuser(self, username, telephone, password, **extra_fields):
  14. extra_fields['is_superuser'] = True
  15. return self._create_user(username, telephone, password, **extra_fields)
  1. 2. 创建模型。示例代码如下:
  2. ```python
  3. # models.py(包含了上面的代码)
  4. from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, AbstractUser
  5. from django.contrib.auth.models import PermissionsMixin
  6. from django.db import models
  7. class UserManager(BaseUserManager):
  8. use_in_migrations = True
  9. def _create_user(self, username, telephone, password, **extra_fields):
  10. if not telephone:
  11. raise ValueError("创建用户必须要传入手机号码!")
  12. if not password:
  13. raise ValueError("创建用户必须要传入密码!")
  14. user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型
  15. user.set_password(password)
  16. user.save()
  17. return user
  18. def create_user(self, username, telephone, password, **extra_fields):
  19. extra_fields.setdefault('is_superuser', False)
  20. return self._create_user(username, telephone, password, **extra_fields)
  21. def create_superuser(self, username, telephone, password, **extra_fields):
  22. extra_fields['is_superuser'] = True
  23. return self._create_user(username, telephone, password, **extra_fields)
  24. class User(AbstractBaseUser, PermissionsMixin):
  25. telephone = models.CharField(max_length=11, unique=True)
  26. username = models.CharField(max_length=150) # AbstractUser的username默认是unique=True的,如果继承自AbstractBaseUser,就可以自定义了
  27. is_active = models.BooleanField(default=True)
  28. USERNAME_FIELD = 'telephone'
  29. REQUIRED_FIELDS = [] # 在命令行创建超级管理员时需要提供的参数列表。如果为空,则只需要USERNAME_FIELD字段(在本例中是telephone)和password
  30. objects = UserManager()
  31. # 我怎么知道需要完善哪些方法呢?可以参考AbstractUser的方法列表
  32. def get_full_name(self):
  33. return self.username
  34. def get_short_name(self):
  35. return self.username


其中passwordlast_login是在AbstractBaseUser中已经添加好了的,我们直接继承就可以了。然后我们再添加我们想要的字段。比如emailusernametelephone等。这样就可以实现自己想要的字段了。但是因为我们重写了User,所以应该尽可能的模拟User模型:

  • USERNAME_FIELD:用来描述User模型名字字段的字符串,作为唯一的标识。如果没有修改,那么会使用USERNAME来作为唯一字段。
  • REQUIRED_FIELDS:一个字段名列表,用于当通过createsuperuser管理命令创建一个用户时的提示。
  • is_active:一个布尔值,用于标识用户当前是否可用。
  • get_full_name():获取完整的名字。
  • get_short_name():一个比较简短的用户名。
  1. 在创建了新的User模型后,还需要在settings中配置好。配置AUTH_USER_MODEL='appname.User'。 此时就可以迁移了。设计阶段到此结束,下面就开始使用啦~
  2. 接下来尝试基本功能:用户的注册和验证,这次加入了异常处理,程序也更加健壮。 ```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):

  1. # 下面三个字段模拟用户注册时提交的表单数据
  2. telephone = '17788889999'
  3. username = 'yyy'
  4. password = '123456'
  5. try:
  6. get_user_model().objects.create_superuser(username, telephone, password)
  7. return HttpResponse("注册成功!")
  8. except Exception as e:
  9. return HttpResponse("注册失败!原因:%s" % e)

def login(request):

  1. # 下面两个数据模拟用户登陆时提交的表单数据
  2. telephone = '17788889999'
  3. password = '123456'
  4. # 注意!下方telephone依然传给username关键字!
  5. user = authenticate(request, username=telephone, password=password)
  6. if user:
  7. msg = '%s:登录成功!' % user.username
  8. else:
  9. msg = '%s:登录验证失败!' % telephone
  10. return HttpResponse(msg)
  1. 值得一提的是,这次我们并没有直接导入我们自定义的User模型,而是使用了`django.contrib.auth.get_user_model()`函数,它将`User`抽象出来,使用`settings.AUTH_USER_MODEL`来表示。使程序具有更好的通用性。
  2. 5. 如何将我们定义的用户模型作为外键?比如以后我们有一个`Article`模型,需要通过外键引用这个`User`模型,那么可以通过以下两种方式引用。示例代码如下:
  3. ```python
  4. from django.db import models
  5. from django.contrib.auth import get_user_model
  6. class Article(models.Model):
  7. title = models.CharField(max_length=100)
  8. content = models.TextField()
  9. 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进行登录。示例代码如下:

  1. from django.contrib.auth import login
  2. user = authenticate(username=username, password=password)
  3. if user is not None:
  4. if user.is_active:
  5. login(request, user)

实战-登录小项目

现在,我们要做一个像样点的demo了,这样才具实战意味~
1、创建一个名为front的app,即前台管理。
2、在上一小节中,我们创建了一个这样一个用户:

  1. telephone = '17788889999'
  2. username = 'yyy'
  3. password = '123456'

我们将用该用户做登录测试。
3、front/models.py文件使用上一小节的models.py

  1. # models.py(包含了上面的代码)
  2. from django.contrib.auth.models import BaseUserManager, AbstractBaseUser, AbstractUser
  3. from django.contrib.auth.models import PermissionsMixin
  4. from django.db import models
  5. class UserManager(BaseUserManager):
  6. use_in_migrations = True
  7. def _create_user(self, username, telephone, password, **extra_fields):
  8. if not telephone:
  9. raise ValueError("创建用户必须要传入手机号码!")
  10. if not password:
  11. raise ValueError("创建用户必须要传入密码!")
  12. user = self.model(username=username, telephone=telephone, **extra_fields) # self.model代表当前的用户模型
  13. user.set_password(password)
  14. user.save()
  15. return user
  16. def create_user(self, username, telephone, password, **extra_fields):
  17. extra_fields.setdefault('is_superuser', False)
  18. return self._create_user(username, telephone, password, **extra_fields)
  19. def create_superuser(self, username, telephone, password, **extra_fields):
  20. extra_fields['is_superuser'] = True
  21. return self._create_user(username, telephone, password, **extra_fields)
  22. class User(AbstractBaseUser, PermissionsMixin):
  23. telephone = models.CharField(max_length=11, unique=True)
  24. username = models.CharField(max_length=150) # AbstractUser的username默认是unique=True的,如果继承自AbstractBaseUser,就可以自定义了
  25. is_active = models.BooleanField(default=True)
  26. USERNAME_FIELD = 'telephone'
  27. REQUIRED_FIELDS = [] # 在命令行创建超级管理员时需要提供的参数列表。如果为空,则只需要USERNAME_FIELD字段(在本例中是telephone)和password
  28. objects = UserManager()
  29. # 我怎么知道需要完善哪些方法呢?可以参考AbstractUser的方法列表
  30. def get_full_name(self):
  31. return self.username
  32. def get_short_name(self):
  33. return self.username

4、配置settings文件

  1. INSTALLED_APPS = [
  2. 'django.contrib.admin',
  3. 'django.contrib.auth',
  4. 'django.contrib.contenttypes',
  5. 'django.contrib.sessions',
  6. 'django.contrib.messages',
  7. 'django.contrib.staticfiles',
  8. 'front', # 这里加入front app
  9. ]
  10. AUTH_USER_MODEL = 'front.User' # 设置全局用户模型,方便后期使用get_user_model()函数

5、front/views.py展示了如何使用login函数,定制我们自己的login函数

  1. from django.http import HttpResponse
  2. from django.shortcuts import render, redirect, reverse
  3. from django.contrib.auth import authenticate
  4. from django.contrib.auth import get_user_model
  5. from django.contrib.auth import login
  6. from .forms import LoginForm
  7. def my_login(request):
  8. if request.method == 'GET':
  9. return render(request, 'login.html')
  10. else:
  11. form = LoginForm(request.POST)
  12. if form.is_valid():
  13. telephone = form.cleaned_data.get('telephone')
  14. password = form.cleaned_data.get('password')
  15. remember = form.cleaned_data.get('remember')
  16. # 注意!下方telephone依然传给username关键字!
  17. user = authenticate(request, username=telephone, password=password)
  18. if user and user.is_active:
  19. login(request, user) # 这个login做的事情:设置session。
  20. if remember:
  21. request.session.set_expiry(None) # 使用全局过期时间
  22. else:
  23. request.session.set_expiry(0) # 浏览器结束会话后过期
  24. return HttpResponse("登录成功!")
  25. else:
  26. return HttpResponse("手机号或密码错误!")
  27. else:
  28. print(form.errors)
  29. return redirect(reverse('login'))

上面用到的login.html文件和LoginForm分别在下面两步中。
6、模板文件

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>登录</title>
  6. </head>
  7. <body>
  8. <form action="" method="POST">
  9. {% csrf_token %}
  10. <table>
  11. <tbody>
  12. <tr>
  13. <td>手机:</td>
  14. <td><input type="text" name="telephone"></td>
  15. </tr>
  16. <tr>
  17. <td>密码:</td>
  18. <td><input type="password" name="password"></td>
  19. </tr>
  20. <tr>
  21. <td>
  22. <label for="">
  23. <input type="checkbox" name="remember" value="1">记住我
  24. </label>
  25. </td>
  26. <td></td>
  27. </tr>
  28. <tr>
  29. <td></td>
  30. <td><input type="submit" value="登录"></td>
  31. </tr>
  32. </tbody>
  33. </table>
  34. </form>
  35. </body>
  36. </html>

7、表单文件

  1. from django import forms
  2. from django.contrib.auth import get_user_model
  3. class LoginForm(forms.ModelForm):
  4. remember = forms.IntegerField(required=False)
  5. telephone = forms.CharField(max_length=11) # 为何把telephone单独拎出来?ModelForm的验证机制问题。看下方注释
  6. class Meta:
  7. model = get_user_model()
  8. fields = ['password']
  9. # 为何不把telephone放在这里,却拿出来放在上面?因为Django的ModelForm会对每一个unique=True的字段做唯一性验证,
  10. # 如果和数据库中的数据重复,就报错!像这样:User with this Telephone already exists.
  11. # 这在用户注册时无疑符合逻辑,但登录时就不对了呀!所以要单独拎出来。

8、url映射

  1. from django.urls import path
  2. from front import views
  3. urlpatterns = [
  4. path('login/', views.my_login, name='login'),
  5. ]

大功告成!

启动项目,输入我们在上一小节创建的用户名和密码,登录成功!
image.png
可以看到,一切数据均符合预期!爽!

注销:

注销,或者说退出登录。我们可以通过django.contrib.auth.logout来实现。他会清理掉这个用户的session数据。

  1. from django.contrib.auth import logout
  2. def my_logout(request):
  3. logout(request)
  4. return redirect(reverse('index'))

执行过上面的视图函数后再查看cookie信息,就会发现session的到期时间和创建时间一样了,也就是过期了,也就达成了退出的目的。

登录限制(重要):

有时候,某个视图函数是需要经过登录后才能访问的。那么我们可以通过django.contrib.auth.decorators.login_required(官网说明:login_required)装饰器来实现。示例代码如下:
在上面的登录小项目实战中,修改front/views.py代码:

  1. from django.http import HttpResponse
  2. from django.shortcuts import render, redirect, reverse
  3. from django.contrib.auth import authenticate
  4. from django.contrib.auth import login, logout
  5. from .forms import LoginForm
  6. from django.contrib.auth.decorators import login_required
  7. def my_login(request):
  8. if request.method == 'GET':
  9. return render(request, 'login.html')
  10. else:
  11. form = LoginForm(request.POST)
  12. if form.is_valid():
  13. telephone = form.cleaned_data.get('telephone')
  14. password = form.cleaned_data.get('password')
  15. remember = form.cleaned_data.get('remember')
  16. user = authenticate(request, username=telephone, password=password)
  17. if user and user.is_active:
  18. login(request, user)
  19. if remember:
  20. request.session.set_expiry(None)
  21. else:
  22. request.session.set_expiry(0)
  23. next_url = request.GET.get('next')
  24. if next_url: # 如果能获得next_url,就重定向到它
  25. return redirect(next_url)
  26. return HttpResponse("登录成功!")
  27. else:
  28. return HttpResponse("手机号或密码错误!")
  29. else:
  30. print(form.errors)
  31. return redirect(reverse('login'))
  32. def my_logout(request):
  33. logout(request)
  34. return HttpResponse("成功退出登录!")
  35. @login_required(login_url='/login/') # 必须要指定登录的URL
  36. def profile(request):
  37. return HttpResponse("这是个人中心,只有登录了以后才能查看!")
  1. from django.urls import path
  2. from front import views
  3. urlpatterns = [
  4. path('login/', views.my_login, name='login'),
  5. path('logout/', views.my_logout, name='logout'),
  6. path('profile/', views.profile, name='profile'),
  7. ]

运行发现:如果已经登录,可以在浏览器中直接访问:http://127.0.0.1:8000/profile/,如果执行过http://127.0.0.1:8000/logout/后再访问profile,就职转向登录界面,登录完成后,再自动跳转回profile页面。一切结果均符合预期~

权限:

Django中内置了权限的功能。他的权限都是针对表或者说是模型级别的。比如对某个模型上的数据是否可以进行增删改查操作。他不能针对数据级别的,比如对某个表中的某条数据能否进行增删改查操作(如果要实现数据级别的,考虑使用django-guardian)。创建完一个模型后,针对这个模型默认就有四种权限,分别是增/删/改/查。可以在执行完migrate命令后,查看数据库中的auth_permission表中的所有权限。
image.png
后两个字段很好理解,其中的codename表示的是权限的名字。name表示的是这个权限的作用。

那么conten_type_id是干嘛的呢?
很显然,他连接了另一张表,一张叫content_type的表,这个content_type是干嘛的来?它专门表明某个模型是属于哪个app下面的,长下面这样:
image.png
如果想看权限模型的源代码,可以导入 from django.contrib.auth.models import Permission导入查看。

添加权限:

通过定义模型添加权限:

如果我们想要增加新的权限,比如查看某个模型的权限,那么我们可以在定义模型的时候在Meta中定义好。示例代码如下:

  1. class Article(models.Model):
  2. title = models.CharField(max_length=100)
  3. content = models.TextField()
  4. author = models.ForeignKey(get_user_model(),on_delete=models.CASCADE)
  5. class Meta:
  6. permissions = (
  7. ('black_article','拉黑文章'), # (codename, name)
  8. )

通过代码添加权限:

权限都是django.contrib.auth.Permission的实例。所以添加权限的本质是创建Permission实例,即在auth_permission表中添加一条数据。这个模型包含三个字段,namecodename以及content_type,其中的content_type表示这个permission是属于哪个app下的哪个models。用Permission模型创建权限的代码如下:

  1. from django.contrib.auth.models import Permission,ContentType
  2. from .models import Article
  3. def add_permission(request):
  4. content_type = ContentType.objects.get_for_model(Article)
  5. try:
  6. Permission.objects.create(codename='black_article', name='拉黑文章', content_type=content_type)
  7. return HttpResponse("添加权限成功!")
  8. except Exception as e:
  9. return HttpResponse("添加权限失败!原因:%s" % e)

运行上述代码,发现auth_permission表中多了一条数据,证明我们的操作成功!
image.png

用户与权限管理:

权限本身只是一个数据,必须和用户进行绑定,才能起到作用。用户与权限的关系是多对多的关系。既然是多对多的关系,那必然存在一张中间表来作桥梁,果不其然,初次迁移后,我们会发现下面这样一张表:
image.png
如果我们没有修改默认的用户模型,其表名是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模型和权限之间的管理,可以通过以下几种方式来管理:

  1. user.user_permissions.set(permission_list):直接给定一个权限的列表。
  2. user.user_permissions.add(permission,permission,...):一个个添加权限。
  3. user.user_permissions.remove(permission,permission,...):一个个删除权限。
  4. user.user_permissions.clear():清除当前用户的所有权限。
  5. user.has_perm('<app_name>.<codename>'):判断是否拥有某个权限。权限参数是一个字符串,格式是app_name.codename
  6. 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(“一次添加多条权限”)

  1. <a name="hxIzM"></a>
  2. #### 权限缓存
  3. [ModelBackend](https://docs.djangoproject.com/zh-hans/3.2/ref/contrib/auth/#django.contrib.auth.backends.ModelBackend) 在首次为权限检查提取用户对象的权限之后**缓存**它们。这对于请求-响应周期来说通常是好的,因为通常在添加权限之后不会立即检查它们(例如,在管理员中)。如果您正在添加权限,并在之后立即检查它们,例如在测试或视图中,最简单的解决方案是从数据库中**重新获取用户**。
  4. ```python
  5. from django.contrib.auth.models import Permission, User
  6. from django.contrib.contenttypes.models import ContentType
  7. from django.shortcuts import get_object_or_404
  8. from myapp.models import BlogPost
  9. def user_gains_perms(request, user_id):
  10. user = get_object_or_404(User, pk=user_id)
  11. # any permission check will cache the current set of permissions
  12. user.has_perm('myapp.change_blogpost')
  13. content_type = ContentType.objects.get_for_model(BlogPost)
  14. permission = Permission.objects.get(
  15. codename='change_blogpost',
  16. content_type=content_type,
  17. )
  18. user.user_permissions.add(permission)
  19. # Checking the cached permission set
  20. user.has_perm('myapp.change_blogpost') # False
  21. # Request new instance of User
  22. # Be aware that user.refresh_from_db() won't clear the cache.
  23. user = get_object_or_404(User, pk=user_id)
  24. # Permission cache is repopulated from the database
  25. user.has_perm('myapp.change_blogpost') # True
  26. ...

上面的代码中用到了get_object_or_404())这个神器函数,值得学习!

权限限定装饰器:permission_required()

以我们先用的知识,如果想要判断一个用户是否拥有添加文章的权限,需要这样:

  1. from django.http import HttpResponse
  2. from django.shortcuts import redirect, reverse
  3. def add_article(request):
  4. if request.user.is_authenticated: # 神器函数:判断是否已经登录
  5. if request.user.has_perm('front.add_article'): # 再判断是否有权限
  6. return HttpResponse("这是添加文章的页面!")
  7. else:
  8. return HttpResponse("您没有访问该页面的权限!", status=403)
  9. else:
  10. return redirect(reverse('login')) # 如果没有权限,就重定向到登录

可以看到代码还是相当臃肿的。其实使用django.contrib.auth.decorators.permission_required可以非常方便的检查用户是否拥有这个权限,如果拥有,那么就可以进入到指定的视图函数中,如果不拥有,那么就会报一个400错误。示例代码如下:

  1. from django.contrib.auth.decorators import permission_required
  2. @permission_required('front.view_article')
  3. def my_view(request):
  4. ...

分组:

权限有很多,一个模型就有最少四个权限,如果一些用户拥有相同的权限,那么每次都要重复添加。这时候分组就可以帮我们解决这种问题了,我们可以把一些权限归类,然后添加到某个分组中,之后再把和把需要赋予这些权限的用户添加到这个分组中,就比较好管理了。分组我们使用的是django.contrib.auth.models.Group模型, 每个用户组拥有idname两个字段,该模型在数据库被映射为auth_group数据表。

分组操作:

  1. Group.object.create(group_name):创建分组。
  2. group.permissions:某个分组上的权限。多对多的关系。
    • group.permissions.add:添加权限。
    • group.permissions.remove:移除权限。
    • group.permissions.clear:清除所有权限。
    • user.get_group_permissions():获取用户所属组的权限。
  3. user.groups:某个用户上的所有分组。多对多的关系。
  1. from django.http import HttpResponse
  2. from django.contrib.auth.models import Permission, ContentType, Group
  3. from .models import Article
  4. def operate_group(request):
  5. group = Group.objects.create(name='运营组')
  6. content_type = ContentType.objects.get_for_model(Article)
  7. permissions = Permission.objects.filter(content_type=content_type)
  8. group.permissions.set(permissions)
  9. return HttpResponse("操作分组界面")

image.png
has_perm()判断顺序:

  1. 首先判断user.permissions下有没有这个权限,如果有就True
  2. 如果user.permissions下没有,则判断其所属分组下有没有这权限,有则True,无则False

    在模板中使用权限:

settings.TEMPLATES.OPTIONS.context_processors下,因为添加了django.contrib.auth.context_processors.auth上下文处理器,因此在模板中可以直接通过perms来获取用户的所有权限。示例代码如下:

  1. {% if perms.front_add_article %}
  2. <a href='article/add/'>添加文章</a>
  3. {% endif %}