Django自定义用户模型:区分普通用户与超级用户创建字段的问题
解决Django自定义用户模型中普通用户与超级用户差异化创建问题
问题背景
用Django做移动应用后端,需求是:
- 普通用户创建时只需要收集
name、DoB、firebaseUID,不需要邮箱或密码 - 超级用户(管理员)通过邮箱和密码创建
选择继承AbstractBaseUser+自定义用户管理器的方案后,遇到两个问题:
- 执行
createsuperuser命令时报错:TypeError: MyUserManager.create_superuser() missing 1 required positional argument: 'email' - 创建超级用户时被要求输入
name、firebaseUID、DoB,不符合仅用邮箱密码创建的预期
问题根源
- 参数不匹配报错:模型的
USERNAME_FIELD设为userName,但create_superuser方法要求传入email作为必填参数,Django的createsuperuser命令默认会把USERNAME_FIELD作为第一个必填项,导致参数传递错误。 - 额外字段强制要求:
REQUIRED_FIELDS包含了普通用户的必填字段,超级用户创建时会被强制校验这些字段,违背需求。
解决方案
1. 调整用户模型配置
- 将
dob字段改为可空可填,因为超级用户不需要这个字段 - 把
USERNAME_FIELD设为email,适配超级用户的登录逻辑;普通用户的登录由自定义认证后端处理 - 清空
REQUIRED_FIELDS,让用户管理器分别处理普通用户和超级用户的必填字段
2. 重构用户管理器逻辑
在管理器中拆分普通用户和超级用户的创建逻辑,各自校验对应的必填字段:
from django.contrib.auth.models import AbstractBaseUser from django.db import models from django.utils import timezone class MyUserManager(models.Manager): def _create_user(self, password=None, **extra_fields): """通用用户创建逻辑,提取公共保存步骤""" user = self.model(**extra_fields) if password: user.set_password(password) user.save(using=self._db) return user def create_user(self, name, firebase_uid, dob, password=None, **extra_fields): # 校验普通用户必填字段 if not name: raise ValueError('普通用户必须填写name') if not firebase_uid: raise ValueError('普通用户必须填写firebaseUID') if not dob: raise ValueError('普通用户必须填写DoB') extra_fields.setdefault('is_staff', False) extra_fields.setdefault('is_superuser', False) # 普通用户无需邮箱,设为None extra_fields.setdefault('email', None) # 绑定普通用户专属字段 extra_fields['name'] = name extra_fields['firebaseUid'] = firebase_uid extra_fields['dob'] = dob return self._create_user(password, **extra_fields) def create_superuser(self, email, password, **extra_fields): # 校验超级用户必填字段 if not email: raise ValueError('超级用户必须填写邮箱') if not password: raise ValueError('超级用户必须设置密码') extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) if extra_fields.get('is_staff') is not True: raise ValueError('超级用户必须设置is_staff=True') if extra_fields.get('is_superuser') is not True: raise ValueError('超级用户必须设置is_superuser=True') # 超级用户无需普通用户字段,设为默认空值 extra_fields.setdefault('name', None) extra_fields.setdefault('firebaseUid', None) extra_fields.setdefault('dob', None) # 标准化邮箱格式 extra_fields['email'] = self.normalize_email(email) return self._create_user(password, **extra_fields) def get_by_natural_key(self, identifier): # 支持通过邮箱、firebaseUid、userName三种方式查找用户 return self.filter( models.Q(email=identifier) | models.Q(firebaseUid=identifier) | models.Q(userName=identifier) ).first() class CustomUser(AbstractBaseUser): name = models.CharField(null=True, blank=True, max_length=100) userName = models.CharField(null=True, blank=True, max_length=100, unique=True) firebaseUid = models.CharField(null=True, blank=True, max_length=100, unique=True) dob = models.DateField(null=True, blank=True) # 改为可空可填 created_at = models.DateTimeField(default=timezone.now) email = models.EmailField(null=True, blank=True, unique=True) # 仅超级用户使用 is_staff = models.BooleanField(default=False) is_superuser = models.BooleanField(default=False) USERNAME_FIELD = 'email' # 超级用户登录标识 REQUIRED_FIELDS = [] # 取消全局必填字段,由管理器分别控制 objects = MyUserManager() def __str__(self): # 优先显示邮箱,其次是firebaseUid或userName,最后显示用户ID return self.email if self.email else self.firebaseUid or self.userName or f"User {self.id}" def get_fBaseUid_name(self): return self.firebaseUid def has_perm(self, perm, obj=None): return self.is_superuser def has_module_perms(self, app_label): return self.is_superuser
3. 适配自定义认证后端
确保你的FirebaseBackend能通过firebaseUid验证普通用户,示例代码:
from django.contrib.auth.backends import BaseBackend from .models import CustomUser class FirebaseBackend(BaseBackend): def authenticate(self, request, firebase_uid=None, **kwargs): try: return CustomUser.objects.get(firebaseUid=firebase_uid) except CustomUser.DoesNotExist: return None def get_user(self, user_id): try: return CustomUser.objects.get(pk=user_id) except CustomUser.DoesNotExist: return None
验证操作
- 创建超级用户:运行
python manage.py createsuperuser,此时只会提示输入邮箱和密码,不会出现name、firebaseUID、DoB的输入要求。 - 创建普通用户:在业务代码中调用
CustomUser.objects.create_user(name="张三", firebase_uid="xxx123", dob=date(2000,1,1)),会自动校验普通用户的必填字段,无需传入邮箱或密码。
内容的提问来源于stack exchange,提问作者watercroc
相关产品推荐
相关产品推荐

