Django - 使用 属性 作为外键
Django - Use a property as a foreign key
我的应用程序的数据库已填充并与外部数据源保持同步。我有一个抽象模型,我的 Django 2.2 应用程序的所有模型都派生自该模型,定义如下:
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
class A(CommonModel):
some_stuff = models.CharField()
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey("myapp.A", on_delete=models.CASCADE)
class C(CommonModel):
more_stuff = models.CharField()
b_m2m = models.ManyToManyField("myapp.B")
object_id
字段 不能设置为唯一,因为我在我的应用程序中使用的每个数据源都可能有一个带有 object_id = 1
的对象。因此需要通过字段 object_origin
.
来追踪对象的来源
不幸的是,Django 的 ORM 不支持多列外键。
问题
同时将自动生成的主键保存在数据库中 (id
),我想在 object_id
和 [=] 上创建外键和多对多关系15=] 字段而不是主键 id
.
我试过的
我想过这样做:
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
def _get_composed_object_origin_id(self):
return f"{self.object_origin}:{self.object_id}"
composed_object_origin_id = property(_get_composed_object_origin_id)
class A(CommonModel):
some_stuff = models.CharField()
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey("myapp.A", to_field="composed_object_origin_id", on_delete=models.CASCADE)
但是 Django 抱怨它:
myapp.B.to_a_fk: (fields.E312) The to_field 'composed_object_origin_id' doesn't exist on the related model 'myapp.A'.
听起来不错,Django 将 to_field
的字段排除在数据库字段之外。但是不需要向我的 CommonModel
添加新字段,因为 composed_object_type_id
是使用两个不可空字段构建的...
有/可以在 object_id
字段上设置 unique
属性吗?
class CommonModel(models.Model):
object_type = models.IntegerField()
object_id = models.IntegerField(unique=True)
如果这不起作用,我会将字段类型更改为 uuid
字段:
class CommonModel(models.Model):
object_type = models.IntegerField()
object_uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
您在另一个答案的评论中提到 object_id 不是唯一的,但它与 object_type 结合时是唯一的,那么您可以在元类中使用 unique_together
吗?即
class CommonModel(models.Model):
object_type = models.IntegerField()
object_id = models.IntegerField()
class Meta:
unique_together = (
("object_type", "object_id"),
)
您在问题中被提及为“不幸的是,Django 的 ORM 不支持多列外键”。
是的,Django 不提供那种类型的支持,因为 Django 比我们想象的更可靠:)
因此,Django 提供了一个元选项来克服此类问题,该选项是 unique_together
。
您可以提供一组字段名称,在您的情况下,这些名称组合在一起必须是唯一的...
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True,
serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
class meta:
unique_together = [['object_origin', 'object_id']]
你可以提供list of list, sets of set or simple list, simple set to unique_together
option of class meta:
.
是的,但 Django 说...
UniqueConstraint provides more functionality than unique_together.
unique_together may be deprecated in the future.
您可以在相同的 class meta:
中添加 UniqueConstraint
而不是 unique_together
在您的情况下,您可以如下编写...
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True,
serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
class meta:
constraints = [ models.UniqueConstraint(fields=['object_origin', 'object_id'], name='unique_object')]
因此,最佳做法是使用 constraints
选项而不是 class meta:
的 unique_together
。
您可以将组合对象源 ID 设为一个字段 (composed_object_origin_id
),该字段 (composed_object_origin_id
) 在 save
上更新并用作 to_field
。
class CommonModel(models.Model):
ORIGIN_SOURCEA = "1"
ORIGIN_SOURCEB = "2"
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, "Source A"),
(ORIGIN_SOURCEB, "Source B"),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
composed_object_origin_id = models.CharField(max_length=100, unique=True)
def save(self, **kwargs):
self.composed_object_origin_id = f"{self.object_origin}:{self.object_id}"
# Just in case you use `update_fields`, force inclusion of the composed object origin ID.
# NOTE: There's definitely a less error-prone way to write this `if` statement but you get
# the gist. e.g., this does not handle passing `update_fields=None`.
if "update_fields" in kwargs:
kwargs["update_fields"].append("composed_object_origin_id")
super().save(**kwargs)
class A(CommonModel):
some_stuff = models.CharField(max_length=1)
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey(
"myapp.A", to_field="composed_object_origin_id", on_delete=models.CASCADE
)
我的应用程序的数据库已填充并与外部数据源保持同步。我有一个抽象模型,我的 Django 2.2 应用程序的所有模型都派生自该模型,定义如下:
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
class A(CommonModel):
some_stuff = models.CharField()
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey("myapp.A", on_delete=models.CASCADE)
class C(CommonModel):
more_stuff = models.CharField()
b_m2m = models.ManyToManyField("myapp.B")
object_id
字段 不能设置为唯一,因为我在我的应用程序中使用的每个数据源都可能有一个带有 object_id = 1
的对象。因此需要通过字段 object_origin
.
不幸的是,Django 的 ORM 不支持多列外键。
问题
同时将自动生成的主键保存在数据库中 (id
),我想在 object_id
和 [=] 上创建外键和多对多关系15=] 字段而不是主键 id
.
我试过的
我想过这样做:
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
def _get_composed_object_origin_id(self):
return f"{self.object_origin}:{self.object_id}"
composed_object_origin_id = property(_get_composed_object_origin_id)
class A(CommonModel):
some_stuff = models.CharField()
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey("myapp.A", to_field="composed_object_origin_id", on_delete=models.CASCADE)
但是 Django 抱怨它:
myapp.B.to_a_fk: (fields.E312) The to_field 'composed_object_origin_id' doesn't exist on the related model 'myapp.A'.
听起来不错,Django 将 to_field
的字段排除在数据库字段之外。但是不需要向我的 CommonModel
添加新字段,因为 composed_object_type_id
是使用两个不可空字段构建的...
有/可以在 object_id
字段上设置 unique
属性吗?
class CommonModel(models.Model):
object_type = models.IntegerField()
object_id = models.IntegerField(unique=True)
如果这不起作用,我会将字段类型更改为 uuid
字段:
class CommonModel(models.Model):
object_type = models.IntegerField()
object_uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
您在另一个答案的评论中提到 object_id 不是唯一的,但它与 object_type 结合时是唯一的,那么您可以在元类中使用 unique_together
吗?即
class CommonModel(models.Model):
object_type = models.IntegerField()
object_id = models.IntegerField()
class Meta:
unique_together = (
("object_type", "object_id"),
)
您在问题中被提及为“不幸的是,Django 的 ORM 不支持多列外键”。
是的,Django 不提供那种类型的支持,因为 Django 比我们想象的更可靠:)
因此,Django 提供了一个元选项来克服此类问题,该选项是 unique_together
。
您可以提供一组字段名称,在您的情况下,这些名称组合在一起必须是唯一的...
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True,
serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
class meta:
unique_together = [['object_origin', 'object_id']]
你可以提供list of list, sets of set or simple list, simple set to unique_together
option of class meta:
.
是的,但 Django 说...
UniqueConstraint provides more functionality than unique_together.
unique_together may be deprecated in the future.
您可以在相同的 class meta:
中添加 UniqueConstraint
而不是 unique_together
在您的情况下,您可以如下编写...
class CommonModel(models.Model):
# Auto-generated by Django, but included in this example for clarity.
# id = models.AutoField(auto_created=True, primary_key=True,
serialize=False, verbose_name='ID')
ORIGIN_SOURCEA = '1'
ORIGIN_SOURCEB = '2'
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, 'Source A'),
(ORIGIN_SOURCEB, 'Source B'),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
class meta:
constraints = [ models.UniqueConstraint(fields=['object_origin', 'object_id'], name='unique_object')]
因此,最佳做法是使用 constraints
选项而不是 class meta:
的 unique_together
。
您可以将组合对象源 ID 设为一个字段 (composed_object_origin_id
),该字段 (composed_object_origin_id
) 在 save
上更新并用作 to_field
。
class CommonModel(models.Model):
ORIGIN_SOURCEA = "1"
ORIGIN_SOURCEB = "2"
ORIGIN_CHOICES = [
(ORIGIN_SOURCEA, "Source A"),
(ORIGIN_SOURCEB, "Source B"),
]
object_origin = models.IntegerField(choices=ORIGIN_CHOICES)
object_id = models.IntegerField()
composed_object_origin_id = models.CharField(max_length=100, unique=True)
def save(self, **kwargs):
self.composed_object_origin_id = f"{self.object_origin}:{self.object_id}"
# Just in case you use `update_fields`, force inclusion of the composed object origin ID.
# NOTE: There's definitely a less error-prone way to write this `if` statement but you get
# the gist. e.g., this does not handle passing `update_fields=None`.
if "update_fields" in kwargs:
kwargs["update_fields"].append("composed_object_origin_id")
super().save(**kwargs)
class A(CommonModel):
some_stuff = models.CharField(max_length=1)
class B(CommonModel):
other_stuff = models.IntegerField()
to_a_fk = models.ForeignKey(
"myapp.A", to_field="composed_object_origin_id", on_delete=models.CASCADE
)