Django自定义用户模型:手机号/邮箱登录方案可行性咨询
Hey there! Let's walk through your approach and cover whether it works, plus some tweaks to make it more robust.
你的方案是否可行?
Technically, setting USERNAME_FIELD = 'pk' is allowed in Django—since the primary key is always unique and non-null, it meets the requirements for this field. However, this approach comes with a few gotchas:
- Admin usability: The default Django admin login form will use
USERNAME_FIELDas the login field, meaning users would have to enter their database primary key (a number or UUID) to log into the admin—this is obviously not user-friendly. - Third-party compatibility: Some Django libraries or packages might assume
USERNAME_FIELDis a human-readable identifier (like email or username), which could lead to unexpected bugs down the line. - Redundant login logic: Your current login flow requires manually fetching the user by phone/email and passing their PK to
authenticate(), which adds unnecessary code duplication.
更优的解决方案:自定义认证后端
Instead of relying on pk as your USERNAME_FIELD, a cleaner approach is to build a custom authentication backend that natively supports logging in with either phone number or email. Here's how to implement it:
1. 自定义用户模型
First, update your user model to ensure phone number and email are optional but unique, and set up a proper manager to enforce that at least one of them exists:
from django.db import models from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager from phonenumber_field.modelfields import PhoneNumberField class CustomUserManager(BaseUserManager): def create_user(self, password=None, **extra_fields): # Require at least phone number or email if not extra_fields.get('phonenumber') and not extra_fields.get('email'): raise ValueError("User must provide either a phone number or email address") user = self.model(**extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) return self.create_user(password, **extra_fields) class CustomUser(AbstractBaseUser, PermissionsMixin): phonenumber = PhoneNumberField(unique=True, null=True, blank=True) email = models.EmailField(unique=True, null=True, blank=True) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) objects = CustomUserManager() # We can set USERNAME_FIELD to a dummy unique field (or even 'email' if you prefer) # Since our custom backend will handle login logic, this field won't be used for user authentication USERNAME_FIELD = 'email' REQUIRED_FIELDS = []
2. 自定义认证后端
Create a new backend file (e.g., backends.py) to handle login via phone or email:
from django.contrib.auth.backends import ModelBackend from .models import CustomUser class PhoneOrEmailBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): if not username or not password: return None # Try authenticating with phone number first try: user = CustomUser.objects.get(phonenumber=username) except CustomUser.DoesNotExist: # Fall back to email if phone number doesn't exist try: user = CustomUser.objects.get(email=username) except CustomUser.DoesNotExist: return None # Validate password and return user if valid if user.check_password(password) and self.user_can_authenticate(user): return user
3. 配置Django使用你的自定义模型和后端
Add these lines to your settings.py:
AUTH_USER_MODEL = 'your_app_name.CustomUser' AUTHENTICATION_BACKENDS = [ 'your_app_name.backends.PhoneOrEmailBackend', # Keep default backend if you want to retain support for other authentication methods 'django.contrib.auth.backends.ModelBackend', ]
4. 简化登录逻辑
Now your login view can be streamlined—just pass the user's input (phone or email) as the username parameter to authenticate():
from django.contrib.auth import authenticate, login def login_view(request): if request.method == 'POST': identifier = request.POST.get('identifier') # This can be phone or email password = request.POST.get('password') user = authenticate(request, username=identifier, password=password) if user is not None: login(request, user) # Redirect to dashboard or home else: # Handle invalid credentials # Render login form
如果坚持用PK作为USERNAME_FIELD?
If you still want to go with your original approach, here are some fixes to mitigate the downsides:
- Custom admin login form: Override the admin's login form to accept phone/email instead of PK.
- Document the caveats: Make sure your team knows about the compatibility risks with third-party tools.
But overall, the custom authentication backend approach is more maintainable and user-friendly.
内容的提问来源于stack exchange,提问作者Dev Aggarwal

