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

Django中关联模型字段为FK后无法创建模型对象的问题

问题描述

我已将Django的Auth模型重写为自定义模型,将USERNAME_FIELD设置为phone_number。OTPLog模型用于记录发送给客户端的消息日志,为避免数据库中重复存储手机号,我将OTPLog的phone_number字段关联为User模型中phone_number字段的外键。

代码实现

自定义User模型:

class User(AbstractUser):
    phone_number = models.CharField(
        unique=True,
        max_length=11,
        validators=[RegexValidator(regex=consts.PHONE_REGEX_PATTERN)]
    )

    USERNAME_FIELD = 'phone_number'

OTPLog模型:

class OTPLog(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    expiry_date = models.DateTimeField(default=default_otp_duration)
    phone_number = models.ForeignKey(settings.AUTH_USER_MODEL, to_field='phone_number', on_delete=models.DO_NOTHING, db_index=True)

遇到的问题

执行otplog = OTPLog.objects.create(phone_number='01145279126')时抛出异常:

ValueError: Cannot assign "'01145279126'": "OTPLog.phone_number" must be a "User" instance

通过User实例调用otplog = OTPLog.objects.create(phone_number=user)可成功创建对象,但这并非期望的方式。我仅需要外键约束,即当指定手机号对应用户不存在时,无法创建OTPLog对象。请问该如何解决此问题?


解决方案

方法一:直接使用外键的_id后缀字段赋值

Django的外键字段在数据库层面会生成一个以字段名_id命名的列,当你通过to_field='phone_number'指定关联User模型的手机号字段后,这个列存储的就是User表中的手机号值。因此可以直接给这个后缀字段赋值:

otplog = OTPLog.objects.create(phone_number_id='01145279126')
  • 优势:无需查询User实例,直接用手机号创建OTPLog
  • 约束生效:如果手机号不存在于User表中,数据库会抛出IntegrityError,自动触发外键约束,满足需求

方法二:封装自定义创建方法(提升代码可读性)

如果觉得_id后缀不够直观,可以在OTPLog模型中封装一个类方法统一处理:

class OTPLog(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    expiry_date = models.DateTimeField(default=default_otp_duration)
    phone_number = models.ForeignKey(settings.AUTH_USER_MODEL, to_field='phone_number', on_delete=models.DO_NOTHING, db_index=True)

    @classmethod
    def create_with_phone(cls, phone_number):
        # 手机号不存在时会抛出User.DoesNotExist异常,可根据业务需求捕获处理
        user = User.objects.get(phone_number=phone_number)
        return cls.objects.create(phone_number=user)

使用方式:

otplog = OTPLog.create_with_phone('01145279126')

原理说明

Django的外键字段在Python层面要求接收关联模型的实例,即使指定了to_field,直接传入字段值(手机号)会被判定为无效。而通过_id后缀字段赋值,是直接操作数据库层面的关联字段值,绕过了Python层面对实例的要求,同时保留了数据库的外键约束。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:23:15