djq添加相关推荐以及查看文章阅读量热门度的功能 #50

Merged
phm9gvnzi merged 1 commits from djq_branch into master 3 months ago

@ -1,7 +1,6 @@
import logging
import re
from abc import abstractmethod
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
@ -129,7 +128,6 @@ class Article(BaseModel):
def get_category_tree(self):
tree = self.category.get_category_tree()
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
return names
def save(self, *args, **kwargs):
@ -176,6 +174,42 @@ class Article(BaseModel):
return match.group(1)
return ""
@cache_decorator(60 * 60 * 24) # 缓存24小时
def get_related_articles(self, limit=5):
"""
根据当前文章的标签和分类获取相关推荐文章
:param limit: 推荐文章数量上限
:return: 相关文章列表
"""
# 1. 获取当前文章的所有标签ID
tag_ids = self.tags.values_list('id', flat=True)
# 2. 查询有相同标签的已发布文章(排除当前文章),并去重
related_by_tag = Article.objects.filter(
tags__id__in=tag_ids,
status='p'
).exclude(id=self.id).distinct()
# 3. 如果标签匹配的文章不足,补充同分类的已发布文章
if related_by_tag.count() < limit:
# 计算还需要补充的文章数量
need = limit - related_by_tag.count()
# 查询同分类的文章(排除当前文章和已通过标签匹配的文章)
related_by_category = Article.objects.filter(
category=self.category,
status='p'
).exclude(
id__in=list(related_by_tag.values_list('id', flat=True)) + [self.id]
).order_by('-pub_time')[:need]
# 合并结果(标签匹配的文章在前,分类匹配的在后)
related_articles = list(related_by_tag) + list(related_by_category)
else:
# 标签匹配的文章足够,直接取前 limit 篇
related_articles = list(related_by_tag)[:limit]
return related_articles
class Category(BaseModel):
"""文章分类"""
@ -373,4 +407,4 @@ class BlogSettings(models.Model):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
from djangoblog.utils import cache
cache.clear()
cache.clear()

