You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:44:15