Compare commits

..

No commits in common. 'master' and 'flj_branch' have entirely different histories.

@ -1 +0,0 @@
mysql

@ -4,12 +4,12 @@ WORKDIR /code/djangoblog/
RUN apt-get update && \ RUN apt-get update && \
apt-get install default-libmysqlclient-dev gettext -y && \ apt-get install default-libmysqlclient-dev gettext -y && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
COPY requirements.txt requirements.txt ADD requirements.txt requirements.txt
RUN pip install --upgrade pip && \ RUN pip install --upgrade pip && \
pip install --no-cache-dir -r requirements.txt && \ pip install --no-cache-dir -r requirements.txt && \
pip install --no-cache-dir gunicorn[gevent] && \ pip install --no-cache-dir gunicorn[gevent] && \
pip cache purge pip cache purge
COPY . . ADD . .
RUN chmod +x /code/djangoblog/deploy/entrypoint.sh RUN chmod +x /code/djangoblog/deploy/entrypoint.sh
ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"] ENTRYPOINT ["/code/djangoblog/deploy/entrypoint.sh"]

@ -48,7 +48,7 @@ class AccountTest(TestCase):
article.body = "nicecontentaaa" article.body = "nicecontentaaa"
article.author = user article.author = user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
@ -96,7 +96,7 @@ class AccountTest(TestCase):
article.body = "nicecontentttt" article.body = "nicecontentttt"
article.author = user article.author = user
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()

@ -124,7 +124,8 @@ class LoginView(FormView):
#zxm 表单验证成功后的处理 #zxm 表单验证成功后的处理
def form_valid(self, form): def form_valid(self, form):
# 不再重新创建form直接使用传入的form参数 form = AuthenticationForm(data=self.request.POST, request=self.request)
if form.is_valid(): if form.is_valid():
delete_sidebar_cache() #zxm 删除侧边栏缓存 delete_sidebar_cache() #zxm 删除侧边栏缓存
logger.info(self.redirect_field_name) logger.info(self.redirect_field_name)
@ -150,11 +151,11 @@ class LoginView(FormView):
#xy 账户结果处理函数 #xy 账户结果处理函数
def account_result(request): def account_result(request):
action_type = request.GET.get('type') #zxm 获取类型参数避免与内置type冲突 type = request.GET.get('type') #zxm 获取类型参数
user_id = request.GET.get('id') #zxm 获取用户ID避免与内置id冲突 id = request.GET.get('id') #zxm 获取用户ID
user = get_object_or_404(get_user_model(), id=user_id) #zxm 获取用户对象 user = get_object_or_404(get_user_model(), id=id) #zxm 获取用户对象
logger.info(action_type) logger.info(type)
if user.is_active: #zxm 如果用户已激活 if user.is_active: #zxm 如果用户已激活
return HttpResponseRedirect('/') #zxm 重定向到首页 return HttpResponseRedirect('/') #zxm 重定向到首页
if type and type in ['register', 'validation']: #zxm 处理注册或验证类型 if type and type in ['register', 'validation']: #zxm 处理注册或验证类型

@ -51,10 +51,10 @@ class ArticlelAdmin(admin.ModelAdmin):
'creation_time', 'creation_time',
'views', 'views',
'status', 'status',
'article_type', 'type',
'article_order') 'article_order')
list_display_links = ('id', 'title') list_display_links = ('id', 'title')
list_filter = ('status', 'article_type', 'category') list_filter = ('status', 'type', 'category')
filter_horizontal = ('tags',) filter_horizontal = ('tags',)
exclude = ('creation_time', 'last_modify_time') exclude = ('creation_time', 'last_modify_time')
view_on_site = True view_on_site = True

@ -27,7 +27,7 @@ def seo_processor(requests):
'ARTICLE_SUB_LENGTH': setting.article_sub_length, 'ARTICLE_SUB_LENGTH': setting.article_sub_length,
'nav_category_list': Category.objects.all(), 'nav_category_list': Category.objects.all(),
'nav_pages': Article.objects.filter( 'nav_pages': Article.objects.filter(
article_type='p', type='p',
status='p'), status='p'),
'OPEN_SITE_COMMENT': setting.open_site_comment, 'OPEN_SITE_COMMENT': setting.open_site_comment,
'BEIAN_CODE': setting.beian_code, 'BEIAN_CODE': setting.beian_code,

@ -197,7 +197,7 @@ class ArticleDocumentManager():
pub_time=article.pub_time, pub_time=article.pub_time,
status=article.status, status=article.status,
comment_status=article.comment_status, comment_status=article.comment_status,
type=article.article_type, type=article.type,
views=article.views, views=article.views,
article_order=article.article_order) for article in articles] article_order=article.article_order) for article in articles]

@ -1,90 +0,0 @@
# Generated by Django 5.2.4 on 2025-11-23 18:24
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0006_alter_blogsettings_options'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='article',
name='type',
),
migrations.RemoveField(
model_name='links',
name='last_mod_time',
),
migrations.AddField(
model_name='article',
name='article_type',
field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'),
),
migrations.AddField(
model_name='links',
name='last_modify_time',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
),
migrations.AlterField(
model_name='blogsettings',
name='beian_code',
field=models.CharField(blank=True, default='', max_length=2000, verbose_name='备案号'),
),
migrations.AlterField(
model_name='blogsettings',
name='global_footer',
field=models.TextField(blank=True, default='', verbose_name='公共尾部'),
),
migrations.AlterField(
model_name='blogsettings',
name='global_header',
field=models.TextField(blank=True, default='', verbose_name='公共头部'),
),
migrations.AlterField(
model_name='blogsettings',
name='gongan_beiancode',
field=models.TextField(blank=True, default='', max_length=2000, verbose_name='公安备案号'),
),
migrations.AlterField(
model_name='blogsettings',
name='google_adsense_codes',
field=models.TextField(blank=True, default='', max_length=2000, verbose_name='adsense code'),
),
migrations.CreateModel(
name='Favorite',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('creation_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time')),
('last_modify_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time')),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'verbose_name': 'favorite',
'verbose_name_plural': 'favorite',
'unique_together': {('user', 'article')},
},
),
migrations.CreateModel(
name='Like',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('creation_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time')),
('last_modify_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time')),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'verbose_name': 'like',
'verbose_name_plural': 'like',
'unique_together': {('user', 'article')},
},
),
]

@ -1,18 +0,0 @@
# Generated by Django 5.2.4 on 2025-11-23 19:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0007_remove_article_type_remove_links_last_mod_time_and_more'),
]
operations = [
migrations.AddField(
model_name='article',
name='slug',
field=models.SlugField(blank=True, max_length=200, null=True, verbose_name='slug'),
),
]

