Django REST Framework 自定义多类型用户模型认证咨询
先明确模型关系
你当前实现中Employee和Patient不是User的子类,是通过OneToOneField和User绑定的独立关联模型,也就是业内常用的「用户 Profile 扩展」方案,这种设计本身是合理的,但你现有代码存在一个会直接导致认证崩溃的硬伤:USERNAME_FIELD配置为identifier,但模型中并没有定义这个字段,要么将该配置改回'username',要么补充identifier字段定义。
问题1:user_type的作用与用户创建流程
user_type 字段的权限控制逻辑
该字段本身只是个类型标记,不会自动完成字段/接口的访问限制,需要你在业务逻辑层手动实现校验:
- 后端层面:视图/接口拿到请求用户后,先判断
request.user.user_type值,若患者账号访问员工专属接口直接返回403;返回响应数据时,根据用户类型选用对应序列化器,只返回当前角色有权限查看的字段,避免泄露员工入职信息、角色权限这类敏感数据。 - 前端层面:登录成功后拿到接口返回的
user_type标识,动态渲染菜单、表单项,员工端才展示角色管理、入职信息等专属控件,患者端直接隐藏相关模块即可。
用户创建的正确流程
必须先创建User实例,再创建关联的Employee/Patient扩展实例。你当前的扩展模型将关联User的OneToOneField设为主键,没有User实例生成的主键值,扩展实例根本无法落库,反过来先建扩展实例的思路走不通。
实际开发不需要让用户分两次提交表单/请求:可以在同一个提交入口同时接收用户基础字段+对应角色的扩展字段,后端用数据库事务包裹创建逻辑,先创建User实例,再用该实例作为主键创建对应扩展表数据,任意一步失败就整体回滚,不会产生脏数据。
注意:你当前
Employee模型里的employment_start_date、employment_end_date字段配置存在矛盾:null=False要求数据库不可为空,同时blank=True允许表单不填,新员工入职时离职日期必然为空,建议改成null=True, blank=True避免写入报错。
问题2:DRF 分角色注册、登录实现方案
登录逻辑
不需要为两类用户单独开发登录接口。Django认证体系本身基于User模型做校验,只需要写一个统一登录接口,账号密码校验通过后,返回Token时把user_type字段一并返回,前端根据该值跳转至对应端页面即可。
权限区分可以通过自定义权限类实现:
- 写
IsEmployee权限类,校验逻辑为request.user.user_type == 2 - 写
IsPatient权限类,校验逻辑为request.user.user_type == 1
不同端的接口对应添加权限类即可,无需拆分登录逻辑。
注册逻辑
可以拆分为两个独立注册接口,也可以在同一个接口通过传入的user_type参数分支处理:
- 患者注册:接收用户名、密码、姓名、生日等User基础字段,校验通过后创建
user_type=1的User实例,同步创建绑定该User的Patient空实例即可,一般该接口开放给用户自助注册。 - 员工注册:除基础User字段外,还需要接入职日期、关联角色ID等Employee专属字段,创建
user_type=2的User实例后,同步写入Employee表的专属字段,该接口一般仅开放给管理员操作,不对外自助注册。
核心实现参考代码:
from django.db import transaction from rest_framework import serializers from .models import User, Employee, Patient class PatientRegisterSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['username', 'password', 'first_name', 'last_name', 'date_of_birth'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): with transaction.atomic(): user = User.objects.create_user(**validated_data, user_type=1) Patient.objects.create(user=user) return user class EmployeeRegisterSerializer(serializers.ModelSerializer): employment_start_date = serializers.DateField(required=True) role_id = serializers.IntegerField(required=True) class Meta: model = User fields = ['username', 'password', 'first_name', 'last_name', 'date_of_birth', 'employment_start_date', 'role_id'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): emp_start_date = validated_data.pop('employment_start_date') role_id = validated_data.pop('role_id') with transaction.atomic(): user = User.objects.create_user(**validated_data, user_type=2) Employee.objects.create( user=user, employment_start_date=emp_start_date, role_id=role_id ) return user
可选优化方向
如果后续两类用户的专属字段持续增加,可以考虑改用Django原生多表继承,让Employee和Patient直接继承User,ORM会自动完成一对一关联的创建,不需要手动定义OneToOneField,底层实现和你当前方案逻辑一致,只是能减少手动跨表关联查询的代码量,可以根据团队开发习惯选择。
内容的提问来源于stack exchange,提问作者TheSunbathingCrow

