在嵌套的 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)
您只使用了标题,但您可以指定任何您想要的模型字段。
上面的解决方案没有将它拆分 Genre
s 所以你需要在那里做更多的事情。您可以使用 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)
我添加的仅供参考;如果您需要添加流派,或者如果您要拥有很多流派,则使用该模型会更好。
我有两个模型,书籍和流派。
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)
您只使用了标题,但您可以指定任何您想要的模型字段。
上面的解决方案没有将它拆分 Genre
s 所以你需要在那里做更多的事情。您可以使用 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)
我添加的仅供参考;如果您需要添加流派,或者如果您要拥有很多流派,则使用该模型会更好。