@ -3,12 +3,10 @@
import logging import logging
import re import re
from abc import abstractmethod from abc import abstractmethod
from django.utils.functional import cached_property
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.db.models import Count
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -32,17 +30,10 @@ class LinkShowType(models.TextChoices):
#fkc 所有模型的基类,包含通用字段,避免重复代码 #fkc 所有模型的基类,包含通用字段,避免重复代码
# 定义缓存时间常量,避免重复使用字面量
CACHE_10_HOURS = 60 * 60 * 10
# 字段名称常量
CREATION_TIME_LABEL = 'creation time'
MODIFY_TIME_LABEL = 'modify time'
CACHE_100_MINUTES = 60 * 100
class BaseModel(models.Model): class BaseModel(models.Model):
id = models.AutoField(primary_key=True) #fkc 主键,自动递增 id = models.AutoField(primary_key=True) #fkc 主键,自动递增
creation_time = models.DateTimeField(_(CREATION_TIME_LABEL), default=now) #fkc 创建时间 creation_time = models.DateTimeField(_('creation time'), default=now) #fkc 创建时间
last_modify_time = models.DateTimeField(_(MODIFY_TIME_LABEL), default=now) #fkc 最后修改时间 last_modify_time = models.DateTimeField(_('modify time'), default=now) #fkc 最后修改时间
#fkc 重写保存方法自动处理slug字段用于生成友好的URL #fkc 重写保存方法自动处理slug字段用于生成友好的URL
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
@ -95,7 +86,6 @@ class Article(BaseModel):
('p', _('Page')), # 静态页面 ('p', _('Page')), # 静态页面
) )
title = models.CharField(_('title'), max_length=200, unique=True) #cll 文章标题 title = models.CharField(_('title'), max_length=200, unique=True) #cll 文章标题
slug = models.SlugField(_('slug'), max_length=200, blank=True, null=True) #cll URL友好的标识符
body = MDTextField(_('body')) #cll 文章正文支持Markdown格式 body = MDTextField(_('body')) #cll 文章正文支持Markdown格式
pub_time = models.DateTimeField( pub_time = models.DateTimeField(
_('publish time'), blank=False, null=False, default=now) #cll 发布时间 _('publish time'), blank=False, null=False, default=now) #cll 发布时间
@ -109,7 +99,7 @@ class Article(BaseModel):
max_length=1, max_length=1,
choices=COMMENT_STATUS, choices=COMMENT_STATUS,
default='o') #cll 评论状态 default='o') #cll 评论状态
article_type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') #cll 内容类型 type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') #cll 内容类型
views = models.PositiveIntegerField(_('views'), default=0) #cll 浏览次数 views = models.PositiveIntegerField(_('views'), default=0) #cll 浏览次数
author = models.ForeignKey( author = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
@ -144,13 +134,13 @@ class Article(BaseModel):
#cll 获取文章的URL #cll 获取文章的URL
def get_absolute_url(self): def get_absolute_url(self):
if self.article_type == 'a': if self.type == 'a':
return reverse('blog:detail', kwargs={'article_id': self.id, 'slug': self.slug}) return reverse('blog:detail', kwargs={'article_id': self.id, 'slug': self.slug})
elif self.article_type == 'p': elif self.type == 'p':
return reverse('blog:page', kwargs={'article_id': self.id, 'slug': self.slug}) return reverse('blog:page', kwargs={'article_id': self.id, 'slug': self.slug})
#cll 获取分类树缓存10小时 #cll 获取分类树缓存10小时
@cache_decorator(CACHE_10_HOURS) # 缓存10小时 @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_category_tree(self): def get_category_tree(self):
category = self.category category = self.category
names = [category.name] names = [category.name]
@ -159,18 +149,9 @@ class Article(BaseModel):
names.append(category.name) names.append(category.name)
return names return names
#cll 保存文章,更新修改时间并自动生成slug #cll 保存文章,更新修改时间
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.last_modify_time = now() self.last_modify_time = now()
# 自动生成slug
if not self.slug:
self.slug = slugify(self.title)
# 确保slug唯一性
original_slug = self.slug
counter = 1
while Article.objects.filter(slug=self.slug).exclude(id=self.id).exists():
self.slug = f"{original_slug}-{counter}"
counter += 1
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
#cll 增加文章浏览次数 #cll 增加文章浏览次数
@ -189,12 +170,12 @@ class Article(BaseModel):
return reverse('admin:%s_%s_change' % info, args=(self.id,)) return reverse('admin:%s_%s_change' % info, args=(self.id,))
#cll 获取下一篇文章缓存100分钟 #cll 获取下一篇文章缓存100分钟
@cache_decorator(expiration=CACHE_100_MINUTES) # 缓存100分钟 @cache_decorator(expiration=60 * 100) # 缓存100分钟
def next_article(self): def next_article(self):
return Article.objects.filter(id__gt=self.id, status='p').order_by('id').first() return Article.objects.filter(id__gt=self.id, status='p').order_by('id').first()
#cll 获取上一篇文章缓存100分钟 #cll 获取上一篇文章缓存100分钟
@cache_decorator(expiration=CACHE_100_MINUTES) # 缓存100分钟 @cache_decorator(expiration=60 * 100) # 缓存100分钟
def prev_article(self): def prev_article(self):
return Article.objects.filter(id__lt=self.id, status='p').order_by('-id').first() return Article.objects.filter(id__lt=self.id, status='p').order_by('-id').first()
@ -206,14 +187,6 @@ class Article(BaseModel):
return result.group(1) return result.group(1)
else: else:
return None return None
@cached_property
def likes_count(self):
return self.like_set.count()
@cached_property
def favorites_count(self):
return self.favorite_set.count()
#xy 分类模型 #xy 分类模型
@ -242,7 +215,7 @@ class Category(BaseModel):
return self.name return self.name
#xy 获取分类树缓存10小时 #xy 获取分类树缓存10小时
@cache_decorator(CACHE_10_HOURS) # 缓存10小时 @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_category_tree(self): def get_category_tree(self):
names = [self.name] names = [self.name]
category = self.parent_category category = self.parent_category
@ -252,7 +225,7 @@ class Category(BaseModel):
return names return names
#xy 获取子分类列表缓存10小时 #xy 获取子分类列表缓存10小时
@cache_decorator(CACHE_10_HOURS) # 缓存10小时 @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_sub_categorys(self): def get_sub_categorys(self):
categories = [] categories = []
all_categories = Category.objects.all() all_categories = Category.objects.all()
@ -281,7 +254,7 @@ class Tag(BaseModel):
return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
#zhj 获取标签下的文章数量缓存10小时 #zhj 获取标签下的文章数量缓存10小时
@cache_decorator(CACHE_10_HOURS) # 缓存10小时 @cache_decorator(60 * 60 * 10) # 缓存10小时
def get_article_count(self): def get_article_count(self):
return Article.objects.filter(tags=self).count() return Article.objects.filter(tags=self).count()
@ -291,9 +264,6 @@ class Tag(BaseModel):
verbose_name_plural = verbose_name #zhj 复数形式 verbose_name_plural = verbose_name #zhj 复数形式
# 文章点赞和收藏模型将在文件末尾使用继承BaseModel的版本定义
#flj 友情链接模型 #flj 友情链接模型
class Links(models.Model): class Links(models.Model):
name = models.CharField(_('link name'), max_length=30, unique=True) #flj 链接名称 name = models.CharField(_('link name'), max_length=30, unique=True) #flj 链接名称
@ -306,8 +276,8 @@ class Links(models.Model):
max_length=1, max_length=1,
choices=LinkShowType.choices, choices=LinkShowType.choices,
default=LinkShowType.I) #flj 显示类型 default=LinkShowType.I) #flj 显示类型
creation_time = models.DateTimeField(_(CREATION_TIME_LABEL), default=now) #flj 创建时间 creation_time = models.DateTimeField(_('creation time'), default=now) #flj 创建时间
last_modify_time = models.DateTimeField(_(MODIFY_TIME_LABEL), default=now) #flj 修改时间,与基类保持一致 last_mod_time = models.DateTimeField(_('modify time'), default=now) #flj 修改时间
class Meta: class Meta:
ordering = ['sequence'] #flj 按序号排序 ordering = ['sequence'] #flj 按序号排序
@ -325,8 +295,8 @@ class SideBar(models.Model):
content = models.TextField(_('content')) #zxm 侧边栏内容支持HTML content = models.TextField(_('content')) #zxm 侧边栏内容支持HTML
sequence = models.IntegerField(_('order'), unique=True) #zxm 排序序号 sequence = models.IntegerField(_('order'), unique=True) #zxm 排序序号
is_enable = models.BooleanField(_('is enable'), default=True) #zxm 是否启用 is_enable = models.BooleanField(_('is enable'), default=True) #zxm 是否启用
creation_time = models.DateTimeField(_(CREATION_TIME_LABEL), default=now) #zxm 创建时间 creation_time = models.DateTimeField(_('creation time'), default=now) #zxm 创建时间
last_mod_time = models.DateTimeField(_(MODIFY_TIME_LABEL), default=now) #zxm 修改时间 last_mod_time = models.DateTimeField(_('modify time'), default=now) #zxm 修改时间
class Meta: class Meta:
ordering = ['sequence'] #zxm 按序号排序 ordering = ['sequence'] #zxm 按序号排序
@ -339,45 +309,6 @@ class SideBar(models.Model):
#fkc 网站设置模型 #fkc 网站设置模型
# 点赞模型
class Like(BaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('user'),
on_delete=models.CASCADE) # 关联用户
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE) # 关联文章
class Meta:
unique_together = ('user', 'article') # 确保一个用户只能对一篇文章点赞一次
verbose_name = _('like')
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.user.username} likes {self.article.title}"
# 收藏模型
class Favorite(BaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_('user'),
on_delete=models.CASCADE) # 关联用户
article = models.ForeignKey(
Article,
verbose_name=_('article'),
on_delete=models.CASCADE) # 关联文章
class Meta:
unique_together = ('user', 'article') # 确保一个用户只能对一篇文章收藏一次
verbose_name = _('favorite')
verbose_name_plural = verbose_name
def __str__(self):
return f"{self.user.username} favorites {self.article.title}"
class BlogSettings(models.Model): class BlogSettings(models.Model):
site_name = models.CharField( site_name = models.CharField(
_('site name'), _('site name'),
@ -407,18 +338,19 @@ class BlogSettings(models.Model):
show_google_adsense = models.BooleanField(_('show adsense'), default=False) #fkc 是否显示广告 show_google_adsense = models.BooleanField(_('show adsense'), default=False) #fkc 是否显示广告
google_adsense_codes = models.TextField( google_adsense_codes = models.TextField(
_('adsense code'), max_length=2000, blank=True, default='') #fkc 广告代码 _('adsense code'), max_length=2000, null=True, blank=True, default='') #fkc 广告代码
open_site_comment = models.BooleanField(_('open site comment'), default=True) #fkc 是否开放评论 open_site_comment = models.BooleanField(_('open site comment'), default=True) #fkc 是否开放评论
comment_need_review = models.BooleanField( comment_need_review = models.BooleanField(
'评论是否需要审核', default=False, null=False) #fkc 评论是否需要审核 '评论是否需要审核', default=False, null=False) #fkc 评论是否需要审核
global_header = models.TextField("公共头部", blank=True, default='') #fkc 公共头部HTML global_header = models.TextField("公共头部", null=True, blank=True, default='') #fkc 公共头部HTML
global_footer = models.TextField("公共尾部", blank=True, default='') #fkc 公共尾部HTML global_footer = models.TextField("公共尾部", null=True, blank=True, default='') #fkc 公共尾部HTML
beian_code = models.CharField( beian_code = models.CharField(
'备案号', '备案号',
max_length=2000, max_length=2000,
null=True,
blank=True, blank=True,
default='') #fkc ICP备案号 default='') #fkc ICP备案号
show_gongan_code = models.BooleanField( show_gongan_code = models.BooleanField(
@ -426,6 +358,7 @@ class BlogSettings(models.Model):
gongan_beiancode = models.TextField( gongan_beiancode = models.TextField(
'公安备案号', '公安备案号',
max_length=2000, max_length=2000,
null=True,
blank=True, blank=True,
default='') #fkc 公安备案号 default='') #fkc 公安备案号

@ -1,64 +0,0 @@
{% extends "blog/base.html" %}
{% load staticfiles %}
{% load custom_markdown %}
{% load likes_tags %}
{% load static %}
{% block title %}{{ title }} - {{ site_name }}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="well well-lg">
<h2 class="text-center">我的收藏</h2>
{% if article_list %}
<div class="article-list">
{% for article in article_list %}
<div class="article">
<div class="title">
<h3><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h3>
</div>
<div class="meta">
<span class="author"><a href="{% url 'blog:author' article.author.username %}">{{ article.author.username }}</a></span>
<span class="views">{{ article.views }} 阅读</span>
<span class="likes">{{ article.total_likes }} 点赞</span>
<span class="favorites">{{ article.total_favorites }} 收藏</span>
<span class="time">{{ article.created_time }}</span>
</div>
<div class="entry-content">
<p>{{ article.body | custom_markdown | truncatechars:200 }}</p>
</div>
<div class="article-footer">
<a href="{{ article.get_absolute_url }}" class="btn btn-primary btn-sm">阅读全文</a>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="alert alert-info text-center">
<p>您还没有收藏任何文章</p>
</div>
{% endif %}
{% if article_list %}
<div class="pagination">
{% if article_list.has_previous %}
<a href="?page={{ article_list.previous_page_number }}" class="page-link">上一页</a>
{% endif %}
<span class="current-page">{{ article_list.number }} / {{ article_list.paginator.num_pages }}</span>
{% if article_list.has_next %}
<a href="?page={{ article_list.next_page_number }}" class="page-link">下一页</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
{% include "blog/right_sidebar.html" %}
</div>
</div>
</div>
{% endblock %}

@ -59,7 +59,7 @@ class ArticleTest(TestCase):
article.body = "nicecontent" article.body = "nicecontent"
article.author = user article.author = user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
@ -74,7 +74,7 @@ class ArticleTest(TestCase):
article.body = "nicetitle" + str(i) article.body = "nicetitle" + str(i)
article.author = user article.author = user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
article.tags.add(tag) article.tags.add(tag)

@ -73,18 +73,4 @@ urlpatterns = [
r'clean', r'clean',
views.clean_cache_view, views.clean_cache_view,
name='clean'), # 清除缓存接口 name='clean'), # 清除缓存接口
# 点赞和收藏功能
path(
r'article/like/',
views.like_article,
name='like_article'), # 文章点赞接口
path(
r'article/favorite/',
views.favorite_article,
name='favorite_article'), # 文章收藏接口
# 我的收藏页面
path(
r'user/favorites/',
views.user_favorites,
name='user_favorites'), # 我的收藏页面
] ]

@ -1,13 +1,9 @@
#flj 博客视图文件,处理博客相关的页面请求 #flj 博客视图文件,处理博客相关的页面请求
import json
import logging import logging
import os import os
import uuid import uuid
from django.conf import settings from django.conf import settings
# 模板常量定义
ERROR_PAGE_TEMPLATE = 'blog/error_page.html'
from django.core.paginator import Paginator # 用于分页 from django.core.paginator import Paginator # 用于分页
from django.http import HttpResponse, HttpResponseForbidden from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
@ -20,7 +16,7 @@ from django.views.generic.detail import DetailView # 详情页视图基类
from django.views.generic.list import ListView # 列表页视图基类 from django.views.generic.list import ListView # 列表页视图基类
from haystack.views import SearchView # 搜索视图 from haystack.views import SearchView # 搜索视图
from blog.models import Article, Category, LinkShowType, Links, Tag, Like, Favorite from blog.models import Article, Category, LinkShowType, Links, Tag
from comments.forms import CommentForm from comments.forms import CommentForm
from djangoblog.plugin_manage import hooks # 插件管理 from djangoblog.plugin_manage import hooks # 插件管理
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
@ -93,7 +89,7 @@ class IndexView(ArticleListView):
#flj 获取首页文章数据,过滤条件为类型为文章(a)且状态为已发布(p) #flj 获取首页文章数据,过滤条件为类型为文章(a)且状态为已发布(p)
def get_queryset_data(self): def get_queryset_data(self):
# 获取所有已发布的文章 # 获取所有已发布的文章
article_list = Article.objects.filter(article_type='a', status='p') article_list = Article.objects.filter(type='a', status='p')
return article_list return article_list
#flj 生成首页的缓存键,包含页码信息 #flj 生成首页的缓存键,包含页码信息
@ -116,38 +112,11 @@ class ArticleDetailView(DetailView):
#flj 获取文章详情页的上下文数据,包括评论表单、相关文章等 #flj 获取文章详情页的上下文数据,包括评论表单、相关文章等
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
#flj 调用父类方法获取基础上下文数据 #flj 调用父类方法获取基础上下文数据
kwargs = super(ArticleDetailView, self).get_context_data(**kwargs)
article = self.object
article.viewed() # 增加浏览次数
#flj 添加评论表单 #flj 添加评论表单
comments = article.comment_list() #flj 获取文章评论列表
comment_form = CommentForm() #flj 添加相关文章
#flj 添加点赞和收藏信息
is_liked = False
is_favorited = False
like_count = article.like_set.count()
favorite_count = article.favorite_set.count()
if self.request.user.is_authenticated:
is_liked = Like.objects.filter(user=self.request.user, article=article).exists()
is_favorited = Favorite.objects.filter(user=self.request.user, article=article).exists()
#flj 添加到上下文
kwargs['comments'] = comments
kwargs['comment_form'] = comment_form
kwargs['like_count'] = like_count
kwargs['is_liked'] = is_liked
kwargs['favorite_count'] = favorite_count
kwargs['is_favorited'] = is_favorited
#flj 调用插件处理文章内容 #flj 调用插件处理文章内容
article_content = article.body_to_string() return super().get_context_data(**kwargs)
article_content = hooks.call_hook(ARTICLE_CONTENT_HOOK_NAME, request=self.request, content=article_content)
article.body = article_content
return kwargs
#flj 分类详情视图,显示指定分类下的文章列表 #flj 分类详情视图,显示指定分类下的文章列表
@ -159,7 +128,6 @@ class CategoryDetailView(ArticleListView):
#flj 获取分类下的文章数据根据URL参数中的分类ID过滤 #flj 获取分类下的文章数据根据URL参数中的分类ID过滤
def get_queryset_data(self): def get_queryset_data(self):
# 使用局部变量避免未使用参数警告
#flj 获取分类ID #flj 获取分类ID
#flj 过滤该分类下的已发布文章 #flj 过滤该分类下的已发布文章
#flj 返回查询结果 #flj 返回查询结果
@ -191,7 +159,6 @@ class AuthorDetailView(ArticleListView):
#flj 获取作者的文章数据根据URL参数中的作者ID过滤 #flj 获取作者的文章数据根据URL参数中的作者ID过滤
def get_queryset_data(self): def get_queryset_data(self):
# 使用局部变量避免未使用参数警告
#flj 获取作者ID #flj 获取作者ID
#flj 过滤该作者的已发布文章 #flj 过滤该作者的已发布文章
return article_list return article_list
@ -212,7 +179,6 @@ class TagDetailView(ArticleListView):
#flj 获取标签下的文章数据根据URL参数中的标签ID过滤 #flj 获取标签下的文章数据根据URL参数中的标签ID过滤
def get_queryset_data(self): def get_queryset_data(self):
# 使用局部变量避免未使用参数警告
#flj 获取标签ID #flj 获取标签ID
#flj 过滤该标签下的已发布文章 #flj 过滤该标签下的已发布文章
return article_list return article_list
@ -294,23 +260,22 @@ def fileupload(request):
#flj 处理页面未找到的情况 #flj 处理页面未找到的情况
def page_not_found_view( def page_not_found_view(
request, request,
template_name=ERROR_PAGE_TEMPLATE): exception,
template_name='blog/error_page.html'):
''' '''
404错误页面 404错误页面
''' '''
#flj 渲染错误页面 #flj 渲染错误页面
context = get_blog_setting()
return render(request, template_name, context, status=404) return render(request, template_name, context, status=404)
#flj 500错误页面视图 #flj 500错误页面视图
#flj 处理服务器内部错误的情况 #flj 处理服务器内部错误的情况
def server_error_view(request, template_name=ERROR_PAGE_TEMPLATE): def server_error_view(request, template_name='blog/error_page.html'):
''' '''
500错误页面 500错误页面
''' '''
#flj 渲染错误页面 #flj 渲染错误页面
context = get_blog_setting()
return render(request, template_name, context, status=500) return render(request, template_name, context, status=500)
@ -318,12 +283,12 @@ def server_error_view(request, template_name=ERROR_PAGE_TEMPLATE):
#flj 处理权限拒绝的情况 #flj 处理权限拒绝的情况
def permission_denied_view( def permission_denied_view(
request, request,
template_name=ERROR_PAGE_TEMPLATE): exception,
template_name='blog/error_page.html'):
''' '''
403错误页面 403错误页面
''' '''
#flj 渲染错误页面 #flj 渲染错误页面
context = get_blog_setting()
return render(request, template_name, context, status=403) return render(request, template_name, context, status=403)
@ -337,113 +302,3 @@ def clean_cache_view(request):
#flj 清理缓存 #flj 清理缓存
#flj 返回成功信息 #flj 返回成功信息
return HttpResponse(_('清理缓存成功')) return HttpResponse(_('清理缓存成功'))
# 点赞视图
@csrf_exempt
def like_article(request):
'''
处理文章点赞
'''
if request.method == 'POST' and request.user.is_authenticated:
try:
data = json.loads(request.body)
article_id = data.get('article_id')
article = Article.objects.get(id=article_id)
like, created = Like.objects.get_or_create(user=request.user, article=article)
if not created:
# 如果已经点赞,则取消点赞
like.delete()
# 获取最新的点赞数
count = article.like_set.count()
return HttpResponse(json.dumps({
'status': 'success',
'count': count
}), content_type="application/json")
except Article.DoesNotExist:
return HttpResponse(json.dumps({
'status': 'error',
'message': _('文章不存在')
}), content_type="application/json")
except Exception as e:
return HttpResponse(json.dumps({
'status': 'error',
'message': str(e)
}), content_type="application/json")
return HttpResponse(json.dumps({
'status': 'error',
'message': _('需要登录才能点赞')
}), content_type="application/json")
# 收藏视图
@csrf_exempt
def favorite_article(request):
'''
处理文章收藏
'''
if request.method == 'POST' and request.user.is_authenticated:
try:
data = json.loads(request.body)
article_id = data.get('article_id')
article = Article.objects.get(id=article_id)
favorite, created = Favorite.objects.get_or_create(user=request.user, article=article)
if not created:
# 如果已经收藏,则取消收藏
favorite.delete()
# 获取最新的收藏数
count = article.favorite_set.count()
return HttpResponse(json.dumps({
'status': 'success',
'count': count
}), content_type="application/json")
except Article.DoesNotExist:
return HttpResponse(json.dumps({
'status': 'error',
'message': _('文章不存在')
}), content_type="application/json")
except Exception as e:
return HttpResponse(json.dumps({
'status': 'error',
'message': str(e)
}), content_type="application/json")
return HttpResponse(json.dumps({
'status': 'error',
'message': _('需要登录才能收藏')
}), content_type="application/json")
# 我的收藏页面视图
def user_favorites(request):
'''
显示用户收藏的文章列表
'''
if not request.user.is_authenticated:
return render(request, ERROR_PAGE_TEMPLATE, {'message': _('请先登录')}, status=403)
# 获取用户收藏的文章
favorites = Favorite.objects.filter(user=request.user).order_by('-created_at')
# 提取文章并进行分页
articles = [fav.article for fav in favorites if fav.article.status == 'p']
paginator = Paginator(articles, settings.PAGINATE_BY)
page = request.GET.get('page', 1)
try:
article_list = paginator.page(page)
except Exception:
article_list = paginator.page(1)
context = {
'article_list': article_list,
'title': _('我的收藏'),
'page_type': '我的收藏',
'has_favorites': len(articles) > 0
}
return render(request, 'blog/user_favorites.html', context)

@ -42,7 +42,7 @@ class CommentsTest(TransactionTestCase):
article.body = "nicecontentccc" article.body = "nicecontentccc"
article.author = self.user article.author = self.user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()

@ -91,7 +91,7 @@ class ElasticSearchBackend(BaseSearchBackend):
search = ArticleDocument.search() \ search = ArticleDocument.search() \
.query('bool', filter=[q]) \ .query('bool', filter=[q]) \
.filter('term', status='p') \ .filter('term', status='p') \
.filter('term', article_type='a') \ .filter('term', type='a') \
.source(False)[start_offset: end_offset] .source(False)[start_offset: end_offset]
results = search.execute() results = search.execute()

@ -21,7 +21,7 @@ class DjangoBlogFeed(Feed):
return get_user_model().objects.first().get_absolute_url() return get_user_model().objects.first().get_absolute_url()
def items(self): def items(self):
return Article.objects.filter(article_type='a', status='p').order_by('-pub_time')[:5] return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5]
def item_title(self, item): def item_title(self, item):
return item.title return item.title

@ -159,7 +159,7 @@ USE_TZ = False # 不使用UTC时间
# 静态文件配置 # 静态文件配置
STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') # 静态文件收集目录 STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') # 静态文件收集目录
STATIC_URL = '/static/' # 静态文件URL前缀 STATIC_URL = '/static/' # 静态文件URL前缀
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] # 静态文件源目录 STATICFILES = os.path.join(BASE_DIR, 'static') # 静态文件源目录
# 搜索配置 # 搜索配置
HAYSTACK_CONNECTIONS = { HAYSTACK_CONNECTIONS = {
@ -303,11 +303,8 @@ STATICFILES_FINDERS = (
# other # other
'compressor.finders.CompressorFinder', 'compressor.finders.CompressorFinder',
) )
# 在开发环境中动态启用压缩根据DEBUG状态自动调整 COMPRESS_ENABLED = True
COMPRESS_ENABLED = not DEBUG # 调试模式下禁用压缩 # COMPRESS_OFFLINE = True
COMPRESS_OFFLINE = False # 禁用离线模式
COMPRESS_ROOT = os.path.join(BASE_DIR, 'static') # 直接指向静态文件源目录
COMPRESS_URL = STATIC_URL # 确保COMPRESS_URL与STATIC_URL一致
COMPRESS_CSS_FILTERS = [ COMPRESS_CSS_FILTERS = [

@ -37,7 +37,7 @@ class ServerManagerTest(TestCase):
article.body = "nicecontentccc" article.body = "nicecontentccc"
article.author = user article.author = user
article.category = c article.category = c
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
s = TextMessage([]) s = TextMessage([])

@ -48,7 +48,7 @@ class AccountTest(TestCase):
article.body = "nicecontentaaa" article.body = "nicecontentaaa"
article.author = user article.author = user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
@ -96,7 +96,7 @@ class AccountTest(TestCase):
article.body = "nicecontentttt" article.body = "nicecontentttt"
article.author = user article.author = user
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()

@ -197,7 +197,7 @@ class ArticleDocumentManager():
pub_time=article.pub_time, pub_time=article.pub_time,
status=article.status, status=article.status,
comment_status=article.comment_status, comment_status=article.comment_status,
type=article.article_type, type=article.type,
views=article.views, views=article.views,
article_order=article.article_order) for article in articles] article_order=article.article_order) for article in articles]

@ -86,7 +86,7 @@ class Article(BaseModel):
max_length=1, max_length=1,
choices=COMMENT_STATUS, choices=COMMENT_STATUS,
default='o') default='o')
article_type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a')
views = models.PositiveIntegerField(_('views'), default=0) views = models.PositiveIntegerField(_('views'), default=0)
author = models.ForeignKey( author = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,

@ -59,7 +59,7 @@ class ArticleTest(TestCase):
article.body = "nicecontent" article.body = "nicecontent"
article.author = user article.author = user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
@ -74,7 +74,7 @@ class ArticleTest(TestCase):
article.body = "nicetitle" + str(i) article.body = "nicetitle" + str(i)
article.author = user article.author = user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
article.tags.add(tag) article.tags.add(tag)

@ -97,7 +97,7 @@ class IndexView(ArticleListView):
link_type = LinkShowType.I link_type = LinkShowType.I
def get_queryset_data(self): def get_queryset_data(self):
article_list = Article.objects.filter(article_type='a', status='p') article_list = Article.objects.filter(type='a', status='p')
return article_list return article_list
def get_queryset_cache_key(self): def get_queryset_cache_key(self):
@ -216,7 +216,7 @@ class AuthorDetailView(ArticleListView):
def get_queryset_data(self): def get_queryset_data(self):
author_name = self.kwargs['author_name'] author_name = self.kwargs['author_name']
article_list = Article.objects.filter( article_list = Article.objects.filter(
author__username=author_name, article_type='a', status='p') author__username=author_name, type='a', status='p')
return article_list return article_list
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -238,7 +238,7 @@ class TagDetailView(ArticleListView):
tag_name = tag.name tag_name = tag.name
self.name = tag_name self.name = tag_name
article_list = Article.objects.filter( article_list = Article.objects.filter(
tags__name=tag_name, article_type='a', status='p') tags__name=tag_name, type='a', status='p')
return article_list return article_list
def get_queryset_cache_key(self): def get_queryset_cache_key(self):

@ -42,7 +42,7 @@ class CommentsTest(TransactionTestCase):
article.body = "nicecontentccc" article.body = "nicecontentccc"
article.author = self.user article.author = self.user
article.category = category article.category = category
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()

@ -91,7 +91,7 @@ class ElasticSearchBackend(BaseSearchBackend):
search = ArticleDocument.search() \ search = ArticleDocument.search() \
.query('bool', filter=[q]) \ .query('bool', filter=[q]) \
.filter('term', status='p') \ .filter('term', status='p') \
.filter('term', article_type='a') \ .filter('term', type='a') \
.source(False)[start_offset: end_offset] .source(False)[start_offset: end_offset]
results = search.execute() results = search.execute()

@ -21,7 +21,7 @@ class DjangoBlogFeed(Feed):
return get_user_model().objects.first().get_absolute_url() return get_user_model().objects.first().get_absolute_url()
def items(self): def items(self):
return Article.objects.filter(article_type='a', status='p').order_by('-pub_time')[:5] return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5]
def item_title(self, item): def item_title(self, item):
return item.title return item.title

