我应该如何构建多种模型类型的树?

How should I structure a tree of multiple model types?

我正在尝试将电视节目建模到剧集级别。鉴于树的每个级别(网络、系列、季节、剧集)都有不同的字段,我想为每个级别使用不同的模型类型。

我最初的方法是在每个级别使用外键跟踪父级(这是一种简化的方法,我知道会有其他字段):

class Network(models.Model):
   ...

class Series(models.Model):
   network = models.ForeignKey(Network)
   ...

class Season(models.Model):
   series = models.ForeignKey(Series)
   ...

class Episode(models.Model):
   season = models.ForeignKey(Season)
   ...

但是如果我想获取特定剧集的网络,我必须查找 Episode->Season->Series->Network。这似乎效率低下且架构不佳,因为它需要大量查询。

我看到了库 django-mptt,但这需要您的树由单一模型类型构建。

从设计的角度来看,构造这种树的标准方法是什么?不同方法的权衡是什么?

并不是那么低效。它需要 "only" 三个连接才能获得某一集的网络。

如果您在 Episode 模型上创建 cached_property,您的生活会更轻松:

class Network(models.Model):
    name = models.CharField(max_length=255)

# ...

class Episode(models.Model):
    season = models.ForeignKey(Season, on_delete=models.CASCADE)

    @cached_property
    def network(self):
        return self.season.series.network

    @cached_property
    def network_name(self):
        return self.season.series.network.name

如果您在访问该值之前不对其进行注释,那么使用起来会很昂贵,但它始终有效,即使您忘记这样做也是如此。

cached_property 的好处是可以通过在实例上设置该属性来覆盖它,这正是 django 在我们注释值时所做的:

episodes = Episode.objects.annotate(network_name=F('season__series__network__name'))
for episode in episodes:
   print(episode.pk, episode.network_name)

通过在访问剧集之前注释网络名称,django 将知道必须加入该名称。这是查询的样子:

SELECT 
    "main_episode"."id", 
    "main_episode"."name", 
    "main_episode"."season_id", 
    "main_network"."name" AS "network_name" 
FROM "main_episode" 
INNER JOIN "main_season" ON ("main_episode"."season_id" = "main_season"."id") 
INNER JOIN "main_series" ON ("main_season"."series_id" = "main_series"."id") 
INNER JOIN "main_network" ON ("main_series"."network_id" = "main_network"."id")

可以看到提前入网了。所以这是一个包含三个连接的查询。联接确实有成本,但在您遇到性能问题之前,您不应该担心这一点。

Live example