如何在Django模型中让ForeignKey存储关联模型指定属性而非主键
解决Django ForeignKey存储关联模型指定属性的问题
问题描述
需求是让OrderDetails模型的invoice_details字段存储InvoiceMaster模型的invoice_number值,而非默认的主键。尝试在ForeignKey后追加.invoice_number,或在ForeignKey参数中传入属性,触发报错:
TypeError: ForeignKey(<django.db.models.query_utils.DeferredAttribute object at 0x10d632fd0>) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string 'self'
尝试的错误代码:
class OrderDetails(models.Model): # 其他字段... invoice_details = models.ForeignKey(InvoiceMaster, on_delete=models.CASCADE, blank=True, null=True).invoice_number # 其他字段...
同时发现InvoiceMaster中类似写法未报错,但实际无效:
class InvoiceMaster(models.Model): invoice_number = models.CharField(max_length=50, blank=True, null=True) # 错误写法:未创建有效ForeignKey字段 equipment_name = models.ForeignKey(EquipmentMaster, on_delete=models.CASCADE, blank=True, null=True).name # 其他字段...
错误原因分析
.invoice_number或.name的写法完全错误:这种写法是试图访问未实例化模型的字段属性,得到的是DeferredAttribute对象,而非Django认可的模型字段。Django不会将其识别为数据库字段,数据库中不会生成对应列,只是代码未触发语法报错而已。- ForeignKey的第一个参数必须是模型类、模型名称字符串或
'self',直接传入字段属性会触发类型错误。
正确实现方案
方案1:修改外键关联字段(数据库存储指定属性)
如果需要数据库中实际存储invoice_number作为外键值,需满足两个条件:
- 关联字段(
invoice_number)必须是唯一的(unique=True),或直接设为主键(primary_key=True) - 在ForeignKey中通过
to_field参数指定关联的字段
步骤1:确保关联字段唯一
修改InvoiceMaster模型,给invoice_number添加唯一性约束:
class InvoiceMaster(models.Model): invoice_number = models.CharField(max_length=50, unique=True, blank=True, null=True) # 正确关联EquipmentMaster的写法 equipment = models.ForeignKey(EquipmentMaster, on_delete=models.CASCADE, blank=True, null=True) quantity = models.IntegerField(blank=True, null=True) is_visible = models.IntegerField(default=1) created_by = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) creation_date = models.DateTimeField(default=timezone.now)
如果希望invoice_number作为InvoiceMaster的主键,可直接设置:
invoice_number = models.CharField(max_length=50, primary_key=True, blank=True, null=True)
步骤2:在OrderDetails中指定关联字段
class OrderDetails(models.Model): purchase_order_number = models.IntegerField(blank=True, null=True) purchase_request_number = models.IntegerField(blank=True, null=True) delivery_no = models.IntegerField(blank=True, null=True) delivery_equipment = models.CharField(max_length=500, blank=True, null=True) delivery_quantity = models.IntegerField(blank=True, null=True) warranty_start_date = models.DateField() warranty_end_date = models.DateField() # 通过to_field指定关联InvoiceMaster的invoice_number字段 invoice_details = models.ForeignKey( InvoiceMaster, on_delete=models.CASCADE, blank=True, null=True, to_field='invoice_number' ) # 同理修正其他外键字段 vendor = models.ForeignKey(VendorDetails, on_delete=models.CASCADE, blank=True, null=True) equipment = models.ForeignKey(EquipmentMaster, on_delete=models.CASCADE, blank=True, null=True) is_visible = models.IntegerField(default=1) created_by = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) creation_date = models.DateTimeField(default=timezone.now)
方案2:仅方便访问属性(不修改数据库存储)
如果不需要改变数据库存储的主键外键关系,只是想快速获取关联模型的指定属性,可以添加@property装饰器的方法:
class OrderDetails(models.Model): # 保持默认ForeignKey关联主键 invoice_details = models.ForeignKey(InvoiceMaster, on_delete=models.CASCADE, blank=True, null=True) # 其他字段... @property def invoice_number(self): return self.invoice_details.invoice_number if self.invoice_details else None
这样在访问OrderDetails实例时,直接通过order.invoice_number就能获取对应的发票编号,数据库仍存储主键值。
修正InvoiceMaster的错误字段
之前InvoiceMaster中的equipment_name写法无效,需修正为:
class InvoiceMaster(models.Model): # 正确创建ForeignKey字段 equipment = models.ForeignKey(EquipmentMaster, on_delete=models.CASCADE, blank=True, null=True) # 添加属性方便获取设备名称 @property def equipment_name(self): return self.equipment.name if self.equipment else None # 其他字段...
内容的提问来源于stack exchange,提问作者Adith Raghav
相关产品推荐
相关产品推荐