@ -37,7 +37,7 @@ class ServerManagerTest(TestCase):
article.body = "nicecontentccc" article.body = "nicecontentccc"
article.author = user article.author = user
article.category = c article.category = c
article.article_type = 'a' article.type = 'a'
article.status = 'p' article.status = 'p'
article.save() article.save()
s = TextMessage([]) s = TextMessage([])

@ -17,7 +17,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
<header class="archive-header"> <header class="archive-header">
@ -47,7 +47,7 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
</main><!-- #content --> </div><!-- #content -->
</div><!-- #primary --> </div><!-- #primary -->
{% endblock %} {% endblock %}

@ -5,10 +5,10 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
{% load_article_detail article False user %} {% load_article_detail article False user %}
{% if article.article_type == 'a' %} {% if article.type == 'a' %}
<nav class="nav-single"> <nav class="nav-single">
<h3 class="assistive-text">文章导航</h3> <h3 class="assistive-text">文章导航</h3>
{% if next_article %} {% if next_article %}
@ -24,7 +24,7 @@
</nav><!-- .nav-single --> </nav><!-- .nav-single -->
{% endif %} {% endif %}
</main><!-- #content --> </div><!-- #content -->
{% if article.comment_status == "o" and OPEN_SITE_COMMENT %} {% if article.comment_status == "o" and OPEN_SITE_COMMENT %}

