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

Django用户创建异常:username为DeferredAttribute对象及登录问题求助

问题解决方案

1. 修复models.py中save方法的核心错误

你当前的save方法把类属性User.phone_number赋值给了username,这就导致存储的是字段对象本身而不是手机号值。修改成实例属性即可:

from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
    image = models.ImageField(upload_to='images/', null=True, blank=True)
    phone_number = models.CharField(max_length=11,
                                    help_text="Введите номер телефона пользователя",
                                    verbose_name="Номер телефона")

    class Meta(AbstractUser.Meta):
        pass

    def save(self, *args, **kwargs):
        # 改为实例的phone_number属性,确保取到当前用户的手机号值
        if self.phone_number:
            self.username = self.phone_number
        super().save(*args, **kwargs)

    def __str__(self):
        return self.last_name

2. 重构UserCreateForm,移除不必要字段并自动处理密码

要实现无需输入username和password的需求,需要从表单中移除这两个字段,同时自动生成不可用密码(适配无密码登录场景),并添加first_name和last_name:

from django import forms
from django.contrib.auth.forms import UserCreationForm
from users.models import User


class UploadImageForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['image']


class UserCreateForm(UserCreationForm):
    # 隐藏密码字段,由系统自动处理
    password1 = forms.CharField(widget=forms.HiddenInput(), required=False)
    password2 = forms.CharField(widget=forms.HiddenInput(), required=False)

    class Meta(UserCreationForm.Meta):
        model = User
        # 只保留需要用户填写的字段
        fields = ('first_name', 'last_name', 'phone_number')

    def save(self, commit=True):
        user = super().save(commit=False)
        # 用手机号填充username
        user.username = self.cleaned_data['phone_number']
        # 标记用户无可用密码(适合仅手机号登录场景)
        user.set_unusable_password()
        if commit:
            user.save()
        return user

3. 修复admin.py配置,适配用户模型认证逻辑

自定义用户Admin必须继承UserAdmin而非ModelAdmin,否则会导致登录验证逻辑异常;同时更新字段配置,移除username并添加必填的个人信息字段:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import gettext_lazy as _
from .forms import UserCreateForm
from .models import User


@admin.register(User)
class CustomUserAdmin(UserAdmin):
    add_form = UserCreateForm
    list_display = ('last_name', 'first_name', 'phone_number', 'is_staff')
    list_display_links = ('last_name', 'phone_number')
    search_fields = ('last_name', 'first_name', 'phone_number')
    ordering = ['last_name']

    # 新增用户时的字段配置
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("first_name", "last_name", "phone_number"),
            },
        ),
    )
    # 编辑用户时的字段配置
    fieldsets = (
        (_("Personal info"), {"fields": ("first_name", "last_name", "phone_number", 'image')}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
    )

4. 配置手机号登录认证后端(可选)

如果要支持直接用手机号登录,需要自定义认证后端,在users应用下创建backends.py:

from django.contrib.auth.backends import ModelBackend
from .models import User


class PhoneBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
        try:
            # 将登录输入的用户名当作手机号查找用户
            user = User.objects.get(phone_number=username)
            # 无密码用户直接通过认证,有密码用户验证密码
            if user.has_usable_password() is False or user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

然后在settings.py中添加认证后端:

AUTHENTICATION_BACKENDS = [
    'users.backends.PhoneBackend',
    'django.contrib.auth.backends.ModelBackend',  # 保留默认后端,兼容用户名登录
]

问题根源总结

  • username显示异常:错误地将类字段对象赋值给username,而非实例的手机号值;
  • 登录异常:Admin继承ModelAdmin导致用户认证逻辑不兼容,加上错误的username值无法匹配登录输入。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 21:47:32