You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django自定义用户模型:区分普通用户与超级用户创建字段的问题

解决Django自定义用户模型中普通用户与超级用户差异化创建问题

问题背景

用Django做移动应用后端,需求是:

  • 普通用户创建时只需要收集name、DoB、firebaseUID,不需要邮箱或密码
  • 超级用户(管理员)通过邮箱和密码创建

选择继承AbstractBaseUser+自定义用户管理器的方案后,遇到两个问题:

  1. 执行createsuperuser命令时报错:TypeError: MyUserManager.create_superuser() missing 1 required positional argument: 'email'
  2. 创建超级用户时被要求输入name、firebaseUID、DoB,不符合仅用邮箱密码创建的预期

问题根源

  1. 参数不匹配报错:模型的USERNAME_FIELD设为userName,但create_superuser方法要求传入email作为必填参数,Django的createsuperuser命令默认会把USERNAME_FIELD作为第一个必填项,导致参数传递错误。
  2. 额外字段强制要求: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

验证操作

  1. 创建超级用户:运行python manage.py createsuperuser,此时只会提示输入邮箱和密码,不会出现name、firebaseUID、DoB的输入要求。
  2. 创建普通用户:在业务代码中调用CustomUser.objects.create_user(name="张三", firebase_uid="xxx123", dob=date(2000,1,1)),会自动校验普通用户的必填字段,无需传入邮箱或密码。

内容的提问来源于stack exchange,提问作者watercroc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 22:50:29