Django模型:如何在外键关系不存在时返回默认值?

3 投票
2 回答
1676 浏览
提问于 2025-04-15 12:03

我正在用Django开发一个词汇训练程序,主要是德语和瑞典语的。

这个应用的词汇数据由许多“词汇卡片”组成,每张卡片上有一个或多个德语单词,和一个或多个对应的瑞典语单词。

只有注册用户才能进行训练,因为应用会记录用户的表现,为每张词汇卡片保存一个score(分数)。

词汇卡片有不同的级别(基础、高级、专家),并且可以分配任意数量的标签。

当注册用户开始训练时,应用需要计算用户在每个级别和标签下的平均分数,以便用户可以进行选择。

我通过引入一个名为CardByUser的模型来解决这个问题,这个模型有一个score字段,并且与UserCard模型有外键关系。现在我可以使用Django的聚合函数来计算平均分数。

但这个方法有个大缺点:只有当数据库中每一张卡片都有对应的CardByUser实例时,这个方法才能工作,即使用户只训练了100张卡片。我现在的解决方案是在创建Card和注册用户时,创建所有这些CardByUser实例。当然,这在数据库内存和计算时间上都不是很高效(注册一个用户需要花费不少时间)。

而且这看起来也不太优雅,这让我最烦恼。

有没有更好的方法呢?

也许在计算某张Card的平均分数时,可以告诉Django以下几点:

  • 如果存在该Card和用户的CardByUser,就使用它的分数。
  • 如果不存在CardByUser,就使用一个默认值——分数为0。

这样可以实现吗?如果可以,怎么做呢?

编辑:澄清

感谢S.Lott的第一个回答,但我觉得我的问题有点复杂。抱歉,我想用一些实际的模型代码来澄清。

class Card(models.Model):
    entry_sv = models.CharField(max_length=200)
    entry_de = models.CharField(max_length=200)
    ... more fields ...

class CardByUser(models.Model):
    user = models.ForeignKey(User)
    card = models.ForeignKey(Card, related_name="user_cards")
    score = models.IntegerField(default=0)
    ... more fields ...

这意味着许多CardByUser对象与单个Card相关联。

现在在我的视图代码中,我需要创建一个符合以下条件的CardByUser对象的查询集:

  • 相关的Card对象的tag字段包含某个特定字符串(我知道这也不是最优的,但这不是我问题的重点……)
  • 用户是当前用户

然后我可以对分数进行聚合。我的当前代码看起来是这样的(简化版):

user_cards = CardByUser.objects.filter(user=current_user)
                               .filter(card__tags__contains=tag.name)
avg = user_cards_agg.aggregate(Avg('score'))['score__avg']

如果当前用户和CardCardByUser不存在,它将不会被包含在聚合中。这就是为什么我创建所有这些分数为0的CardByUser

那么我该如何去掉这些呢?任何想法都很受欢迎!

2 个回答

1

这可能跟你的问题不完全相关,但看起来 CardByUser 实际上应该是一个多对多的关系,并且还需要一个额外的字段。(可以参考这个链接:http://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships

也许你可以这样修改你的模型?

class Card(models.Model):
    entry_sv = models.CharField(max_length=200)
    entry_de = models.CharField(max_length=200)
    ... more fields ...
    users = models.ManyToManyField(User, through='CardByUser')

class CardByUser(models.Model):
    user = models.ForeignKey(User)
    card = models.ForeignKey(Card)
    score = models.IntegerField(default=0)

这样你就不需要手动创建 CardByUser 对象了,因为这些都由 Django 自动处理。你也应该能够简化你的聚合查询:

user_cards = Card.objects.filter(users=current_user)
                         .filter(tags__contains=tag.name)
...
2

这就是方法(也可能是属性)的用途。

class OptionalFKWithDefault( models.Model ):
    another = models.ForeignKey( AnotherModel, blank=True, null=True )
    @property
    def another_score( self ):
        if self.another is None:
            return 0
        else:
            return self.another.score

撰写回答