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

基于手机号的Django自定义用户模型与认证序列化器问题咨询

药房管理系统后端手机号认证方案代码检查与最佳实践

问题背景

作为Django新手,正在开发药房管理系统后端,采用DRF+dj-rest-auth+django-allauth实现手机号注册登录(替代默认邮箱/用户名),仅关注账号模块,以下是自定义用户模型、序列化器及配置代码,请求检查错误并提供最佳实践。


自定义用户模型代码

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _
from .managers import CustomUserManager
from django.core.validators import RegexValidator

phone_validator = RegexValidator(regex=r"^[1-9]\d{9}$", message="Invalid Number")


class UserRole(models.TextChoices):
    RETAILER = "RETAILER", "retailer"
    STAFF = "STAFF", "staff"


class CustomUser(AbstractUser):
    username = None
    mobile = models.CharField(max_length=10, validators=[phone_validator], unique=True)
    role = models.CharField(
        max_length=20, choices=UserRole.choices, default=UserRole.STAFF
    )
    tenant = models.ForeignKey(
        "tenants.Tenant",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        related_name="users",
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    objects = CustomUserManager()

    USERNAME_FIELD = "mobile"

    REQUIRED_FIELDS = []

    def __str__(self):
        return f"{self.mobile} ({self.get_full_name()})"

自定义序列化器代码

from dj_rest_auth.serializers import LoginSerializer
from dj_rest_auth.registration.serializers import RegisterSerializer
from rest_framework import serializers
from allauth.account.adapter import get_adapter


class MobileLoginSerializer(LoginSerializer):
    username = None

    mobile = serializers.CharField(required=True)

    def validate(self, attrs):
        attrs["username"] = attrs.get("mobile")
        return super().validate(attrs)


class MobileRegisterSerializer(RegisterSerializer):
    username = None
    first_name = serializers.CharField()
    last_name = serializers.CharField()
    mobile = serializers.CharField()
    pharmacy_name = serializers.CharField(write_only=True)
    role = serializers.ChoiceField(choices=["RETAILER", "STAFF"])

    def get_cleaned_data(self):
        data = super().get_cleaned_data()
        data.update(
            {
                "first_name": self.validated_data.get("first_name", ""),
                "last_name": self.validated_data.get("last_name", ""),
                "mobile": self.validated_data.get("mobile", ""),
                "pharmacy_name": self.validated_data.get("pharmacy_name", ""),
                "role": self.validated_data.get("role", ""),
            }
        )
        return data

    def save(self, request):
        adapter = get_adapter()
        user = adapter.new_user(request)
        self.cleaned_data = self.get_cleaned_data()

        user = adapter.save_user(request, user, self, commit=False)
        user.first_name = self.cleaned_data.get("first_name")
        user.last_name = self.cleaned_data.get("last_name")
        user.mobile = self.cleaned_data.get("mobile")
        # user.pharmacy_name = self.cleaned_data.get("pharmacy_name")
        user.role = self.cleaned_data.get("role")
        user.save()

        self.custom_signup(request, user)
        return user

settings.py相关配置

...
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["dj_rest_auth.jwt_auth.JWTCookieAuthentication"]
}

REST_AUTH = {
    "USE_JWT": True,
    "JWT_AUTH_COOKIE": "access",
    "JWT_AUTH_REFRESH_COOKIE": "refresh",
    "JWT_AUTH_HTTPONLY": True,
    "LOGIN_SERIALIZER": "accounts.serializers.MobileLoginSerializer",
    "REGISTER_SERIALIZER": "accounts.serializers.MobileRegisterSerializer",
}

AUTH_USER_MODEL = "accounts.CustomUser"

ACCOUNT_USER_MODEL_USERNAME_FIELD = "mobile"
ACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = "username"

...

SITE_ID = 1

代码错误检查与最佳实践建议

一、自定义用户模型优化点

  1. 手机号验证器增强
    • 当前正则仅校验10位数字,建议调整为r"^\+?86?1[3-9]\d{9}$",覆盖带+86前缀的国内手机号场景,同时将max_length改为15以容纳前缀。
    • 已设置unique=True,迁移时需确保数据库层面唯一性约束生效。
  2. 冗余字段清理
    • AbstractUser自带date_joined和last_login字段,无需重复定义created_at;若需自定义更新逻辑,可保留updated_at并删除created_at,或直接使用现有字段。
  3. UserRole枚举优化
    • TextChoices的第二个参数建议使用翻译字符串,比如_("零售商")和_("员工"),支持多语言扩展。
  4. 租户外键处理
    • 外键使用字符串引用"tenants.Tenant"需确保tenants应用已在INSTALLED_APPS中注册,否则会导致迁移错误。

二、序列化器优化点

  1. MobileLoginSerializer简化
    • 无需手动将mobile映射到username,直接设置序列化器的username_field属性即可,更符合框架设计逻辑:
      class MobileLoginSerializer(LoginSerializer):
          username = None
          mobile = serializers.CharField(required=True)
          username_field = "mobile"
      
  2. MobileRegisterSerializer改进
    • 角色选项复用:直接引用UserRole.choices而非硬编码["RETAILER", "STAFF"],避免枚举变更时同步修改序列化器。
    • 手机号验证:在序列化器中复用模型的phone_validator,添加到mobile字段的validators中。
    • pharmacy_name处理:若该字段属于Tenant模型,应在custom_signup方法中创建或关联租户,而非尝试赋值给用户模型。
    • save方法简化:adapter.save_user已支持通过cleaned_data传递字段,无需重复设置first_name、last_name等,减少冗余代码。

三、settings配置修正

  1. allauth配置完善
    • 添加ACCOUNT_UNIQUE_EMAIL = False,避免allauth强制检查邮箱唯一性;设置ACCOUNT_EMAIL_VERIFICATION = "none",关闭邮箱验证流程。
  2. JWT配置补充
    • 添加生产环境安全配置:
      JWT_AUTH_SECURE = True  # HTTPS环境开启
      JWT_AUTH_REFRESH_COOKIE_PATH = '/api/auth/refresh/'
      
    • 可自定义JWT过期时间:SIMPLE_JWT = {'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15), 'REFRESH_TOKEN_LIFETIME': timedelta(days=1)}

四、其他最佳实践

  1. 自定义用户管理器实现
    • 确保CustomUserManager正确适配手机号作为用户名:
      from django.contrib.auth.base_user import BaseUserManager
      
      class CustomUserManager(BaseUserManager):
          def create_user(self, mobile, password=None, **extra_fields):
              if not mobile:
                  raise ValueError("手机号必须填写")
              user = self.model(mobile=mobile, **extra_fields)
              user.set_password(password)
              user.save(using=self._db)
              return user
      
          def create_superuser(self, mobile, password=None, **extra_fields):
              extra_fields.setdefault('is_staff', True)
              extra_fields.setdefault('is_superuser', True)
              return self.create_user(mobile, password, **extra_fields)
      
  2. 测试覆盖
    • 编写单元测试覆盖手机号注册、登录、角色分配等核心场景,确保逻辑正确性。
  3. 生产环境安全
    • 开启密码强度验证,添加AUTH_PASSWORD_VALIDATORS配置;确保JWT的HTTPONLY属性开启,防止XSS攻击。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 02:24:53