@ -18,7 +18,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
{% if page_type and tag_name %} {% if page_type and tag_name %}
<header class="archive-header"> <header class="archive-header">
@ -33,7 +33,7 @@
{% load_pagination_info page_obj page_type tag_name %} {% load_pagination_info page_obj page_type tag_name %}
{% endif %} {% endif %}
</main><!-- #content --> </div><!-- #content -->
</div><!-- #primary --> </div><!-- #primary -->
{% endblock %} {% endblock %}

@ -26,13 +26,13 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
<header class="archive-header"> <header class="archive-header">
<h1 class="archive-title">{{ message }}</h1> <h1 class="archive-title">{{ message }}</h1>
</header><!-- .archive-header --> </header><!-- .archive-header -->
</main><!-- #content --> </div><!-- #content -->
</div><!-- #primary --> </div><!-- #primary -->
{% endblock %} {% endblock %}

@ -16,7 +16,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
<header class="archive-header"> <header class="archive-header">
@ -31,7 +31,7 @@
</li> </li>
{% endfor %} </ul> {% endfor %} </ul>
</div> </div>
</main><!-- #content --> </div><!-- #content -->
</div><!-- #primary --> </div><!-- #primary -->
{% endblock %} {% endblock %}

@ -37,7 +37,7 @@
</div> </div>
</div><!-- .comments-link --> </div><!-- .comments-link -->
<br/> <br/>
{% if article.article_type == 'a' %} {% if article.type == 'a' %}
{% if not isindex %} {% if not isindex %}
{% cache 36000 breadcrumb article.pk %} {% cache 36000 breadcrumb article.pk %}
{% load_breadcrumb article %} {% load_breadcrumb article %}

