From 3b7bf44b52a9f577475862feb196c9290777c612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=A5=E6=AC=A3=E6=80=A1?= <3093609022@qq.com> Date: Sun, 9 Nov 2025 22:32:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20biogs.py=20=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../blog/templatetags/blog_tags.py | 376 ++++++++++-------- 1 file changed, 220 insertions(+), 156 deletions(-) diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py index d6cd5d5a..683a9d34 100644 --- a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py +++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py @@ -1,37 +1,53 @@ -import hashlib -import logging -import random -import urllib - -from django import template -from django.conf import settings -from django.db.models import Q -from django.shortcuts import get_object_or_404 -from django.template.defaultfilters import stringfilter -from django.templatetags.static import static -from django.urls import reverse -from django.utils.safestring import mark_safe - +import hashlib # 用于Gravatar头像的MD5哈希计算 +import logging # 日志记录 +import random # 随机选择样式 +import urllib # URL编码处理 + +from django import template # 模板标签核心模块 +from django.conf import settings # 项目配置 +from django.db.models import Q # 数据库查询条件 +from django.shortcuts import get_object_or_404 # 获取对象或返回404 +from django.template.defaultfilters import stringfilter # 字符串过滤器装饰器 +from django.templatetags.static import static # 静态文件URL生成 +from django.urls import reverse # URL反向解析 +from django.utils.safestring import mark_safe # 标记安全HTML字符串 + +# 导入项目模型 from blog.models import Article, Category, Tag, Links, SideBar, LinkShowType from comments.models import Comment -from djangoblog.utils import CommonMarkdown, sanitize_html -from djangoblog.utils import cache -from djangoblog.utils import get_current_site -from oauth.models import OAuthUser -from djangoblog.plugin_manage import hooks - +# 导入工具类和插件 +from djangoblog.utils import CommonMarkdown, sanitize_html # Markdown处理和HTML净化 +from djangoblog.utils import cache # 缓存工具 +from djangoblog.utils import get_current_site # 获取当前站点信息 +from oauth.models import OAuthUser # OAuth用户模型 +from djangoblog.plugin_manage import hooks # 插件钩子 + +# 日志配置 logger = logging.getLogger(__name__) +# 注册模板标签库 register = template.Library() @register.simple_tag(takes_context=True) def head_meta(context): + """ + 页面头部元信息标签(通过插件钩子扩展) + 用于动态生成SEO相关的meta标签(如title、keywords等) + :param context: 模板上下文 + :return: 经过插件处理的安全HTML字符串 + """ return mark_safe(hooks.apply_filters('head_meta', '', context)) @register.simple_tag def timeformat(data): + """ + 时间格式化标签 + 将 datetime 对象格式化为 settings.TIME_FORMAT 定义的样式 + :param data: datetime对象 + :return: 格式化后的时间字符串,失败返回空 + """ try: return data.strftime(settings.TIME_FORMAT) except Exception as e: @@ -41,6 +57,12 @@ def timeformat(data): @register.simple_tag def datetimeformat(data): + """ + 日期时间格式化标签 + 将 datetime 对象格式化为 settings.DATE_TIME_FORMAT 定义的样式 + :param data: datetime对象 + :return: 格式化后的日期时间字符串,失败返回空 + """ try: return data.strftime(settings.DATE_TIME_FORMAT) except Exception as e: @@ -51,11 +73,23 @@ def datetimeformat(data): @register.filter() @stringfilter def custom_markdown(content): + """ + Markdown渲染过滤器 + 将Markdown格式的文本转换为HTML并标记为安全 + :param content: Markdown文本 + :return: 安全的HTML字符串 + """ return mark_safe(CommonMarkdown.get_markdown(content)) @register.simple_tag def get_markdown_toc(content): + """ + 获取Markdown内容的目录(TOC) + 用于生成文章目录导航 + :param content: Markdown文本 + :return: 目录的HTML字符串 + """ from djangoblog.utils import CommonMarkdown body, toc = CommonMarkdown.get_markdown_with_toc(content) return mark_safe(toc) @@ -64,6 +98,12 @@ def get_markdown_toc(content): @register.filter() @stringfilter def comment_markdown(content): + """ + 评论内容的Markdown渲染过滤器 + 先转换为HTML,再通过sanitize_html净化(过滤危险标签) + :param content: 评论的Markdown文本 + :return: 安全的HTML字符串 + """ content = CommonMarkdown.get_markdown(content) return mark_safe(sanitize_html(content)) @@ -72,37 +112,44 @@ def comment_markdown(content): @stringfilter def truncatechars_content(content): """ - 获得文章内容的摘要 - :param content: - :return: + 文章内容摘要过滤器 + 根据网站配置的摘要长度截断HTML内容(保留标签结构) + :param content: 文章HTML内容 + :return: 截断后的安全HTML字符串 """ from django.template.defaultfilters import truncatechars_html from djangoblog.utils import get_blog_setting - blogsetting = get_blog_setting() + blogsetting = get_blog_setting() # 获取网站配置 return truncatechars_html(content, blogsetting.article_sub_length) @register.filter(is_safe=True) @stringfilter def truncate(content): + """ + 简单截断过滤器(纯文本) + 去除HTML标签后截断前150个字符 + :param content: 带HTML的文本 + :return: 截断后的纯文本 + """ from django.utils.html import strip_tags - return strip_tags(content)[:150] @register.inclusion_tag('blog/tags/breadcrumb.html') def load_breadcrumb(article): """ - 获得文章面包屑 - :param article: - :return: + 面包屑导航标签 + 生成文章的分类层级导航(如:首页 > 技术 > Python > 文章标题) + :param article: 文章对象 + :return: 包含导航层级和标题的上下文 """ - names = article.get_category_tree() + names = article.get_category_tree() # 获取分类层级列表 from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() site = get_current_site().domain - names.append((blogsetting.site_name, '/')) - names = names[::-1] + names.append((blogsetting.site_name, '/')) # 添加首页 + names = names[::-1] # 反转层级顺序(从顶级到当前) return { 'names': names, @@ -114,63 +161,75 @@ def load_breadcrumb(article): @register.inclusion_tag('blog/tags/article_tag_list.html') def load_articletags(article): """ - 文章标签 - :param article: - :return: + 文章标签列表标签 + 生成文章关联的标签列表,包含标签URL、文章数量和随机样式 + :param article: 文章对象 + :return: 包含标签信息的上下文 """ tags = article.tags.all() tags_list = [] for tag in tags: - url = tag.get_absolute_url() - count = tag.get_article_count() + url = tag.get_absolute_url() # 标签页URL + count = tag.get_article_count() # 标签关联的文章数 + # 随机选择Bootstrap样式(如primary、success等) tags_list.append(( url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES) )) - return { - 'article_tags_list': tags_list - } + return {'article_tags_list': tags_list} @register.inclusion_tag('blog/tags/sidebar.html') def load_sidebar(user, linktype): """ - 加载侧边栏 - :return: + 侧边栏内容标签 + 加载侧边栏所需数据(热门文章、分类、标签云等),并使用缓存优化性能 + :param user: 当前用户 + :param linktype: 链接显示类型(控制友情链接显示场景) + :return: 侧边栏数据上下文 """ - value = cache.get("sidebar" + linktype) - if value: + # 缓存键:区分不同链接类型的侧边栏 + cachekey = "sidebar" + linktype + value = cache.get(cachekey) + if value: # 命中缓存直接返回 value['user'] = user return value - else: + else: # 未命中缓存,重新计算并缓存 logger.info('load sidebar') from djangoblog.utils import get_blog_setting - blogsetting = get_blog_setting() - recent_articles = Article.objects.filter( - status='p')[:blogsetting.sidebar_article_count] - sidebar_categorys = Category.objects.all() - extra_sidebars = SideBar.objects.filter( - is_enable=True).order_by('sequence') - most_read_articles = Article.objects.filter(status='p').order_by( - '-views')[:blogsetting.sidebar_article_count] - dates = Article.objects.datetimes('creation_time', 'month', order='DESC') + blogsetting = get_blog_setting() # 网站配置 + + # 侧边栏数据查询 + recent_articles = Article.objects.filter(status='p')[:blogsetting.sidebar_article_count] # 最新文章 + sidebar_categorys = Category.objects.all() # 所有分类 + extra_sidebars = SideBar.objects.filter(is_enable=True).order_by('sequence') # 自定义侧边栏 + most_read_articles = Article.objects.filter(status='p').order_by('-views')[ + :blogsetting.sidebar_article_count] # 热门文章 + dates = Article.objects.datetimes('creation_time', 'month', order='DESC') # 文章归档日期 + # 符合显示类型的友情链接 links = Links.objects.filter(is_enable=True).filter( - Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) - commment_list = Comment.objects.filter(is_enable=True).order_by( - '-id')[:blogsetting.sidebar_comment_count] - # 标签云 计算字体大小 - # 根据总数计算出平均值 大小为 (数目/平均值)*步长 - increment = 5 - tags = Tag.objects.all() + Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A) + ) + # 最新评论 + commment_list = Comment.objects.filter(is_enable=True).order_by('-id')[:blogsetting.sidebar_comment_count] + + # 标签云(根据文章数量计算字体大小) sidebar_tags = None + tags = Tag.objects.all() if tags and len(tags) > 0: - s = [t for t in [(t, t.get_article_count()) for t in tags] if t[1]] - count = sum([t[1] for t in s]) - dd = 1 if (count == 0 or not len(tags)) else count / len(tags) - import random - sidebar_tags = list( - map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s)) - random.shuffle(sidebar_tags) - + # 过滤有文章的标签 + tag_with_count = [(t, t.get_article_count()) for t in tags if t.get_article_count()] + if tag_with_count: + total = sum([t[1] for t in tag_with_count]) + avg = total / len(tag_with_count) # 平均文章数 + increment = 5 # 字体大小增量 + # 计算每个标签的字体大小(与平均数量成正比) + sidebar_tags = [ + (t[0], t[1], (t[1] / avg) * increment + 10) + for t in tag_with_count + ] + random.shuffle(sidebar_tags) # 随机排序 + + # 组装侧边栏数据 value = { 'recent_articles': recent_articles, 'sidebar_categorys': sidebar_categorys, @@ -185,8 +244,9 @@ def load_sidebar(user, linktype): 'sidebar_tags': sidebar_tags, 'extra_sidebars': extra_sidebars } - cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) - logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype)) + # 缓存3小时 + cache.set(cachekey, value, 60 * 60 * 3) + logger.info(f'set sidebar cache.key:{cachekey}') value['user'] = user return value @@ -194,77 +254,63 @@ def load_sidebar(user, linktype): @register.inclusion_tag('blog/tags/article_meta_info.html') def load_article_metas(article, user): """ - 获得文章meta信息 - :param article: - :return: + 文章元信息标签 + 加载文章的元数据(作者、发布时间、分类等) + :param article: 文章对象 + :param user: 当前用户 + :return: 包含文章和用户的上下文 """ - return { - 'article': article, - 'user': user - } + return {'article': article, 'user': user} @register.inclusion_tag('blog/tags/article_pagination.html') def load_pagination_info(page_obj, page_type, tag_name): + """ + 分页导航标签 + 根据不同页面类型(首页、标签页、分类页等)生成上一页/下一页链接 + :param page_obj: Django分页对象 + :param page_type: 页面类型(如分类标签归档、作者文章归档等) + :param tag_name: 标签/分类/作者名称(用于URL参数) + :return: 包含分页链接的上下文 + """ previous_url = '' next_url = '' + + # 首页分页 if page_type == '': if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse('blog:index_page', kwargs={'page': next_number}) + next_url = reverse('blog:index_page', kwargs={'page': page_obj.next_page_number()}) if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:index_page', kwargs={ - 'page': previous_number}) - if page_type == '分类标签归档': + previous_url = reverse('blog:index_page', kwargs={'page': page_obj.previous_page_number()}) + + # 标签页分页 + elif page_type == '分类标签归档': tag = get_object_or_404(Tag, name=tag_name) if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( - 'blog:tag_detail_page', - kwargs={ - 'page': next_number, - 'tag_name': tag.slug}) + next_url = reverse('blog:tag_detail_page', + kwargs={'page': page_obj.next_page_number(), 'tag_name': tag.slug}) if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:tag_detail_page', - kwargs={ - 'page': previous_number, - 'tag_name': tag.slug}) - if page_type == '作者文章归档': + previous_url = reverse('blog:tag_detail_page', + kwargs={'page': page_obj.previous_page_number(), 'tag_name': tag.slug}) + + # 作者文章分页 + elif page_type == '作者文章归档': if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( - 'blog:author_detail_page', - kwargs={ - 'page': next_number, - 'author_name': tag_name}) + next_url = reverse('blog:author_detail_page', + kwargs={'page': page_obj.next_page_number(), 'author_name': tag_name}) if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:author_detail_page', - kwargs={ - 'page': previous_number, - 'author_name': tag_name}) - - if page_type == '分类目录归档': + previous_url = reverse('blog:author_detail_page', + kwargs={'page': page_obj.previous_page_number(), 'author_name': tag_name}) + + # 分类页分页 + elif page_type == '分类目录归档': category = get_object_or_404(Category, name=tag_name) if page_obj.has_next(): - next_number = page_obj.next_page_number() - next_url = reverse( - 'blog:category_detail_page', - kwargs={ - 'page': next_number, - 'category_name': category.slug}) + next_url = reverse('blog:category_detail_page', + kwargs={'page': page_obj.next_page_number(), 'category_name': category.slug}) if page_obj.has_previous(): - previous_number = page_obj.previous_page_number() - previous_url = reverse( - 'blog:category_detail_page', - kwargs={ - 'page': previous_number, - 'category_name': category.slug}) + previous_url = reverse('blog:category_detail_page', + kwargs={'page': page_obj.previous_page_number(), 'category_name': category.slug}) return { 'previous_url': previous_url, @@ -276,69 +322,87 @@ def load_pagination_info(page_obj, page_type, tag_name): @register.inclusion_tag('blog/tags/article_info.html') def load_article_detail(article, isindex, user): """ - 加载文章详情 - :param article: - :param isindex:是否列表页,若是列表页只显示摘要 - :return: + 文章详情标签 + 加载文章详情页或列表页的展示内容(列表页显示摘要,详情页显示完整内容) + :param article: 文章对象 + :param isindex: 是否为列表页(True/False) + :param user: 当前用户 + :return: 包含文章展示信息的上下文 """ from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() - return { 'article': article, 'isindex': isindex, 'user': user, - 'open_site_comment': blogsetting.open_site_comment, + 'open_site_comment': blogsetting.open_site_comment, # 是否允许评论 } -# return only the URL of the gravatar -# TEMPLATE USE: {{ email|gravatar_url:150 }} @register.filter def gravatar_url(email, size=40): - """获得gravatar头像""" - cachekey = 'gravatat/' + email + """ + Gravatar头像URL过滤器 + 生成用户的Gravatar头像URL(优先使用OAuth用户的头像) + :param email: 用户邮箱 + :param size: 头像尺寸 + :return: 头像URL字符串 + """ + cachekey = f'gravatat/{email}' url = cache.get(cachekey) - if url: + if url: # 缓存命中 return url - else: - usermodels = OAuthUser.objects.filter(email=email) - if usermodels: - o = list(filter(lambda x: x.picture is not None, usermodels)) - if o: - return o[0].picture + else: # 缓存未命中 + # 优先使用OAuth用户的头像 + oauth_users = OAuthUser.objects.filter(email=email) + if oauth_users: + valid_avatars = [user for user in oauth_users if user.picture] + if valid_avatars: + return valid_avatars[0].picture + + # 生成Gravatar URL(邮箱MD5哈希 + 尺寸 + 默认头像) email = email.encode('utf-8') + default_avatar = static('blog/img/avatar.png') # 本地默认头像 + url = f"https://www.gravatar.com/avatar/{hashlib.md5(email.lower()).hexdigest()}?{urllib.parse.urlencode({'d': default_avatar, 's': str(size)})}" - default = static('blog/img/avatar.png') - - url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5( - email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)})) + # 缓存10小时 cache.set(cachekey, url, 60 * 60 * 10) - logger.info('set gravatar cache.key:{key}'.format(key=cachekey)) + logger.info(f'set gravatar cache.key:{cachekey}') return url @register.filter def gravatar(email, size=40): - """获得gravatar头像""" + """ + Gravatar头像标签 + 生成包含头像图片的HTML标签 + :param email: 用户邮箱 + :param size: 头像尺寸 + :return: 安全的img标签HTML字符串 + """ url = gravatar_url(email, size) - return mark_safe( - '' % - (url, size, size)) + return mark_safe(f'') @register.simple_tag def query(qs, **kwargs): - """ template tag which allows queryset filtering. Usage: - {% query books author=author as mybooks %} - {% for book in mybooks %} - ... - {% endfor %} + """ + 查询集过滤标签 + 在模板中对查询集进行过滤(如{% query books author=author as mybooks %}) + :param qs: Django查询集 + :param kwargs: 过滤条件(键值对) + :return: 过滤后的查询集 """ return qs.filter(**kwargs) @register.filter def addstr(arg1, arg2): - """concatenate arg1 & arg2""" - return str(arg1) + str(arg2) + """ + 字符串拼接过滤器 + 将两个参数转换为字符串并拼接 + :param arg1: 第一个参数 + :param arg2: 第二个参数 + :return: 拼接后的字符串 + """ + return str(arg1) + str(arg2) \ No newline at end of file