Django博客归档:实现按年月分组的含文年月列表展示
解决Django博客侧边栏年月分组归档列表的问题
嘿,我完全懂你想要的效果——侧边栏展示按年份聚合、带当月文章数量的归档导航,格式像2018 - 3月(3) - 2月(2) - 1月(6)这种对吧?其实不用手动去生成月份列表,Django的ORM和上下文处理器就能轻松搞定,而且能让这个归档列表在所有页面的模板里都能访问到,不用逐个修改视图。
步骤1:创建上下文处理器,全局提供归档数据
首先,在你的blog应用下新建一个context_processors.py文件,用来生成并传递归档数据到所有模板:
from django.db.models.functions import TruncMonth from django.db.models import Count from .models import Post def archive_list(request): # 用ORM按月份截断发布日期,统计每个月的文章数量,按时间倒序排列 archive_data = Post.objects.annotate( month=TruncMonth('posted') ).values('month').annotate(count=Count('id')).order_by('-month') # 把数据整理成年份分组的结构,方便模板渲染 archive_groups = {} for item in archive_data: year = item['month'].year month = item['month'].month if year not in archive_groups: archive_groups[year] = [] archive_groups[year].append({ 'month_num': month, 'month_name': item['month'].strftime('%B'), # 英文月份名,要中文看后面的说明 'count': item['count'] }) # 按年份倒序排列归档组 sorted_archive = sorted(archive_groups.items(), key=lambda x: x[0], reverse=True) return {'archive_list': sorted_archive}
步骤2:注册上下文处理器
打开项目根目录的settings.py,找到TEMPLATES配置里的OPTIONS,把刚才的上下文处理器添加进去:
TEMPLATES = [ { # ... 其他已有配置 'OPTIONS': { 'context_processors': [ # ... 原有上下文处理器(比如django.template.context_processors.request等) 'blog.context_processors.archive_list', # 添加这一行 ], }, }, ]
步骤3:在模板中渲染归档列表
现在你可以在侧边栏的模板(比如sidebar.html或者直接在基础模板里)添加以下代码,就能渲染出你想要的格式:
<div class="sidebar-section"> <h3>文章归档</h3> {% for year, months in archive_list %} <div class="archive-year"> <strong>{{ year }}</strong> {% for month in months %} <a href="{% url 'post_archive_month' year=year month=month.month_num %}"> {{ month.month_name }}({{ month.count }}) </a> {% if not forloop.last %} - {% endif %} {% endfor %} </div> {% endfor %} </div>
额外优化:显示中文月份
如果需要显示中文月份(比如“3月”而不是“March”),只需要在settings.py里配置好中文本地化:
LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True
这样strftime('%B')就会自动显示中文月份名称了。
备选方案:用Mixin给视图添加上下文
如果你不想用全局上下文处理器,也可以写一个Mixin类,让所有视图继承它来添加归档数据:
from django.db.models.functions import TruncMonth from django.db.models import Count from .models import Post class ArchiveMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # 生成归档数据的代码和上下文处理器里的一样 archive_data = Post.objects.annotate( month=TruncMonth('posted') ).values('month').annotate(count=Count('id')).order_by('-month') archive_groups = {} for item in archive_data: year = item['month'].year month = item['month'].month if year not in archive_groups: archive_groups[year] = [] archive_groups[year].append({ 'month_num': month, 'month_name': item['month'].strftime('%B'), 'count': item['count'] }) sorted_archive = sorted(archive_groups.items(), key=lambda x: x[0], reverse=True) context['archive_list'] = sorted_archive return context
然后修改你的视图,让它们继承这个Mixin:
class BlogListView(ArchiveMixin, generic.ListView): model = Post context_object_name = 'post_list' queryset = Post.objects.all() template_name = 'post_list.html' class ArticleMonthArchiveView(ArchiveMixin, MonthArchiveView): queryset = Post.objects.all() date_field = "posted" allow_future = True # 函数视图需要手动添加上下文 def blog_post(request, slug): blog_post = Post.objects.get(slug=slug) # 同样生成归档数据 archive_data = Post.objects.annotate( month=TruncMonth('posted') ).values('month').annotate(count=Count('id')).order_by('-month') archive_groups = {} for item in archive_data: year = item['month'].year month = item['month'].month if year not in archive_groups: archive_groups[year] = [] archive_groups[year].append({ 'month_num': month, 'month_name': item['month'].strftime('%B'), 'count': item['count'] }) sorted_archive = sorted(archive_groups.items(), key=lambda x: x[0], reverse=True) context = {'blog_post': blog_post, 'archive_list': sorted_archive} return render(request, 'blog/blog_post.html', context)
不过这种方法需要修改每个视图,不如上下文处理器来得省心,所以更推荐第一种全局上下文的方式。
内容的提问来源于stack exchange,提问作者John Rogerson
相关产品推荐
相关产品推荐