@ -8,7 +8,7 @@
</a> </a>
{% if article.article_type == 'a' %} {% if article.type == 'a' %}
{% if article.tags.all %} {% if article.tags.all %}
{% trans 'and tagged' %} {% trans 'and tagged' %}

@ -4,7 +4,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
<header class="archive-header"> <header class="archive-header">
@ -17,6 +17,6 @@
| |
<a href="/">回到首页</a> <a href="/">回到首页</a>
</header><!-- .archive-header --> </header><!-- .archive-header -->
</main><!-- #content --> </div>
</div> </div>
{% endblock %} {% endblock %}

@ -12,7 +12,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
{% if query %} {% if query %}
<header class="archive-header"> <header class="archive-header">
{% if suggestion %} {% if suggestion %}
@ -54,7 +54,7 @@
<h1 class="archive-title">哎呀,关键字:<span>{{ query }}</span>没有找到结果,要不换个词再试试?</h1> <h1 class="archive-title">哎呀,关键字:<span>{{ query }}</span>没有找到结果,要不换个词再试试?</h1>
</header><!-- .archive-header --> </header><!-- .archive-header -->
{% endif %} {% endif %}
</main><!-- #content --> </div><!-- #content -->
</div><!-- #primary --> </div><!-- #primary -->
{% endblock %} {% endblock %}