@ -38,7 +38,7 @@ class ArticleListView(ListView):
link_type = LinkShowType.L
def get_view_cache_key(self):
return self.request.get['pages']
return self.request.GET.get('page', 1)
@property
def page_number(self):
@ -86,6 +86,14 @@ class ArticleListView(ListView):
def get_context_data(self, **kwargs):
kwargs['linktype'] = self.link_type
# 新增:添加热门文章数据(所有列表页共享)
hot_articles_cache_key = 'hot_articles'
hot_articles = cache.get(hot_articles_cache_key)
if not hot_articles:
hot_articles = Article.objects.filter(type='a', status='p').order_by('-views')[:5]
cache.set(hot_articles_cache_key, hot_articles, 60 * 60) # 缓存1小时
logger.info('set hot articles cache')
kwargs['hot_articles'] = hot_articles
return super(ArticleListView, self).get_context_data(** kwargs)
@ -147,14 +155,27 @@ class ArticleDetailView(DetailView):
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
kwargs['next_article'] = self.object.next_article()
kwargs['prev_article'] = self.object.prev_article()
# 新增:获取相关推荐文章
related_articles = self.object.get_related_articles(limit=5)
kwargs['related_articles'] = related_articles
# 新增:添加热门文章数据(详情页也显示)
hot_articles_cache_key = 'hot_articles'
hot_articles = cache.get(hot_articles_cache_key)
if not hot_articles:
hot_articles = Article.objects.filter(type='a', status='p').order_by('-views')[:5]
cache.set(hot_articles_cache_key, hot_articles, 60 * 60)
logger.info('set hot articles cache')
kwargs['hot_articles'] = hot_articles
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, 允许插件修改文章正文
# Filter Hook, 允许插件修改文章正文
article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article,
request=self.request)
@ -189,7 +210,6 @@ class CategoryDetailView(ArticleListView):
return cache_key
def get_context_data(self, **kwargs):
categoryname = self.categoryname
try:
categoryname = categoryname.split('/')[-1]
@ -251,7 +271,6 @@ class TagDetailView(ArticleListView):
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
@ -274,6 +293,17 @@ class ArchivesView(ArticleListView):
cache_key = 'archives'
return cache_key
def get_context_data(self,** kwargs):
# 归档页单独添加热门文章因继承自ArticleListView但需确保显示
hot_articles_cache_key = 'hot_articles'
hot_articles = cache.get(hot_articles_cache_key)
if not hot_articles:
hot_articles = Article.objects.filter(type='a', status='p').order_by('-views')[:5]
cache.set(hot_articles_cache_key, hot_articles, 60 * 60)
logger.info('set hot articles cache')
kwargs['hot_articles'] = hot_articles
return super(ArchivesView, self).get_context_data(**kwargs)
class LinkListView(ListView):
model = Links
@ -282,21 +312,29 @@ class LinkListView(ListView):
def get_queryset(self):
return Links.objects.filter(is_enable=True)
def get_context_data(self,** kwargs):
# 链接页添加热门文章
hot_articles_cache_key = 'hot_articles'
hot_articles = cache.get(hot_articles_cache_key)
if not hot_articles:
hot_articles = Article.objects.filter(type='a', status='p').order_by('-views')[:5]
cache.set(hot_articles_cache_key, hot_articles, 60 * 60)
logger.info('set hot articles cache')
kwargs['hot_articles'] = hot_articles
return super(LinkListView, self).get_context_data(**kwargs)
class EsSearchView(SearchView):
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())
context = super().get_context()
# 搜索页添加热门文章
hot_articles_cache_key = 'hot_articles'
hot_articles = cache.get(hot_articles_cache_key)
if not hot_articles:
hot_articles = Article.objects.filter(type='a', status='p').order_by('-views')[:5]
cache.set(hot_articles_cache_key, hot_articles, 60 * 60)
logger.info('set hot articles cache')
context['hot_articles'] = hot_articles
return context
@ -319,7 +357,7 @@ def fileupload(request):
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)
base_dir = os.path.join(settings.STATICFILES_DIRS[0], "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]}"))
@ -332,7 +370,9 @@ def fileupload(request):
from PIL import Image
image = Image.open(savepath)
image.save(savepath, quality=20, optimize=True)
url = static(savepath)
# 修正静态文件URL路径
relative_path = os.path.relpath(savepath, settings.STATICFILES_DIRS[0])
url = static(relative_path)
response.append(url)
return HttpResponse(response)
@ -403,7 +443,7 @@ class DjangoBlogFeed(Feed):
返回要在订阅中显示的项目列表
这里我们返回最新的 10 篇已发布的文章
"""
return Article.objects.filter(status='p').order_by('-created_time')[:10]
return Article.objects.filter(status='p').order_by('-creation_time')[:10]
def item_title(self, item):
"""
@ -425,11 +465,16 @@ class DjangoBlogFeed(Feed):
"""
返回单个项目文章的绝对链接
"""
return reverse('blog:article_detail', kwargs={'article_id': item.pk})
return reverse('blog:detailbyid', kwargs={
'article_id': item.pk,
'year': item.creation_time.year,
'month': item.creation_time.month,
'day': item.creation_time.day
})
def item_pubdate(self, item):
"""
返回单个项目文章的发布日期
这是可选的但推荐添加以符合 RSS 规范
"""
return item.created_time
return item.creation_time

