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.
tentest/doc/DjangoBlog/blog/views.py

639 lines
18 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):
"""
mk:
文章列表视图基类继承自Django的ListView
Attributes:
template_name (str): 模板文件路径
context_object_name (str): 上下文对象名称
page_type (str): 页面类型标识
paginate_by (int): 每页显示的文章数量
page_kwarg (str): 分页参数名称
link_type (LinkShowType): 链接显示类型
"""
# mk:template_name属性用于指定使用哪个模板进行渲染
template_name = 'blog/article_index.html'
# mk:context_object_name属性用于给上下文变量取名在模板中使用该名字
context_object_name = 'article_list'
# mk:页面类型,分类目录或标签列表等
page_type = ''
paginate_by = settings.PAGINATE_BY
page_kwarg = 'page'
link_type = LinkShowType.L
def get_view_cache_key(self):
"""
mk:
获取视图缓存键
Returns:
str: 缓存键值
"""
return self.request.get['pages']
@property
def page_number(self):
"""
mk:
获取当前页码
Returns:
int: 当前页码
"""
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):
"""
mk:
子类重写.获得queryset的缓存key
Raises:
NotImplementedError: 子类必须实现此方法
"""
raise NotImplementedError()
def get_queryset_data(self):
"""
mk:
子类重写.获取queryset的数据
Raises:
NotImplementedError: 子类必须实现此方法
"""
raise NotImplementedError()
def get_queryset_from_cache(self, cache_key):
"""
mk:
从缓存获取查询结果集
Args:
cache_key (str): 缓存键
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):
"""
mk:
重写默认方法,从缓存获取数据
Returns:
QuerySet: 查询结果集
"""
key = self.get_queryset_cache_key()
value = self.get_queryset_from_cache(key)
return value
def get_context_data(self, **kwargs):
"""
mk:
获取上下文数据
Args:
**kwargs: 额外的关键字参数
Returns:
dict: 上下文字典
"""
kwargs['linktype'] = self.link_type
return super(ArticleListView, self).get_context_data(**kwargs)
class IndexView(ArticleListView):
"""
mk:
首页视图类,显示所有已发布的文章列表
"""
# 友情链接类型
link_type = LinkShowType.I
def get_queryset_data(self):
"""
mk:
获取首页文章数据
Returns:
QuerySet: 已发布文章的查询集
"""
article_list = Article.objects.filter(type='a', status='p')
return article_list
def get_queryset_cache_key(self):
"""
mk:
获取首页缓存键
Returns:
str: 首页缓存键
"""
cache_key = 'index_{page}'.format(page=self.page_number)
return cache_key
class ArticleDetailView(DetailView):
"""
mk:
文章详情页面视图类
Attributes:
template_name (str): 模板文件路径
model: 数据模型类
pk_url_kwarg (str): URL中的主键参数名
context_object_name (str): 上下文对象名称
"""
template_name = 'blog/article_detail.html'
model = Article
pk_url_kwarg = 'article_id'
context_object_name = "article"
def get_context_data(self, **kwargs):
"""
mk:
获取文章详情页上下文数据,包括评论分页、前后文章等信息
Args:
**kwargs: 额外的关键字参数
Returns:
dict: 包含文章详情和相关数据的上下文字典
"""
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
# mk:触发文章详情加载钩子,让插件可以添加额外的上下文数据
from djangoblog.plugin_manage.hook_constants import ARTICLE_DETAIL_LOAD
hooks.run_action(ARTICLE_DETAIL_LOAD, article=article, context=context, request=self.request)
# mk:Action Hook, 通知插件"文章详情已获取"
hooks.run_action('after_article_body_get', article=article, request=self.request)
return context
class CategoryDetailView(ArticleListView):
"""
mk:
分类目录列表视图类,显示指定分类下的文章列表
"""
page_type = "分类目录归档"
def get_queryset_data(self):
"""
mk:
根据分类名称获取该分类及其子分类下的所有文章
Returns:
QuerySet: 指定分类下的文章查询集
"""
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):
"""
mk:
获取分类页面缓存键
Returns:
str: 分类页面缓存键
"""
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):
"""
mk:
获取分类页面上下文数据
Args:
**kwargs: 额外的关键字参数
Returns:
dict: 包含分类信息的上下文字典
"""
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):
"""
mk:
作者详情页视图类,显示指定作者的文章列表
"""
page_type = '作者文章归档'
def get_queryset_cache_key(self):
"""
mk:
获取作者页面缓存键
Returns:
str: 作者页面缓存键
"""
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):
"""
mk:
根据作者用户名获取该作者的所有文章
Returns:
QuerySet: 指定作者的文章查询集
"""
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):
"""
mk:
获取作者页面上下文数据
Args:
**kwargs: 额外的关键字参数
Returns:
dict: 包含作者信息的上下文字典
"""
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):
"""
mk:
标签列表页面视图类,显示指定标签下的文章列表
"""
page_type = '分类标签归档'
def get_queryset_data(self):
"""
mk:
根据标签名称获取该标签下的所有文章
Returns:
QuerySet: 指定标签下的文章查询集
"""
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):
"""
mk:
获取标签页面缓存键
Returns:
str: 标签页面缓存键
"""
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):
"""
mk:
获取标签页面上下文数据
Args:
**kwargs: 额外的关键字参数
Returns:
dict: 包含标签信息的上下文字典
"""
# 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):
"""
mk:
文章归档页面视图类,显示所有文章的时间归档
"""
page_type = '文章归档'
paginate_by = None
page_kwarg = None
template_name = 'blog/article_archives.html'
def get_queryset_data(self):
"""
mk:
获取所有已发布文章的数据
Returns:
QuerySet: 所有已发布文章的查询集
"""
return Article.objects.filter(status='p').all()
def get_queryset_cache_key(self):
"""
mk:
获取归档页面缓存键
Returns:
str: 归档页面缓存键
"""
cache_key = 'archives'
return cache_key
class LinkListView(ListView):
"""
mk:
友情链接列表视图类
Attributes:
model: 链接数据模型
template_name (str): 模板文件路径
"""
model = Links
template_name = 'blog/links_list.html'
def get_queryset(self):
"""
mk:
获取启用的友情链接查询集
Returns:
QuerySet: 启用的友情链接查询集
"""
return Links.objects.filter(is_enable=True)
class EsSearchView(SearchView):
"""
mk:
Elasticsearch搜索视图类继承自haystack的SearchView
"""
def get_context(self):
"""
mk:
获取搜索结果上下文数据
Returns:
dict: 包含搜索结果的上下文字典
"""
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):
"""
mk:
文件上传处理函数,提供图床功能
Args:
request (HttpRequest): HTTP请求对象
Returns:
HttpResponse: 包含上传文件URL的响应对象
"""
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'):
"""
mk:
404页面未找到错误处理函数
Args:
request (HttpRequest): HTTP请求对象
exception (Exception): 异常对象
template_name (str): 错误页面模板名称
Returns:
HttpResponse: 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'):
"""
mk:
500服务器内部错误处理函数
Args:
request (HttpRequest): HTTP请求对象
template_name (str): 错误页面模板名称
Returns:
HttpResponse: 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'):
"""
mk:
403权限拒绝错误处理函数
Args:
request (HttpRequest): HTTP请求对象
exception (Exception): 异常对象
template_name (str): 错误页面模板名称
Returns:
HttpResponse: 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):
"""
mk:
清除缓存视图函数
Args:
request (HttpRequest): HTTP请求对象
Returns:
HttpResponse: 返回'ok'表示清除成功
"""
cache.clear()
return HttpResponse('ok')