@ -17,7 +17,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
<header class="archive-header"> <header class="archive-header">

@ -5,10 +5,10 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div id="primary" class="site-content"> <div id="primary" class="site-content">
<main id="content"> <div id="content" role="main">
{% load_article_detail article False user %} {% load_article_detail article False user %}
{% if article.article_type == 'a' %} {% if article.type == 'a' %}
<nav class="nav-single"> <nav class="nav-single">
<h3 class="assistive-text">文章导航</h3> <h3 class="assistive-text">文章导航</h3>
{% if next_article %} {% if next_article %}

@ -37,7 +37,7 @@
</div> </div>
</div><!-- .comments-link --> </div><!-- .comments-link -->
<br/> <br/>
{% if article.article_type == 'a' %} {% if article.type == 'a' %}
{% if not isindex %} {% if not isindex %}
{% cache 36000 breadcrumb article.pk %} {% cache 36000 breadcrumb article.pk %}
{% load_breadcrumb article %} {% load_breadcrumb article %}
@ -69,82 +69,6 @@
</div><!-- .entry-content --> </div><!-- .entry-content -->
{% if not isindex %}
<div class="article-engagement" style="margin: 1rem 0; padding: 1rem; border-top: 1px solid var(--border-color);">
<button class="engagement-btn like-btn" data-article-id="{{ article.id }}">
<span class="icon">👍</span>
<span class="count">{{ article.likes_count|default:0 }}</span>
<span class="text">{% trans 'Like' %}</span>
</button>
<button class="engagement-btn favorite-btn" data-article-id="{{ article.id }}">
<span class="icon"></span>
<span class="count">{{ article.favorites_count|default:0 }}</span>
<span class="text">{% trans 'Favorite' %}</span>
</button>
{% if not user.is_authenticated %}
<div class="auth-hint">
<span><a href="{% url 'account:login' %}?next={{ request.path }}">登录</a> 后进行操作</span>
</div>
{% endif %}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 点赞功能
document.querySelector('.like-btn').addEventListener('click', function() {
const articleId = this.dataset.articleId;
const countEl = this.querySelector('.count');
fetch(`/article/like/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({ article_id: articleId })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
this.classList.toggle('is-active');
countEl.textContent = data.count;
} else {
alert(data.message || '操作失败');
}
})
.catch(error => console.error('Error:', error));
});
// 收藏功能
document.querySelector('.favorite-btn').addEventListener('click', function() {
const articleId = this.dataset.articleId;
const countEl = this.querySelector('.count');
fetch(`/article/favorite/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify({ article_id: articleId })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
this.classList.toggle('is-active');
countEl.textContent = data.count;
} else {
alert(data.message || '操作失败');
}
})
.catch(error => console.error('Error:', error));
});
});
</script>
{% endif %}
{% load_article_metas article user %} {% load_article_metas article user %}
</article><!-- #post --> </article><!-- #post -->

