生产环境下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
相关产品推荐
相关产品推荐

