Django 在多对一关系中引用特定对象

Posted

技术标签:

【中文标题】Django 在多对一关系中引用特定对象【英文标题】:Django referencing a specific object in a many-to-one relationship 【发布时间】:2021-07-10 17:41:12 【问题描述】:

假设我有以下模型:

from django.db import models

class Reporter(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    email = models.EmailField()

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)

我想向我的Reporter 模型添加一个favorite_article 字段,该字段将引用来自reporter.articles 的特定Article

一种选择是将信息放入Article 模型中:

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
    is_favorite = models.BooleanField()

但这似乎不是一个非常干净的解决方案。有没有更好的方法来做到这一点?

【问题讨论】:

你有没有尝试过,或者你对如何去做有任何想法? @HenryWoody 我添加了一个我考虑过的可能方法。我希望有一个更清洁/更好的解决方案。 【参考方案1】:

您建议的方法可行,但在目前的形式下,它允许多篇文章成为一名记者的最爱。通过一些额外的处理,您可以确保每个记者只有一篇(最多)一篇文章是最喜欢的。

对问题Unique BooleanField value in Django? 的几个答案进行一些修改,我们可以限制每个记者一个True 值,而不是整个文章模型的一个True 值。方法是检查同一 Reporter 的其他收藏文章,并在保存实例时将它们设置为不收藏(而不是使用验证限制)。

我还建议在 save 方法中使用单个事务,这样如果保存实例失败,则不会修改其他实例。

这是一个例子:

from django.db import transaction

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
    is_favorite = models.BooleanField(default=False)

    def save(self, *args, **kwargs):
        with transaction.atomic():
            if self.is_favorite:
                reporter_id = self.reporter.id if self.reporter is not None else self.reporter_id
                other_favorites = Article.objects.filter(is_favorite=True, reporter_id=reporter_id)
                if self.pk is not None: # is None when creating a new instance
                    other_favorites.exclude(pk=self.pk)
                other_favorites.update(is_favorite=False)
            return super().save(*args, **kwargs)

为了以防万一,我还更改了使用filter 而不是get 的方法。

然后要获得喜欢的文章为reporter,您可以使用:

try:
    favorite_article = reporter.articles.get(is_favorite=True)
except Article.DoesNotExist:
    favorite_article = None

您可以将其包装到 Reporter 类的方法/属性中。

【讨论】:

感谢@Henry Woody,这行得通,但我希望有一些东西可以将favorite_article 字段与记者联系起来,以便可以直接引用它,而不必搜索(可能许多) 文章以找到标记为收藏的文章。 @USS1994 您可以使用reporter.articles.get(is_favorite=True)(或只是filter)检索收藏的文章,因此您无需搜索。此外,如果记者没有收藏的文章,您应该将其包装在 try/except 中。这种方法对你有用吗?

以上是关于Django 在多对一关系中引用特定对象的主要内容,如果未能解决你的问题,请参考以下文章

JPA - 如何在多对一关系中检查条件

Django models 多对一关系中 on_delete 参数的设置

如何防止 EntityType 在与同一实体(父)的多对一关系中显示当前对象?

hibernate的一对多和多对一关联

HQL 检索多对一关系中不同实体的列表

Hibernate 多对一关联查询