|
|
# 导入Python内置logging模块,用于记录视图运行日志(如缓存命中、错误信息)
|
|
|
import logging
|
|
|
# 导入Python内置os模块,用于文件路径操作(如文件上传时创建目录)
|
|
|
import os
|
|
|
# 导入Python内置uuid模块,用于生成唯一文件名(避免文件上传时重名)
|
|
|
import uuid
|
|
|
|
|
|
# 导入Django项目配置模块,用于获取项目设置(如分页数量、文件上传路径)
|
|
|
from django.conf import settings
|
|
|
# 导入Django分页类,用于处理评论分页逻辑
|
|
|
from django.core.paginator import Paginator
|
|
|
# 导入DjangoHTTP响应类:
|
|
|
# 1. HttpResponse:返回普通响应
|
|
|
# 2. HttpResponseForbidden:返回403禁止访问响应
|
|
|
from django.http import HttpResponse, HttpResponseForbidden
|
|
|
# 导入Django快捷函数:
|
|
|
# 1. get_object_or_404:获取对象,不存在则返回404页面
|
|
|
# 2. render:渲染模板并返回响应
|
|
|
from django.shortcuts import get_object_or_404
|
|
|
from django.shortcuts import render
|
|
|
# 导入Django静态文件模板标签,用于生成静态文件URL(如上传文件的访问URL)
|
|
|
from django.templatetags.static import static
|
|
|
# 导入Django时区工具,用于处理时间相关操作(如文件上传目录按日期划分)
|
|
|
from django.utils import timezone
|
|
|
# 导入Django国际化翻译工具,用于错误信息的多语言支持
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
# 导入Django CSRF豁免装饰器,用于文件上传接口(避免CSRF验证)
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
# 导入Django通用视图:
|
|
|
# 1. DetailView:详情页通用视图(适用于单条数据展示,如文章详情)
|
|
|
# 2. ListView:列表页通用视图(适用于多条数据展示,如文章列表、分类列表)
|
|
|
from django.views.generic.detail import DetailView
|
|
|
from django.views.generic.list import ListView
|
|
|
# 导入Haystack搜索视图,用于实现全文搜索功能
|
|
|
from haystack.views import SearchView
|
|
|
|
|
|
# 从当前应用导入核心模型:文章、分类、链接显示类型、友情链接、标签
|
|
|
from blog.models import Article, Category, LinkShowType, Links, Tag
|
|
|
# 从comments应用导入评论表单,用于文章详情页的评论提交
|
|
|
from comments.forms import CommentForm
|
|
|
# 从自定义工具模块导入工具函数:
|
|
|
# 1. cache:缓存操作对象,用于读写缓存
|
|
|
# 2. get_blog_setting:获取博客全局配置(如评论分页数量)
|
|
|
# 3. get_sha256:生成SHA256加密字符串(用于文件上传签名验证)
|
|
|
from djangoblog.utils import cache, get_blog_setting, get_sha256
|
|
|
|
|
|
# 创建日志记录器,日志名称与当前模块一致(__name__),便于定位日志来源
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
# 定义文章列表基础视图类ArticleListView,继承自Django的ListView(列表页通用视图)
|
|
|
# 作用:封装所有列表类视图(首页、分类列表、标签列表、作者列表)的公共逻辑(分页、缓存、上下文处理)
|
|
|
class ArticleListView(ListView):
|
|
|
# template_name:指定列表页渲染模板(所有子类可复用或重写)
|
|
|
template_name = 'blog/article_index.html'
|
|
|
|
|
|
# context_object_name:指定模板中使用的上下文变量名(模板中通过{{ article_list }}访问列表数据)
|
|
|
context_object_name = 'article_list'
|
|
|
|
|
|
# page_type:页面类型标识(用于模板显示标题,如"分类目录归档"),子类需重写
|
|
|
page_type = ''
|
|
|
# paginate_by:分页数量,从项目配置中读取(settings.PAGINATE_BY)
|
|
|
paginate_by = settings.PAGINATE_BY
|
|
|
# page_kwarg:分页参数名(URL中用于传递页码的参数,默认'page')
|
|
|
page_kwarg = 'page'
|
|
|
# link_type:友情链接显示类型(关联LinkShowType枚举,控制不同页面显示不同链接)
|
|
|
link_type = LinkShowType.L
|
|
|
|
|
|
# 定义获取视图缓存key的方法(当前未实际使用,预留扩展)
|
|
|
def get_view_cache_key(self):
|
|
|
return self.request.get['pages']
|
|
|
|
|
|
# 页码属性:通过@property装饰器将方法转为属性,统一获取当前页码(从URL参数或默认值1)
|
|
|
@property
|
|
|
def page_number(self):
|
|
|
page_kwarg = self.page_kwarg
|
|
|
# 从URL路径参数、GET参数中获取页码,均无则默认1
|
|
|
page = self.kwargs.get(
|
|
|
page_kwarg) or self.request.GET.get(page_kwarg) or 1
|
|
|
return page
|
|
|
|
|
|
# 定义获取查询集缓存key的抽象方法(子类必须重写,确保缓存key唯一)
|
|
|
def get_queryset_cache_key(self):
|
|
|
"""
|
|
|
子类重写.获得queryset的缓存key
|
|
|
"""
|
|
|
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:
|
|
|
# 缓存未命中:调用子类实现的get_queryset_data获取数据库数据
|
|
|
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
|
|
|
|
|
|
# 重写ListView的get_queryset方法:从缓存获取数据(替代默认直接查询数据库)
|
|
|
def get_queryset(self):
|
|
|
'''
|
|
|
重写默认,从缓存获取数据
|
|
|
:return: 文章列表查询集
|
|
|
'''
|
|
|
key = self.get_queryset_cache_key() # 获取子类定义的缓存key
|
|
|
value = self.get_queryset_from_cache(key) # 从缓存获取数据
|
|
|
return value
|
|
|
|
|
|
# 重写ListView的get_context_data方法:添加额外上下文变量(友情链接显示类型)
|
|
|
def get_context_data(self, **kwargs):
|
|
|
kwargs['linktype'] = self.link_type # 传递友情链接显示类型到模板
|
|
|
# 调用父类方法,保留原有上下文数据(如分页数据、文章列表)
|
|
|
return super(ArticleListView, self).get_context_data(** kwargs)
|
|
|
|
|
|
|
|
|
# 定义首页视图类IndexView,继承自ArticleListView(复用列表页公共逻辑)
|
|
|
class IndexView(ArticleListView):
|
|
|
'''
|
|
|
首页视图:展示已发布的普通文章列表
|
|
|
'''
|
|
|
# 重写友情链接显示类型:首页显示(LinkShowType.I)
|
|
|
link_type = LinkShowType.I
|
|
|
|
|
|
# 实现父类抽象方法:获取首页文章数据(查询已发布的普通文章)
|
|
|
def get_queryset_data(self):
|
|
|
# 筛选条件:type='a'(普通文章)、status='p'(已发布)
|
|
|
article_list = Article.objects.filter(type='a', status='p')
|
|
|
return article_list
|
|
|
|
|
|
# 实现父类抽象方法:生成首页缓存key(包含页码,确保不同分页缓存独立)
|
|
|
def get_queryset_cache_key(self):
|
|
|
cache_key = 'index_{page}'.format(page=self.page_number)
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
# 定义文章详情页视图类ArticleDetailView,继承自Django的DetailView(详情页通用视图)
|
|
|
class ArticleDetailView(DetailView):
|
|
|
'''
|
|
|
文章详情页面视图:展示单篇文章详情、评论列表及评论分页
|
|
|
'''
|
|
|
template_name = 'blog/article_detail.html' # 详情页渲染模板
|
|
|
model = Article # 关联的模型(自动从数据库查询文章数据)
|
|
|
pk_url_kwarg = 'article_id' # URL中传递文章ID的参数名(与路由配置一致)
|
|
|
context_object_name = "article" # 模板中使用的上下文变量名({{ article }}访问文章数据)
|
|
|
|
|
|
# 重写DetailView的get_object方法:获取文章对象后更新浏览量
|
|
|
def get_object(self, queryset=None):
|
|
|
# 调用父类方法获取文章对象
|
|
|
obj = super(ArticleDetailView, self).get_object()
|
|
|
obj.viewed() # 调用Article模型的viewed方法,浏览量+1
|
|
|
self.object = obj # 保存文章对象到实例属性
|
|
|
return obj
|
|
|
|
|
|
# 重写DetailView的get_context_data方法:添加评论表单、评论列表、分页等额外上下文
|
|
|
def get_context_data(self, **kwargs):
|
|
|
comment_form = CommentForm() # 初始化评论表单(供用户提交评论)
|
|
|
|
|
|
# 获取当前文章的评论列表(从缓存或数据库,Article模型的comment_list方法已实现缓存)
|
|
|
article_comments = self.object.comment_list()
|
|
|
# 筛选顶级评论(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)
|
|
|
|
|
|
# 从GET参数获取评论页码,默认1(若参数非法则重置为1)
|
|
|
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)
|
|
|
# 计算下一页、上一页页码(无则为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
|
|
|
|
|
|
# 生成下一页评论URL(含锚点#commentlist-container,直接跳转到评论区)
|
|
|
if next_page:
|
|
|
kwargs[
|
|
|
'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container'
|
|
|
# 生成上一页评论URL
|
|
|
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 # 评论总数
|
|
|
|
|
|
# 上一篇、下一篇文章(从缓存获取,Article模型的next_article/prev_article方法已实现缓存)
|
|
|
kwargs['next_article'] = self.object.next_article
|
|
|
kwargs['prev_article'] = self.object.prev_article
|
|
|
|
|
|
# 调用父类方法,保留原有上下文数据(如文章对象)
|
|
|
return super(ArticleDetailView, self).get_context_data(** kwargs)
|
|
|
|
|
|
|
|
|
# 定义分类详情视图类CategoryDetailView,继承自ArticleListView
|
|
|
class CategoryDetailView(ArticleListView):
|
|
|
'''
|
|
|
分类目录列表视图:展示指定分类及子分类下的已发布文章
|
|
|
'''
|
|
|
page_type = "分类目录归档" # 页面类型标识(模板中显示该标题)
|
|
|
|
|
|
# 实现父类抽象方法:获取分类下的文章数据
|
|
|
def get_queryset_data(self):
|
|
|
# 从URL路径参数获取分类slug(与路由配置的<slug:category_name>对应)
|
|
|
slug = self.kwargs['category_name']
|
|
|
# 获取分类对象,不存在则返回404
|
|
|
category = get_object_or_404(Category, slug=slug)
|
|
|
|
|
|
categoryname = category.name # 分类名称
|
|
|
self.categoryname = categoryname # 保存到实例属性,供后续生成缓存key和上下文使用
|
|
|
# 获取当前分类的所有子分类名称(含自身)
|
|
|
categorynames = list(
|
|
|
map(lambda c: c.name, category.get_sub_categorys()))
|
|
|
# 筛选条件:分类名称在子分类列表中、状态为已发布(status='p')
|
|
|
article_list = Article.objects.filter(
|
|
|
category__name__in=categorynames, status='p')
|
|
|
return article_list
|
|
|
|
|
|
# 实现父类抽象方法:生成分类列表缓存key(含分类名称和页码,确保缓存唯一)
|
|
|
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
|
|
|
|
|
|
# 重写父类的get_context_data方法:添加分类相关上下文(页面类型、分类名称)
|
|
|
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)
|
|
|
|
|
|
|
|
|
# 定义作者详情视图类AuthorDetailView,继承自ArticleListView
|
|
|
class AuthorDetailView(ArticleListView):
|
|
|
'''
|
|
|
作者文章列表视图:展示指定作者发布的已发布文章
|
|
|
'''
|
|
|
page_type = '作者文章归档' # 页面类型标识
|
|
|
|
|
|
# 实现父类抽象方法:生成作者文章列表缓存key
|
|
|
def get_queryset_cache_key(self):
|
|
|
from uuslug import slugify # 延迟导入slugify函数,避免循环导入
|
|
|
# 从URL路径参数获取作者名称,转换为slug格式(确保缓存key统一)
|
|
|
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):
|
|
|
# 从URL路径参数获取作者名称
|
|
|
author_name = self.kwargs['author_name']
|
|
|
# 筛选条件:作者用户名匹配、类型为普通文章(type='a')、状态为已发布(status='p')
|
|
|
article_list = Article.objects.filter(
|
|
|
author__username=author_name, type='a', status='p')
|
|
|
return article_list
|
|
|
|
|
|
# 重写父类的get_context_data方法:添加作者相关上下文(页面类型、作者名称)
|
|
|
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)
|
|
|
|
|
|
|
|
|
# 定义标签详情视图类TagDetailView,继承自ArticleListView
|
|
|
class TagDetailView(ArticleListView):
|
|
|
'''
|
|
|
标签列表视图:展示指定标签下的已发布文章
|
|
|
'''
|
|
|
page_type = '分类标签归档' # 页面类型标识
|
|
|
|
|
|
# 实现父类抽象方法:获取标签下的文章数据
|
|
|
def get_queryset_data(self):
|
|
|
# 从URL路径参数获取标签slug
|
|
|
slug = self.kwargs['tag_name']
|
|
|
# 获取标签对象,不存在则返回404
|
|
|
tag = get_object_or_404(Tag, slug=slug)
|
|
|
tag_name = tag.name # 标签名称
|
|
|
self.name = tag_name # 保存到实例属性
|
|
|
# 筛选条件:标签名称匹配、状态为已发布(status='p')
|
|
|
article_list = Article.objects.filter(
|
|
|
tags__name=tag_name, type='a', status='p')
|
|
|
return article_list
|
|
|
|
|
|
# 实现父类抽象方法:生成标签列表缓存key(含标签名称和页码)
|
|
|
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
|
|
|
|
|
|
# 重写父类的get_context_data方法:添加标签相关上下文(页面类型、标签名称)
|
|
|
def get_context_data(self, **kwargs):
|
|
|
tag_name = self.name # 从实例属性获取标签名称
|
|
|
kwargs['page_type'] = TagDetailView.page_type # 页面类型
|
|
|
kwargs['tag_name'] = tag_name # 标签名称(模板中显示)
|
|
|
return super(TagDetailView, self).get_context_data(** kwargs)
|
|
|
|
|
|
|
|
|
# 定义文章归档视图类ArchivesView,继承自ArticleListView
|
|
|
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()
|
|
|
|
|
|
# 实现父类抽象方法:生成归档页缓存key(无页码,因不分页)
|
|
|
def get_queryset_cache_key(self):
|
|
|
cache_key = 'archives'
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
# 定义友情链接列表视图类LinkListView,继承自Django的ListView
|
|
|
class LinkListView(ListView):
|
|
|
model = Links # 关联Links模型
|
|
|
template_name = 'blog/links_list.html' # 友情链接页面模板
|
|
|
|
|
|
# 重写get_queryset方法:仅查询已启用的友情链接
|
|
|
def get_queryset(self):
|
|
|
return Links.objects.filter(is_enable=True)
|
|
|
|
|
|
|
|
|
# 定义搜索视图类EsSearchView,继承自Haystack的SearchView
|
|
|
class EsSearchView(SearchView):
|
|
|
# 重写get_context方法:构建搜索结果页面的上下文数据(分页、搜索关键词、拼写建议)
|
|
|
def get_context(self):
|
|
|
# 构建分页器和当前页数据(Haystack内置方法)
|
|
|
paginator, page = self.build_page()
|
|
|
# 基础上下文数据:搜索关键词、搜索表单、分页数据、拼写建议
|
|
|
context = {
|
|
|
"query": self.query, # 搜索关键词
|
|
|
"form": self.form, # 搜索表单
|
|
|
"page": page, # 当前页搜索结果
|
|
|
"paginator": paginator, # 分页器
|
|
|
"suggestion": None, # 拼写建议(默认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豁免装饰器:关闭CSRF验证(文件上传接口通常由第三方工具调用,无需CSRF令牌)
|
|
|
@csrf_exempt
|
|
|
def fileupload(request):
|
|
|
"""
|
|
|
文件上传接口:提供图床功能,支持图片/文件上传,返回文件访问URL
|
|
|
注意:需通过调用端传递正确签名才能上传,仅允许POST请求
|
|
|
:param request: HTTP请求对象
|
|
|
:return: 上传成功返回文件URL列表,失败返回403/错误信息
|
|
|
"""
|
|
|
# 仅允许POST请求(文件上传需用POST)
|
|
|
if request.method == 'POST':
|
|
|
# 从GET参数获取签名(用于验证上传权限)
|
|
|
sign = request.GET.get('sign', None)
|
|
|
if not sign:
|
|
|
return HttpResponseForbidden() # 无签名,返回403禁止访问
|
|
|
# 验证签名:双重SHA256加密项目SECRET_KEY,与传递的sign比对(防止未授权上传)
|
|
|
if not sign == get_sha256(get_sha256(settings.SECRET_KEY)):
|
|
|
return HttpResponseForbidden() # 签名不匹配,返回403
|
|
|
|
|
|
response = [] # 存储上传成功的文件URL
|
|
|
# 遍历请求中的所有上传文件(支持多文件同时上传)
|
|
|
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
|
|
|
|
|
|
# 定义文件保存根目录:
|
|
|
# - 图片文件保存到 static/files/image/年/月/日
|
|
|
# - 其他文件保存到 static/files/files/年/月/日
|
|
|
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)
|
|
|
# 生成唯一文件名:UUID+原文件后缀(避免重名)
|
|
|
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 # 延迟导入PIL库(图片处理)
|
|
|
image = Image.open(savepath)
|
|
|
image.save(savepath, quality=20, optimize=True) # 质量20,开启优化
|
|
|
|
|
|
# 生成文件访问URL(通过static标签生成静态文件URL)
|
|
|
url = static(savepath)
|
|
|
response.append(url) # 将URL添加到响应列表
|
|
|
|
|
|
# 返回URL列表(字符串格式)
|
|
|
return HttpResponse(response)
|
|
|
|
|
|
else:
|
|
|
# 非POST请求,返回错误信息
|
|
|
return HttpResponse("only for post")
|
|
|
|
|
|
|
|
|
# 404页面未找到视图:处理所有不存在的URL请求
|
|
|
def page_not_found_view(
|
|
|
request,
|
|
|
exception,
|
|
|
template_name='blog/error_page.html'):
|
|
|
if exception:
|
|
|
logger.error(exception) # 记录异常信息到日志
|
|
|
url = request.get_full_path() # 获取用户访问的不存在的URL
|
|
|
# 渲染404错误页面,传递错误信息和状态码
|
|
|
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)
|
|
|
|
|
|
|
|
|
# 500服务器错误视图:处理服务器内部错误
|
|
|
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)
|
|
|
|
|
|
|
|
|
# 403权限拒绝视图:处理无权限访问的请求
|
|
|
def permission_denied_view(
|
|
|
request,
|
|
|
exception,
|
|
|
template_name='blog/error_page.html'):
|
|
|
if exception:
|
|
|
logger.error(exception) # 记录异常信息到日志
|
|
|
# 渲染403错误页面,传递错误信息和状态码
|
|
|
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() # 调用缓存工具的clear方法,清空所有缓存
|
|
|
return HttpResponse('ok') # 返回成功响应 |