@ -8,7 +8,7 @@
</a> </a>
{% if article.article_type == 'a' %} {% if article.type == 'a' %}
{% if article.tags.all %} {% if article.tags.all %}
{% trans 'and tagged' %} {% trans 'and tagged' %}

@ -1,7 +1,7 @@
{% load blog_tags %} {% load blog_tags %}
{% load i18n %} {% load i18n %}
<aside id="secondary" class="widget-area" aria-label="侧边栏"> <div id="secondary" class="widget-area" role="complementary">
<aside id="search-2" class="widget widget_search" aria-label="搜索功能"> <aside id="search-2" class="widget widget_search">
<form role="search" method="get" id="searchform" class="searchform" action="/search"> <form role="search" method="get" id="searchform" class="searchform" action="/search">
<div> <div>
<label class="screen-reader-text" for="s">{% trans 'search' %}</label> <label class="screen-reader-text" for="s">{% trans 'search' %}</label>
@ -13,7 +13,7 @@
{% if extra_sidebars %} {% if extra_sidebars %}
{% for sidebar in extra_sidebars %} {% for sidebar in extra_sidebars %}
<aside class="widget_text widget widget_custom_html" aria-label="自定义侧边栏"><p class="widget-title"> <aside class="widget_text widget widget_custom_html"><p class="widget-title">
{{ sidebar.name }}</p> {{ sidebar.name }}</p>
<div class="textwidget custom-html-widget"> <div class="textwidget custom-html-widget">
{{ sidebar.content|custom_markdown|safe }} {{ sidebar.content|custom_markdown|safe }}
@ -23,7 +23,7 @@
{% endif %} {% endif %}
{% if most_read_articles %} {% if most_read_articles %}
<aside id="views-4" class="widget widget_views" aria-label="热门阅读"><p class="widget-title">Views</p> <aside id="views-4" class="widget widget_views"><p class="widget-title">Views</p>
<ul> <ul>
{% for a in most_read_articles %} {% for a in most_read_articles %}
<li> <li>
@ -37,7 +37,7 @@
</aside> </aside>
{% endif %} {% endif %}
{% if sidebar_categorys %} {% if sidebar_categorys %}
<aside id="su_siloed_terms-2" class="widget widget_su_siloed_terms" aria-label="文章分类"><p class="widget-title">{% trans 'category' %}</p> <aside id="su_siloed_terms-2" class="widget widget_su_siloed_terms"><p class="widget-title">{% trans 'category' %}</p>
<ul> <ul>
{% for c in sidebar_categorys %} {% for c in sidebar_categorys %}
<li class="cat-item cat-item-184"><a href={{ c.get_absolute_url }}>{{ c.name }}</a> <li class="cat-item cat-item-184"><a href={{ c.get_absolute_url }}>{{ c.name }}</a>
@ -47,7 +47,7 @@
</aside> </aside>
{% endif %} {% endif %}
{% if sidebar_comments and open_site_comment %} {% if sidebar_comments and open_site_comment %}
<aside id="ds-recent-comments-4" class="widget ds-widget-recent-comments" aria-label="最新评论"><p class="widget-title">{% trans 'recent comments' %}</p> <aside id="ds-recent-comments-4" class="widget ds-widget-recent-comments"><p class="widget-title">{% trans 'recent comments' %}</p>
<ul id="recentcomments"> <ul id="recentcomments">
{% for c in sidebar_comments %} {% for c in sidebar_comments %}
@ -62,7 +62,7 @@
</aside> </aside>
{% endif %} {% endif %}
{% if recent_articles %} {% if recent_articles %}
<aside id="recent-posts-2" class="widget widget_recent_entries" aria-label="最新文章"><p class="widget-title">{% trans 'recent articles' %}</p> <aside id="recent-posts-2" class="widget widget_recent_entries"><p class="widget-title">{% trans 'recent articles' %}</p>
<ul> <ul>
{% for a in recent_articles %} {% for a in recent_articles %}
@ -74,7 +74,7 @@
</aside> </aside>
{% endif %} {% endif %}
{% if sidabar_links %} {% if sidabar_links %}
<aside id="linkcat-0" class="widget widget_links" aria-label="书签链接"><p class="widget-title">{% trans 'bookmark' %}</p> <aside id="linkcat-0" class="widget widget_links"><p class="widget-title">{% trans 'bookmark' %}</p>
<ul class='xoxo blogroll'> <ul class='xoxo blogroll'>
{% for l in sidabar_links %} {% for l in sidabar_links %}
<li> <li>
@ -86,24 +86,25 @@
</aside> </aside>
{% endif %} {% endif %}
{% if show_google_adsense %} {% if show_google_adsense %}
<aside id="text-2" class="widget widget_text" aria-label="广告"><p class="widget-title">Google AdSense</p> <aside id="text-2" class="widget widget_text"><p class="widget-title">Google AdSense</p>
<div class="textwidget"> <div class="textwidget">
{{ google_adsense_codes|safe }} {{ google_adsense_codes|safe }}
</div> </div>
</aside> </aside>
{% endif %} {% endif %}
{% if sidebar_tags %} {% if sidebar_tags %}
<aside id="tag_cloud-2" class="widget widget_tag_cloud" aria-label="标签云"><p class="widget-title">{% trans 'Tag Cloud' %}</p> <aside id="tag_cloud-2" class="widget widget_tag_cloud"><p class="widget-title">{% trans 'Tag Cloud' %}</p>
<div class="tagcloud"> <div class="tagcloud">
{% for tag, count, size in sidebar_tags %} {% for tag,count,size in sidebar_tags %}
<a href="{{ tag.get_absolute_url }}" class="tag-link-{{ tag.id }} tag-link-position-{{ tag.id }}" <a href="{{ tag.get_absolute_url }}"
style="font-size: '{{ size }}pt';" class="tag-link-{{ tag.id }} tag-link-position-{{ tag.id }}"
title="{{ count }}个话题">{{ tag.name }}</a> style="font-size: {{ size }}pt;" title="{{ count }}个话题"> {{ tag.name }}
</a>
{% endfor %} {% endfor %}
</div> </div>
</aside> </aside>
{% endif %} {% endif %}
<aside id="text-3" class="widget widget_text" aria-label="源码链接"><p class="widget-title">{% trans 'Welcome to star or fork the source code of this site' %}</p> <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"> <div class="textwidget">
<p><a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img <p><a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow"><img
@ -114,7 +115,7 @@
</div> </div>
</aside> </aside>
<aside id="meta-3" class="widget widget_meta" aria-label="功能链接"><p class="widget-title">{% trans 'Function' %}</p> <aside id="meta-3" class="widget widget_meta"><p class="widget-title">{% trans 'Function' %}</p>
<ul> <ul>
<li><a href="/admin/" rel="nofollow">{% trans 'management site' %}</a></li> <li><a href="/admin/" rel="nofollow">{% trans 'management site' %}</a></li>
{% if user.is_authenticated %} {% if user.is_authenticated %}
@ -132,4 +133,4 @@
</aside> </aside>
<div id="rocket" class="show" title="{% trans 'Click me to return to the top' %}"></div> <div id="rocket" class="show" title="{% trans 'Click me to return to the top' %}"></div>
</aside><!-- #secondary --> </div><!-- #secondary -->

