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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 15:10:56