You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DjangoBlog-Maintenance-Anal.../DjangoBlog/blog/views.py

438 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
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__)
class ArticleListView(ListView):
"""
文章列表基类视图
提供通用的文章列表功能和缓存机制
"""
# template_name属性用于指定使用哪个模板进行渲染
template_name = 'blog/article_index.html'
# context_object_name属性用于给上下文变量取名在模板中使用该名字
context_object_name = 'article_list'
# 页面类型,分类目录或标签列表等
page_type = ''
paginate_by = settings.PAGINATE_BY
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):
"""
获取查询集缓存键
子类必须重写此方法
"""
raise NotImplementedError()
def get_queryset_data(self):
"""
获取查询集数据
子类必须重写此方法
"""
raise NotImplementedError()
def get_queryset_from_cache(self, cache_key):
"""
从缓存获取查询集数据
Args:
cache_key: 缓存键
Returns:
QuerySet: 文章查询集
"""
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):
"""
获取查询集 - 从缓存获取数据
Returns:
QuerySet: 文章查询集
"""
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
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
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):
"""添加上下文数据 - 文章详情和评论信息"""
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, 允许插件修改文章正文
article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article,
request=self.request)
return context
class CategoryDetailView(ArticleListView):
"""
分类目录列表视图
显示指定分类下的所有文章
"""
page_type = "分类目录归档"
def get_queryset_data(self):
"""获取分类文章数据"""
slug = self.kwargs['category_name']
category = get_object_or_404(Category, slug=slug)
categoryname = category.name
self.categoryname = categoryname
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):
"""获取分类页面缓存键"""
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):
"""
作者详情页视图
显示指定作者的所有文章
"""
page_type = '作者文章归档'
def get_queryset_cache_key(self):
"""获取作者页面缓存键"""
from uuslug import slugify
author_name = slugify(self.kwargs['author_name'])
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
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):
"""
标签列表页面视图
显示指定标签下的所有文章
"""
page_type = '分类标签归档'
def get_queryset_data(self):
"""获取标签文章数据"""
slug = self.kwargs['tag_name']
tag = get_object_or_404(Tag, slug=slug)
tag_name = tag.name
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):
"""获取标签页面缓存键"""
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
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):
"""获取归档数据"""
return Article.objects.filter(status='p').all()
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):
"""
Elasticsearch搜索视图
扩展Haystack的搜索功能
"""
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):
"""
文件上传视图
提供图床功能,支持图片和文件上传
Args:
request: HTTP请求对象
Returns:
HttpResponse: 上传结果
"""
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'):
"""
404页面未找到视图
"""
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'):
"""
500服务器错误视图
"""
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'):
"""
403权限拒绝视图
"""
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')