@ -1,139 +0,0 @@
{% extends 'blog/base.html' %}
{% load i18n %}
{% load comment_tags %}
{% load staticfiles %}
{% load custom_markdown %}
{% block title %}{% trans "我的收藏" %} - {{ SITE_NAME }}{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="panel panel-default">
<div class="panel-heading">
<div class="title">
<h2>{% trans "我的收藏" %}</h2>
</div>
</div>
<div class="panel-body">
{% if has_favorites %}
{% for article in article_list %}
<div class="article post-preview">
<div class="article-inner">
<h3 class="article-title">
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
</h3>
<div class="article-meta">
<span class="article-date">
<i class="fa fa-calendar"></i>
{{ article.created_time }}
</span>
<span class="article-category">
<i class="fa fa-folder-o"></i>
<a href="{{ article.category.get_absolute_url }}">{{ article.category.name }}</a>
</span>
<span class="article-author">
<i class="fa fa-user-o"></i>
<a href="{{ article.author.get_absolute_url }}">{{ article.author.nickname }}</a>
</span>
<span class="article-view-count">
<i class="fa fa-eye"></i>
{{ article.views }}
</span>
<span class="article-comment-count">
<i class="fa fa-comment-o"></i>
{% get_comment_count article %}
</span>
</div>
<div class="article-content">
<p>
{{ article.body|custom_markdown|truncatechars:100 }}
</p>
</div>
<div class="article-footer">
<div class="article-more-link">
<a href="{{ article.get_absolute_url }}" class="btn btn-default">
阅读全文
</a>
</div>
</div>
</div>
</div>
<hr>
{% endfor %}
{% if page_obj.has_previous or page_obj.has_next %}
<div class="pagination">
<ul class="pagination">
{% if page_obj.has_previous %}
<li>
<a href="?page={{ page_obj.previous_page_number }}">{% trans "上一页" %}</a>
</li>
{% endif %}
{% if page_obj.has_next %}
<li>
<a href="?page={{ page_obj.next_page_number }}">{% trans "下一页" %}</a>
</li>
{% endif %}
</ul>
</div>
{% endif %}
{% else %}
<div class="no-favorites">
<p class="text-center">您还没有收藏任何文章</p>
<div class="text-center">
<a href="/" class="btn btn-primary">
去首页看看
</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
{% include 'blog/tags.html' %}
{% include 'blog/hotest_articles.html' %}
{% include 'blog/archives.html' %}
</div>
</div>
</div>
{% endblock %}
{% block extra_footer %}
<style>
.article-meta {
margin-bottom: 10px;
font-size: 14px;
color: #666;
}
.article-meta span {
margin-right: 15px;
}
.article-meta a {
color: #666;
}
.article-meta a:hover {
color: #337ab7;
text-decoration: none;
}
.article-title {
margin-bottom: 10px;
}
.article-title a {
color: #333;
}
.article-title a:hover {
color: #337ab7;
text-decoration: none;
}
.no-favorites {
padding: 30px 0;
}
.no-favorites p {
font-size: 16px;
color: #666;
margin-bottom: 20px;
}
</style>
{% endblock %}

@ -25,11 +25,6 @@
<a href="{% url "blog:archives" %}">{% trans 'Article archive' %}</a> <a href="{% url "blog:archives" %}">{% trans 'Article archive' %}</a>
</li> </li>
{% if user.is_authenticated %}
<li class="menu-item menu-item-type-taxonomy menu-item-object-category">
<a href="{% url "blog:user_favorites" %}">{% trans '我的收藏' %}</a>
</li>
{% endif %}
</ul> </ul>
</div> </div>
</nav><!-- #site-navigation --> </nav><!-- #site-navigation -->
Loading…
Cancel
Save