在嵌套的 for 循环中过滤(django 模板)

filtering in nested for loops (django templates)

我有两个模型,书籍和流派。

class Book(models.Model):
     title = models.CharField(max_length=300)
     author = models.CharField(max_length=100)
     year = models.IntegerField()
     genre = models.ForeignKey('Genre', on_delete=models.CASCADE)

class Genre(models.Model):
     title = models.CharField(max_length=20)

在我的网页上,我想显示每个流派,并且在每个流派中,只显示年份为 1950 年或之后的书籍。我的模板中的代码如下所示:

{% for genre in genres %}
     <h1> {{genre.title}} </h1>
     {% for book in genre.book_set.all %}
          {{book.title}}
     {% endfor %}
{% endfor %}

问题在于它显示了该类型的所有书籍,而不仅仅是 1950 年以后出版的书籍。是否可以通过某种方式过滤 genre.book_set.all 的结果以仅获取那些匹配某个属性?

编辑 1

我的观点是这样的:

def books_home(request):
    books = Book.objects.filter(year__gt='1950')
    genres = Genre.objects.all()
    return render(request, "index.html", {"books": books, "genres": genres})

即我试图在这个阶段过滤掉书籍,但它没有进入模板。

我建议您在视图中进行过滤,而不是模板本身。 在那里你可以使用所有的 djangos 功能,你不需要为模板实现自定义 tags/filters。 然后你也可以稍后使用 select_/prefetch_related 进行查询优化(如果你有更多的查询)。

示例:

class myview(...):
    qs = Genre.objects.filter(book_set__year__gt=1950)
    ...

另请参阅 docs 中的字段查找以使用 gt 进行过滤。

我知道您已将答案标记为正确,但根据我在该答案下的评论,它确实不是正确答案。 @wfehr 为您提供了一个更好的解决方案,尽管这是一个非常简短的答案,并且根据您的问题,我怀疑您需要更多详细信息。

class Book(models.Model):
     title = models.CharField(max_length=300)
     author = models.CharField(max_length=100)
     year = models.IntegerField()
     genre = models.ForeignKey('Genre', on_delete=models.CASCADE)

class Genre(models.Model):
     title = models.CharField(max_length=20)

您的模型很好,但可以通过以下方式改进:

Book class 变化:

class Book(models.Model):    
    ...
    genre = models.ForeignKey('Genre', on_delete=models.CASCADE, related_name="books")
    ...

通过添加此相关名称,您将能够使用 Genre 进行反向查找,例如:

myGenre.books.all()

这将为您提供该类型的所有书籍。

总之,我跑题了

在您的 View.py 中,return 是模板,而不是根据您已标记为正确的答案在模板中包含该逻辑,只需过滤那里的查询集以获得确切的答案您要显示的信息。

我想你现在有一些看起来像这样的东西:

render("template.html", {genres=Genre.objects.all()})

而不是 Genre.objects.all(),使用 Book 并反转 Book:

Book.objects.order_by('-genre').filter(year__gte=1950)

这样你只会得到你真正想要的书。 gte 是 "greater than or equal to";如果你只想在 1950 之后,然后将其更改为 year__gt.

此外,如果您只需要书名和类型,您可以这样做:

Book.objects.order_by('-genre').filter(year__gte=1950).values_list("title", pk)

您只使用了标题,但您可以指定任何您想要的模型字段。

上面的解决方案没有将它拆分 Genres 所以你需要在那里做更多的事情。您可以使用 annotate 而不是值列表 further reading here 来做到这一点,或者您可以先获取流派并执行以下操作:

(N.B.: 使用 annotate 会将数据库调用减少到 1,但根据流派的数量,这可能不是问题:

books = {}
for genre in Genre.objects.all().order_by('-title'):
     books[genre.title] = Book.objects.order_by('-genre').filter(year__gte=1950, genre=genre).values_list("title")

使用 order_by('-title') 只是确保我们在流派中得到一些顺序——在本例中是按字母顺序排列的。

然后您可以 return context=books 或将其添加到您当前的上下文 object,然后相应地调整您的模板,如下所示:

{% for key, values in books.items %}
    <h1> {{key}} </h1>
    {% for book in values %}
        {{book}}
    {% endfor %}
{% endear%}

通过这种方式,您无需为每本书查询数据库,也无需在模板中进行过滤。

在我离开之前还有一件事,如果您不打算让用户添加类型,即它们是预先确定的,您可以取消 Genre 模型,完全交换 genre Book 模型上的简单字段

genres = (
("scf", "SciFi)",
("hrr", "Horror"),
("rom","Romance"),
("gen", "General"),
)
genre = models.CharField(choices=genres, default="gen", max_length=3)

我添加的仅供参考;如果您需要添加流派,或者如果您要拥有很多流派,则使用该模型会更好。