Django实战: 利用自定义模板标签实现仿CSDN博客月度归档
应网友慕之岩的请求,现提供下Django项目中如何使用自定义标签实现仿CSDN博客的月度归档(如下图所示)。要求按月统计每个月发表的博文篇数, 跳过空白月份,最后结果按发布时间逆序排列。点击每个月份可以看到详细博文列表清单。本文着重讲述如何实现,不包括样式美化。如代码手机上看不全,可以搜索知乎大江狗或csdn大江狗。
实现原理
假如我们有如下一个Article模型(完整的见Django实战专题: 开发专业博客(1)之内容管理后台开发),里面包含了status和发布日期pub_date两个关键字段。
class Article(models.Model):
"""文章模型"""
STATUS_CHOICES = (
('d', '草稿'),
('p', '发表'),
)
title = models.CharField('标题', max_length=200, db_index=True)
slug = models.SlugField('slug', max_length=60, blank=True)
body = RichTextUploadingField('正文')
pub_date = models.DateTimeField('发布时间', null=True)
create_date = models.DateTimeField('创建时间', auto_now_add=True)
mod_date = models.DateTimeField('修改时间', auto_now=True)
status = models.CharField('文章状态', max_length=1, choices=STATUS_CHOICES, default='p')
我们实现原理如下:
我们需要构建一个由年份、月份和和当月发表文章数量组成的字典列表。新的列表由如{'year': 2019, 'month': 8, total:'4'}这样的多个字典组成。
我们先获取所有已发表文章的最大年份和最小年份,缩小归档时间范围。
利用for循环查询最大年份和最小年份间每年的12个月中是否有文章发表(逆序查找),如果有,我们将统计该月发表文章的数量,并将由年份、月份和和文章数量构建成的字典插入列表。如果当月无文章发表,我们直接跳过。
最后我们在模板中循环输出每条记录的year, month和total即可。同时我们还需要为每条记录建个url,用于展示对应year和month的月度文章详细列表。
自定义模板标签
我们将自定义一个{% show_monthly_archive %}的标签,用于显示月度归档。这样做的好处是显而易见的,因为如果你将来想在不同页面比如文章详情、文章列表或搜索页面显示月度归档信息时,你只需要在相应页面模板加入这么一行代码就可以了,而不需要更改视图增加新的context,也不需要对模板进行大的调整。
更多关于自定义模板标签的内容见Django基础(16): 模板标签(tags)的分类及如何自定义模板标签。本文只做简要介绍。首先你要在你的app目录下新建一个叫templatetags的文件夹(不能取其它名字), 里面必需包含__init__.py的空文件。在该目录下你还要新建一个python文件专门存放你自定义的模板标签函数,本例中为blog_extras.py,当然你也可以取其它名字。整个目录结构如下所示:
blog/
__init__.py
models.py
templatetags/
__init__.py
blog_extras.py
views.py
在模板中使用自定义的模板标签时,需要先使用{% load blog_extras %}载入自定义的过滤器,然后通过{% show_monthly_archive %} 使用它。
在blog_extras.py中添加如下代码。该自定义标签的作用创建字典列表, 并通过包含的模板_monthly_archive_list.html循环输出列表结果。因为这个模板并不是用于显示整个页面的主模板,而只是些模板片段,所以我们在它前面加了下划线_。
#blog_extras.py
from django import template
from ..models import Article
register = template.Library()
# show rendered template
@register.inclusion_tag('blog/_monthly_archive_list.html')
def show_monthly_archive():
#按日期逆序排序
articles = Article.objects.filter(status='p').order_by('-pub_date')
#获取最大和最小年份
max_year = articles[0].pub_date.year
min_year = articles[len(articles)-1].pub_date.year
#按年和月循环,排除空月份,生成子一个字典列表
rows = []
for year in range(max_year, min_year -1, -1):
for month in range(12, 0, -1):
total = Article.objects.filter(pub_date__year=year,
pub_date__month=month).count()
if total > 0:
rows.append({"year": year, "month": month, "total": total, })
else:
continue
return {'rows': rows, }
模板blog/_monthly_archive_list.html代码如下:
归档{% for row in rows %}
href="{% url 'blog:month_archive' row.year row.month %}">
{{ row.year }}年{{ row.month }}月 {{ row.total }}篇
{% endfor %}
视图views.py和urls.py
事情还没结束。我们还需要自定义一个名为month_archive的视图及其对应urls来根据年份year和月份month来显示文章清单。注意这里的month_archive.html是主模板哦,我们还加入了分页。
#urls.py
path('articles///' , views.month_archive, name='month_archive'),
#views.py
def month_archive(request, year, month):
articles = Article.objects.filter(status='p', pub_date__year=year,
pub_date__month=month).order_by('-pub_date')
paginator = Paginator(articles, 3)
page = request.GET.get('page')
page_obj = paginator.get_page(page)
context = {'page_obj': page_obj, 'paginator': paginator, 'is_paginated': True}
return render(request, 'blog/month_archive.html', context)
#blog/month_archive.html
{% extends "blog/base.html" %}
{% block content %}
月度归档
{# 注释: page_obj不要改。Article可以改成自己对象 #}
{% if page_obj %}class="table table-striped">
标题
发布日期
查看
{% for article in page_obj %}
{{ article.title }}
{{ article.pub_date | date:"Y-m-d" }}
href="{% url 'blog:article_detail' article.id article.slug %}">class="glyphicon glyphicon-eye-open">
{% endfor %}
{% else %}
{# 注释: 这里可以换成自己的对象 #}
没有文章。
{% endif %}
{# 注释: 下面代码一点也不要动, #}
{% if is_paginated %}class="pagination">
{% if page_obj.has_previous %}class="page-item">class="page-link" href="?page={{ page_obj.previous_page_number }}">«
{% else %}class="page-item disabled">class="page-link">«
{% endif %}
{% for i in paginator.page_range %}
{% if page_obj.number == i %}class="page-item active">class="page-link"> {{ i }} class="sr-only">(current)
{% else %}class="page-item">class="page-link" href="?page={{ i }}">{{ i }}
{% endif %}
{% endfor %}
{% if page_obj.has_next %}class="page-item">class="page-link" href="?page={{ page_obj.next_page_number }}">»
{% else %}class="page-item disabled">class="page-link">»
{% endif %}
{% endif %}
{% endblock %}
查看效果
我们们在article_detail.html加入{% load blog_extras %}和 {% show_monthly_archive %}, 这时的文章详情页面就包含月度归档信息了。点击链接可以查看每月文章列表。
最后的话
统计哪些月份是否表了文章以及每个月发表了多少篇文章是非常耗时的运算。如果用户每访问一个页面,都需要重新计算一次会造成很大的资源浪费。这部分信息其实更新的并不是那么快,完全可以使用缓存把计算结果缓存起来,如下所示。更多内容见Django基础(8): 缓存Cache应用场景及工作原理,Cache设置及如何使用。
#在模板中使用cache
{% load cache %}
{% cache 500 %}
{% show_monthly_archive %}
{% endcache %}你有更好的实现办法吗? 如果你有什么功能或业务需求需要实现的,欢迎留言。小编我会根据个人兴趣有选择性地提供解决方案。