在Django模板中对相关项进行排序

108

在Django模板中,是否可以对一组相关项目进行排序?

也就是说,对于下面的代码(为了清晰起见省略了HTML标记):

{% for event in eventsCollection %}
   {{ event.location }}
   {% for attendee in event.attendee_set.all %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}

这几乎完全显示了我想要的内容。唯一我想要改变的是参与者名单按姓氏排序。我尝试过如下:

{% for event in events %}
   {{ event.location }}
   {% for attendee in event.attendee_set.order_by__last_name %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}
很遗憾,上面的语法不起作用(它生成一个空列表),我想过其他的变化方式,但没有一种是有效的(报告了许多语法错误,但没有成功)。当然,我可以在我的视图中生成某种排序后的与会者列表数组,但那是一个丑陋而脆弱的解决方案(我提到了它很丑陋,你们明白吧)。
无需多言,我已经查看了在线文档并搜索了Stack Overflow和django-user的存档,但都没有找到有用的信息(啊,如果查询集是一个字典dictsort就可以完成这项工作,但它不是,也不能)。
编辑添加额外的想法,接受Tawmas的答案后。
Tawmas恰好解决了我提出的问题-虽然解决方案不是我期望的。因此,我学到了一种在其他情况下也可以使用的有用技巧。
Tom的答案提出了一种我已经在我的原始帖子中提到并暂时拒绝作为"丑陋"的方法。"丑陋"是一种直觉反应,我想澄清它的问题所在。在这样做时,我意识到它是一个丑陋的方法,是因为我固定在将查询集传递到模板进行渲染这个想法上了。如果我放宽这个要求,就有一种不丑陋的方法可以解决问题。
我还没有尝试过这个方法,但假设不是传递查询集,而是视图代码迭代查询集生成一个事件列表,然后装饰每个事件与相应的与会者查询集,这些查询集是按所需方式排序(或过滤、或其他)。就像这样:
eventCollection = []   
events = Event.object.[filtered and sorted to taste]
for event in events:
   event.attendee_list = event.attendee_set.[filtered and sorted to taste]
   eventCollection.append(event)

现在模板变成了:

{% for event in events %}
   {{ event.location }}
   {% for attendee in event.attendee_list %}
     {{ attendee.first_name }} {{ attendee.last_name }}
   {% endfor %}
 {% endfor %}
缺点是视图必须一次“实际化”所有事件,如果有大量事件可能会成为问题。当然可以添加分页,但这会使视图变得更加复杂。
优点是“准备要显示的数据”的代码在视图中,这正是它应该所在的地方,让模板专注于对视图提供的数据进行格式化以供显示。这是正确和适当的。
因此,我的计划是在大表格上使用Tawmas的技术,在小表格上使用上述技术,大和小的定义留给读者(微笑)。
4个回答

174

40
太好了!对于那些想知道的人,还有dictsortreversed:https://docs.djangoproject.com/en/dev/ref/templates/builtins/#dictsortreversed - Mickaël
1
引用我的原始帖子:啊,如果查询集是一个字典,dictsort就可以胜任这项工作,但它不是,也不能。 - Dale Wilson
1
@DaleWilson,我已经在几乎与你的代码完全相同的代码上成功地使用了dictsort。有趣的是,它似乎在查询集上运行良好。 - mlissner
@mlissner 好的。我想他们在这个问题被提出四年后修复了它。 - Dale Wilson
3
为了更清晰明确,空格很重要:{% for attendee in event.attendee_set.all|dictsort:"last_name" %} 对出席者进行排序,但 {% for attendee in event.attendee_set.all | dictsort:"last_name" %} 尝试对 for 循环的输出进行排序并破坏了 for - mattsl
显示剩余2条评论

157

你需要在参与者模型中指定排序方式,就像这样。例如(假设你的模型类名为 Attendee):

class Attendee(models.Model):
    class Meta:
        ordering = ['last_name']

请参阅 手册 以获取更多信息。

编辑。另一个解决方案是向您的Event模型添加一个属性,您可以从模板中访问该属性:

class Event(models.Model):
# ...
@property
def sorted_attendee_set(self):
    return self.attendee_set.order_by('last_name')

您可以根据需要定义更多这样的内容...


谢谢您的评论,但这仅适用于我想将显示顺序作为参与者的永久属性的情况,而我并不想这样做。例如,我可能希望按其注册日期排序显示参与者,以便知道哪些参与者应该被通知没有他们的座位。 - Dale Wilson
我为你添加了另一种解决方案。它应该能够给你所需的灵活性。 - tawmas
@Mark 确实如此。据我所知,在这里使用 @property 是过度的,因为没有涉及到 getter 或 setter:https://dev59.com/questions/unI_5IYBdhLWcg3wDOZC - Rick Westera
1
虽然在模板中并不是必需的,但我发现在应用程序代码中使用@property可以使访问更加清晰,尽管这会带来一些小的性能损失(在我的笔记本电脑上小于30纳秒)。当然,这是一个风格问题,高度主观。 - tawmas
如果您想根据两个属性对集合进行排序,则可以使用以下命令:class Meta: ordering = ['last_name', 'first_name'] - Tms91
要进行反向排序,请使用 ordering = ['-last_name'] - Apoorv pandey

8

一个解决方案是创建自定义模板标签:

@register.filter
def order_by(queryset, args):
    args = [x.strip() for x in args.split(',')]
    return queryset.order_by(*args)

使用方法如下:

{% for image in instance.folder.files|order_by:"original_filename" %}
   ...
{% endfor %}

1
这个可以运行,但会重复查询数据库。 - Redgren Grumbholdt
@RedgrenGrumbholdt你确定它会复制查询吗?Django查询集是惰性的,所以除非查询集已经被评估,否则这应该是没问题的。 - bjw

0

regroup应该能够满足您的需求,但是您不能在视图中按照自己想要的方式对它们进行排序吗?


为了在视图中对它们进行排序,我需要遍历事件,并为每个事件创建一个容器,以某种方式将与该事件相关的参与者按正确的顺序排序,然后将整个容器集合传递给模板,以便让模板在遍历事件时找到相应的参与者集合。正如我所说,这是可行的,但不是一个好的解决方案。它需要视图和模板之间过多的耦合、并行迭代等。我希望能够找到一个更好的解决方案,因为这应该是一个常见的问题。 - Dale Wilson
我查看了regroup,但它似乎不能实现我想要的功能。特别是文档中提到: [引用] 请注意{% regroup %}不会对其输入进行排序!我们的示例依赖于人员列表在第一次排序时按性别排序的事实。[结束引用] - Dale Wilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接