|
|
|
|
@ -5,6 +5,7 @@ import uuid
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
from django.core.paginator import Paginator
|
|
|
|
|
from django.http import HttpResponse, HttpResponseForbidden
|
|
|
|
|
from django.http import JsonResponse, HttpResponseBadRequest
|
|
|
|
|
from django.shortcuts import get_object_or_404
|
|
|
|
|
from django.shortcuts import render
|
|
|
|
|
from django.templatetags.static import static
|
|
|
|
|
@ -59,6 +60,20 @@ class ArticleListView(ListView):
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
def get_sort(self):
|
|
|
|
|
sort = self.request.GET.get('sort', 'latest')
|
|
|
|
|
return sort if sort in ('latest', 'hot') else 'latest'
|
|
|
|
|
|
|
|
|
|
def order_queryset(self, qs):
|
|
|
|
|
sort = self.get_sort()
|
|
|
|
|
if sort == 'hot':
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
week_ago = timezone.now() - timedelta(days=7)
|
|
|
|
|
qs = qs.filter(pub_time__gte=week_ago)
|
|
|
|
|
return qs.order_by('-article_order', '-views', '-pub_time')
|
|
|
|
|
return qs.order_by('-article_order', '-pub_time')
|
|
|
|
|
|
|
|
|
|
def get_queryset_from_cache(self, cache_key):
|
|
|
|
|
'''
|
|
|
|
|
缓存页面数据
|
|
|
|
|
@ -86,6 +101,7 @@ class ArticleListView(ListView):
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
kwargs['linktype'] = self.link_type
|
|
|
|
|
kwargs['sort'] = self.get_sort()
|
|
|
|
|
return super(ArticleListView, self).get_context_data(**kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -95,17 +111,13 @@ class IndexView(ArticleListView):
|
|
|
|
|
'''
|
|
|
|
|
# 友情链接类型
|
|
|
|
|
link_type = LinkShowType.I
|
|
|
|
|
|
|
|
|
|
# 缓存键常量
|
|
|
|
|
CACHE_KEY_PREFIX = 'blog'
|
|
|
|
|
CACHE_KEY_INDEX = f'{CACHE_KEY_PREFIX}_index_{{page}}'
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
article_list = Article.objects.filter(type=Article.ARTICLE_TYPE, status=Article.STATUS_PUBLISHED)
|
|
|
|
|
return article_list
|
|
|
|
|
qs = Article.objects.filter(type='a', status='p')
|
|
|
|
|
return self.order_queryset(qs)
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
cache_key = self.CACHE_KEY_INDEX.format(page=self.page_number)
|
|
|
|
|
cache_key = 'index_{page}_sort_{sort}'.format(page=self.page_number, sort=self.get_sort())
|
|
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -163,6 +175,14 @@ class ArticleDetailView(DetailView):
|
|
|
|
|
|
|
|
|
|
# Action Hook, 通知插件"文章详情已获取"
|
|
|
|
|
hooks.run_action('after_article_body_get', article=article, request=self.request)
|
|
|
|
|
liked_ids = self.request.session.get('liked_articles', [])
|
|
|
|
|
favorited_ids = self.request.session.get('favorited_articles', [])
|
|
|
|
|
context['has_liked'] = (
|
|
|
|
|
self.request.user.is_authenticated and article.like_users.filter(pk=self.request.user.pk).exists()
|
|
|
|
|
) or (article.id in liked_ids)
|
|
|
|
|
context['has_favorited'] = (
|
|
|
|
|
self.request.user.is_authenticated and article.favorite_users.filter(pk=self.request.user.pk).exists()
|
|
|
|
|
) or (article.id in favorited_ids)
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -171,43 +191,35 @@ class CategoryDetailView(ArticleListView):
|
|
|
|
|
分类目录列表
|
|
|
|
|
'''
|
|
|
|
|
page_type = "分类目录归档"
|
|
|
|
|
|
|
|
|
|
# 缓存键常量
|
|
|
|
|
CACHE_KEY_PREFIX = 'blog'
|
|
|
|
|
CACHE_KEY_CATEGORY_LIST = f'{CACHE_KEY_PREFIX}_category_list_{{categoryname}}_{{page}}'
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
slug = self.kwargs['category_name']
|
|
|
|
|
category = self._get_category_by_slug(slug)
|
|
|
|
|
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=Article.STATUS_PUBLISHED)
|
|
|
|
|
return article_list
|
|
|
|
|
|
|
|
|
|
def _get_category_by_slug(self, slug):
|
|
|
|
|
"""通过slug获取分类对象"""
|
|
|
|
|
return get_object_or_404(Category, slug=slug)
|
|
|
|
|
qs = Article.objects.filter(
|
|
|
|
|
category__name__in=categorynames, status='p')
|
|
|
|
|
return self.order_queryset(qs)
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
slug = self.kwargs['category_name']
|
|
|
|
|
category = self._get_category_by_slug(slug)
|
|
|
|
|
category = get_object_or_404(Category, slug=slug)
|
|
|
|
|
categoryname = category.name
|
|
|
|
|
self.categoryname = categoryname
|
|
|
|
|
cache_key = self.CACHE_KEY_CATEGORY_LIST.format(
|
|
|
|
|
categoryname=categoryname, page=self.page_number)
|
|
|
|
|
cache_key = 'category_list_{categoryname}_{page}_sort_{sort}'.format(
|
|
|
|
|
categoryname=categoryname, page=self.page_number, sort=self.get_sort())
|
|
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
|
|
|
|
|
categoryname = self.categoryname
|
|
|
|
|
try:
|
|
|
|
|
categoryname = categoryname.split('/')[-1]
|
|
|
|
|
except (AttributeError, IndexError) as e:
|
|
|
|
|
logger.warning(f"Error processing category name: {e}")
|
|
|
|
|
categoryname = self.categoryname or 'Unknown'
|
|
|
|
|
except BaseException:
|
|
|
|
|
pass
|
|
|
|
|
kwargs['page_type'] = CategoryDetailView.page_type
|
|
|
|
|
kwargs['tag_name'] = categoryname
|
|
|
|
|
return super(CategoryDetailView, self).get_context_data(**kwargs)
|
|
|
|
|
@ -218,23 +230,19 @@ class AuthorDetailView(ArticleListView):
|
|
|
|
|
作者详情页
|
|
|
|
|
'''
|
|
|
|
|
page_type = '作者文章归档'
|
|
|
|
|
|
|
|
|
|
# 缓存键常量
|
|
|
|
|
CACHE_KEY_PREFIX = 'blog'
|
|
|
|
|
CACHE_KEY_AUTHOR = f'{CACHE_KEY_PREFIX}_author_{{author_name}}_{{page}}'
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
from uuslug import slugify
|
|
|
|
|
author_name = slugify(self.kwargs['author_name'])
|
|
|
|
|
cache_key = self.CACHE_KEY_AUTHOR.format(
|
|
|
|
|
author_name=author_name, page=self.page_number)
|
|
|
|
|
cache_key = 'author_{author_name}_{page}_sort_{sort}'.format(
|
|
|
|
|
author_name=author_name, page=self.page_number, sort=self.get_sort())
|
|
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
author_name = self.kwargs['author_name']
|
|
|
|
|
article_list = Article.objects.filter(
|
|
|
|
|
author__username=author_name, type=Article.ARTICLE_TYPE, status=Article.STATUS_PUBLISHED)
|
|
|
|
|
return article_list
|
|
|
|
|
qs = Article.objects.filter(
|
|
|
|
|
author__username=author_name, type='a', status='p')
|
|
|
|
|
return self.order_queryset(qs)
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
author_name = self.kwargs['author_name']
|
|
|
|
|
@ -248,31 +256,23 @@ class TagDetailView(ArticleListView):
|
|
|
|
|
标签列表页面
|
|
|
|
|
'''
|
|
|
|
|
page_type = '分类标签归档'
|
|
|
|
|
|
|
|
|
|
# 缓存键常量
|
|
|
|
|
CACHE_KEY_PREFIX = 'blog'
|
|
|
|
|
CACHE_KEY_TAG = f'{CACHE_KEY_PREFIX}_tag_{{tag_name}}_{{page}}'
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
slug = self.kwargs['tag_name']
|
|
|
|
|
tag = self._get_tag_by_slug(slug)
|
|
|
|
|
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=Article.ARTICLE_TYPE, status=Article.STATUS_PUBLISHED)
|
|
|
|
|
return article_list
|
|
|
|
|
|
|
|
|
|
def _get_tag_by_slug(self, slug):
|
|
|
|
|
"""通过slug获取标签对象"""
|
|
|
|
|
return get_object_or_404(Tag, slug=slug)
|
|
|
|
|
qs = Article.objects.filter(
|
|
|
|
|
tags__name=tag_name, type='a', status='p')
|
|
|
|
|
return self.order_queryset(qs)
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
slug = self.kwargs['tag_name']
|
|
|
|
|
tag = self._get_tag_by_slug(slug)
|
|
|
|
|
tag = get_object_or_404(Tag, slug=slug)
|
|
|
|
|
tag_name = tag.name
|
|
|
|
|
self.name = tag_name
|
|
|
|
|
cache_key = self.CACHE_KEY_TAG.format(
|
|
|
|
|
tag_name=tag_name, page=self.page_number)
|
|
|
|
|
cache_key = 'tag_{tag_name}_{page}_sort_{sort}'.format(
|
|
|
|
|
tag_name=tag_name, page=self.page_number, sort=self.get_sort())
|
|
|
|
|
return cache_key
|
|
|
|
|
|
|
|
|
|
def get_context_data(self, **kwargs):
|
|
|
|
|
@ -293,7 +293,7 @@ class ArchivesView(ArticleListView):
|
|
|
|
|
template_name = 'blog/article_archives.html'
|
|
|
|
|
|
|
|
|
|
def get_queryset_data(self):
|
|
|
|
|
return Article.objects.filter(status=Article.STATUS_PUBLISHED).all()
|
|
|
|
|
return Article.objects.filter(status='p').all()
|
|
|
|
|
|
|
|
|
|
def get_queryset_cache_key(self):
|
|
|
|
|
cache_key = 'archives'
|
|
|
|
|
@ -333,42 +333,34 @@ def fileupload(request):
|
|
|
|
|
:return:
|
|
|
|
|
"""
|
|
|
|
|
if request.method == 'POST':
|
|
|
|
|
try:
|
|
|
|
|
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:
|
|
|
|
|
try:
|
|
|
|
|
from PIL import Image
|
|
|
|
|
image = Image.open(savepath)
|
|
|
|
|
image.save(savepath, quality=20, optimize=True)
|
|
|
|
|
except ImportError:
|
|
|
|
|
logger.warning("PIL library not available for image optimization")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Image optimization failed: {e}")
|
|
|
|
|
url = static(savepath)
|
|
|
|
|
response.append(url)
|
|
|
|
|
return HttpResponse(response)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"File upload error: {e}")
|
|
|
|
|
return HttpResponse("Upload failed", status=500)
|
|
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
@ -387,6 +379,50 @@ def page_not_found_view(
|
|
|
|
|
status=404)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@csrf_exempt
|
|
|
|
|
def like_article(request, article_id):
|
|
|
|
|
if request.method != 'POST':
|
|
|
|
|
return HttpResponseBadRequest('only post')
|
|
|
|
|
article = get_object_or_404(Article, pk=article_id)
|
|
|
|
|
if request.user.is_authenticated:
|
|
|
|
|
if article.like_users.filter(pk=request.user.pk).exists():
|
|
|
|
|
return JsonResponse({'ok': False, 'message': '已点赞', 'like_count': article.like_count})
|
|
|
|
|
article.like_users.add(request.user)
|
|
|
|
|
article.like_count += 1
|
|
|
|
|
article.save(update_fields=['like_count'])
|
|
|
|
|
return JsonResponse({'ok': True, 'message': '点赞成功', 'like_count': article.like_count})
|
|
|
|
|
liked = request.session.get('liked_articles', [])
|
|
|
|
|
if article.id in liked:
|
|
|
|
|
return JsonResponse({'ok': False, 'message': '已点赞', 'like_count': article.like_count})
|
|
|
|
|
liked.append(article.id)
|
|
|
|
|
request.session['liked_articles'] = liked
|
|
|
|
|
article.like_count += 1
|
|
|
|
|
article.save(update_fields=['like_count'])
|
|
|
|
|
return JsonResponse({'ok': True, 'message': '点赞成功', 'like_count': article.like_count})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@csrf_exempt
|
|
|
|
|
def favorite_article(request, article_id):
|
|
|
|
|
if request.method != 'POST':
|
|
|
|
|
return HttpResponseBadRequest('only post')
|
|
|
|
|
article = get_object_or_404(Article, pk=article_id)
|
|
|
|
|
if request.user.is_authenticated:
|
|
|
|
|
if article.favorite_users.filter(pk=request.user.pk).exists():
|
|
|
|
|
return JsonResponse({'ok': False, 'message': '已收藏', 'favorite_count': article.favorite_count})
|
|
|
|
|
article.favorite_users.add(request.user)
|
|
|
|
|
article.favorite_count += 1
|
|
|
|
|
article.save(update_fields=['favorite_count'])
|
|
|
|
|
return JsonResponse({'ok': True, 'message': '收藏成功', 'favorite_count': article.favorite_count})
|
|
|
|
|
favorited = request.session.get('favorited_articles', [])
|
|
|
|
|
if article.id in favorited:
|
|
|
|
|
return JsonResponse({'ok': False, 'message': '已收藏', 'favorite_count': article.favorite_count})
|
|
|
|
|
favorited.append(article.id)
|
|
|
|
|
request.session['favorited_articles'] = favorited
|
|
|
|
|
article.favorite_count += 1
|
|
|
|
|
article.save(update_fields=['favorite_count'])
|
|
|
|
|
return JsonResponse({'ok': True, 'message': '收藏成功', 'favorite_count': article.favorite_count})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def server_error_view(request, template_name='blog/error_page.html'):
|
|
|
|
|
return render(request,
|
|
|
|
|
template_name,
|
|
|
|
|
|