本站的文章分类目录页面是由Django的模板标签regroup实现的,使用regroup可以很方便的对文章进行各种形式的分组显示,比如按文章发布时间来显示文章归档页,按文章分类来显示分类目录页,按标签来显示标签大全页,但是今天遇到了一个特别让人迷离的问题:重复分组问题!

1.问题再现

下面是我写的分类目录页面的代码,使用了“标准”的regroup模板标签语法,代码要实现的功能是按照文章分类目录来遍历文章:

{% regroup posts  by category.id as category_list %}
{% for cate in category_list %}
    <h4>{{ cate.grouper }} ({{ cate.list | length }})</h4>
    {% for post in cate.list %}
        <ul style="list-style-type:none">
            <span>{{ post.created_time | date:"Y-m-d" }}</span>&nbsp;&nbsp;&nbsp;
            <a href="{{ post.get_absolute_url }}">{{ post.title|slice:":50"}}</a>
            <span style="float: right">{{ post.views }}&nbsp; views</span></span>
        </ul>
    {% endfor %}
{% endfor %}

效果如下:

目前我的网站只有两个分类目录。一个是Django(id为1),一个是Git(id为2)。从上图的结果来说还没有任何问题,可是当我继续添加一个分类为Django的文章的时候,它竟奇迹般地重新创建了一个Django的分组!(黑人脸???)

2.问题分析

上面明明已经有了一个Django分组,为什么要重开一个?此时我已经意识到代码出了bug,于是我又回去看了看代码,但是左看右看,上看下看,看了好几遍,都没有发现什么问题。于是我又尝试着添加了分组为Django的文章,这篇文章隶属于新开辟的Django分组。然后我又添加了一个分组为Git的文章,然后它竟然又开辟了一个新的Git分组。

此时我已经大概发现了这个bug的规律:只要这篇新发表的文章分类与上一篇文章分类不相同,那么就会产生一个新的分组,无论之前有没有与之相同的分组。然后我又看了看数据表中的category_id字段,证实了这一现象。

于是带着这个疑惑,我Google到了一篇与我遇到了相同问题的文章(见底部参考资料),文章博主在遇到这个问题的时候去查阅了官方文档对regroup的说明,没想到官方文档已经就这个问题给出了说明以及解决办法......(PS:官方文档还是要认真阅读一下鸭!)

3.问题缘由

Note that {% regroup %} does not order its input! Our example relies on the fact that the cities list was ordered by country in the first place. If the cities list did not order its members by country, the regrouping would naively display more than one group for a single country. For example, say the cities list was set to this (note that the countries are not grouped together):

上面是官方文档对于此问题的说明,大意是说regroup模板标签并不会自动对需要进行分组的变量进行排序,但是该标签的成功实现需要依赖被分组的变量是已经排序好的。如果没有排序就进行regroup的话,就会出现上面的分组重复的问题。

4.解决办法

The easiest solution to this gotcha is to make sure in your view code that the data is ordered according to how you want to display it.

Another solution is to sort the data in the template using the dictsort filter, if your data is in a list of dictionaries:

随后文档提出了两个解决办法:最简单的一个办法就是在对posts进行regroup之前,在views.py中对posts进行排序后再传到模板中。比如我们可以在views.py中这样写:

def categories(request):
    posts = Post.objects.all().order_by("category_id")
    return render(request,'blog/categories.html',context={'posts':posts})

另外一个解决办法就是直接在模板中使用dictsort过滤器进行排序:

{% regroup posts|dictsort:"category_id"  by category.id as category_list %}
{% for cate in category_list %}
    <h4>{{ cate.grouper }} ({{ cate.list | length }})</h4>
    {% for post in cate.list %}
        <ul style="list-style-type:none">
            <span>{{ post.created_time | date:"Y-m-d" }}</span>&nbsp;&nbsp;&nbsp;
            <a href="{{ post.get_absolute_url }}">{{ post.title|slice:":50"}}</a>
            <span style="float: right">{{ post.views }}&nbsp; views</span></span>
        </ul>
    {% endfor %}
{% endfor %}

通过上面两个办法中的任意一种,我们就能立刻解决使用regroup模板标签不当而造成的重复分组的问题。

5.参考资料