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

Django:创建与Person一对一关联的自定义用户及解决关联赋值错误

错误原因与修复

直接错误修复

你遇到的Cannot assign "1": "CustomUser.dni" must be a "Person" instance.错误,本质是给CustomUser.dni赋值时传了数字ID(比如1),但这个字段要求的是Person模型的实例对象,不是ID数值。

修复代码示例

如果是代码中创建用户,把直接传ID的写法改成先获取Person实例再赋值:

# 错误写法:直接传ID
user = CustomUser.objects.create(dni=1, username='xxx', ...)

# 正确写法:先获取Person实例
person = Person.objects.get(dni=1)
user = CustomUser.objects.create(dni=person, username='xxx', ...)

如果是Django后台操作时出错,调整CustomUserAdmin配置,让后台显示已有的Person实例选择框,而非手动输入ID:

from django.contrib import admin
from .models import CustomUser, Person

class CustomUserAdmin(admin.ModelAdmin):
    # 让dni字段显示为下拉选择框(关联已存在的Person)
    autocomplete_fields = ('dni',)
    fields = ('dni', 'username', 'password', 'is_active')

admin.site.register(CustomUser, CustomUserAdmin)

需求匹配的最优实现方案

结合「先注册Person、仅后台为部分Person创建账号、外部无法自行注册」的需求,可从以下维度优化实现:

1. 模型层优化

  • 重命名关联字段:把CustomUser中的dni改为person,明确关联的是Person实例而非单纯的证件号,提升代码可读性。
  • 给Person添加账号状态标记,方便后台快速筛选未创建账号的人员:
# models.py
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager

class Person(models.Model):
    dni = models.CharField(max_length=20, unique=True, verbose_name='证件号')
    name = models.CharField(max_length=100)
    has_account = models.BooleanField(default=False, verbose_name='已创建系统账号')
    # 其他业务字段...

    def __str__(self):
        return f'{self.name} ({self.dni})'

class CustomUserManager(BaseUserManager):
    def create_user(self, person, username, password=None, **extra_fields):
        if not person:
            raise ValueError('必须关联已存在的Person实例')
        if not username:
            raise ValueError('必须设置用户名')
        
        user = self.model(person=person, username=username, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        
        # 更新Person的账号状态
        person.has_account = True
        person.save()
        return user

class CustomUser(AbstractBaseUser):
    person = models.OneToOneField(Person, on_delete=models.CASCADE, unique=True, verbose_name='关联人员')
    username = models.CharField(max_length=100, unique=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = CustomUserManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['person']

    def __str__(self):
        return f'{self.person.name} - {self.username}'

2. 后台管理优化

  • 自定义后台表单,限制仅能选择未创建账号的Person:
# admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser, Person

class CustomUserAdmin(UserAdmin):
    list_display = ('username', 'person', 'is_staff', 'is_active')
    fieldsets = (
        (None, {'fields': ('person', 'username', 'password')}),
        ('权限设置', {'fields': ('is_staff', 'is_active')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('person', 'username', 'password1', 'password2', 'is_staff', 'is_active')}
        ),
    )
    # 限制Person选择范围:仅显示未创建账号的人员
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == 'person':
            kwargs['queryset'] = Person.objects.filter(has_account=False)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(Person)

3. 禁止外部注册

  • 移除前端的注册视图(如果存在),仅保留后台创建用户的入口。
  • 可选:在CustomUserManager中添加权限校验,仅允许管理员创建账号:
def create_user(self, person, username, password=None, **extra_fields):
    # 仅staff用户可创建账号(需结合后台上下文或权限校验逻辑)
    if not extra_fields.get('is_staff') and not self.model.objects.filter(is_staff=True).exists():
        raise ValueError('仅系统管理员可创建账号')
    # 后续创建逻辑同上

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 00:25:32