@ -3,6 +3,7 @@
{% block header %}
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
@ -12,7 +13,6 @@
<nav class="nav-single">
<h3 class="assistive-text">文章导航</h3>
{% if next_article %}
<span class="nav-previous"><a href="{{ next_article.get_absolute_url }}" rel="prev"><span
class="meta-nav">&larr;</span> {{ next_article.title }}</a></span>
{% endif %}
@ -24,10 +24,39 @@
</nav><!-- .nav-single -->
{% endif %}
</div><!-- #content -->
{% if article.comment_status == "o" and OPEN_SITE_COMMENT %}
<!-- ===================== 新增:相关推荐文章区域 ===================== -->
{% if related_articles %}
<section class="related-posts">
<h3 class="related-title">相关推荐</h3>
<ul class="related-posts-list">
{% for rel_article in related_articles %}
<li>
<article class="related-post">
<header>
<h4 class="related-post-title">
<a href="{{ rel_article.get_absolute_url }}" rel="bookmark">
{{ rel_article.title }}
</a>
</h4>
<div class="related-post-meta">
<time datetime="{{ rel_article.pub_time|date:"c" }}">
{{ rel_article.pub_time|date:"Y年m月d日" }}
</time>
<!-- 新增:在相关推荐中显示阅读量 -->
<span class="views"> | 阅读量:{{ rel_article.views }}</span>
</div>
</header>
</article>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
<!-- ===================== 相关推荐区域结束 ===================== -->
</div><!-- #content -->
{% if article.comment_status == "o" and OPEN_SITE_COMMENT %}
{% include 'comments/tags/comment_list.html' %}
{% if user.is_authenticated %}
{% include 'comments/tags/post_comment.html' %}
@ -36,15 +65,12 @@
<h3 class="comment-meta">您还没有登录,请您<a
href="{% url "account:login" %}?next={{ request.get_full_path }}" rel="nofollow">登录</a>后发表评论。
</h3>
{% load oauth_tags %}
{% load_oauth_applications request %}
</div>
{% endif %}
{% endif %}
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}

@ -1,16 +1,12 @@
{% load i18n %}
{% load blog_tags %}
<footer class="entry-meta">
{% trans 'posted in' %}
<a href="{{ article.category.get_absolute_url }}" rel="category tag">{{ article.category.name }}</a>
</a>
{% if article.type == 'a' %}
{% if article.tags.all %}
{% trans 'and tagged' %}
{% for t in article.tags.all %}
<a href="{{ t.get_absolute_url }}" rel="tag">{{ t.name }}</a>
@ -18,42 +14,41 @@
,
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
.{% trans 'by ' %}
. {% trans 'by ' %}
<span class="by-author">
<span class="author vcard">
<a class="url fn n" href="{{ article.author.get_absolute_url }}"
{% blocktranslate %}
{% blocktranslate %}
title="View all articles published by {{ article.author.username }}"
{% endblocktranslate %}
{% endblocktranslate %}
rel="author">
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span itemprop="name" itemprop="publisher">
{{ article.author.username }}
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span itemprop="name" itemprop="publisher">
{{ article.author.username }}
</span>
</span>
</a>
</span>
</span>
</span>
{% trans 'on' %}
<a href="{{ article.get_absolute_url }}"
title="{% datetimeformat article.pub_time %}"
itemprop="datePublished" content="{% datetimeformat article.pub_time %}"
rel="bookmark">
<time class="entry-date updated"
datetime="{{ article.pub_time }}">
{% datetimeformat article.pub_time %}
</time>
</a>
</span>
{% trans 'on' %}
<a href="{{ article.get_absolute_url }}"
title="{% datetimeformat article.pub_time %}"
itemprop="datePublished" content="{% datetimeformat article.pub_time %}"
rel="bookmark">
<time class="entry-date updated"
datetime="{{ article.pub_time }}">
{% datetimeformat article.pub_time %}</time>
{% if user.is_superuser %}
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a>
{% endif %}
<!-- 新增:显示阅读量 -->
<span class="meta-sep"> | </span>
<span class="views">
{% trans 'Views' %}: <span itemprop="interactionCount">{{ article.views }}</span>
</span>
</footer><!-- .entry-meta -->
{% if user.is_superuser %}
<span class="meta-sep"> | </span>
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a>
{% endif %}
</footer><!-- .entry-meta -->

