在 Django 中过滤层次结构的有效方法?

Efficient way to filter a hierarchical structure in Django?

我有一个简单的分层模型,3 只海龟高。让我们说艺术家,专辑,歌曲。在我看来过滤结果树的有效方法是什么?

为了让 Artist/Album/Song 树传递给我的模板,用任意条件过滤,我目前正在做类似的事情:

for current_artist in Artist.objects.filter(album__song__genre='funkadelic mariachi').distinct():
    yield current_artist

    for current_album in Album.objects.filter(song__genre='funkadelic mariachi').distinct():
        yield current_album

        for current_song in Song.objects.filter(genre='funkadelic mariachi'):
            yield current_song

        yield 'End of album'
    yield 'End of artist'

但我很确定肯定有一种比在每个级别一直查询到叶子更有效的方法,除非 distinct() 和 Django 的优化从彩虹的另一边提供了一些神奇的缓存。

也许创建一棵完整的树(v.g。每个艺术家和专辑,不检查树叶),然后修剪光秃秃的树枝?或者我应该看看 select_related()?

如需加分,欢迎提供一些实际的 test/benchmark/write-up。蛋客!

P.S:我知道 django-mptt 的优点,但它太过分了。

详细的模型并不重要,因为我正在寻找一个通用的解决方案,但它可能是这样的:

class Artist:
    name = models.CharField(max_length=200)

class Album:
    name = models.CharField(max_length=200)
    artist = models.ForeignKey(Artist, on_delete=models.CASCADE)

class Song:
    name = models.CharField(max_length=200)
    album= models.ForeignKey(Album, on_delete=models.CASCADE)
    genre = models.CharField(max_length=200)

我得到以下结果:

filters = { "genre": 'funkadelic mariachi' }
artist = None
album = None
result = []

# select_related() fetches our chosen songs, and their albums and artists, in a single query
for song in Song.objects.select_related(
        'album__artist').filter(**filters):

    if album != song.album and album != None:
        result.append('End of Album')

    if artist != song.album.artist:
        if artist != None:
            result.append('End of Artist')
        artist = song.album.artist
        result.append(artist)

    if album != song.album:
        album = song.album
        result.append(album)

    result.append(song)

if result:
    result.append('End of Album')
    result.append('End of Artist')

不是很漂亮,但效率更高。也许 prefetch_related() 允许保留三个循环,使用 Prefetch('artist', to_attr='filtered_artists') 左右,但每个海龟有一个额外的查询。