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

生产环境下Django动态设置USERNAME_FIELD为手机号/邮箱的实现问题

解决DRF支持手机号/邮箱动态登录的方案

你不需要动态修改USERNAME_FIELD——这个字段是Django模型初始化时的固定配置,运行时无法动态切换,强行修改还会触发数据库唯一约束冲突(也就是你遇到的psycopg.errors.UniqueViolationError)。正确的做法是自定义认证后端,无需改动现有数据库结构,就能在生产环境运行状态下实现手机号/邮箱二选一登录的需求,具体步骤如下:

1. 确保User模型字段配置正确

如果你的自定义User模型还没有phone和email字段,先添加这两个字段并设置唯一约束:

# 自定义User模型示例
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models

class UserManager(BaseUserManager):
    # 这里保留你已有的管理器代码
    def create_user(self, username, password=None, **extra_fields):
        # ... 原有逻辑 ...
        user = self.model(username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

class User(AbstractBaseUser, PermissionsMixin):
    username = models.CharField(max_length=50, unique=True)  # 保留原username字段,无需修改
    phone = models.CharField(max_length=11, unique=True, blank=True, null=True)
    email = models.EmailField(unique=True, blank=True, null=True)
    # ... 其他已有的字段(如is_active、is_staff等) ...

    USERNAME_FIELD = 'username'  # 保持原有配置,不要修改
    REQUIRED_FIELDS = []

    objects = UserManager()

注意:添加唯一约束前,先排查生产数据库中是否存在重复的手机号/邮箱,手动修复重复数据后再执行python manage.py makemigrations和migrate,避免触发唯一冲突。

2. 编写自定义认证后端

在用户应用下创建backends.py文件,继承ModelBackend并重写authenticate方法,实现手机号/邮箱的自动识别与验证:

# apps/users/backends.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
import re

User = get_user_model()

class PhoneEmailBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        # 判断输入内容是手机号还是邮箱
        if re.match(r'^1[3-9]\d{9}$', username):
            # 按手机号查询用户
            try:
                user = User.objects.get(phone=username)
            except User.DoesNotExist:
                return None
        elif '@' in username:
            # 按邮箱查询用户
            try:
                user = User.objects.get(email=username)
            except User.DoesNotExist:
                return None
        else:
            # 保留原username登录逻辑(可选,根据需求决定是否保留)
            try:
                user = User.objects.get(username=username)
            except User.DoesNotExist:
                return None
        
        # 验证密码正确性
        if user.check_password(password):
            return user
        return None

3. 配置Django使用自定义认证后端

在settings.py中添加自定义后端到认证列表:

AUTHENTICATION_BACKENDS = [
    'apps.users.backends.PhoneEmailBackend',  # 优先使用自定义后端
    'django.contrib.auth.backends.ModelBackend',  # 保留默认后端(可选)
]

4. 适配DRF登录视图

如果使用DRF自带的ObtainAuthToken视图,无需额外修改——它会自动调用自定义认证后端的authenticate方法。如果是自定义登录视图,只需将用户输入的登录凭证(手机号/邮箱)传入authenticate方法即可:

# 自定义登录视图示例
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import authenticate

class LoginView(APIView):
    def post(self, request):
        login_id = request.data.get('login_id')
        password = request.data.get('password')
        
        # 调用自定义后端认证
        user = authenticate(request, username=login_id, password=password)
        if user:
            # 这里替换成你的token生成或登录逻辑(比如用SimpleJWT)
            from rest_framework_simplejwt.tokens import RefreshToken
            refresh = RefreshToken.for_user(user)
            return Response({
                'refresh': str(refresh),
                'access': str(refresh.access_token),
            }, status=status.HTTP_200_OK)
        
        return Response({'error': '登录凭证无效'}, status=status.HTTP_401_UNAUTHORIZED)

5. 生产环境部署注意事项

  • 先在测试环境验证代码逻辑,确保无问题后再部署到VPS
  • 部署时只需替换代码、重启应用服务(如uWSGI/Gunicorn)和Nginx,无需停止数据库或删除现有数据
  • 回滚之前错误的migrations(如果已经执行了修改username字段的操作),恢复原username字段的逻辑,避免继续触发唯一约束冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:59:55