Django无法同时适配超级用户与普通用户模型的问题求助
问题与解决方案
背景
数据库中存在Django默认生成的auth_user表(存储超级用户),另有自定义的authorization_userprofile表(authorization为应用名)存储普通用户。目标是让所有普通用户与超级用户都存储在同一表中,且均可正常登录、后台操作无异常。
现有代码
1. models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.db import models class UserProfileManager(BaseUserManager): def create_user(self, phone_number, password=None, **extra_fields): if not phone_number: raise ValueError('The Phone Number field must be set') user = self.model(phone_number=phone_number, **extra_fields) user.set_password(password) # Hash the password user.save(using=self._db) return user def create_superuser(self, phone_number, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_superuser', True) return self.create_user(phone_number, password, **extra_fields) class UserProfile(AbstractBaseUser, PermissionsMixin): phone_number = models.CharField(max_length=15, unique=True) first_name = models.CharField(max_length=255, default='') # Added first name field last_name = models.CharField(max_length=255, default='') # Added last name field dob = models.DateField(null=True, blank=True) # Added Date of Birth field email = models.EmailField(max_length=255, default='') # Added email field address = models.TextField(default='') # Added address field is_staff = models.BooleanField(default=False) is_superuser = models.BooleanField(default=False) # other fields if any ... objects = UserProfileManager() USERNAME_FIELD = 'phone_number' # Unique related name to avoid clashes with auth.User model groups = models.ManyToManyField('auth.Group', related_name='user_profiles', blank=True) user_permissions = models.ManyToManyField('auth.Permission', related_name='user_profiles', blank=True) # other fields if any ...
2. settings.py 配置矛盾
- 设置
AUTH_USER_MODEL = 'authentication.UserProfile':普通用户可登录,is_staff=True的用户能进入后台,但后台修改数据时触发完整性错误——admin_log无法从auth_user表获取外键(普通用户存储在authorization_userprofile表)。 - 设置
AUTH_USER_MODEL = 'auth.user':超级用户可正常登录操作后台,但普通用户无法登录。
3. views.py
from django.shortcuts import render, redirect from django.contrib import messages from django.http import HttpResponse from twilio.rest import Client import random from django.conf import settings # Import Django settings module from django.contrib.auth import authenticate, login, logout from django.contrib.auth.hashers import make_password, check_password from .models import UserProfile # Import your UserProfile model def mylogin(request): if request.method == 'POST': phone_number = request.POST.get('phone_number') password = request.POST.get('password') # Retrieve user profile based on phone number user_profile = UserProfile.objects.filter(phone_number=phone_number).first() print(user_profile) # Debug: Print user_profile to check if it's retrieved correctly if user_profile is not None and check_password(password, user_profile.password): # Password matches, log the user in user = authenticate(request, username=phone_number, password=password) print(user) if user is not None: login(request, user) return redirect('home') # Redirect to the home page after successful login else: # User authentication failed, show error message messages.error(request, 'Invalid phone number or password. Please try again.') return redirect('mylogin') # Redirect back to the login page if authentication fails return render(request, 'login.html') def verify_otp(request): if request.method == 'POST': user_otp = request.POST.get('otp') stored_otp = request.session.get('otp') phone_number = request.session.get('phone_number') if user_otp == stored_otp: # OTP is correct, create a user profile password = request.POST.get('password') # Hash the password hashed_password = make_password(password) # Check if a user profile with the given phone number already exists user_profile, created = UserProfile.objects.get_or_create( phone_number=phone_number, defaults={'password': hashed_password} ) # Clear the OTP data from the session del request.session['otp'] del request.session['phone_number'] messages.success(request, 'OTP verified successfully!') return redirect('mylogin') # Redirect to the login page else: messages.error(request, 'Invalid OTP. Please try again.') return redirect('myregister') # Redirect back to the registration page if OTP is invalid # Handle GET requests (if any) here return render(request, 'authentication/login.html') def myregister(request): return render(request, 'register.html') def send_otp(request): if request.method == 'POST': phone_number = request.POST.get('phone_number') otp = str(random.randint(1000, 9999)) # Generate a random 4-digit OTP # Send the OTP via Twilio client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN) message = client.messages.create( body=f'Your OTP is: {otp}', from_=settings.TWILIO_PHONE_NUMBER, to=phone_number ) # Store the OTP in session for verification request.session['otp'] = otp request.session['phone_number'] = phone_number print(otp) return HttpResponse("OTP sent successfully") def mylogout(request): logout(request) return redirect('home')
解决方案
核心思路
放弃双表结构,用自定义用户模型替换默认auth_user,让所有用户(普通+超级)都存储在同一表中,同时处理数据迁移与后台适配。
步骤1:修正settings.py配置
确保AUTH_USER_MODEL指向正确的自定义用户模型(注意应用名为authorization):
AUTH_USER_MODEL = 'authorization.UserProfile'
步骤2:修复后台完整性错误
错误源于前期使用默认auth_user生成的django_admin_log表,外键指向旧表。解决方式:
- 开发环境(数据可丢弃):删除数据库后重新执行迁移
python manage.py flush python manage.py makemigrations python manage.py migrate - 生产环境(需保留数据):手动修改
django_admin_log表的user_id外键指向authorization_userprofile,或编写迁移脚本将auth_user中的超级用户数据迁移到UserProfile表。
步骤3:创建超级用户(存入自定义模型)
执行命令创建超级用户,此时用户会存入authorization_userprofile表:
python manage.py createsuperuser
根据提示输入手机号(USERNAME_FIELD为phone_number)、密码即可。
步骤4:简化登录视图代码
原视图手动校验密码再调用authenticate属于冗余操作,直接用authenticate即可:
def mylogin(request): if request.method == 'POST': phone_number = request.POST.get('phone_number') password = request.POST.get('password') # 自定义用户模型会自动用phone_number作为username校验 user = authenticate(request, username=phone_number, password=password) if user is not None: login(request, user) return redirect('home') else: messages.error(request, 'Invalid phone number or password. Please try again.') return redirect('mylogin') return render(request, 'login.html')
步骤5:注册自定义用户模型到admin(可选)
在authorization/admin.py中添加配置,方便后台管理用户:
from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import UserProfile class CustomUserAdmin(UserAdmin): list_display = ('phone_number', 'first_name', 'last_name', 'is_staff') fieldsets = ( (None, {'fields': ('phone_number', 'password')}), ('Personal info', {'fields': ('first_name', 'last_name', 'dob', 'email', 'address')}), ('Permissions', {'fields': ('is_staff', 'is_superuser', 'groups', 'user_permissions')}), ) add_fieldsets = ( (None, { 'classes': ('wide',), 'fields': ('phone_number', 'password1', 'password2', 'is_staff', 'is_superuser'), }), ) ordering = ('phone_number',) admin.site.register(UserProfile, CustomUserAdmin)
关键说明
auth_user是Django默认认证系统自动生成的表,设置AUTH_USER_MODEL后,Django会使用指定模型生成对应表,不再依赖默认auth_user。- 自定义用户模型建议在项目初始化时设置,中途切换需处理数据迁移,这也是你之前出现错误的核心原因。
内容的提问来源于stack exchange,提问作者Jubayer Hossain
相关产品推荐
相关产品推荐

