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/views.py

418 lines
15 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
# 导入目标模型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):
'''
閲嶅啓榛樿<E6A69B>锛屼粠缂撳瓨鑾峰彇鏁版嵁
: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>敼鏂囩珷姝
article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article,
request=self.request)
return context
class CategoryDetailView(ArticleListView):
'''
鍒嗙被鐩<E8A2AB>綍鍒楄〃
'''
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):
'''
浣滆€呰<E282AC>鎯呴〉
'''
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):
'''
鏍囩<E98F8D>鍒楄〃椤甸潰
'''
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):
# 省略文件上传逻辑...
"""
璇ユ柟娉曢渶鑷<E6B8B6>繁鍐欒皟鐢ㄧ<E990A2>鏉ヤ笂浼犲浘鐗囷紝璇ユ柟娉曚粎鎻愪緵鍥惧簥鍔熻兘
: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')