@ -12,125 +12,118 @@
</aside>
{% if extra_sidebars %}
{% for sidebar in extra_sidebars %}
<aside class="widget_text widget widget_custom_html"><p class="widget-title">
{{ sidebar.name }}</p>
<aside class="widget_text widget widget_custom_html"><p class="widget-title">{{ sidebar.name }}</p>
<div class="textwidget custom-html-widget">
{{ sidebar.content|custom_markdown|safe }}
{{ sidebar.content|safe }}
</div>
</aside>
{% endfor %}
{% endif %}
{% if most_read_articles %}
<aside id="views-4" class="widget widget_views"><p class="widget-title">Views</p>
<!-- 热门文章模块优化 -->
{% if most_read_articles %}
<aside id="views-4" class="widget widget_views">
<p class="widget-title">{% trans '热门文章' %}</p>
<ul>
{% for a in most_read_articles %}
<li>
<a href="{{ a.get_absolute_url }}" title="{{ a.title }}">
<a href="{{ a.get_absolute_url }}" title="{{ a.title }}" class="hot-article-link">
{{ a.title }}
</a> - {{ a.views }} views
</a>
<span class="post-views">
<i class="fa fa-eye" aria-hidden="true"></i> {{ a.views }}
</span>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
{% if sidebar_categorys %}
<aside id="su_siloed_terms-2" class="widget widget_su_siloed_terms"><p class="widget-title">{% trans 'category' %}</p>
<aside id="su_siloed_terms-2" class="widget widget_su_siloed_terms"><p class="widget-title">{% trans '分类' %}</p>
<ul>
{% for c in sidebar_categorys %}
<li class="cat-item cat-item-184"><a href={{ c.get_absolute_url }}>{{ c.name }}</a>
</li>
<li class="cat-item"><a href="{{ c.get_absolute_url }}">{{ c.name }}</a></li>
{% endfor %}
</ul>
</aside>
{% endif %}
{% if sidebar_comments and open_site_comment %}
<aside id="ds-recent-comments-4" class="widget ds-widget-recent-comments"><p class="widget-title">{% trans 'recent comments' %}</p>
{% if sidebar_comments and open_site_comment %}
<aside id="ds-recent-comments-4" class="widget ds-widget-recent-comments"><p class="widget-title">{% trans '最新评论' %}</p>
<ul id="recentcomments">
{% for c in sidebar_comments %}
<li class="recentcomments">
<span class="comment-author-link">
{{ c.author.username }}</span>
{% trans 'published on' %}《
<a href="{{ c.article.get_absolute_url }}#comment-{{ c.pk }}">{{ c.article.title }}</a>
<span class="comment-author-link">{{ c.author.username }}</span>
{% trans '发表于' %}《<a href="{{ c.article.get_absolute_url }}#comment-{{ c.pk }}">{{ c.article.title }}</a>
</li>
{% endfor %}
</ul>
</aside>
{% endif %}
{% if recent_articles %}
<aside id="recent-posts-2" class="widget widget_recent_entries"><p class="widget-title">{% trans 'recent articles' %}</p>
<aside id="recent-posts-2" class="widget widget_recent_entries"><p class="widget-title">{% trans '最新文章' %}</p>
<ul>
{% for a in recent_articles %}
<li><a href="{{ a.get_absolute_url }}" title="{{ a.title }}">
{{ a.title }}
</a></li>
{% for a in recent_articles %}
<li><a href="{{ a.get_absolute_url }}" title="{{ a.title }}">{{ a.title }}</a></li>
{% endfor %}
</ul>
</aside>
{% endif %}
{% if sidabar_links %}
<aside id="linkcat-0" class="widget widget_links"><p class="widget-title">{% trans 'bookmark' %}</p>
<aside id="linkcat-0" class="widget widget_links"><p class="widget-title">{% trans '友情链接' %}</p>
<ul class='xoxo blogroll'>
{% for l in sidabar_links %}
<li>
<a href="{{ l.link }}" target="_blank" title="{{ l.name }}">{{ l.name }}</a>
</li>
<li><a href="{{ l.link }}" target="_blank" title="{{ l.name }}">{{ l.name }}</a></li>
{% endfor %}
</ul>
</aside>
{% endif %}
{% if show_google_adsense %}
<aside id="text-2" class="widget widget_text"><p class="widget-title">Google AdSense</p>
<div class="textwidget">
{{ google_adsense_codes|safe }}
</div>
<div class="textwidget">{{ google_adsense_codes|safe }}</div>
</aside>
{% endif %}
{% if sidebar_tags %}
<aside id="tag_cloud-2" class="widget widget_tag_cloud"><p class="widget-title">{% trans 'Tag Cloud' %}</p>
<aside id="tag_cloud-2" class="widget widget_tag_cloud"><p class="widget-title">{% trans '标签云' %}</p>
<div class="tagcloud">
{% for tag,count,size in sidebar_tags %}
<a href="{{ tag.get_absolute_url }}"
class="tag-link-{{ tag.id }} tag-link-position-{{ tag.id }}"
style="font-size: {{ size }}pt;" title="{{ count }}个话题"> {{ tag.name }}
</a>
<a href="{{ tag.get_absolute_url }}" class="tag-link-{{ tag.id }}"
style="font-size: {{ size }}pt;" title="{{ count }}个话题"> {{ tag.name }}</a>
{% endfor %}
</div>
</aside>
{% endif %}
<aside id="text-2" class="widget widget_text"><p class="widget-title">{% trans 'Welcome to star or fork the source code of this site' %}</p>
<div class="textwidget">
<aside id="text-2" class="widget widget_text"><p class="widget-title">{% trans '欢迎Star或Fork源码' %}</p>
<div class="textwidget">
<p><a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://resource.lylinux.net/img.shields.io/github/stars/liangliangyy/djangoblog.svg?style=social&amp;label=Star"
alt="GitHub stars"></a> <a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://resource.lylinux.net/img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&amp;label=Fork"
alt="GitHub forks"></a></p>
src="https://resource.lylinux.net/img.shields.io/github/stars/liangliangyy/djangoblog.svg?style=social&amp;label=Star"
alt="GitHub stars"></a>
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
src="https://resource.lylinux.net/img.shields.io/github/forks/liangliangyy/djangoblog.svg?style=social&amp;label=Fork"
alt="GitHub forks"></a></p>
</div>
</aside>
<aside id="meta-3" class="widget widget_meta"><p class="widget-title">{% trans 'Function' %}</p>
<aside id="meta-3" class="widget widget_meta"><p class="widget-title">{% trans '功能导航' %}</p>
<ul>
<li><a href="/admin/" rel="nofollow">{% trans 'management site' %}</a></li>
<li><a href="/admin/" rel="nofollow">{% trans '管理后台' %}</a></li>
{% if user.is_authenticated %}
<li><a href="{% url "account:logout" %}" rel="nofollow">{% trans 'logout' %}</a>
</li>
<li><a href="{% url "account:logout" %}" rel="nofollow">{% trans '退出登录' %}</a></li>
{% else %}
<li><a href="{% url "account:login" %}" rel="nofollow">{% trans 'login' %}</a></li>
<li><a href="{% url "account:login" %}" rel="nofollow">{% trans '登录' %}</a></li>
{% endif %}
{% if user.is_superuser %}
<li><a href="{% url 'owntracks:show_dates' %}" target="_blank">{% trans 'Track record' %}</a></li>
<li><a href="{% url 'owntracks:show_dates' %}" target="_blank">{% trans '轨迹记录' %}</a></li>
{% endif %}
<li><a href="http://gitbook.lylinux.net" target="_blank" rel="nofollow">GitBook</a></li>
</ul>
</aside>
<div id="rocket" class="show" title="{% trans 'Click me to return to the top' %}"></div>
</div><!-- #secondary -->
<div id="rocket" class="show" title="{% trans '返回顶部' %}"></div>
</div><!-- #secondary -->
Loading…
Cancel
Save