【问题标题】:Best way to handle one ForeignKey field that can be sourced from multiple Database Tables处理可以来自多个数据库表的一个 ForeignKey 字段的最佳方法
【发布时间】:2019-10-29 21:00:20
【问题描述】:

我遇到了一些独特的问题,想看看哪种解决方案适合最佳实践,或者我的设计中是否遗漏了任何东西。

我有一个模型 - 它上面有一个表示指标的字段。该指标是可以来自多个数据库表的对象的外键。

想法一:

多个外键字段。我将受益于级联选项、从MyModel 直接访问外键模型实例(尽管这是一个易于添加的属性)以及相关的查找。陷阱包括需要检查模型上任意数量的字段以查找 FK。另一个是确保在给定时间只有一个 FK 字段具有值的逻辑(易于检查 presave),尽管 .update 存在问题。然后从所有列中在数据库中添加空间,尽管这不太重要。

class MyModel(models.Model):

    source_one = models.ForeignKey(
        SourceOne,
        null=True, 
        blank=True, 
        on_delete=models.SET_NULL,
        db_index=True
    )

    source_two = models.ForeignKey(
        SourceTwo,
        null=True, 
        blank=True, 
        on_delete=models.SET_NULL,
        db_index=True
    )

    source_three = models.ForeignKey(
        SourceThree,
        null=True, 
        blank=True, 
        on_delete=models.SET_NULL,
        db_index=True
    )

想法二:

在模型上存储一个 source_id 和 source。如果源被删除,我对此最大的担忧是需要维护将这些字段设置为 null 的逻辑。否则,它似乎是一个更清洁的解决方案,但不确定确保数据准确的开销是否值得。如有必要,我可能可以在 fk 模型上的删除挂钩中编写一些逻辑来清理 MyModel

class MyModel(models.Model):

    ONE = 1
    TWO = 2
    THREE = 3

    SOURCES = (
        (ONE, "SourceOne"),
        (TWO, "SourceTwo"),
        (THREE, "SourceThree")
    )

    source_id = models.PositiveIntegerField(null=True, blank=True)
    source = models.PositiveIntegerField(null=True, blank=True, choices=SOURCES)

我会喜欢社区的意见。

【问题讨论】:

  • 你知道 Django 对generic relations 的内置支持吗?它似乎基本上等同于您的想法 2。
  • @KevinChristopherHenry 看起来是一个很好的解决方案

标签: django django-models django-rest-framework


【解决方案1】:

您的第二个想法似乎很脆弱,因为正如您自己指出的那样,数据库无法确保完整性。

如果不了解用例的更多信息,很难提供有见地的建议,但是如果您的“度量”对象被许多其他表引用,我想知道您是否应该考虑反过来处理这个问题,即定义从使用此指标的模型。

举例来说,假设您的项目是一个照片库,而您的模型代表一个标签。标签可以与照片、相册或用户相关联(例如,他们想要关注的标签)。

方法如下:

class Tag(models.Model):
    pass

class Photo(models.Model):
    tags = models.ManyToManyField(Tag)


class Album(models.Model):
    tags = models.ManyToManyField(Tag)


class User(AbstractUser):
    followed_tags = models.ManyToManyField(Tag)

您甚至可以考虑在抽象模型中考虑这种关系,如下所述:

class Tag(models.Model):
    pass

class TaggedModel(models.Model):
    tags = models.ManyToManyField(Tag)

    class Meta:
        abstract = True


class Photo(TaggedModel):
    pass

【讨论】:

    【解决方案2】:

    如 cmets 中所述,您正在寻找 Generic Relation

    从 django.contrib.contenttypes.fields 导入 GenericForeignKey 从 django.contrib.contenttypes.models 导入 ContentType

    class SourceA(models.Model):
        name = models.CharField(max_length=45)
    
    class SourceB(models.Model):
        name = models.CharField(max_length=45)
    
    class MyModel(models.Model):
        content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
        object_id = models.PositiveIntegerField()
        source = GenericForeignKey('content_type', 'object_id')
    

    设置通用关系分为三个部分:

    • 为您的模型提供一个指向 ContentType 的 ForeignKey。该字段的常用名称是“content_type”。
    • 为您的模型提供一个字段,该字段可以存储与您相关的模型的主键值。对于大多数模型,这意味着 PositiveIntegerField。该字段的常用名称是“object_id”。
    • 为您的模型提供一个 GenericForeignKey,并将上述两个字段的名称传递给它。如果这些字段被命名为“content_type”和“object_id”,您可以省略它——这些是 GenericForeignKey 将查找的默认字段名称。

    现在您可以将任何Source 实例传递给MyModelsource 字段,无论它属于哪个模型:

    source_a = SourceA.objects.first()
    source_b = SourceB.objects.first()
    
    MyModel.objects.create(source=source_a)
    MyModel.objects.create(source=source_b)
    

    【讨论】:

      猜你喜欢
      • 2016-11-28
      • 1970-01-01
      • 2011-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多