|
|
|
|
@ -0,0 +1,417 @@
|
|
|
|
|
import logging
|
|
|
|
|
import os
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
from django.conf import settings·
|
|
|
|
|
from django.core.paginator import Paginator
|
|
|
|
|
from django.http import HttpResponse, HttpResponseForbidden
|
|
|
|
|
from django.shortcuts import get_object_or_404
|
|
|
|
|
from django.shortcuts import render
|
|
|
|
|
from django.templatetags.static import static
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
|
from django.views.generic.detail import DetailView
|
|
|
|
|
from django.views.generic.list import ListView
|
|
|
|
|
from haystack.views import SearchView
|
|
|
|
|
# 导入目标模型:Article(文章)、Category(分类)、Tag(标签),及其他关联模型
|
|
|
|
|
|
|
|
|
|
from blog.models import Article, Category, LinkShowType, Links, Tag
|
|
|
|
|
from comments.forms import CommentForm
|
|
|
|
|
from djangoblog.plugin_manage import hooks
|
|
|
|
|
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
|
|
|
|
|
from djangoblog.utils import cache, get_blog_setting, get_sha256
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
# 导入目标模型:Article(文章)、Category(分类)、Tag(标签),及其他关联模型
|
|
|
|
|
|
|
|
|
|
class ArticleListView(ListView):
|
|
|
|
|
# template_name灞炴€х敤浜庢寚瀹氫娇鐢ㄥ摢涓<E691A2>ā鏉胯繘琛屾覆鏌?
|
|
|
|
|
template_name = 'blog/article_index.html'
|
|
|
|
|
|
|
|
|
|
# context_object_name灞炴€х敤浜庣粰涓婁笅鏂囧彉閲忓彇鍚嶏紙鍦ㄦā鏉夸腑浣跨敤璇ュ悕瀛楋級
|
|
|
|
|
context_object_name = 'article_list'
|
|
|
|
|
# 上下文变量名,对应模板中遍历的“文章列表”
|
|
|
|
|
|
|
|
|
|
# 椤甸潰绫诲瀷锛屽垎绫荤洰褰曟垨鏍囩<E98F8D>鍒楄〃绛?
|
|
|
|
|
page_type = ''
|
|
|
|
|
paginate_by = settings.PAGINATE_BY
|
|
|
|
|
# 每页显示文章数量,关联Article模型的“列表展示”逻辑
|
|
|
|
|
page_kwarg = 'page'
|
|
|
|
|
link_type = LinkShowType.L
|
|
|
|
|
# 省略基类中不涉及模型字段的通用方法
|
|
|
|
|
def get_view_cache_key(self):
|
|
|
|
|
return self.request.get['pages']
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def page_number(self):
|
|
|
|
|
page_kwarg = self.page_kwarg
|
|
|
|
|
page = self.kwargs.get(
|
|
|
|
|
page_kwarg) or self.request.GET.get(page_kwarg) or 1
|
|
|
|
|
return page
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
"""
|
|
|
|
|
瀛愮被閲嶅啓.鑾峰緱queryset鐨勭紦瀛榢ey
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
"""
|
|
|
|
|
瀛愮被閲嶅啓.鑾峰彇queryset鐨勬暟鎹?
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
def get_queryset_from_cache(self, cache_key):
|
|
|
|
|
'''
|
|
|
|
|
缂撳瓨椤甸潰鏁版嵁
|
|
|
|
|
:param cache_key: 缂撳瓨key
|
|
|
|
|
:return:
|
|
|
|
|
'''
|
|
|
|
|
value = cache.get(cache_key)
|
|
|
|
|
if value:
|
|
|
|
|
logger.info('get view cache.key:{key}'.format(key=cache_key))
|
|
|
|
|
return value
|
|
|
|
|
else:
|
|
|
|
|
article_list = self.get_queryset_data()
|
|
|
|
|
cache.set(cache_key, article_list)
|
|
|
|
|
logger.info('set view cache.key:{key}'.format(key=cache_key))
|
|
|
|
|
return article_list
|
|
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
'''
|
|
|
|
|
閲嶅啓榛樿<EFBFBD>锛屼粠缂撳瓨鑾峰彇鏁版嵁
|
|
|
|
|
:return:
|
|
|
|
|
'''
|
|
|
|
|
key = self.get_queryset_cache_key()
|
|
|
|
|
value = self.get_queryset_from_cache(key)
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
kwargs['linktype'] = self.link_type
|
|
|
|
|
return super(ArticleListView, self).get_context_data(**kwargs)
|
|
|
|
|
# 分类详情页视图(继承文章列表基类)
|
|
|
|
|
|
|
|
|
|
class IndexView(ArticleListView):
|
|
|
|
|
'''
|
|
|
|
|
棣栭〉
|
|
|
|
|
'''
|
|
|
|
|
# 鍙嬫儏閾炬帴绫诲瀷
|
|
|
|
|
link_type = LinkShowType.I
|
|
|
|
|
# 从URL参数获取分类的slug(对应Category.slug字段,分类友好URL标识)
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
|
|
|
|
|
article_list = Article.objects.filter(type='a', status='p')
|
|
|
|
|
return article_list
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
cache_key = 'index_{page}'.format(page=self.page_number)
|
|
|
|
|
return cache_key
|
|
|
|
|
# 作者详情页视图(涉及Article.author字段,此处仅作关联说明)
|
|
|
|
|
|
|
|
|
|
class ArticleDetailView(DetailView):
|
|
|
|
|
'''
|
|
|
|
|
鏂囩珷璇︽儏椤甸潰
|
|
|
|
|
'''
|
|
|
|
|
template_name = 'blog/article_detail.html'
|
|
|
|
|
model = Article
|
|
|
|
|
pk_url_kwarg = 'article_id'
|
|
|
|
|
context_object_name = "article"
|
|
|
|
|
# 归档页不分页,显示所有文章
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
# 筛选Article模型数据:status='p'(仅已发布文章),无分页返回所有数据
|
|
|
|
|
# 归档逻辑依赖Article.create_time字段(文章创建时间),模板中按时间分组展示
|
|
|
|
|
comment_form = CommentForm()
|
|
|
|
|
|
|
|
|
|
article_comments = self.object.comment_list()
|
|
|
|
|
parent_comments = article_comments.filter(parent_comment=None)
|
|
|
|
|
blog_setting = get_blog_setting()
|
|
|
|
|
paginator = Paginator(parent_comments, blog_setting.article_comment_count)
|
|
|
|
|
page = self.request.GET.get('comment_page', '1')
|
|
|
|
|
if not page.isnumeric():
|
|
|
|
|
page = 1
|
|
|
|
|
else:
|
|
|
|
|
page = int(page)
|
|
|
|
|
if page < 1:
|
|
|
|
|
page = 1
|
|
|
|
|
if page > paginator.num_pages:
|
|
|
|
|
page = paginator.num_pages
|
|
|
|
|
|
|
|
|
|
p_comments = paginator.page(page)
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
if next_page:
|
|
|
|
|
kwargs[
|
|
|
|
|
'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container'
|
|
|
|
|
if prev_page:
|
|
|
|
|
kwargs[
|
|
|
|
|
'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container'
|
|
|
|
|
kwargs['form'] = comment_form
|
|
|
|
|
kwargs['article_comments'] = article_comments
|
|
|
|
|
kwargs['p_comments'] = p_comments
|
|
|
|
|
kwargs['comment_count'] = len(
|
|
|
|
|
article_comments) if article_comments else 0
|
|
|
|
|
|
|
|
|
|
kwargs['next_article'] = self.object.next_article
|
|
|
|
|
kwargs['prev_article'] = self.object.prev_article
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
# # Filter Hook, 鍏佽<E98D8F>鎻掍欢淇<E6ACA2>敼鏂囩珷姝f枃
|
|
|
|
|
article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article,
|
|
|
|
|
request=self.request)
|
|
|
|
|
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CategoryDetailView(ArticleListView):
|
|
|
|
|
'''
|
|
|
|
|
鍒嗙被鐩<EFBFBD>綍鍒楄〃
|
|
|
|
|
'''
|
|
|
|
|
page_type = "鍒嗙被鐩<EFBFBD>綍褰掓。"
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
# 从URL参数获取分类的slug(对应Category.slug字段,分类友好URL标识)
|
|
|
|
|
slug = self.kwargs['category_name']
|
|
|
|
|
# 通过slug查询唯一分类:get_object_or_404表示不存在则返回404
|
|
|
|
|
# 关联Category.slug字段(分类的URL唯一标识,避免中文/特殊字符问题)
|
|
|
|
|
category = get_object_or_404(Category, slug=slug)
|
|
|
|
|
|
|
|
|
|
categoryname = category.name
|
|
|
|
|
# 获取分类名称(关联Category.name字段,分类显示名称)
|
|
|
|
|
self.categoryname = categoryname
|
|
|
|
|
# 获取当前分类的所有子分类名称:调用Category模型的自定义方法get_sub_categorys()
|
|
|
|
|
# 关联Category.parent字段(分类自关联,实现多级分类,如“技术→Python”)
|
|
|
|
|
categorynames = list(
|
|
|
|
|
map(lambda c: c.name, category.get_sub_categorys()))
|
|
|
|
|
# 筛选Article模型数据:
|
|
|
|
|
# category__name__in=categorynames:匹配Article.category.name字段(文章所属分类名称)
|
|
|
|
|
# 表示“文章所属分类在‘当前分类+子分类’列表中”
|
|
|
|
|
# status='p':匹配Article.status字段(仅显示已发布文章)
|
|
|
|
|
article_list = Article.objects.filter(
|
|
|
|
|
category__name__in=categorynames, status='p')
|
|
|
|
|
return article_list
|
|
|
|
|
# 省略缓存key生成、上下文添加方法...
|
|
|
|
|
# 作者详情页视图(涉及Article.author字段,此处仅作关联说明)
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
slug = self.kwargs['category_name']
|
|
|
|
|
category = get_object_or_404(Category, slug=slug)
|
|
|
|
|
categoryname = category.name
|
|
|
|
|
self.categoryname = categoryname
|
|
|
|
|
cache_key = 'category_list_{categoryname}_{page}'.format(
|
|
|
|
|
categoryname=categoryname, page=self.page_number)
|
|
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
|
|
|
|
|
categoryname = self.categoryname
|
|
|
|
|
try:
|
|
|
|
|
categoryname = categoryname.split('/')[-1]
|
|
|
|
|
except BaseException:
|
|
|
|
|
pass
|
|
|
|
|
kwargs['page_type'] = CategoryDetailView.page_type
|
|
|
|
|
kwargs['tag_name'] = categoryname
|
|
|
|
|
return super(CategoryDetailView, self).get_context_data(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AuthorDetailView(ArticleListView):
|
|
|
|
|
'''
|
|
|
|
|
浣滆€呰<EFBFBD>鎯呴〉
|
|
|
|
|
'''
|
|
|
|
|
page_type = '浣滆€呮枃绔犲綊妗?'
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
from uuslug import slugify
|
|
|
|
|
author_name = slugify(self.kwargs['author_name'])
|
|
|
|
|
# 筛选Article模型数据:
|
|
|
|
|
# author__username=author_name:匹配Article.author.username字段(文章作者的用户名)
|
|
|
|
|
# 关联Article.author字段(文章与用户的一对多关系,一篇文章对应一个作者)
|
|
|
|
|
# type='a'、status='p':同首页,筛选“已发布的普通文章”
|
|
|
|
|
cache_key = 'author_{author_name}_{page}'.format(
|
|
|
|
|
author_name=author_name, page=self.page_number)
|
|
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
author_name = self.kwargs['author_name']
|
|
|
|
|
article_list = Article.objects.filter(
|
|
|
|
|
author__username=author_name, type='a', status='p')
|
|
|
|
|
return article_list
|
|
|
|
|
# 省略其他方法...
|
|
|
|
|
.0# 标签详情页视图(继承文章列表基类)
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
author_name = self.kwargs['author_name']
|
|
|
|
|
kwargs['page_type'] = AuthorDetailView.page_type
|
|
|
|
|
kwargs['tag_name'] = author_name
|
|
|
|
|
return super(AuthorDetailView, self).get_context_data(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TagDetailView(ArticleListView):
|
|
|
|
|
'''
|
|
|
|
|
鏍囩<EFBFBD>鍒楄〃椤甸潰
|
|
|
|
|
'''
|
|
|
|
|
page_type = '鍒嗙被鏍囩<EFBFBD>褰掓。'
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
# 从URL参数获取标签的slug(对应Tag.slug字段,标签友好URL标识)
|
|
|
|
|
slug = self.kwargs['tag_name']
|
|
|
|
|
# 通过slug查询唯一标签:关联Tag.slug字段(标签的URL唯一标识)
|
|
|
|
|
tag = get_object_or_404(Tag, slug=slug)
|
|
|
|
|
tag_name = tag.name
|
|
|
|
|
# 获取标签名称(关联Tag.name字段,标签显示名称)
|
|
|
|
|
self.name = tag_name
|
|
|
|
|
# 筛选Article模型数据:
|
|
|
|
|
# tags__name=tag_name:匹配Article.tags.name字段(文章关联的标签名称)
|
|
|
|
|
# 关联Article.tags字段(文章与标签的多对多关系,一篇文章可关联多个标签,一个标签可关联多篇文章)
|
|
|
|
|
# type='a'、status='p':筛选“已发布的普通文章”
|
|
|
|
|
article_list = Article.objects.filter(
|
|
|
|
|
tags__name=tag_name, type='a', status='p')
|
|
|
|
|
return article_list
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
slug = self.kwargs['tag_name']
|
|
|
|
|
tag = get_object_or_404(Tag, slug=slug)
|
|
|
|
|
tag_name = tag.name
|
|
|
|
|
self.name = tag_name
|
|
|
|
|
cache_key = 'tag_{tag_name}_{page}'.format(
|
|
|
|
|
tag_name=tag_name, page=self.page_number)
|
|
|
|
|
return cache_key
|
|
|
|
|
# 省略缓存key生成、上下文添加方法...
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
# tag_name = self.kwargs['tag_name']
|
|
|
|
|
tag_name = self.name
|
|
|
|
|
kwargs['page_type'] = TagDetailView.page_type
|
|
|
|
|
kwargs['tag_name'] = tag_name
|
|
|
|
|
return super(TagDetailView, self).get_context_data(**kwargs)
|
|
|
|
|
# 文章归档页视图
|
|
|
|
|
|
|
|
|
|
class ArchivesView(ArticleListView):
|
|
|
|
|
'''
|
|
|
|
|
鏂囩珷褰掓。椤甸潰
|
|
|
|
|
'''
|
|
|
|
|
page_type = '鏂囩珷褰掓。'
|
|
|
|
|
paginate_by = None
|
|
|
|
|
# 归档页不分页,显示所有文章
|
|
|
|
|
page_kwarg = None
|
|
|
|
|
template_name = 'blog/article_archives.html'
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
# 筛选Article模型数据:status='p'(仅已发布文章),无分页返回所有数据
|
|
|
|
|
# 归档逻辑依赖Article.create_time字段(文章创建时间),模板中按时间分组展示
|
|
|
|
|
return Article.objects.filter(status='p').all()
|
|
|
|
|
# 省略缓存key生成方法...
|
|
|
|
|
# 以下为不涉及文章/分类/标签模型字段的视图(如链接列表、搜索、文件上传、错误页等),省略注释...
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
cache_key = 'archives'
|
|
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LinkListView(ListView):
|
|
|
|
|
model = Links
|
|
|
|
|
template_name = 'blog/links_list.html'
|
|
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
return Links.objects.filter(is_enable=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class EsSearchView(SearchView):
|
|
|
|
|
# 省略搜索视图逻辑(若涉及文章搜索,实际关联Article.title/body字段,此处代码未直接体现)...
|
|
|
|
|
def get_context(self):
|
|
|
|
|
paginator, page = self.build_page()
|
|
|
|
|
context = {
|
|
|
|
|
"query": self.query,
|
|
|
|
|
"form": self.form,
|
|
|
|
|
"page": page,
|
|
|
|
|
"paginator": paginator,
|
|
|
|
|
"suggestion": None,
|
|
|
|
|
}
|
|
|
|
|
if hasattr(self.results, "query") and self.results.query.backend.include_spelling:
|
|
|
|
|
context["suggestion"] = self.results.query.get_spelling_suggestion()
|
|
|
|
|
context.update(self.extra_context())
|
|
|
|
|
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@csrf_exempt
|
|
|
|
|
def fileupload(request):
|
|
|
|
|
# 省略文件上传逻辑...
|
|
|
|
|
"""
|
|
|
|
|
璇ユ柟娉曢渶鑷<EFBFBD>繁鍐欒皟鐢ㄧ<EFBFBD>鏉ヤ笂浼犲浘鐗囷紝璇ユ柟娉曚粎鎻愪緵鍥惧簥鍔熻兘
|
|
|
|
|
:param request:
|
|
|
|
|
:return:
|
|
|
|
|
"""
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
sign = request.GET.get('sign', None)
|
|
|
|
|
if not sign:
|
|
|
|
|
return HttpResponseForbidden()
|
|
|
|
|
if not sign == get_sha256(get_sha256(settings.SECRET_KEY)):
|
|
|
|
|
return HttpResponseForbidden()
|
|
|
|
|
response = []
|
|
|
|
|
for filename in request.FILES:
|
|
|
|
|
timestr = timezone.now().strftime('%Y/%m/%d')
|
|
|
|
|
imgextensions = ['jpg', 'png', 'jpeg', 'bmp']
|
|
|
|
|
fname = u''.join(str(filename))
|
|
|
|
|
isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0
|
|
|
|
|
base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr)
|
|
|
|
|
if not os.path.exists(base_dir):
|
|
|
|
|
os.makedirs(base_dir)
|
|
|
|
|
savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}"))
|
|
|
|
|
if not savepath.startswith(base_dir):
|
|
|
|
|
return HttpResponse("only for post")
|
|
|
|
|
with open(savepath, 'wb+') as wfile:
|
|
|
|
|
for chunk in request.FILES[filename].chunks():
|
|
|
|
|
wfile.write(chunk)
|
|
|
|
|
if isimage:
|
|
|
|
|
from PIL import Image
|
|
|
|
|
image = Image.open(savepath)
|
|
|
|
|
image.save(savepath, quality=20, optimize=True)
|
|
|
|
|
url = static(savepath)
|
|
|
|
|
response.append(url)
|
|
|
|
|
return HttpResponse(response)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
return HttpResponse("only for post")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def page_not_found_view(
|
|
|
|
|
request,
|
|
|
|
|
exception,
|
|
|
|
|
template_name='blog/error_page.html'):
|
|
|
|
|
if exception:
|
|
|
|
|
logger.error(exception)
|
|
|
|
|
url = request.get_full_path()
|
|
|
|
|
return render(request,
|
|
|
|
|
template_name,
|
|
|
|
|
{'message': _('Sorry, the page you requested is not found, please click the home page to see other?'),
|
|
|
|
|
'statuscode': '404'},
|
|
|
|
|
status=404)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def server_error_view(request, template_name='blog/error_page.html'):
|
|
|
|
|
return render(request,
|
|
|
|
|
template_name,
|
|
|
|
|
{'message': _('Sorry, the server is busy, please click the home page to see other?'),
|
|
|
|
|
'statuscode': '500'},
|
|
|
|
|
status=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def permission_denied_view(
|
|
|
|
|
request,
|
|
|
|
|
exception,
|
|
|
|
|
template_name='blog/error_page.html'):
|
|
|
|
|
if exception:
|
|
|
|
|
logger.error(exception)
|
|
|
|
|
return render(
|
|
|
|
|
request, template_name, {
|
|
|
|
|
'message': _('Sorry, you do not have permission to access this page?'),
|
|
|
|
|
'statuscode': '403'}, status=403)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clean_cache_view(request):
|
|
|
|
|
cache.clear()
|
|
|
|
|
return HttpResponse('ok')
|