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

