@ -5,316 +5,288 @@ import uuid
from django . conf import settings
from django . conf import settings
from django . core . paginator import Paginator
from django . core . paginator import Paginator
from django . http import HttpResponse , HttpResponseForbidden
from django . http import HttpResponse , HttpResponseForbidden
from django . shortcuts import get_object_or_404
from django . shortcuts import get_object_or_404 , render
from django . shortcuts import render
from django . templatetags . static import static
from django . templatetags . static import static
from django . utils import timezone
from django . utils import timezone
from django . utils . translation import gettext_lazy as _
from django . utils . translation import gettext_lazy as _
from django . views . decorators . csrf import csrf_exempt
from django . views . decorators . csrf import csrf_exempt
from django . views . generic . detail import DetailView
from django . views . generic . detail import DetailView
from django . views . generic . list import ListView
from django . views . generic . list import ListView
from haystack . views import SearchView
from haystack . views import SearchView # Haystack 全文搜索视图
from blog . models import Article , Category , LinkShowType , Links , Tag
from blog . models import Article , Category , LinkShowType , Links , Tag # 博客核心模型
from comments . forms import CommentForm
from comments . forms import CommentForm # 评论表单
from djangoblog . plugin_manage import hooks
from djangoblog . plugin_manage import hooks # 插件管理系统
from djangoblog . plugin_manage . hook_constants import ARTICLE_CONTENT_HOOK_NAME
from djangoblog . plugin_manage . hook_constants import ARTICLE_CONTENT_HOOK_NAME # 插件钩子常量
from djangoblog . utils import cache , get_blog_setting , get_sha256
from djangoblog . utils import cache , get_blog_setting , get_sha256 # 工具函数:缓存、站点配置、加密
logger = logging . getLogger ( __name__ )
logger = logging . getLogger ( __name__ ) # 日志记录器
# -------------------------------
# 基础:通用文章列表视图(支持缓存、分页)
# -------------------------------
class ArticleListView ( ListView ) :
class ArticleListView ( ListView ) :
# template_name属性用于指定使用哪个模板进行渲染
template_name = ' blog/article_index.html ' # 默认模板
template_name = ' blog/article_index.html '
context_object_name = ' article_list ' # 模板中使用的上下文变量名
page_type = ' ' # 页面类型描述,子类可重写
# context_object_name属性用于给上下文变量取名( 在模板中使用该名字)
paginate_by = settings . PAGINATE_BY # 每页文章数,从配置中读取
context_object_name = ' article_list '
page_kwarg = ' page ' # URL 中页码参数名
link_type = LinkShowType . L # 友情链接展示类型,子类可重写
# 页面类型,分类目录或标签列表等
page_type = ' '
paginate_by = settings . PAGINATE_BY
page_kwarg = ' page '
link_type = LinkShowType . L
def get_view_cache_key ( self ) :
def get_view_cache_key ( self ) :
return self . request . get [ ' pages ' ]
# 获取当前视图的缓存键(注意:原代码有误,应使用 self.request.GET 而非 self.request.get)
return self . request . GET . get ( ' pages ' , ' ' ) # 临时占位,实际应由子类实现
@property
@property
def page_number ( self ) :
def page_number ( self ) :
# 获取当前页码,默认为 1
page_kwarg = self . page_kwarg
page_kwarg = self . page_kwarg
page = self . kwargs . get (
return self . kwargs . get ( page_kwarg ) or self . request . GET . get ( page_kwarg ) or 1
page_kwarg ) or self . request . GET . get ( page_kwarg ) or 1
return page
def get_queryset_cache_key ( self ) :
def get_queryset_cache_key ( self ) :
"""
# 子类必须重写:返回当前页面数据对应的缓存键
子类重写 . 获得queryset的缓存key
"""
raise NotImplementedError ( )
raise NotImplementedError ( )
def get_queryset_data ( self ) :
def get_queryset_data ( self ) :
"""
# 子类必须重写:返回当前页面要展示的数据(通常是 QuerySet)
子类重写 . 获取queryset的数据
"""
raise NotImplementedError ( )
raise NotImplementedError ( )
def get_queryset_from_cache ( self , cache_key ) :
def get_queryset_from_cache ( self , cache_key ) :
'''
# 尝试从缓存中获取数据,若无则查询并缓存
缓存页面数据
: param cache_key : 缓存key
: return :
'''
value = cache . get ( cache_key )
value = cache . get ( cache_key )
if value :
if value :
logger . info ( ' get view cache.key: {key} ' . format ( key = cache_key ) )
logger . info ( f ' get view cache. key: { cache_key } ' )
return value
return value
else :
else :
article_list = self . get_queryset_data ( )
article_list = self . get_queryset_data ( )
cache . set ( cache_key , article_list )
cache . set ( cache_key , article_list )
logger . info ( ' set view cache.key: {key} ' . format ( key = cache_key ) )
logger . info ( f' set view cache. key: { cache_key } ' )
return article_list
return article_list
def get_queryset ( self ) :
def get_queryset ( self ) :
'''
# 重写默认的查询集,优先从缓存中读取
重写默认 , 从缓存获取数据
: return :
'''
key = self . get_queryset_cache_key ( )
key = self . get_queryset_cache_key ( )
value = self . get_queryset_from_cache ( key )
value = self . get_queryset_from_cache ( key )
return value
return value
def get_context_data ( self , * * kwargs ) :
def get_context_data ( self , * * kwargs ) :
# 给模板上下文添加 linktype, 用于控制友情链接展示类型
kwargs [ ' linktype ' ] = self . link_type
kwargs [ ' linktype ' ] = self . link_type
return super ( ArticleListView , self ) . get_context_data ( * * kwargs )
return super ( ) . get_context_data ( * * kwargs )
# -------------------------------
# 首页视图:展示所有已发布文章
# -------------------------------
class IndexView ( ArticleListView ) :
class IndexView ( ArticleListView ) :
'''
link_type = LinkShowType . I # 首页链接类型为 ‘首页展示’
首页
'''
# 友情链接类型
link_type = LinkShowType . I
def get_queryset_data ( self ) :
def get_queryset_data ( self ) :
article_list = Article . objects . filter ( type = ' a ' , status = ' p ' )
# 只获取类型为 'a'(文章),状态为 'p'(已发布)的文章
return article_list
return Article . objects . filter ( type = ' a ' , status = ' p ' )
def get_queryset_cache_key ( self ) :
def get_queryset_cache_key ( self ) :
cache_key = ' index_ {page} ' . format ( page = self . page_number )
# 缓存键包含页码,如 index_1, index_2...
return cache_key
return f ' index_ { self . page_number } '
# -------------------------------
# 文章详情页
# -------------------------------
class ArticleDetailView ( DetailView ) :
class ArticleDetailView ( DetailView ) :
'''
文章详情页面
'''
template_name = ' blog/article_detail.html '
template_name = ' blog/article_detail.html '
model = Article
model = Article
pk_url_kwarg = ' article_id '
pk_url_kwarg = ' article_id ' # URL 中的文章 ID 参数名
context_object_name = " article "
context_object_name = " article "
def get_context_data ( self , * * kwargs ) :
def get_context_data ( self , * * kwargs ) :
# 添加评论表单
comment_form = CommentForm ( )
comment_form = CommentForm ( )
# 获取当前文章的所有评论,并筛选出顶级评论(无父评论)
article_comments = self . object . comment_list ( )
article_comments = self . object . comment_list ( )
parent_comments = article_comments . filter ( parent_comment = None )
parent_comments = article_comments . filter ( parent_comment = None )
blog_setting = get_blog_setting ( )
paginator = Paginator ( parent_comments , blog_setting . article_comment_count )
blog_setting = get_blog_setting ( ) # 获取博客配置
paginator = Paginator ( parent_comments , blog_setting . article_comment_count ) # 评论分页
page = self . request . GET . get ( ' comment_page ' , ' 1 ' )
page = self . request . GET . get ( ' comment_page ' , ' 1 ' )
if not page . isnumeric ( ) :
page = 1
try :
else :
page = int ( page )
page = int ( page )
if page < 1 :
if page < 1 :
page = 1
page = 1
if page > paginator . num_pages :
if page > paginator . num_pages :
page = paginator . num_pages
page = paginator . num_pages
except :
page = 1
p_comments = paginator . page ( page )
p_comments = paginator . page ( page ) # 当前页的评论
next_page = p_comments . next_page_number ( ) if p_comments . has_next ( ) else None
next_page = p_comments . next_page_number ( ) if p_comments . has_next ( ) else None
prev_page = p_comments . previous_page_number ( ) if p_comments . has_previous ( ) else None
prev_page = p_comments . previous_page_number ( ) if p_comments . has_previous ( ) else None
# 若有下一页/上一页,在上下文中添加对应 URL( 带锚点定位到评论区)
if next_page :
if next_page :
kwargs [
kwargs [ ' comment_next_page_url ' ] = f " { self . object . get_absolute_url ( ) } ?comment_page= { next_page } #commentlist-container "
' comment_next_page_url ' ] = self . object . get_absolute_url ( ) + f ' ?comment_page= { next_page } #commentlist-container '
if prev_page :
if prev_page :
kwargs [
kwargs [ ' comment_prev_page_url ' ] = f " { self . object . get_absolute_url ( ) } ?comment_page= { prev_page } #commentlist-container "
' comment_prev_page_url ' ] = self . object . get_absolute_url ( ) + f ' ?comment_page= { prev_page } #commentlist-container '
kwargs [ ' form ' ] = comment_form
kwargs [ ' form ' ] = comment_form
kwargs [ ' article_comments ' ] = article_comments
kwargs [ ' article_comments ' ] = article_comments
kwargs [ ' p_comments ' ] = p_comments
kwargs [ ' p_comments ' ] = p_comments
kwargs [ ' comment_count ' ] = len (
kwargs [ ' comment_count ' ] = len ( article_comments ) if article_comments else 0
article_comments ) if article_comments else 0
kwargs [ ' next_article ' ] = self . object . next_article # 下一篇文章
kwargs [ ' prev_article ' ] = self . object . prev_article # 上一篇文章
kwargs [ ' next_article ' ] = self . object . next_article
context = super ( ) . get_context_data ( * * kwargs )
kwargs [ ' prev_article ' ] = self . object . prev_article
# 调用插件钩子:文章内容获取后通知
hooks . run_action ( ' after_article_body_get ' , article = self . object , request = self . request )
context = super ( ArticleDetailView , self ) . get_context_data ( * * kwargs )
article = self . object
# Action Hook, 通知插件"文章详情已获取"
hooks . run_action ( ' after_article_body_get ' , article = article , request = self . request )
return context
return context
# -------------------------------
# 分类页视图
# -------------------------------
class CategoryDetailView ( ArticleListView ) :
class CategoryDetailView ( ArticleListView ) :
'''
分类目录列表
'''
page_type = " 分类目录归档 "
page_type = " 分类目录归档 "
def get_queryset_data ( self ) :
def get_queryset_data ( self ) :
slug = self . kwargs [ ' category_name ' ]
slug = self . kwargs [ ' category_name ' ] # 从 URL 获取分类别名
category = get_object_or_404 ( Category , slug = slug )
category = get_object_or_404 ( Category , slug = slug )
categorynames = [ c . name for c in category . get_sub_categorys ( ) ] # 获取所有子分类名称
categoryname = category . name
# 获取这些分类下的所有已发布文章
self . categoryname = categoryname
return Article . objects . filter ( category__name__in = categorynames , status = ' p ' )
categorynames = list (
map ( lambda c : c . name , category . get_sub_categorys ( ) ) )
article_list = Article . objects . filter (
category__name__in = categorynames , status = ' p ' )
return article_list
def get_queryset_cache_key ( self ) :
def get_queryset_cache_key ( self ) :
slug = self . kwargs [ ' category_name ' ]
slug = self . kwargs [ ' category_name ' ]
category = get_object_or_404 ( Category , slug = slug )
category = get_object_or_404 ( Category , slug = slug )
categoryname = category . name
cache_key = f ' category_list_ { category . name } _ { self . page_number } '
self . categoryname = categoryname
cache_key = ' category_list_ {categoryname} _ {page} ' . format (
categoryname = categoryname , page = self . page_number )
return cache_key
return cache_key
def get_context_data ( self , * * kwargs ) :
def get_context_data ( self , * * kwargs ) :
categoryname = self . kwargs [ ' category_name ' ]
categoryname = self . categoryname
try :
try :
categoryname = categoryname . split ( ' / ' ) [ - 1 ]
categoryname = categoryname . split ( ' / ' ) [ - 1 ] # 尝试提取最后一段(美化展示用)
except BaseException :
except :
pass
pass
kwargs [ ' page_type ' ] = CategoryDetailView . page_type
kwargs [ ' page_type ' ] = self . page_type
kwargs [ ' tag_name ' ] = categoryname
kwargs [ ' tag_name ' ] = categoryname
return super ( CategoryDetailView , self ) . get_context_data ( * * kwargs )
return super ( ) . get_context_data ( * * kwargs )
# -------------------------------
# 作者页视图
# -------------------------------
class AuthorDetailView ( ArticleListView ) :
class AuthorDetailView ( ArticleListView ) :
'''
作者详情页
'''
page_type = ' 作者文章归档 '
page_type = ' 作者文章归档 '
def get_queryset_cache_key ( self ) :
def get_queryset_cache_key ( self ) :
from uuslug import slugify
from uuslug import slugify
author_name = slugify ( self . kwargs [ ' author_name ' ] )
author_name = slugify ( self . kwargs [ ' author_name ' ] )
cache_key = ' author_ {author_name} _ {page} ' . format (
return f ' author_ { author_name } _ { self . page_number } '
author_name = author_name , page = self . page_number )
return cache_key
def get_queryset_data ( self ) :
def get_queryset_data ( self ) :
author_name = self . kwargs [ ' author_name ' ]
author_name = self . kwargs [ ' author_name ' ]
article_list = Article . objects . filter (
return Article . objects . filter ( author__username = author_name , type = ' a ' , status = ' p ' )
author__username = author_name , type = ' a ' , status = ' p ' )
return article_list
def get_context_data ( self , * * kwargs ) :
def get_context_data ( self , * * kwargs ) :
author_name = self . kwargs [ ' author_name ' ]
author_name = self . kwargs [ ' author_name ' ]
kwargs [ ' page_type ' ] = AuthorDetailView . page_type
kwargs [ ' page_type ' ] = self . page_type
kwargs [ ' tag_name ' ] = author_name
kwargs [ ' tag_name ' ] = author_name
return super ( AuthorDetailView , self ) . get_context_data ( * * kwargs )
return super ( ) . get_context_data ( * * kwargs )
# -------------------------------
# 标签页视图
# -------------------------------
class TagDetailView ( ArticleListView ) :
class TagDetailView ( ArticleListView ) :
'''
标签列表页面
'''
page_type = ' 分类标签归档 '
page_type = ' 分类标签归档 '
def get_queryset_data ( self ) :
def get_queryset_data ( self ) :
slug = self . kwargs [ ' tag_name ' ]
slug = self . kwargs [ ' tag_name ' ]
tag = get_object_or_404 ( Tag , slug = slug )
tag = get_object_or_404 ( Tag , slug = slug )
tag_name = tag . name
return Article . objects . filter ( tags__name = tag . name , type = ' a ' , status = ' p ' )
self . name = tag_name
article_list = Article . objects . filter (
tags__name = tag_name , type = ' a ' , status = ' p ' )
return article_list
def get_queryset_cache_key ( self ) :
def get_queryset_cache_key ( self ) :
slug = self . kwargs [ ' tag_name ' ]
slug = self . kwargs [ ' tag_name ' ]
tag = get_object_or_404 ( Tag , slug = slug )
tag = get_object_or_404 ( Tag , slug = slug )
tag_name = tag . name
return f ' tag_ { tag . name } _ { self . page_number } '
self . name = tag_name
cache_key = ' tag_ {tag_name} _ {page} ' . format (
tag_name = tag_name , page = self . page_number )
return cache_key
def get_context_data ( self , * * kwargs ) :
def get_context_data ( self , * * kwargs ) :
# tag_name = self.kwargs['tag_name']
tag_name = self . kwargs [ ' tag_name ' ]
tag_name = self . name
kwargs [ ' page_type ' ] = self . page_type
kwargs [ ' page_type ' ] = TagDetailView . page_type
kwargs [ ' tag_name ' ] = tag_name
kwargs [ ' tag_name ' ] = tag_name
return super ( TagDetailView , self ) . get_context_data ( * * kwargs )
return super ( ) . get_context_data ( * * kwargs )
# -------------------------------
# 归档页视图:展示所有已发布文章
# -------------------------------
class ArchivesView ( ArticleListView ) :
class ArchivesView ( ArticleListView ) :
'''
文章归档页面
'''
page_type = ' 文章归档 '
page_type = ' 文章归档 '
paginate_by = None
paginate_by = None # 不分页
page_kwarg = None
page_kwarg = None
template_name = ' blog/article_archives.html '
template_name = ' blog/article_archives.html '
def get_queryset_data ( self ) :
def get_queryset_data ( self ) :
return Article . objects . filter ( status = ' p ' ) . all ( )
return Article . objects . filter ( status = ' p ' )
def get_queryset_cache_key ( self ) :
def get_queryset_cache_key ( self ) :
cache_key = ' archives '
return ' archives '
return cache_key
# -------------------------------
# 友情链接页
# -------------------------------
class LinkListView ( ListView ) :
class LinkListView ( ListView ) :
model = Links
model = Links
template_name = ' blog/links_list.html '
template_name = ' blog/links_list.html '
def get_queryset ( self ) :
def get_queryset ( self ) :
return Links . objects . filter ( is_enable = True )
return Links . objects . filter ( is_enable = True ) # 只展示启用的链接
# -------------------------------
# Haystack 搜索视图
# -------------------------------
class EsSearchView ( SearchView ) :
class EsSearchView ( SearchView ) :
def get_context ( self ) :
def get_context ( self ) :
paginator , page = self . build_page ( )
paginator , page = self . build_page ( )
context = {
context = {
" query " : self . query ,
" query " : self . query , # 搜索关键词
" form " : self . form ,
" form " : self . form , # 搜索表单
" page " : page ,
" page " : page , # 当前页
" paginator " : paginator ,
" paginator " : paginator , # 分页器
" suggestion " : None ,
" suggestion " : None , # 搜索建议,可后续补充
}
}
# 如果后端支持拼写建议,则添加
if hasattr ( self . results , " query " ) and self . results . query . backend . include_spelling :
if hasattr ( self . results , " query " ) and self . results . query . backend . include_spelling :
context [ " suggestion " ] = self . results . query . get_spelling_suggestion ( )
context [ " suggestion " ] = self . results . query . get_spelling_suggestion ( )
context . update ( self . extra_context ( ) )
context . update ( self . extra_context ( ) ) # 添加额外上下文
return context
return context
# -------------------------------
# 图床上传接口(带签名校验,仅限 POST)
# -------------------------------
@csrf_exempt
@csrf_exempt
def fileupload ( request ) :
def fileupload ( request ) :
"""
该方法需自己写调用端来上传图片 , 该方法仅提供图床功能
: param request :
: return :
"""
if request . method == ' POST ' :
if request . method == ' POST ' :
sign = request . GET . get ( ' sign ' , None )
sign = request . GET . get ( ' sign ' , None )
if not sign :
if not sign :
return HttpResponseForbidden ( )
return HttpResponseForbidden ( )
# 校验签名(双重 SHA256, 与 settings.SECRET_KEY 相关)
if not sign == get_sha256 ( get_sha256 ( settings . SECRET_KEY ) ) :
if not sign == get_sha256 ( get_sha256 ( settings . SECRET_KEY ) ) :
return HttpResponseForbidden ( )
return HttpResponseForbidden ( )
response = [ ]
response = [ ]
for filename in request . FILES :
for filename in request . FILES :
timestr = timezone . now ( ) . strftime ( ' % Y/ % m/ %d ' )
timestr = timezone . now ( ) . strftime ( ' % Y/ % m/ %d ' )
imgextensions = [ ' jpg ' , ' png ' , ' jpeg ' , ' bmp ' ]
imgextensions = [ ' jpg ' , ' png ' , ' jpeg ' , ' bmp ' ]
fname = u ' ' . join ( str ( filename ) )
fname = ' ' . join ( str ( filename ) )
isimage = len( [ i for i in imgextensions if fname . find ( i ) > = 0 ] ) > 0
isimage = any( ext in fname . lower ( ) for ext in imgextensions )
base_dir = os . path . join ( settings . STATICFILES , " files " if not isimage else " image " , timestr )
base_dir = os . path . join ( settings . STATICFILES , " files " if not isimage else " image " , timestr )
if not os . path . exists ( base_dir ) :
if not os . path . exists ( base_dir ) :
os . makedirs ( base_dir )
os . makedirs ( base_dir )
@ -328,48 +300,45 @@ def fileupload(request):
from PIL import Image
from PIL import Image
image = Image . open ( savepath )
image = Image . open ( savepath )
image . save ( savepath , quality = 20 , optimize = True )
image . save ( savepath , quality = 20 , optimize = True )
url = static ( savepath )
url = static ( savepath ) # 生成静态文件访问 URL
response . append ( url )
response . append ( url )
return HttpResponse ( response )
return HttpResponse ( response )
else :
else :
return HttpResponse ( " only for post " )
return HttpResponse ( " only for post " )
def page_not_found_view (
# -------------------------------
request ,
# 错误页面视图
exception ,
# -------------------------------
template_name = ' blog/error_page.html ' ) :
def page_not_found_view ( request , exception , template_name = ' blog/error_page.html ' ) :
if exception :
if exception :
logger . error ( exception )
logger . error ( exception )
url = request . get_full_path ( )
url = request . get_full_path ( )
return render ( request ,
return render ( request , template_name , {
template_name ,
' message ' : _ ( ' Sorry, the page you requested is not found, please click the home page to see other? ' ) ,
{ ' message ' : _ ( ' Sorry, the page you requested is not found, please click the home page to see other? ' ) ,
' statuscode ' : ' 404 '
' statuscode ' : ' 404 ' } ,
} , status = 404 )
status = 404 )
def server_error_view ( request , template_name = ' blog/error_page.html ' ) :
def server_error_view ( request , template_name = ' blog/error_page.html ' ) :
return render ( request ,
return render ( request , template_name , {
template_name ,
' message ' : _ ( ' Sorry, the server is busy, please click the home page to see other? ' ) ,
{ ' message ' : _ ( ' Sorry, the server is busy, please click the home page to see other? ' ) ,
' statuscode ' : ' 500 '
' statuscode ' : ' 500 ' } ,
} , status = 500 )
status = 500 )
def permission_denied_view (
def permission_denied_view ( request , exception , template_name = ' blog/error_page.html ' ) :
request ,
exception ,
template_name = ' blog/error_page.html ' ) :
if exception :
if exception :
logger . error ( exception )
logger . error ( exception )
return render (
return render ( request , template_name , {
request , template_name , {
' message ' : _ ( ' Sorry, you do not have permission to access this page? ' ) ,
' message ' : _ ( ' Sorry, you do not have permission to access this page? ' ) ,
' statuscode ' : ' 403 '
' statuscode ' : ' 403 ' } , status = 403 )
} , status = 403 )
# -------------------------------
# 手动清理缓存视图(通常用于后台或调试)
# -------------------------------
def clean_cache_view ( request ) :
def clean_cache_view ( request ) :
cache . clear ( )
cache . clear ( )
return HttpResponse ( ' ok ' )
return HttpResponse ( ' ok ' )