Django User模型
- 用户管理
- 自定义用户模型
- Django自定义验证
- 引用User模型
- 视图开发
- 创建序列器
- 创建视图
- 创建路由
- 用户注册
- 注册序列化器
- 注册视图
- 注册路由
用户管理
在开发登录功能的时候需要数据库来保存用户登录信息和状态,Django中有个内置app 名为 django.contrib.auth
,缺省包含在项目Installed App设置中。
这个app 的 models 定义中包含了一张 用户表,名为 auth_user
。当执行 migrate 创建数据库表时,就创建了 用户表 auth_user
,这张用户表包含的字段有:
自定义用户模型
Django 内置app django.contrib.auth
已经做好了登录验证功能。如果用户表 auth_user
的字段足够在项目中使用,那么直接使用默认User模型即可。
如果用户表 auth_user
的字段不符合项目的需求,需要重新定义,例如需要新增一些字段,像真实姓名、手机号、用户类型等,推荐的方式是通过继承 django.contrib.auth.models
里面的 AbstractUser
类的方式。
千万不要去改 Django 内置模块 contrib.auth.models 里面的类定义,如果改动源码,那么项目就依赖当前库,如果更新了库或者环境移植重写装库,那原来的代码就失效了
在 models
包下新增一个 auth.py
文件
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
usertype = (
(0, '开发'),
(1, '测试'),
(2, '需求'),
(3, '运维'),
)
realname = models.CharField(max_length=32, verbose_name='真实姓名')
phone = models.CharField(max_length=11, unique=True, null=True, blank=True, verbose_name='手机号')
user_tpye = models.SmallIntegerField(choices=usertype, default=0, verbose_name='用户类型')
这样就在原来的 contrib.auth
里面的 user表的基础上 新增了 usertype、realname、phone 这些字段
Django自定义验证
然后需要告诉Django,使用这个表作为 系统的 user表
在 models
包下的 __init__.py
文件中导入User模型
from .hr3 import Step,Case,Config,Request
from .mgr import Enviroment,Project
from .auth import User # 导入User模型
将
在settings.py
文件底部,添加如下代码进行设置
AUTH_USER_MODEL = 'sqtp.User'
其中 sqtp
为定义 User 所在的 django app 名称,一定要确保这个 sqtp 在在settings.py
文件中的 INSTALLED_APPS 里面设置了。
将修改同步至数据库
python manage.py makemigrations
python manage.py migrate
可能会遇到
ValueError: The field admin.LogEntry.user was declared with a lazy reference to 'sqtp.user', but app 'sqtp' doesn't provide model 'user'.
这个错误是由于原有的用户模型被依赖,使用自定义会改变依赖关系,这个改动并不能自动完成,需要手动修复架构,将数据从旧的用户表移出,并有可能需要手动执行一些迁移操作。
由于 Django 对可交换模型的动态依赖特性的限制, AUTH_USER_MODEL 所引用的模型必须在其应用的第一次迁移中创建(通常称为 0001_initial );否则,会出现依赖问题。
所以,需要先删除migrations目录所有文件,然后再删除数据库并重写创建。再执行迁移动作就可以了。
执行迁移命令后,sqtp_user
表中新增了 usertype、realname、phone 这些字段,且伴随着sqtp_user
表的创建成功,会新增sqtp_user_groups
和sqtp_user_user_permissions
两个表,一个是用户组,一个是用户权限
引用User模型
用户模型创建好之后,其他模型需要关联的部分就可以加上了。首先是Project
,增加项目管理员admin
和成员 members
,关联了同一个模型,需要设置反向查询名 related_name
如果直接引用User模型
,那么以后项目改成不同的用户模型时就无法自动切到 AUTH_USER_MODEL
配置的用户模型,代码的可复用性就大打折扣了。因此应该直接引用 AUTH_USER_MODEL
配置的用户模型
对mgr.py
中的项目类模型进行修改
# 存放所有项目管理相关模型
from django.db import models
from .base import CommonInfo
from django.conf import settings # 导入setting文件中配置的内容
# 定义项目类
class Project(CommonInfo):
Pro_Status = (
('developing','开发中'),
('operating','维护中'),
('stable','稳定运行中'),
)
# 引用 `AUTH_USER_MODEL` 配置的用户模型
admin = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.DO_NOTHING,null=True,verbose_name='管理员',related_name='project_admin')
members = models.ManyToManyField(settings.AUTH_USER_MODEL,verbose_name='项目成员',related_name='project_members')
name = models.CharField(max_length=32,unique=True,verbose_name='项目名称')
status = models.CharField(max_length=32,choices=Pro_Status,default='stable',verbose_name='项目状态')
version = models.CharField(max_length=32,default='V1.0',verbose_name='版本')
# 模型元类
class Meta(CommonInfo.Meta): # 元类也需要显示继承父类的元类才会生效
db_table = 'Project' # 如果不设置,默认的名称是 app名_模型名
verbose_name = '项目表' # 表的对外显示名称
接下来是通用表中的创建者和更新者字段
对base.py
中的公共模型进行修改
# 公共模型
from django.db import models
from django.conf import settings # 导入setting文件中配置的内容
class CommonInfo(models.Model):
# 公共字段部分: 创建时间、更新时间、描述
create_time = models.DateTimeField(auto_now_add=True,verbose_name='创建时间',null=True)
# auto_now_add 第一次创建数据时自动添加当前时间
update_time = models.DateTimeField(auto_now=True,verbose_name='更新时间',null=True)
# auto_now 每次更新数据时自动添加当前时间
desc = models.TextField(null=True,blank=True,verbose_name='描述')
create_by = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.DO_NOTHING,null=True,verbose_name='创建者',related_name='create_member')
update_by = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.DO_NOTHING,null=True,verbose_name='更新者',related_name='update_member')
def __str__(self):
# 判断当前数据对象是否有name属性,如果有,返回name,如果没有,返回描述
if hasattr(self,'name'): # hasattr 是Python中是反射的一种用法
return self.name
else:
return self.desc
class Meta:
abstract =True # 定义抽象表,不会创建数据库表
ordering = ['id'] # 根据id排序(默认为顺序排序)
将修改同步至数据库
python manage.py makemigrations
python manage.py migrate
会产生报错
sqtp.Case.create_by: (fields.E304) Reverse accessor for 'sqtp.Case.create_by' clashes with reverse accessor for 'sqtp.Config.create_by'.
HINT: Add or change a related_name argument to the definition for 'sqtp.Case.create_by' or 'sqtp.Config.create_by'.
如果在抽象基类中存在ForeignKey
或者ManyToManyField
字段,并且使用了 related_name
或者related_query_name
参数,那么一定要小心。因为按照默认规则,每一个子类都将拥有同样的字段,这显然会导致错误。为了解决这个问题,当在抽象基类中使用 related_name
或者 related_query_name
参数时,它们两者的值中应该包含 %(app_label)s
和 %(class)s
部分
%(class)s
用字段所属子类的小写名替换%(app_label)s
用子类所属app的小写名替换
父类的字段被子类继承,都使用了相同的反向查询名,者显然是不行的,所以利用 %(app_label)s
和 %(class)s
让子类继承的字段可以自由替换反向查询名
对base.py
中的公共模型进行修改
# 公共模型
from django.db import models
from django.conf import settings # 导入setting文件中配置的内容
class CommonInfo(models.Model):
# 公共字段部分: 创建时间、更新时间、描述
create_time = models.DateTimeField(auto_now_add=True,verbose_name='创建时间',null=True)
# auto_now_add 第一次创建数据时自动添加当前时间
update_time = models.DateTimeField(auto_now=True,verbose_name='更新时间',null=True)
# auto_now 每次更新数据时自动添加当前时间
desc = models.TextField(null=True,blank=True,verbose_name='描述')
create_by = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.DO_NOTHING,null=True,verbose_name='创建者',related_name='create_member')
update_by = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.DO_NOTHING,null=True,verbose_name='更新者',related_name='update_member')
def __str__(self):
# 判断当前数据对象是否有name属性,如果有,返回name,如果没有,返回描述
if hasattr(self,'name'): # hasattr 是Python中是反射的一种用法
return self.name
else:
return self.desc
class Meta:
abstract =True # 定义抽象表,不会创建数据库表
ordering = ['id'] # 根据id排序(默认为顺序排序)
将修改同步至数据库
python manage.py makemigrations
python manage.py migrate
此时就可以同步成功了
视图开发
采用REST框架开发项目管理和用户管理
创建序列器
在sqtp
应用目录下的serializers.py
文件中添加代码
from rest_framework import serializers
from sqtp.models import Config,Case,Step,Request,Enviroment,Project,User
# 项目部分序列化器
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = '__all__'
# 环境部分序列化器
class EnviromentSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = '__all__'
# 用户部分序列化器
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
创建视图
在sqtp
应用目录下的views.py
文件中添加代码
from sqtp.models import Request,Case,Config,Step,Enviroment,Project # 模型
from sqtp.serializers import RequestSerializer,CaseSerializer,StepSerializer,ConfigSerializer,EnviromentSerializer,ProjectSerializer # 自定义的序列化类
class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
class EnviromentViewSet(viewsets.ModelViewSet):
queryset = Enviroment.objects.all()
serializer_class = EnviromentSerializer
# 用户
@api_view(['GET'])
def user_list(request):
query_set = User.objects.all()
serializer = UserSerializer(query_set,many=True)
return Response(serializer.data)
@api_view(['GET'])
def user_detail(request,_id):
try:
req_obj = User.objects.get(pk=_id)
except User.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
serializer = UserSerializer(req_obj)
return Response(serializer.data)
创建路由
在sqtp
应用目录下的urls.py
文件中添加代码
router = DefaultRouter()
router.register(r'requests',sqtp_view.RequestViewSet)
router.register(r'cases',sqtp_view.CaseViewSet)
router.register(r'steps',sqtp_view.StepViewSet)
router.register(r'project',sqtp_view.ProjectViewSet) # 将Project视图信息注册路由列表
router.register(r'envs',sqtp_view.EnviromentViewSet) # 将Enviroment视图信息注册路由列表
urlpatterns = [
path('',include(router.urls)),
path('swagger/',schema_view.with_ui('swagger',cache_timeout=0),name='schema-swagger-ui'), #互动模式
path('redoc/',schema_view.with_ui('redoc',cache_timeout=0),name='schema-redoc'), #文档模式
path('users/',sqtp_view.user_list),
path('users/<int:_id>',sqtp_view.user_detail),
]
用户注册
用户认证机制的核心内容是用户模型,而用户认证机制首先要实现的就是用户注册功能–创建用户。
不同系统有不同的注册流程,但是大体上都差不都,都是提交信息给服务器,然后服务器判断没有问题后创建用户。注册流程可以参考下图
结合REST框架帮助我们完成验证的工作
注册序列化器
在sqtp
应用目录下的serializers.py
文件中新增注册序列化器
# 注册序列化器
class RegisrerSerializer(serializers.ModelSerializer):
# admin_code 不在User字段中,需要单独定义
admin_code = serializers.CharField(default='')
class Meta:
model = User
fields = ['username','password','email','phone','realname','admin_code']
# 校验入参是否合法
def validate(self, attrs): # attrs为入参的字典形式
if attrs.get('admin_code') and attrs['admin_code'] != 'sqtp_code':
raise ValidationError('错误的admin_code')
return attrs
# 重写序列化器的保存方法
def register(self):
# 入参内容
in_param = self.data
# 入参内容是否有传 admin_code
if 'admin_code' in in_param and in_param['admin_code'] == 'sqtp_code':
in_param.pop('admin_code') # 创建用户数据不需要admin_code,校验通过即可,需要将这个数据剔除出去
# 创建管理员
user = User.objects.create_superuser(**in_param) # 将数据进行解包,创建管理员
else:
in_param.pop('admin_code')
user = User.objects.create_user(**in_param) # 将数据进行解包,创建普通用户
return user
注册视图
在sqtp
应用目录下的views.py
文件中新增注册视图
from sqtp.serializers import RequestSerializer, CaseSerializer, StepSerializer, ConfigSerializer, EnviromentSerializer, \
ProjectSerializer, UserSerializer, RegisrerSerializer # 自定义的序列化类
from django.contrib import auth # 导入django自带的auth模块
@api_view(['POST'])
def register(request):
# 获取序列化器
serializer = RegisrerSerializer(data=request.data)
if serializer.is_valid(): #自动根据模型定义来判断数据入参是否合法
user = serializer.register() # 创建用户数据
auth.login(request,user) # 完成用户登录状态的设置
return Response(data={'msg':'register success','is_admin':user.is_superuser,'retcode':status.HTTP_201_CREATED},status=status.HTTP_201_CREATED)
return Response(data={'msg':'error','retcode':status.HTTP_400_BAD_REQUEST,'error':serializer.errors},status=status.HTTP_400_BAD_REQUEST)
注册路由
在sqtp
应用目录下的urls.py
文件中新增路由
urlpatterns = [
path('',include(router.urls)),
path('swagger/',schema_view.with_ui('swagger',cache_timeout=0),name='schema-swagger-ui'), #互动模式
path('redoc/',schema_view.with_ui('redoc',cache_timeout=0),name='schema-redoc'), #文档模式
path('users/',sqtp_view.user_list),
path('users/<int:_id>',sqtp_view.user_detail),
path('register/',sqtp_view.register), # 注册
]
在浏览器中输入http://127.0.0.1:8888/register.html
,进入注册页面,输入注册信息创建账户
可以看到数据库sqtp_user
表中新增了一条用户数据