|
|
|
|
@ -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(
|
|
|
|
|
'<img src="%s" height="%d" width="%d">' %
|
|
|
|
|
(url, size, size))
|
|
|
|
|
return mark_safe(f'<img src="{url}" height="{size}" width="{size}">')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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)
|