nch #52

Closed
phm9gvnzi wants to merge 0 commits from nch_branch into master

42
.gitignore vendored

@ -0,0 +1,42 @@
# 系统和用户目录
Application Data/
Cookies/
Local Settings/
My Documents/
NetHood/
PrintHood/
Recent/
SendTo/
Templates/
「开始」菜单/
AppData/
Contacts/
Desktop/
Documents/
Downloads/
Favorites/
Links/
Music/
NTUSER.*
OneDrive/
Pictures/
Saved Games/
Searches/
Videos/
WPS Cloud Files/
wechat_files/
# 临时文件和IDE配置
.bash_history
.eclipse/
.gitconfig
.idlerc/
.matplotlib/
.p2/
.ssh/
.vscode/
eclipse-workspace/
eclipse/
ntuser.*

Binary file not shown.

@ -4,192 +4,109 @@ from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
# 导入 blog 应用下的所有模型
from .models import (
Article, Category, Tag, Links, SideBar, BlogSettings,
UserProfile, Favorite # 确保导入了 UserProfile 和 Favorite
)
# Register your models here.
from .models import Article
# ------------------------------------------------------------------------------
# Article Admin
# ------------------------------------------------------------------------------
class ArticleForm(forms.ModelForm):
# body = forms.CharField(widget=AdminPagedownWidget())
class Meta:
model = Article
fields = '__all__'
def make_article_publish(modeladmin, request, queryset):
def makr_article_publish(modeladmin, request, queryset):
queryset.update(status='p')
make_article_publish.short_description = _('Publish selected articles')
def draft_article(modeladmin, request, queryset):
queryset.update(status='d')
draft_article.short_description = _('Set selected articles to draft')
def close_article_commentstatus(modeladmin, request, queryset):
queryset.update(comment_status='c')
close_article_commentstatus.short_description = _('Close comments for selected articles')
def open_article_commentstatus(modeladmin, request, queryset):
queryset.update(comment_status='o')
open_article_commentstatus.short_description = _('Open comments for selected articles')
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
makr_article_publish.short_description = _('Publish selected articles')
draft_article.short_description = _('Draft selected articles')
close_article_commentstatus.short_description = _('Close article comments')
open_article_commentstatus.short_description = _('Open article comments')
class ArticlelAdmin(admin.ModelAdmin):
list_per_page = 20
search_fields = ('body', 'title')
form = ArticleForm
list_display = (
'id', 'title', 'author', 'link_to_category',
'pub_time', 'views', 'status', 'type', 'article_order'
)
'id',
'title',
'author',
'link_to_category',
'creation_time',
'views',
'status',
'type',
'article_order')
list_display_links = ('id', 'title')
list_filter = ('status', 'type', 'category', 'tags')
list_filter = ('status', 'type', 'category')
filter_horizontal = ('tags',)
# 使用 fieldsets 来组织编辑页面的字段布局,更清晰
fieldsets = (
(_('Basic Information'), {
'fields': ('title', 'author', 'category', 'tags', 'status', 'type')
}),
(_('Content'), {
'fields': ('body',)
}),
(_('Settings'), {
'fields': ('article_order', 'show_toc', 'comment_status'),
'classes': ('collapse',) # 默认折叠
}),
)
exclude = ('creation_time', 'last_modify_time')
view_on_site = True
actions = [
make_article_publish, draft_article,
close_article_commentstatus, open_article_commentstatus
]
makr_article_publish,
draft_article,
close_article_commentstatus,
open_article_commentstatus]
def link_to_category(self, obj):
if obj.category:
info = (obj.category._meta.app_label, obj.category._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
return format_html(u'<a href="{}">{}</a>', link, obj.category.name)
return _('None')
link_to_category.short_description = _('Category')
info = (obj.category._meta.app_label, obj.category._meta.model_name)
link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
return format_html(u'<a href="%s">%s</a>' % (link, obj.category.name))
link_to_category.short_description = _('category')
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj,** kwargs)
# 限制作者只能是超级管理员
form.base_fields['author'].queryset = get_user_model().objects.filter(is_superuser=True)
form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['author'].queryset = get_user_model(
).objects.filter(is_superuser=True)
return form
def save_model(self, request, obj, form, change):
super(ArticlelAdmin, self).save_model(request, obj, form, change)
def get_view_on_site_url(self, obj=None):
if obj:
return obj.get_full_url()
return super().get_view_on_site_url(obj)
url = obj.get_full_url()
return url
else:
from djangoblog.utils import get_current_site
site = get_current_site().domain
return site
# ------------------------------------------------------------------------------
# Category Admin
# ------------------------------------------------------------------------------
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'parent_category', 'index')
list_display_links = ('id', 'name')
list_filter = ('parent_category',)
search_fields = ('name',)
exclude = ('slug', 'last_mod_time', 'creation_time')
# ------------------------------------------------------------------------------
# Tag Admin
# ------------------------------------------------------------------------------
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
list_display_links = ('id', 'name')
search_fields = ('name',)
exclude = ('slug', 'last_mod_time', 'creation_time')
# ------------------------------------------------------------------------------
# Links Admin
# ------------------------------------------------------------------------------
@admin.register(Links)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'parent_category', 'index')
exclude = ('slug', 'last_mod_time', 'creation_time')
class LinksAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'link', 'sequence', 'is_enable', 'show_type')
list_display_links = ('id', 'name')
list_filter = ('is_enable', 'show_type')
search_fields = ('name', 'link')
exclude = ('last_mod_time', 'creation_time')
# ------------------------------------------------------------------------------
# SideBar Admin
# ------------------------------------------------------------------------------
@admin.register(SideBar)
class SideBarAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'is_enable', 'sequence')
list_display_links = ('id', 'name')
list_filter = ('is_enable',)
search_fields = ('name', 'content')
exclude = ('last_mod_time', 'creation_time')
# ------------------------------------------------------------------------------
# BlogSettings Admin (Singleton Pattern)
# ------------------------------------------------------------------------------
@admin.register(BlogSettings)
class BlogSettingsAdmin(admin.ModelAdmin):
list_display = ('id', 'site_name')
class SideBarAdmin(admin.ModelAdmin):
list_display = ('name', 'content', 'is_enable', 'sequence')
exclude = ('last_mod_time', 'creation_time')
def has_add_permission(self, request):
"""
限制只能有一个配置实例
如果已经存在一条记录则禁用添加按钮
"""
if BlogSettings.objects.exists():
return False
return True
def save_model(self, request, obj, form, change):
"""
确保始终只有一个配置实例
"""
if not change and BlogSettings.objects.exists():
raise ValidationError(_('There can be only one Blog Settings instance.'))
super().save_model(request, obj, form, change)
# ------------------------------------------------------------------------------
# UserProfile Admin
# ------------------------------------------------------------------------------
@admin.register(UserProfile)
class UserProfileAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'created_at')
list_display_links = ('id', 'user')
list_filter = ('created_at',)
search_fields = ('user__username', 'user__email', 'bio')
fieldsets = (
(_('User'), {
'fields': ('user',)
}),
(_('Profile Information'), {
'fields': ('bio', 'avatar')
}),
(_('Social Links'), {
'fields': ('website', 'github', 'twitter', 'weibo'),
'classes': ('collapse',)
}),
)
readonly_fields = ('created_at', 'updated_at') # 时间戳设为只读
# ------------------------------------------------------------------------------
# Favorite Admin (Optional)
# ------------------------------------------------------------------------------
@admin.register(Favorite)
class FavoriteAdmin(admin.ModelAdmin):
list_display = ('id', 'user', 'article', 'created_at')
list_display_links = ('id',)
list_filter = ('created_at',)
search_fields = ('user__username', 'article__title')
readonly_fields = ('created_at',)
# 通常不希望管理员手动创建或修改收藏,所以可以禁用相关权限
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
class BlogSettingsAdmin(admin.ModelAdmin):
pass

@ -17,22 +17,3 @@ class BlogSearchForm(SearchForm):
if self.cleaned_data['querydata']:
logger.info(self.cleaned_data['querydata'])
return datas
# blog/forms.py
from django import forms
from .models import UserProfile
class UserProfileUpdateForm(forms.ModelForm):
"""
用户资料更新表单
"""
class Meta:
model = UserProfile
fields = ['avatar', 'bio', 'website', 'github', 'twitter', 'weibo']
widgets = {
'bio': forms.Textarea(attrs={'rows': 5, 'placeholder': 'Tell us about yourself...'}),
'website': forms.URLInput(attrs={'placeholder': 'https://'}),
'github': forms.URLInput(attrs={'placeholder': 'https://github.com/'}),
'twitter': forms.URLInput(attrs={'placeholder': 'https://twitter.com/'}),
'weibo': forms.URLInput(attrs={'placeholder': 'https://weibo.com/'}),
}

@ -1,30 +0,0 @@
# Generated by Django 5.2.6 on 2025-11-22 23:35
import django.db.models.deletion
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.CreateModel(
name='Favorite',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorites', to='blog.article')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='favorite_articles', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': '收藏',
'verbose_name_plural': '收藏',
'unique_together': {('article', 'user')},
},
),
]

@ -1,37 +0,0 @@
# Generated by Django 5.2.6 on 2025-11-23 00:03
import blog.models
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0007_favorite'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('avatar', models.ImageField(blank=True, help_text='Upload your avatar image (recommended size: 100x100px)', null=True, upload_to=blog.models.user_avatar_path, verbose_name='Avatar')),
('bio', models.TextField(blank=True, help_text='Tell us a little about yourself', null=True, verbose_name='Biography')),
('website', models.URLField(blank=True, null=True, verbose_name='Website')),
('github', models.URLField(blank=True, null=True, verbose_name='GitHub')),
('twitter', models.URLField(blank=True, null=True, verbose_name='Twitter')),
('weibo', models.URLField(blank=True, null=True, verbose_name='Weibo')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'User Profile',
'verbose_name_plural': 'User Profiles',
'ordering': ['-created_at'],
},
),
]

@ -1,6 +1,5 @@
import logging
import re
import os
from abc import abstractmethod
from django.conf import settings
from django.core.exceptions import ValidationError
@ -14,18 +13,8 @@ from uuslug import slugify
from djangoblog.utils import cache_decorator, cache
from djangoblog.utils import get_current_site
# 确保导入了 post_save 信号和 receiver 装饰器
from django.db.models.signals import post_save
from django.dispatch import receiver
logger = logging.getLogger(__name__)
def user_avatar_path(instance, filename):
"""
定义用户头像的上传路径
"""
# 文件将被上传到 MEDIA_ROOT/user_<id>/avatar/<filename>
return os.path.join(f'user_{instance.user.id}', 'avatar', filename)
class LinkShowType(models.TextChoices):
I = ('i', _('index'))
@ -34,6 +23,7 @@ class LinkShowType(models.TextChoices):
A = ('a', _('all'))
S = ('s', _('slide'))
class BaseModel(models.Model):
id = models.AutoField(primary_key=True)
creation_time = models.DateTimeField(_('creation time'), default=now)
@ -66,6 +56,7 @@ class BaseModel(models.Model):
def get_absolute_url(self):
pass
class Article(BaseModel):
"""文章"""
STATUS_CHOICES = (
@ -139,6 +130,9 @@ class Article(BaseModel):
names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
return names
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
def viewed(self):
self.views += 1
self.save(update_fields=['views'])
@ -216,11 +210,6 @@ class Article(BaseModel):
return related_articles
# 新增:获取文章的收藏数
def get_favorite_count(self):
"""获取文章的收藏数"""
return self.favorites.count()
class Category(BaseModel):
"""文章分类"""
@ -284,6 +273,7 @@ class Category(BaseModel):
parse(self)
return categorys
class Tag(BaseModel):
"""文章标签"""
name = models.CharField(_('tag name'), max_length=30, unique=True)
@ -304,6 +294,7 @@ class Tag(BaseModel):
verbose_name = _('tag')
verbose_name_plural = verbose_name
class Links(models.Model):
"""友情链接"""
@ -328,6 +319,7 @@ class Links(models.Model):
def __str__(self):
return self.name
class SideBar(models.Model):
"""侧边栏,可以展示一些html内容"""
name = models.CharField(_('title'), max_length=100)
@ -345,6 +337,7 @@ class SideBar(models.Model):
def __str__(self):
return self.name
class BlogSettings(models.Model):
"""blog的配置"""
site_name = models.CharField(
@ -414,79 +407,4 @@ class BlogSettings(models.Model):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
from djangoblog.utils import cache
cache.clear()
class Favorite(models.Model):
"""
文章收藏模型
"""
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='favorites')
# 使用 settings.AUTH_USER_MODEL
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='favorite_articles')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ('article', 'user') # 一个用户只能收藏同一篇文章一次
verbose_name = "收藏"
verbose_name_plural = "收藏"
def __str__(self):
return f'{self.user.username} favorites {self.article.title}'
class UserProfile(models.Model):
"""
用户资料扩展模型
用于存储用户头像个人简介等额外信息
"""
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile', verbose_name=_('User'))
# 基本信息
avatar = models.ImageField(
upload_to=user_avatar_path,
blank=True,
null=True,
verbose_name=_('Avatar'),
help_text=_('Upload your avatar image (recommended size: 100x100px)')
)
bio = models.TextField(
blank=True,
null=True,
verbose_name=_('Biography'),
help_text=_('Tell us a little about yourself')
)
# 可选的社交链接
website = models.URLField(blank=True, null=True, verbose_name=_('Website'))
github = models.URLField(blank=True, null=True, verbose_name=_('GitHub'))
twitter = models.URLField(blank=True, null=True, verbose_name=_('Twitter'))
weibo = models.URLField(blank=True, null=True, verbose_name=_('Weibo'))
# 时间戳
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At'))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At'))
class Meta:
verbose_name = _('User Profile')
verbose_name_plural = _('User Profiles')
ordering = ['-created_at']
def __str__(self):
return f"{self.user.username}'s Profile"
def get_absolute_url(self):
"""
定义用户资料页的绝对URL
"""
return reverse('blog:user_profile', kwargs={'username': self.user.username})
# 信号当一个新的自定义用户settings.AUTH_USER_MODEL被创建时自动创建一个关联的UserProfile
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def save_user_profile(sender, instance, **kwargs):
# 使用 get_or_create 防止在某些情况下如手动创建用户时profile不存在
UserProfile.objects.get_or_create(user=instance)
instance.profile.save()
cache.clear()

@ -341,4 +341,4 @@ def query(qs, **kwargs):
@register.filter
def addstr(arg1, arg2):
"""concatenate arg1 & arg2"""
return str(arg1) + str(arg2)
return str(arg1) + str(arg2)

@ -1,130 +1,108 @@
# blog/urls.py
# 导入 Django 内置的路径配置工具和缓存装饰器
from django.urls import path
from django.views.decorators.cache import cache_page
# 导入当前应用blog的视图模块
# 导入当前应用blog的视图模块,用于关联路由与视图逻辑
from . import views
# 定义应用命名空间
# 定义应用命名空间namespace用于在模板或反向解析时区分不同应用的路由
# 例如:在模板中使用 {% url 'blog:index' %} 生成首页链接
app_name = "blog"
# 路由配置列表
# 路由配置列表,每个 path 对应一个 URL 规则与视图的映射
urlpatterns = [
# 首页路由
# 首页路由:匹配根路径(网站域名/
path(
'',
views.IndexView.as_view(),
name='index'
r'', # URL 路径表达式,空字符串表示根路径
views.IndexView.as_view(), # 关联的视图类IndexView通过 as_view() 转换为可调用视图
name='index' # 路由名称,用于反向解析(如 reverse('blog:index')
),
# 分页首页路由:匹配带页码的首页(如 /page/2/
path(
'page/<int:page>/',
views.IndexView.as_view(),
r'page/<int:page>/', # <int:page> 是路径参数int 表示接收整数类型page 是参数名
views.IndexView.as_view(), # 复用首页视图类,视图中会通过 page 参数处理分页
name='index_page'
),
# 文章详情页路由
# 文章详情页路由按日期和文章ID匹配如 /article/2023/10/20/100.html
path(
'article/<int:article_id>/',
views.ArticleDetailView.as_view(),
name='article_detail'
),
# 原始的带日期的URL保持不变
path(
'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html',
views.ArticleDetailView.as_view(),
r'article/<int:year>/<int:month>/<int:day>/<int:article_id>.html',
# 路径参数year、month、day、article_id文章ID均为整数
views.ArticleDetailView.as_view(), # 文章详情视图类,处理文章展示逻辑
name='detailbyid'
),
# 文章收藏功能路由
# 分类详情页路由:按分类名匹配(如 /category/tech.html
path(
'article/<int:article_id>/favorite/',
views.ArticleFavoriteView.as_view(),
name='article_favorite'
),
# 分类详情页路由
path(
'category/<slug:category_name>.html',
views.CategoryDetailView.as_view(),
r'category/<slug:category_name>.html',
# <slug:category_name>slug 类型表示接收字母、数字、下划线和连字符组成的字符串适合URL友好的名称
views.CategoryDetailView.as_view(), # 分类详情视图类,展示该分类下的文章
name='category_detail'
),
# 分类详情分页路由:带页码的分类页(如 /category/tech/2.html
path(
'category/<slug:category_name>/<int:page>.html',
views.CategoryDetailView.as_view(),
r'category/<slug:category_name>/<int:page>.html',
views.CategoryDetailView.as_view(), # 复用分类视图类,通过 page 参数分页
name='category_detail_page'
),
# 作者详情页路由
# 作者详情页路由:按作者名匹配(如 /author/alice.html
path(
'author/<author_name>.html',
views.AuthorDetailView.as_view(),
r'author/<author_name>.html',
# <author_name>:未指定类型,默认接收字符串(除特殊字符外)
views.AuthorDetailView.as_view(), # 作者详情视图类,展示该作者的文章
name='author_detail'
),
# 作者详情分页路由:带页码的作者页(如 /author/alice/2.html
path(
'author/<author_name>/<int:page>.html',
views.AuthorDetailView.as_view(),
r'author/<author_name>/<int:page>.html',
views.AuthorDetailView.as_view(), # 复用作者视图类,通过 page 参数分页
name='author_detail_page'
),
# 标签详情页路由
# 标签详情页路由:按标签名匹配(如 /tag/python.html
path(
'tag/<slug:tag_name>.html',
views.TagDetailView.as_view(),
r'tag/<slug:tag_name>.html',
views.TagDetailView.as_view(), # 标签详情视图类,展示该标签下的文章
name='tag_detail'
),
# 标签详情分页路由:带页码的标签页(如 /tag/python/2.html
path(
'tag/<slug:tag_name>/<int:page>.html',
views.TagDetailView.as_view(),
r'tag/<slug:tag_name>/<int:page>.html',
views.TagDetailView.as_view(), # 复用标签视图类,通过 page 参数分页
name='tag_detail_page'
),
# 归档页路由
# 归档页路由:匹配 /archives.html
path(
'archives.html',
# 缓存装饰器cache_page(60*60) 表示缓存该页面1小时60秒*60减轻服务器压力
cache_page(60 * 60)(views.ArchivesView.as_view()),
name='archives'
name='archives' # 归档视图,通常展示按日期分组的文章列表
),
# 友情链接页路由
# 友情链接页路由:匹配 /links.html
path(
'links.html',
views.LinkListView.as_view(),
views.LinkListView.as_view(), # 友情链接视图类,展示网站链接列表
name='links'
),
# ==================== 关键修正:调整 URL 顺序 ====================
# 用户资料相关路由
# 将具体的路径放在通用路径之前
path(
'profile/edit/',
views.UserProfileUpdateView.as_view(),
name='user_profile_update'
),
# 我的收藏页面路由
path(
'profile/favorites/',
views.UserFavoritesView.as_view(),
name='user_favorites'
),
# 通用的用户资料路径放在最后
# 文件上传路由:匹配 /upload
path(
'profile/<str:username>/',
views.UserProfileDetailView.as_view(),
name='user_profile'
),
# =================================================================
# 其他功能路由
path(
'upload',
views.fileupload,
r'upload',
views.fileupload, # 关联函数视图(非类视图),处理文件上传逻辑
name='upload'
),
# 缓存清理路由:匹配 /clean
path(
'clean',
views.clean_cache_view,
r'clean',
views.clean_cache_view, # 关联缓存清理视图,用于手动触发缓存清理
name='clean'
),
]

@ -4,26 +4,18 @@ import uuid
from django.conf import settings
from django.core.paginator import Paginator
from django.http import HttpResponse, HttpResponseForbidden, JsonResponse
from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.shortcuts import render, redirect
from django.shortcuts import render
from django.templatetags.static import static
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from django.views.generic import View, TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin
from haystack.views import SearchView
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import HttpResponseRedirect
# 导入自定义用户模型
from accounts.models import BlogUser
from blog.models import Article, Category, LinkShowType, Links, Tag, Favorite, UserProfile
from blog.models import Article, Category, LinkShowType, Links, Tag
from comments.forms import CommentForm
from djangoblog.plugin_manage import hooks
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
@ -31,6 +23,7 @@ from djangoblog.utils import cache, get_blog_setting, get_sha256
logger = logging.getLogger(__name__)
class ArticleListView(ListView):
# template_name属性用于指定使用哪个模板进行渲染
template_name = 'blog/article_index.html'
@ -103,6 +96,7 @@ class ArticleListView(ListView):
kwargs['hot_articles'] = hot_articles
return super(ArticleListView, self).get_context_data(** kwargs)
class IndexView(ArticleListView):
'''
首页
@ -118,6 +112,7 @@ class IndexView(ArticleListView):
cache_key = 'index_{page}'.format(page=self.page_number)
return cache_key
class ArticleDetailView(DetailView):
'''
文章详情页面
@ -176,10 +171,6 @@ class ArticleDetailView(DetailView):
logger.info('set hot articles cache')
kwargs['hot_articles'] = hot_articles
# 新增:判断当前用户是否收藏了该文章
if self.request.user.is_authenticated:
kwargs['is_favorited'] = Favorite.objects.filter(article=self.object, user=self.request.user).exists()
context = super(ArticleDetailView, self).get_context_data(** kwargs)
article = self.object
# Action Hook, 通知插件"文章详情已获取"
@ -190,6 +181,7 @@ class ArticleDetailView(DetailView):
return context
class CategoryDetailView(ArticleListView):
'''
分类目录列表
@ -227,6 +219,7 @@ class CategoryDetailView(ArticleListView):
kwargs['tag_name'] = categoryname
return super(CategoryDetailView, self).get_context_data(** kwargs)
class AuthorDetailView(ArticleListView):
'''
作者详情页
@ -242,7 +235,6 @@ class AuthorDetailView(ArticleListView):
def get_queryset_data(self):
author_name = self.kwargs['author_name']
# 这里使用 BlogUser 或 settings.AUTH_USER_MODEL 都是安全的
article_list = Article.objects.filter(
author__username=author_name, type='a', status='p')
return article_list
@ -253,6 +245,7 @@ class AuthorDetailView(ArticleListView):
kwargs['tag_name'] = author_name
return super(AuthorDetailView, self).get_context_data(** kwargs)
class TagDetailView(ArticleListView):
'''
标签列表页面
@ -283,6 +276,7 @@ class TagDetailView(ArticleListView):
kwargs['tag_name'] = tag_name
return super(TagDetailView, self).get_context_data(** kwargs)
class ArchivesView(ArticleListView):
'''
文章归档页面
@ -299,7 +293,7 @@ class ArchivesView(ArticleListView):
cache_key = 'archives'
return cache_key
def get_context_data(self, **kwargs):
def get_context_data(self,** kwargs):
# 归档页单独添加热门文章因继承自ArticleListView但需确保显示
hot_articles_cache_key = 'hot_articles'
hot_articles = cache.get(hot_articles_cache_key)
@ -308,7 +302,8 @@ class ArchivesView(ArticleListView):
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)
return super(ArchivesView, self).get_context_data(**kwargs)
class LinkListView(ListView):
model = Links
@ -317,7 +312,7 @@ class LinkListView(ListView):
def get_queryset(self):
return Links.objects.filter(is_enable=True)
def get_context_data(self, **kwargs):
def get_context_data(self,** kwargs):
# 链接页添加热门文章
hot_articles_cache_key = 'hot_articles'
hot_articles = cache.get(hot_articles_cache_key)
@ -326,7 +321,8 @@ class LinkListView(ListView):
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)
return super(LinkListView, self).get_context_data(**kwargs)
class EsSearchView(SearchView):
def get_context(self):
@ -341,6 +337,7 @@ class EsSearchView(SearchView):
context['hot_articles'] = hot_articles
return context
@csrf_exempt
def fileupload(request):
"""
@ -382,6 +379,7 @@ def fileupload(request):
else:
return HttpResponse("only for post")
def page_not_found_view(
request,
exception,
@ -395,6 +393,7 @@ def page_not_found_view(
'statuscode': '404'},
status=404)
def server_error_view(request, template_name='blog/error_page.html'):
return render(request,
template_name,
@ -402,6 +401,7 @@ def server_error_view(request, template_name='blog/error_page.html'):
'statuscode': '500'},
status=500)
def permission_denied_view(
request,
exception,
@ -413,6 +413,7 @@ def permission_denied_view(
'message': _('Sorry, you do not have permission to access this page?'),
'statuscode': '403'}, status=403)
def clean_cache_view(request):
cache.clear()
return HttpResponse('ok')
@ -476,118 +477,4 @@ class DjangoBlogFeed(Feed):
返回单个项目文章的发布日期
这是可选的但推荐添加以符合 RSS 规范
"""
return item.creation_time
# ======================================================================
# 收藏功能视图 (修正和统一)
# ======================================================================
class ArticleFavoriteView(LoginRequiredMixin, View):
"""
处理文章收藏/取消收藏的视图 (统一处理)
"""
http_method_names = ['post'] # 只允许 POST 请求
def post(self, request, *args, **kwargs):
article_id = kwargs.get('article_id')
article = get_object_or_404(Article, pk=article_id)
# get_or_create 是一个原子操作,能有效防止并发问题
favorite, created = Favorite.objects.get_or_create(article=article, user=request.user)
if not created:
# 如果记录已存在,则删除(取消收藏)
favorite.delete()
is_favorite = False
else:
# 如果是新创建,则表示收藏成功
is_favorite = True
# 返回更新后的收藏数和状态
favorite_count = article.get_favorite_count()
return JsonResponse({'is_favorite': is_favorite, 'favorite_count': favorite_count})
class UserFavoritesView(LoginRequiredMixin, ListView):
"""
展示用户收藏的所有文章 (基于类的视图)
"""
model = Article
template_name = 'blog/user_favorites.html'
context_object_name = 'favorite_articles'
paginate_by = 10 # 每页显示10篇
def get_queryset(self):
# 获取当前用户的所有收藏,并按收藏时间倒序排列
# 使用 select_related 和 prefetch_related 优化数据库查询
return Article.objects.filter(
favorites__user=self.request.user
).select_related('author', 'category').prefetch_related('tags').order_by('-favorites__created_at')
def get_context_data(self,** kwargs):
context = super().get_context_data(**kwargs)
# 添加 profile_user 用于复用个人资料页面的侧边栏
context['profile_user'] = self.request.user
return context
# ======================================================================
# 用户资料视图
# ======================================================================
from django.views.generic import UpdateView
from django.urls import reverse_lazy
from .forms import UserProfileUpdateForm # 确保你有这个表单文件
class UserProfileDetailView(DetailView):
"""
显示用户公开资料的视图
"""
model = UserProfile
template_name = 'blog/user_profile_detail.html'
context_object_name = 'profile'
def get_object(self, queryset=None):
"""通过用户名查找用户,然后返回其 profile"""
username = self.kwargs.get('username')
# 修正:使用自定义的 BlogUser 模型进行查询
user = get_object_or_404(BlogUser, username=username)
return get_object_or_404(UserProfile, user=user)
def get_context_data(self, **kwargs):
"""添加额外的上下文数据,例如用户发布的文章"""
context = super().get_context_data(** kwargs)
user = self.object.user # 从 profile 对象获取 user
# 获取该用户发布的所有公开文章,并按发布时间排序
context['user_articles'] = Article.objects.filter(author=user, status='p').order_by('-pub_time')[:10]
return context
class UserProfileUpdateView(LoginRequiredMixin, UpdateView):
"""
用户编辑自己资料的视图
"""
model = UserProfile
form_class = UserProfileUpdateForm
template_name = 'blog/user_profile_update.html'
def get_queryset(self):
"""
重写此方法以确保用户只能编辑自己的资料
这是对象级权限控制的最佳实践
"""
# 只返回与当前登录用户关联的 UserProfile 对象
return UserProfile.objects.filter(user=self.request.user)
def get_object(self, queryset=None):
"""
用户只能编辑自己的 profile
此方法与 get_queryset 结合提供了双重保障
"""
return self.request.user.profile
def get_success_url(self):
"""编辑成功后重定向到自己的资料页"""
return reverse_lazy('blog:user_profile', kwargs={'username': self.request.user.username})
def form_valid(self, form):
"""表单验证成功后,显示成功消息"""
messages.success(self.request, 'Your profile has been updated successfully!')
return super().form_valid(form)
return item.creation_time

@ -40,7 +40,7 @@ class DjangoBlogAdminSite(AdminSite):
admin_site = DjangoBlogAdminSite(name='admin')
admin_site.register(Article, ArticleAdmin)
admin_site.register(Article, ArticlelAdmin)
admin_site.register(Category, CategoryAdmin)
admin_site.register(Tag, TagAdmin)
admin_site.register(Links, LinksAdmin)

@ -12,8 +12,15 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
import os
import sys
from pathlib import Path
from django.utils.translation import gettext_lazy as _
def env_to_bool(env, default):
str_val = os.environ.get(env)
return default if str_val is None else str_val == 'True'
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -23,17 +30,18 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get(
'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ.get('DJANGO_DEBUG', 'True') == 'True'
DEBUG = env_to_bool('DJANGO_DEBUG', True)
# DEBUG = False
TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test'
# ALLOWED_HOSTS = []
ALLOWED_HOSTS = os.environ.get('DJANGO_ALLOWED_HOSTS', '*').split(',')
ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com']
# django 4.0新增配置
CSRF_TRUSTED_ORIGINS = os.environ.get('DJANGO_CSRF_TRUSTED_ORIGINS', 'http://localhost,http://127.0.0.1').split(',')
CSRF_TRUSTED_ORIGINS = ['http://example.com']
# Application definition
INSTALLED_APPS = [
# 'django.contrib.admin',
'django.contrib.admin.apps.SimpleAdminConfig',
@ -57,6 +65,7 @@ INSTALLED_APPS = [
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
@ -95,6 +104,8 @@ WSGI_APPLICATION = 'djangoblog.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
@ -102,16 +113,15 @@ DATABASES = {
'USER': os.environ.get('DJANGO_MYSQL_USER') or 'root',
'PASSWORD': os.environ.get('DJANGO_MYSQL_PASSWORD') or '123456',
'HOST': os.environ.get('DJANGO_MYSQL_HOST') or '127.0.0.1',
'PORT': int(os.environ.get('DJANGO_MYSQL_PORT') or 3306),
'PORT': int(
os.environ.get('DJANGO_MYSQL_PORT') or 3306),
'OPTIONS': {
'charset': 'utf8mb4',
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
'charset': 'utf8mb4'},
}}
# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
@ -137,31 +147,18 @@ LOCALE_PATHS = (
)
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = False
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic')
# --- MODIFICATION: 自动创建静态文件目录 ---
STATIC_DIR = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = [STATIC_DIR]
# 如果 static 目录不存在,则自动创建
if not os.path.exists(STATIC_DIR):
os.makedirs(STATIC_DIR)
# --- MODIFICATION END ---
# 媒体文件配置 (用户上传的文件,如头像)
# 确保此配置已正确设置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 同样,自动创建媒体文件目录
if not os.path.exists(MEDIA_ROOT):
os.makedirs(MEDIA_ROOT)
HAYSTACK_CONNECTIONS = {
'default': {
@ -171,11 +168,15 @@ HAYSTACK_CONNECTIONS = {
}
# Automatically update searching index
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# Allow user login with username and password
AUTHENTICATION_BACKENDS = [
'accounts.user_login_backend.EmailOrUsernameModelBackend']
STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic')
STATIC_URL = '/static/'
STATICFILES = os.path.join(BASE_DIR, 'static')
AUTH_USER_MODEL = 'accounts.BlogUser'
LOGIN_URL = '/login/'
@ -191,7 +192,6 @@ BOOTSTRAP_COLOR_TYPES = [
PAGINATE_BY = 10
# http cache timeout
CACHE_CONTROL_MAX_AGE = 2592000
# cache setting
CACHES = {
'default': {
@ -215,18 +215,16 @@ BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \
# Email:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = os.environ.get('DJANGO_EMAIL_TLS', 'False') == 'True'
EMAIL_USE_SSL = os.environ.get('DJANGO_EMAIL_SSL', 'True') == 'True'
EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False)
EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True)
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com'
EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465)
EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER')
EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD')
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER
# Setting debug=false did NOT handle except email notifications
ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')]
# WX ADMIN password(Two times md5)
WXADMIN = os.environ.get(
'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7'
@ -302,14 +300,10 @@ STATICFILES_FINDERS = (
# other
'compressor.finders.CompressorFinder',
)
# --- MODIFICATION: 动态设置压缩开关 ---
# 在开发环境(DEBUG=True)关闭压缩,在生产环境(DEBUG=False)开启压缩
COMPRESS_ENABLED = not DEBUG
# --- MODIFICATION END ---
COMPRESS_ENABLED = True
# COMPRESS_OFFLINE = True
COMPRESS_CSS_FILTERS = [
# creates absolute urls from relative ones
'compressor.filters.css_default.CssAbsoluteFilter',
@ -320,6 +314,8 @@ COMPRESS_JS_FILTERS = [
'compressor.filters.jsmin.JSMinFilter'
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')
MEDIA_URL = '/media/'
X_FRAME_OPTIONS = 'SAMEORIGIN'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
@ -345,16 +341,3 @@ ACTIVE_PLUGINS = [
'view_count',
'seo_optimizer'
]
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# ... 其他配置 ...
# 媒体文件(用户上传的文件)的存储根目录
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 媒体文件的 URL 前缀。浏览器通过这个 URL 来访问媒体文件。
MEDIA_URL = '/media/'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 KiB

@ -1,30 +0,0 @@
{% extends 'share_layout/base_account.html' %}
{% load i18n %}
{% load static %}
{% block content %}
<div class="container">
<h2 class="form-signin-heading text-center">{% trans 'forget the password' %}</h2>
<div class="card card-signin">
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
<form class="form-signin" action="{% url 'account:forget_password' %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{{ field.errors }}
{% endfor %}
<input type="button" class="button" id="btn" value="{% trans 'get verification code' %}">
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans 'submit' %}</button>
</form>
</div>
<p class="text-center">
<a href="/">Home Page</a>
|
<a href="{% url "account:login" %}">login page</a>
</p>
</div> <!-- /container -->
{% endblock %}

@ -1,46 +0,0 @@
{% extends 'share_layout/base_account.html' %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="container">
<h2 class="form-signin-heading text-center">Sign in with your Account</h2>
<div class="card card-signin">
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
<form class="form-signin" action="{% url 'account:login' %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{{ field.errors }}
{% endfor %}
<input type="hidden" name="next" value="{{ redirect_to }}">
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<div class="checkbox">
{% comment %}<a class="pull-right">Need help?</a>{% endcomment %}
<label>
<input type="checkbox" value="remember-me" name="remember"> Stay signed in
</label>
</div>
{% load oauth_tags %}
{% load_oauth_applications request%}
</form>
</div>
<p class="text-center">
<a href="{% url "account:register" %}">
{% trans 'Create Account' %}
</a>
|
<a href="/">Home Page</a>
|
<a href="{% url "account:forget_password" %}">
{% trans 'Forget Password' %}
</a>
</p>
</div> <!-- /container -->
{% endblock %}

@ -1,29 +0,0 @@
{% extends 'share_layout/base_account.html' %}
{% load static %}
{% block content %}
<div class="container">
<h2 class="form-signin-heading text-center">Create Your Account</h2>
<div class="card card-signin">
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
<form class="form-signin" action="{% url 'account:register' %}" method="post">
{% csrf_token %}
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{{ field.errors }}
{% endfor %}
<button class="btn btn-lg btn-primary btn-block" type="submit">Create Your Account</button>
</form>
</div>
<p class="text-center">
<a href="{% url "account:login" %}">Sign In</a>
</p>
</div> <!-- /container -->
{% endblock %}

@ -1,27 +0,0 @@
{% extends 'share_layout/base.html' %}
{% load i18n %}
{% block header %}
<title> {{ title }}</title>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<header class="archive-header">
<h2 class="archive-title"> {{ content }}</h2>
</header><!-- .archive-header -->
<br/>
<header class="archive-header" style="text-align: center">
<a href="{% url "account:login" %}">
{% trans 'login' %}
</a>
|
<a href="/">
{% trans 'back to the homepage' %}
</a>
</header><!-- .archive-header -->
</div>
</div>
{% endblock %}

@ -1,60 +0,0 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% load i18n %}
{% block header %}
<title>{% trans 'article archive' %} | {{ SITE_DESCRIPTION }}</title>
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
<meta property="og:type" content="blog"/>
<meta property="og:title" content="{{ SITE_NAME }}"/>
<meta property="og:description" content="{{ SITE_DESCRIPTION }}"/>
<meta property="og:url" content="{{ SITE_BASE_URL }}"/>
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<header class="archive-header">
<p class="archive-title">{% trans 'article archive' %}</p>
</header><!-- .archive-header -->
<div class="entry-content">
{% regroup article_list by pub_time.year as year_post_group %}
<ul>
{% for year in year_post_group %}
<li>{{ year.grouper }} {% trans 'year' %}
{% regroup year.list by pub_time.month as month_post_group %}
<ul>
{% for month in month_post_group %}
<li>{{ month.grouper }} {% trans 'month' %}
<ul>
{% for article in month.list %}
<li><a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</div>
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user 'i' %}
{% endblock %}

@ -1,166 +0,0 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load static %}
{% block header %}
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<!-- 修复:确保标签参数完整 -->
<div id="article-content">
{% load_article_detail article False user %}
</div>
{% if article.type == 'a' %}
<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 %}
{% if prev_article %}
<span class="nav-next"><a href="{{ prev_article.get_absolute_url }}"
rel="next">{{ prev_article.title }} <span
class="meta-nav">&rarr;</span></a></span>
{% endif %}
</nav><!-- .nav-single -->
{% endif %}
<!-- 文章收藏功能 -->
<div style="margin: 20px 0; padding: 10px; border-top: 1px solid #eee; border-bottom: 1px solid #eee;">
{% if user.is_authenticated %}
{# 为按钮添加一个 data-action 属性用于JS判断当前操作 #}
<button id="favorite-btn"
data-article-id="{{ article.id }}"
data-action="{% if article in user.favorite_articles.all %}remove{% else %}add{% endif %}"
style="background: none; border: none; cursor: pointer; color: #333; font-size: 1em;">
{% if article in user.favorite_articles.all %}
<i class="fas fa-bookmark" style="color: #f0ad4e;"></i>
<span>已收藏</span>
{% else %}
<i class="far fa-bookmark"></i>
<span>收藏</span>
{% endif %}
<span id="favorite-count" style="margin-left: 5px;">({{ article.get_favorite_count }})</span>
</button>
{% else %}
<span style="color: #999;">
<i class="far fa-bookmark"></i>
<span>收藏</span>
<span style="margin-left: 5px;">({{ article.get_favorite_count }})</span>
</span>
<p style="margin-top: 5px; font-size: 0.9em; color: #999;">
<a href="{% url "account:login" %}?next={{ request.get_full_path }}">登录</a> 后收藏文章
</p>
{% endif %}
</div>
<!-- 相关推荐文章区域 -->
{% 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' %}
{% else %}
<div class="comments-area">
<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 %}
{% load_sidebar user "p" %}
{% endblock %}
{% block footer %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script>
$(document).ready(function() {
// 收藏功能 JS
$('#favorite-btn').on('click', function() {
var $btn = $(this);
var articleId = $btn.data('article-id');
var action = $btn.data('action'); // 获取当前操作add 或 remove
// 动态生成URL
// 修改后的代码
// 无论 add 还是 remove都使用同一个 URL 名称 article_favorite
var url = "{% url 'blog:article_favorite' article_id=999999999 %}".replace('999999999', articleId);
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
beforeSend: function(xhr) {
xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
},
success: function(data) {
var $icon = $btn.find('i');
var $textSpan = $btn.find('span:eq(0)'); // 找到第一个span'收藏'/'已收藏'
var $countSpan = $('#favorite-count');
// 更新收藏数
$countSpan.text('(' + data.favorite_count + ')');
// 更新按钮状态和文本
if (action === 'add') {
$icon.removeClass('far').addClass('fas').css('color', '#f0ad4e');
$textSpan.text('已收藏');
$btn.data('action', 'remove'); // 切换action状态
} else {
$icon.removeClass('fas').addClass('far').css('color', '#333');
$textSpan.text('收藏');
$btn.data('action', 'add'); // 切换action状态
}
},
error: function(xhr, status, error) {
console.error("收藏操作失败:", error);
// 尝试解析错误信息
var errorMsg = "操作失败,请稍后重试。";
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMsg = xhr.responseJSON.message;
}
alert(errorMsg);
}
});
});
});
</script>
{% endblock %}

@ -1,42 +0,0 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% block header %}
{% if tag_name %}
<title>{{ page_type }}:{{ tag_name }} | {{ SITE_DESCRIPTION }}</title>
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
{% else %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
{% endif %}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
<meta property="og:type" content="blog"/>
<meta property="og:title" content="{{ SITE_NAME }}"/>
<meta property="og:description" content="{{ SITE_DESCRIPTION }}"/>
<meta property="og:url" content="{{ SITE_BASE_URL }}"/>
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
{% if page_type and tag_name %}
<header class="archive-header">
<p class="archive-title">{{ page_type }}<span>{{ tag_name }}</span></p>
</header><!-- .archive-header -->
{% endif %}
{% for article in article_list %}
{% load_article_detail article True user %}
{% endfor %}
{% if is_paginated %}
{% load_pagination_info page_obj page_type tag_name %}
{% endif %}
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user linktype %}
{% endblock %}

@ -1,45 +0,0 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% block header %}
{% if tag_name %}
{% if statuscode == '404' %}
<title>404 NotFound</title>
{% elif statuscode == '403' %}
<title>Permission Denied</title>
{% elif statuscode == '500' %}
<title>500 Error</title>
{% else %}
<title></title>
{% endif %}
{% comment %}<meta name="description" content="{{ page_type }}:{{ tag_name }}"/>{% endcomment %}
{% else %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
{% endif %}
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
<meta property="og:type" content="blog"/>
<meta property="og:title" content="{{ SITE_NAME }}"/>
<meta property="og:description" content="{{ SITE_DESCRIPTION }}"/>
<meta property="og:url" content="{{ SITE_BASE_URL }}"/>
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<header class="archive-header">
<h1 class="archive-title">{{ message }}</h1>
</header><!-- .archive-header -->
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user 'i' %}
{% endblock %}

@ -1,44 +0,0 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% load cache %}
{% block header %}
<title>友情链接 | {{ SITE_DESCRIPTION }}</title>
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
<meta property="og:type" content="blog"/>
<meta property="og:title" content="{{ SITE_NAME }}"/>
<meta property="og:description" content="{{ SITE_DESCRIPTION }}"/>
<meta property="og:url" content="{{ SITE_BASE_URL }}"/>
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<header class="archive-header">
<p class="archive-title">友情链接</p>
</header><!-- .archive-header -->
<div class="entry-content">
<ul>
{% for obj in object_list %}
<li>
<a href="{{ obj.link }}">{{ obj.name }}</a>
</li>
{% endfor %} </ul>
</div>
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar user 'i' %}
{% endblock %}

@ -1,86 +0,0 @@
{% load blog_tags %}
{% load cache %}
{% load i18n %}
<article id="post-{{ article.pk }} "
class="post-{{ article.pk }} post type-post status-publish format-standard hentry">
<header class="entry-header">
<h1 class="entry-title">
{% if isindex %}
{% if article.article_order > 0 %}
<a href="{{ article.get_absolute_url }}"
rel="bookmark">【{% trans 'pin to top' %}】{{ article.title }}</a>
{% else %}
<a href="{{ article.get_absolute_url }}"
rel="bookmark">{{ article.title }}</a>
{% endif %}
{% else %}
{{ article.title }}
{% endif %}
</h1>
<div class="comments-link">
{% if article.comment_status == "o" and open_site_comment %}
<a href="{{ article.get_absolute_url }}#comments" class="ds-thread-count" data-thread-key="3815"
rel="nofollow">
<span class="leave-reply">
{% if article.comment_set and article.comment_set.count %}
{{ article.comment_set.count }} {% trans 'comments' %}
{% else %}
{% trans 'comment' %}
{% endif %}
</span>
</a>
{% endif %}
<div style="float:right">
{{ article.views }} views
</div>
</div><!-- .comments-link -->
<br/>
{% if article.type == 'a' %}
{% if not isindex %}
{% cache 36000 breadcrumb article.pk %}
{% load_breadcrumb article %}
{% endcache %}
{% endif %}
{% endif %}
</header><!-- .entry-header -->
<div class="entry-content" itemprop="articleBody">
{% if isindex %}
{% if isindex %}
{# 如果是列表页isindex=True只显示摘要 #}
{{ article.summary|default:article.body|truncatechars:200|safe }}
{% else %}
{# 如果是详情页isindex=False显示完整内容 #}
{{ article.body|safe }}
{% endif %}
<p class='read-more'><a
href=' {{ article.get_absolute_url }}'>Read more</a></p>
{% else %}
{% if article.show_toc %}
{% get_markdown_toc article.body as toc %}
<b>{% trans 'toc' %}:</b>
{{ toc|safe }}
<hr class="break_line"/>
{% endif %}
<div class="article">
{% if isindex %}
{# 列表页显示摘要或正文前200字符 #}
{{ article.summary|default:article.body|truncatechars:200|safe }}
{% else %}
{# 详情页显示完整正文(因传入的是 False对应详情页 #}
{{ article.body|safe }}
{% endif %}
</div>
{% endif %}
</div><!-- .entry-content -->
{% load_article_metas article user %}
</article><!-- #post -->

@ -1,54 +0,0 @@
{% 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>
{% 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>
{% if t != article.tags.all.last %}
,
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
. {% trans 'by ' %}
<span class="by-author">
<span class="author vcard">
<a class="url fn n" href="{{ article.author.get_absolute_url }}"
{% blocktranslate %}
title="View all articles published by {{ article.author.username }}"
{% endblocktranslate %}
rel="author">
<span itemprop="author" itemscope itemtype="http://schema.org/Person">
<span itemprop="name" itemprop="publisher">
{{ article.author.username }}
</span>
</span>
</a>
</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 class="meta-sep"> | </span>
<span class="views">
{% trans 'Views' %}: <span itemprop="interactionCount">{{ article.views }}</span>
</span>
{% if user.is_superuser %}
<span class="meta-sep"> | </span>
<a href="{{ article.get_admin_url }}">{% trans 'edit' %}</a>
{% endif %}
</footer><!-- .entry-meta -->

@ -1,17 +0,0 @@
{% load i18n %}
<nav id="nav-below" class="navigation" role="navigation">
<h3 class="assistive-text">
{% trans 'article navigation' %}
</h3>
{% if page_obj.has_next and next_url%}
<div class="nav-previous"><a
href="{{ next_url }}"><span
class="meta-nav">&larr;</span> {% trans 'earlier articles' %}</a></div>
{% endif %}
{% if page_obj.has_previous and previous_url %}
<div class="nav-next"><a href="{{ previous_url }}">{% trans 'newer articles' %}
<span
class="meta-nav">→</span></a>
</div>
{% endif %}
</nav><!-- .navigation -->

@ -1,19 +0,0 @@
{% load i18n %}
{% if article_tags_list %}
<div class="panel panel-default">
<div class="panel-heading">
{% trans 'tags' %}
</div>
<div class="panel-body">
{% for url,count,tag,color in article_tags_list %}
<a class="label label-{{ color }}" style="display: inline-block;" href="{{ url }}"
title="{{ tag.name }}">
{{ tag.name }}
<span class="badge">{{ count }}</span>
</a>
{% endfor %}
</div>
</div>
{% endif %}

@ -1,19 +0,0 @@
<ul itemscope itemtype="https://schema.org/BreadcrumbList" class="breadcrumb">
{% for name,url in names %}
<li itemprop="itemListElement" itemscope
itemtype="https://schema.org/ListItem">
<a href="{{ url }}" itemprop="item" >
<span itemprop="name">{{ name }}</span></a>
<meta itemprop="position" content="{{ forloop.counter }}"/>
</li>
{% endfor %}
<li class="active" itemprop="itemListElement" itemscope
itemtype="https://schema.org/ListItem">
<span itemprop="name">{{ title }}</span>
<meta itemprop="position" content="{{ count }}"/>
</li>
</ul>

@ -1,129 +0,0 @@
{% load blog_tags %}
{% load i18n %}
<div id="secondary" class="widget-area" role="complementary">
<aside id="search-2" class="widget widget_search">
<form role="search" method="get" id="searchform" class="searchform" action="/search">
<div>
<label class="screen-reader-text" for="s">{% trans 'search' %}</label>
<input type="text" value="" name="q" id="q"/>
<input type="submit" id="searchsubmit" />
</div>
</form>
</aside>
{% if extra_sidebars %}
{% for sidebar in extra_sidebars %}
<aside class="widget_text widget widget_custom_html"><p class="widget-title">{{ sidebar.name }}</p>
<div class="textwidget custom-html-widget">
{{ sidebar.content|safe }}
</div>
</aside>
{% endfor %}
{% endif %}
<!-- 热门文章模块优化 -->
{% 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 }}" class="hot-article-link">
{{ a.title }}
</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 '分类' %}</p>
<ul>
{% for c in sidebar_categorys %}
<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 '最新评论' %}</p>
<ul id="recentcomments">
{% for c in sidebar_comments %}
<li class="recentcomments">
<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 '最新文章' %}</p>
<ul>
{% 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 '友情链接' %}</p>
<ul class='xoxo blogroll'>
{% for l in sidabar_links %}
<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>
</aside>
{% endif %}
{% if sidebar_tags %}
<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 }}"
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 '欢迎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>
</div>
</aside>
<aside id="meta-3" class="widget widget_meta"><p class="widget-title">{% trans '功能导航' %}</p>
<ul>
<li><a href="/admin/" rel="nofollow">{% trans '管理后台' %}</a></li>
{% if user.is_authenticated %}
<li><a href="{% url "account:logout" %}" rel="nofollow">{% trans '退出登录' %}</a></li>
{% else %}
<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 '轨迹记录' %}</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 '返回顶部' %}"></div>
</div><!-- #secondary -->

@ -1,97 +0,0 @@
<!-- blog/templates/blog/user_favorites.html -->
{% extends 'share_layout/base.html' %}
{% load static %}
{% load blog_tags %}
{% block header %}
<title>我的收藏 | {{ SITE_NAME }}</title>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<article class="hentry">
<header class="entry-header">
<h1 class="entry-title">我的收藏</h1>
<p>这里是你收藏的所有文章。</p>
</header>
<div class="entry-content">
{% if favorite_articles %}
<ul class="article-list">
{% for article in favorite_articles %}
<li>
<h2><a href="{{ article.get_absolute_url }}">{{ article.title }}</a></h2>
<div class="meta">
<time datetime="{{ article.pub_time|date:"c" }}">
{{ article.pub_time|date:"F j, Y" }}
</time>
<span>作者: <a href="{% url 'blog:author_detail' article.author.username %}">{{ article.author.username }}</a></span>
<!-- 修正第 31 行的 URL 名称 -->
<span>分类:<a href="{% url 'blog:category_detail' article.category.slug %}">{{ article.category.name }}</a></span>
</div>
<div class="summary">
{{ article.body|striptags|truncatechars:150 }}
</div>
<a href="{{ article.get_absolute_url }}" class="read-more">阅读全文</a>
</li>
{% endfor %}
</ul>
{% else %}
<p class="no-favorites">你还没有收藏任何文章。快去 <a href="{% url 'blog:index' %}">文章列表</a> 看看吧!</p>
{% endif %}
</div>
</article>
</div>
</div>
{% endblock %}
{% block sidebar %}
{% load_sidebar profile_user "p" %}
{% endblock %}
{% block extra_footer %}
<style>
.article-list {
list-style: none;
padding: 0;
}
.article-list li {
padding: 20px 0;
border-bottom: 1px solid #eee;
}
.article-list li:last-child {
border-bottom: none;
}
.article-list h2 {
margin-top: 0;
}
.meta {
color: #777;
font-size: 0.9em;
margin-bottom: 10px;
}
.summary {
margin-bottom: 15px;
}
.read-more {
display: inline-block;
padding: 5px 10px;
background-color: #007bff;
color: white;
border-radius: 3px;
text-decoration: none;
}
.read-more:hover {
background-color: #0056b3;
color: white;
}
.no-favorites {
text-align: center;
padding: 50px 0;
color: #555;
}
</style>
{% endblock %}

@ -1,230 +0,0 @@
<!-- blog/templates/blog/user_profile_detail.html -->
{% extends 'share_layout/base.html' %}
{% load static %}
{% load blog_tags %} <!-- 添加这一行,加载自定义标签库 -->
{% block header %}
<title>{{ profile.user.username }}'s Profile | {{ SITE_NAME }}</title>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<article class="hentry">
<header class="entry-header profile-header">
<div class="profile-avatar">
{% if profile.avatar %}
<img src="{{ profile.avatar.url }}" alt="{{ profile.user.username }}'s avatar">
{% else %}
<img src="{% static 'blog/images/default-avatar.png' %}" alt="Default Avatar">
{% endif %}
</div>
<div class="profile-info">
<h1 class="entry-title">{{ profile.user.username }}</h1>
<div class="profile-meta">
<span>Joined on {{ profile.created_at|date:"F j, Y" }}</span>
{% if user.is_authenticated and user == profile.user %}
<a href="{% url 'blog:user_profile_update' %}" class="edit-profile-btn">
Edit Profile
</a>
{% endif %}
</div>
</div>
</header>
<div class="entry-content profile-content">
{% if profile.bio %}
<section class="profile-bio">
<h3>About Me</h3>
<p>{{ profile.bio|linebreaks }}</p>
</section>
{% endif %}
{% if profile.website or profile.github or profile.twitter or profile.weibo %}
<section class="profile-links">
<h3>Connect with Me</h3>
<ul>
{% if profile.website %}
<li><a href="{{ profile.website }}" target="_blank" rel="noopener noreferrer"><i class="fas fa-globe"></i> {{ profile.website }}</a></li>
{% endif %}
{% if profile.github %}
<li><a href="{{ profile.github }}" target="_blank" rel="noopener noreferrer"><i class="fab fa-github"></i> GitHub</a></li>
{% endif %}
{% if profile.twitter %}
<li><a href="{{ profile.twitter }}" target="_blank" rel="noopener noreferrer"><i class="fab fa-twitter"></i> Twitter</a></li>
{% endif %}
{% if profile.weibo %}
<li><a href="{{ profile.weibo }}" target="_blank" rel="noopener noreferrer"><i class="fab fa-weibo"></i> Weibo</a></li>
{% endif %}
</ul>
</section>
{% endif %}
<!-- ==================== 新增:我的收藏链接 ==================== -->
{% if user.is_authenticated and user == profile.user %}
<section class="profile-actions">
<a href="{% url 'blog:user_favorites' %}" class="btn-favorite">
<i class="fas fa-heart"></i> 我的收藏 ({{ user.favorite_articles.count }})
</a>
</section>
{% endif %}
<!-- ========================================================== -->
</div>
</article>
{% if user_articles %}
<section class="profile-articles">
<h2>Articles by {{ profile.user.username }} <span>({{ user_articles|length }})</span></h2>
<ul>
{% for article in user_articles %}
<li>
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
<time datetime="{{ article.pub_time|date:"c" }}">{{ article.pub_time|date:"F j, Y" }}</time>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
</div>
</div>
{% endblock %}
{% block sidebar %}
{% load_sidebar user "p" %}
{% endblock %}
{% block extra_footer %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
<style>
/* 个人资料头部布局 */
.profile-header {
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
padding-bottom: 20px;
margin-bottom: 30px; /* 增加了下边距,与内容区隔开 */
}
/* 头像样式调整 */
.profile-avatar img {
width: 80px; /* 从 120px 调整为 80px */
height: 80px; /* 从 120px 调整为 80px */
border-radius: 50%;
object-fit: cover;
margin-right: 20px; /* 稍微减少了右边距 */
border: 3px solid #fff;
box-shadow: 0 2px 5px rgba(0,0,0,0.1); /* 增强了一点阴影 */
}
/* 个人信息区域 */
.profile-info h1 {
margin-bottom: 5px; /* 减少了标题下边距 */
}
.edit-profile-btn {
display: inline-block;
margin-top: 10px;
padding: 5px 15px;
background-color: #007bff;
color: white;
border-radius: 4px;
text-decoration: none;
font-size: 0.9em; /* 字体稍小 */
}
.edit-profile-btn:hover {
background-color: #0056b3;
color: white;
}
/* 内容区块通用样式 */
.profile-bio, .profile-links, .profile-articles, .profile-actions { /* 新增 .profile-actions */
margin-bottom: 30px;
padding: 20px;
background: #f9f9f9;
border-radius: 4px;
border: 1px solid #eee; /* 增加了一个边框 */
}
/* ==================== 新增:我的收藏链接样式 ==================== */
.btn-favorite {
display: inline-flex; /* 使用 flex 布局让图标和文字垂直居中 */
align-items: center;
padding: 10px 20px;
background-color: #f0ad4e; /* 使用一个醒目的颜色,如橙色 */
color: white;
border-radius: 4px;
text-decoration: none;
font-weight: bold;
transition: background-color 0.2s ease;
}
.btn-favorite i {
margin-right: 8px;
font-size: 1.1em;
}
.btn-favorite:hover {
background-color: #ec971f; /* hover 时颜色加深 */
color: white;
}
/* ================================================================ */
/* 链接列表 */
.profile-links ul {
list-style: none;
padding: 0;
}
.profile-links li {
margin-bottom: 10px;
}
.profile-links a {
display: flex;
align-items: center;
color: #007bff;
text-decoration: none;
transition: color 0.2s; /* 增加了过渡效果 */
}
.profile-links a:hover {
text-decoration: underline;
color: #0056b3; /* hover 时颜色加深 */
}
.profile-links i {
margin-right: 10px;
font-size: 1.2em;
width: 20px;
text-align: center;
color: #555; /* 图标颜色稍微暗一点 */
}
/* 文章列表 */
.profile-articles ul {
list-style: none;
padding: 0;
}
.profile-articles li {
padding: 10px 0;
border-bottom: 1px dotted #ddd;
display: flex;
justify-content: space-between;
align-items: center; /* 垂直居中对齐 */
}
.profile-articles li:last-child {
border-bottom: none; /* 去掉最后一个的边框 */
}
.profile-articles li time {
color: #777;
font-size: 0.9em;
}
</style>
{% endblock %}

@ -1,104 +0,0 @@
<!-- blog/templates/blog/user_profile_update.html -->
{% extends 'share_layout/base.html' %}
{% load static %}
{% load blog_tags %} <!-- 加载自定义标签库 -->
{% block header %}
<title>Edit Profile | {{ SITE_NAME }}</title>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<article class="hentry">
<header class="entry-header">
<h1 class="entry-title">Edit Your Profile</h1>
</header>
<div class="entry-content">
<form enctype="multipart/form-data" method="post" action="{% url 'blog:user_profile_update' %}">
{% csrf_token %}
{% if form.errors %}
<div class="alert alert-danger">
<strong>Please correct the errors below.</strong>
</div>
{% endif %}
<div class="form-group">
<label for="{{ form.avatar.id_for_label }}">Current Avatar:</label><br>
{% if user.profile.avatar %}
<img src="{{ user.profile.avatar.url }}" alt="Current Avatar" style="width: 100px; border-radius: 50%; margin-bottom: 10px;">
{% else %}
<img src="{% static 'blog/images/default-avatar.png' %}" alt="Default Avatar" style="width: 100px; border-radius: 50%; margin-bottom: 10px;">
{% endif %}
<br>
{{ form.avatar.label_tag }} {{ form.avatar }}
<small class="form-text text-muted">{{ form.avatar.help_text }}</small>
{{ form.avatar.errors }}
</div>
<div class="form-group">
{{ form.bio.label_tag }}
{{ form.bio }}
{{ form.bio.errors }}
</div>
<div class="form-group">
{{ form.website.label_tag }}
{{ form.website }}
{{ form.website.errors }}
</div>
<div class="form-group">
{{ form.github.label_tag }}
{{ form.github }}
{{ form.github.errors }}
</div>
<div class="form-group">
{{ form.twitter.label_tag }}
{{ form.twitter }}
{{ form.twitter.errors }}
</div>
<div class="form-group">
{{ form.weibo.label_tag }}
{{ form.weibo }}
{{ form.weibo.errors }}
</div>
<button type="submit" class="btn btn-primary">Save Changes</button>
<a href="{% url 'blog:user_profile' username=user.username %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</article>
</div>
</div>
{% endblock %}
{% block sidebar %}
{% load_sidebar user "p" %}
{% endblock %}
{% block extra_footer %}
<style>
.form-group { margin-bottom: 20px; }
label { font-weight: bold; display: block; margin-bottom: 5px; }
input[type="text"], input[type="url"], textarea {
width: 100%; padding: 8px 12px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;
}
textarea { resize: vertical; }
.btn {
display: inline-block; padding: 10px 16px; margin-bottom: 0; font-size: 14px; font-weight: 400; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; cursor: pointer; background-image: none; border: 1px solid transparent; border-radius: 4px; text-decoration: none;
}
.btn-primary { color: #fff; background-color: #337ab7; border-color: #2e6da4; }
.btn-primary:hover { background-color: #286090; border-color: #204d74; color: #fff; }
.btn-secondary { color: #333; background-color: #fff; border-color: #ccc; }
.btn-secondary:hover { background-color: #e6e6e6; border-color: #adadad; color: #333; }
.alert { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; }
.alert-danger { color: #a94442; background-color: #f2dede; border-color: #ebccd1; }
</style>
{% endblock %}

@ -1,37 +0,0 @@
{% load blog_tags %}
<li class="comment even thread-even depth-{{ depth }} parent" id="comment-{{ comment_item.pk }}">
<div id="div-comment-{{ comment_item.pk }}" class="comment-body">
<div class="comment-author vcard">
<img alt="{{ comment_item.author.username }}的头像"
src="{{ comment_item.author.email|gravatar_url:150 }}"
srcset="{{ comment_item.author.email|gravatar_url:150 }}"
class="avatar avatar-96 photo"
loading="lazy"
decoding="async"
style="max-width:100%;height:auto;">
<cite class="fn">
<a rel="nofollow"
{% if comment_item.author.is_superuser %}
href="{{ comment_item.author.get_absolute_url }}"
{% else %}
href="#"
{% endif %}
rel="external nofollow"
class="url">{{ comment_item.author.username }}
</a>
</cite>
</div>
<div class="comment-meta commentmetadata">
<div>{{ comment_item.creation_time }}</div>
<div>回复给:@{{ comment_item.author.parent_comment.username }}</div>
</div>
<p>{{ comment_item.body|escape|comment_markdown }}</p>
<div class="reply"><a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)"
onclick="do_reply({{ comment_item.pk }})"
aria-label="回复给{{ comment_item.author.username }}">回复</a></div>
</div>
</li><!-- #comment-## -->

@ -1,57 +0,0 @@
{% load blog_tags %}
<li class="comment even thread-even depth-{{ depth }} parent" id="comment-{{ comment_item.pk }}"
style="margin-left: {% widthratio depth 1 3 %}rem">
<div id="div-comment-{{ comment_item.pk }}" class="comment-body">
<div class="comment-author vcard">
<img alt="{{ comment_item.author.username }}的头像"
src="{{ comment_item.author.email|gravatar_url:150 }}"
srcset="{{ comment_item.author.email|gravatar_url:150 }}"
class="avatar avatar-96 photo"
loading="lazy"
decoding="async"
style="max-width:100%;height:auto;">
<cite class="fn">
<a rel="nofollow"
{% if comment_item.author.is_superuser %}
href="{{ comment_item.author.get_absolute_url }}"
{% else %}
href="#"
{% endif %}
rel="external nofollow"
class="url">{{ comment_item.author.username }}
</a>
</cite>
</div>
<div class="comment-meta commentmetadata">
{{ comment_item.creation_time }}
</div>
<p>
{% if comment_item.parent_comment %}
<div>回复 <a
href="#comment-{{ comment_item.parent_comment.pk }}">@{{ comment_item.parent_comment.author.username }}</a>
</div>
{% endif %}
</p>
<p>{{ comment_item.body|escape|comment_markdown }}</p>
<div class="reply"><a rel="nofollow" class="comment-reply-link"
href="javascript:void(0)" data-pk="{{ comment_item.pk }}"
aria-label="回复给{{ comment_item.author.username }}">回复</a></div>
</div>
</li><!-- #comment-## -->
{% query article_comments parent_comment=comment_item as cc_comments %}
{% for cc in cc_comments %}
{% with comment_item=cc template_name="comments/tags/comment_item_tree.html" %}
{% if depth >= 1 %}
{% include template_name %}
{% else %}
{% with depth=depth|add:1 %}
{% include template_name %}
{% endwith %}
{% endif %}
{% endwith %}
{% endfor %}

@ -1,45 +0,0 @@
<dev>
<section id="comments" class="themeform">
{% load blog_tags %}
{% load comments_tags %}
{% load cache %}
<ul class="comment-tabs group">
<li class="active"><a href="#commentlist-container"><i
class="fa fa-comments-o"></i>评论<span>{{ comment_count }}</span></a></li>
</ul>
{% if article_comments %}
<div id="commentlist-container" class="comment-tab" style="display: block;">
<ol class="commentlist">
{# {% query article_comments parent_comment=None as parent_comments %}#}
{% for comment_item in p_comments %}
{% with 0 as depth %}
{% include "comments/tags/comment_item_tree.html" %}
{% endwith %}
{% endfor %}
</ol><!--/.commentlist-->
<div class="navigation">
<nav class="nav-single">
{% if comment_prev_page_url %}
<div class="nav-previous">
<span><a href="{{ comment_prev_page_url }}" rel="prev"><span
class="meta-nav">←</span> 上一页</a></span>
</div>
{% endif %}
{% if comment_next_page_url %}
<div class="nav-next">
<span><a href="{{ comment_next_page_url }}" rel="next">下一页 <span
class="meta-nav">→</span></a></span>
</div>
{% endif %}
</nav>
</div>
<br/>
</div>
{% endif %}
</section>
</dev>

@ -1,33 +0,0 @@
<div id="comments" class="comments-area">
<div id="respond" class="comment-respond">
<h3 id="reply-title" class="comment-reply-title">发表评论
<small><a rel="nofollow" id="cancel-comment-reply-link" href="/wordpress/?p=3786#respond"
style="display:none;">取消回复</a></small>
</h3>
<form action="{% url 'comments:postcomment' article.pk %}" method="post" id="commentform"
class="comment-form">{% csrf_token %}
<p class="comment-form-comment">
{{ form.body.label_tag }}
{{ form.body }}
{{ form.body.errors }}
</p>
{{ form.parent_comment_id }}
<div class="form-submit">
{% if COMMENT_NEED_REVIEW %}
<span class="comment-markdown"> 支持markdown评论经审核后才会显示。</span>
{% else %}
<span class="comment-markdown"> 支持markdown。</span>
{% endif %}
<input name="submit" type="submit" id="submit" class="submit" value="发表评论"/>
<small class="cancel-comment" id="cancel_comment" style="display: none">
<a href="javascript:void(0)" id="cancel-comment-reply-link" onclick="cancel_reply()">取消回复</a>
</small>
</div>
</form>
</div><!-- #respond -->
</div><!-- #comments .comments-area -->

@ -1,22 +0,0 @@
{% extends 'share_layout/base.html' %}
{% block header %}
<title> {{ title }}</title>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
<header class="archive-header">
<h2 class="archive-title"> {{ content }}</h2>
</header><!-- .archive-header -->
<br/>
<header class="archive-header" style="text-align: center">
<a href="{% url "account:login" %}">登录</a>
|
<a href="/">回到首页</a>
</header><!-- .archive-header -->
</div>
</div>
{% endblock %}

@ -1,13 +0,0 @@
{% load i18n %}
<div class="widget-login">
{% if apps %}
<small>
{% trans 'quick login' %}:
</small>
{% for icon,url in apps %}
<a href="{{ url }}" rel="nofollow">
<span class="icon-sn-{{ icon }}"></span>
</a>
{% endfor %}
{% endif %}
</div>

@ -1,46 +0,0 @@
{% extends 'share_layout/base_account.html' %}
{% load static %}
{% block content %}
<div class="container">
<h2 class="form-signin-heading text-center">绑定您的邮箱账号</h2>
<div class="card card-signin">
{% if picture %}
<img class="img-circle profile-img" src="{{ picture }}" alt="">
{% else %}
<img class="img-circle profile-img" src="{% static 'blog/img/avatar.png' %}" alt="">
{% endif %}
<form class="form-signin" action="" method="post">
{% csrf_token %}
{% comment %}<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword" class="form-control" placeholder="Password" required>{% endcomment %}
{{ form.non_field_errors }}
{% for field in form %}
{{ field }}
{{ field.errors }}
{% endfor %}
<button class="btn btn-lg btn-primary btn-block" type="submit">提交</button>
{% comment %}
<div class="checkbox">
<a class="pull-right">Need help?</a>
<label>
<input type="checkbox" value="remember-me"> Stay signed in
</label>
</div>
{% endcomment %}
</form>
</div>
<p class="text-center">
<a href="{% url "account:login" %}">登录</a>
</p>
</div> <!-- /container -->
{% endblock %}

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>记录日期</title>
</head>
<body>
<ul>
{% for date in results %}
<li>
<a href="{% url 'owntracks:show_maps' %}?date={{ date }}" target="_blank">{{ date }}</a>
</li>
{% endfor %}
</ul>
</body>
</html>

@ -1,135 +0,0 @@
<!doctype html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<style>
html,
body,
#container {
width: 100%;
height: 100%;
margin: 0px;
}
#loadingTip {
position: absolute;
z-index: 9999;
top: 0;
left: 0;
padding: 3px 10px;
background: red;
color: #fff;
font-size: 14px;
}
</style>
<title>运动轨迹</title>
</head>
<body>
<div id="container"></div>
<script type="text/javascript" src='//webapi.amap.com/maps?v=1.4.4&key=9c89950bdfbcecd46f814309384655cd'></script>
<!-- UI组件库 1.0 -->
<script src="//webapi.amap.com/ui/1.0/main.js?v=1.0.11"></script>
<script type="text/javascript">
//创建地图
var map = new AMap.Map('container', {
zoom: 4
});
AMapUI.load(['ui/misc/PathSimplifier', 'lib/$'], function (PathSimplifier, $) {
if (!PathSimplifier.supportCanvas) {
alert('当前环境不支持 Canvas');
return;
}
//just some colors
var colors = [
"#3366cc", "#dc3912", "#ff9900", "#109618", "#990099", "#0099c6", "#dd4477", "#66aa00",
"#b82e2e", "#316395", "#994499", "#22aa99", "#aaaa11", "#6633cc", "#e67300", "#8b0707",
"#651067", "#329262", "#5574a6", "#3b3eac"
];
var pathSimplifierIns = new PathSimplifier({
zIndex: 100,
//autoSetFitView:false,
map: map, //所属的地图实例
getPath: function (pathData, pathIndex) {
return pathData.path;
},
getHoverTitle: function (pathData, pathIndex, pointIndex) {
if (pointIndex >= 0) {
//point
return pathData.name + ',点:' + pointIndex + '/' + pathData.path.length;
}
return pathData.name + ',点数量' + pathData.path.length;
},
renderOptions: {
pathLineStyle: {
dirArrowStyle: true
},
getPathStyle: function (pathItem, zoom) {
var color = colors[pathItem.pathIndex % colors.length],
lineWidth = Math.round(4 * Math.pow(1.1, zoom - 3));
return {
pathLineStyle: {
strokeStyle: color,
lineWidth: lineWidth
},
pathLineSelectedStyle: {
lineWidth: lineWidth + 2
},
pathNavigatorStyle: {
fillStyle: color
}
};
}
}
});
window.pathSimplifierIns = pathSimplifierIns;
$('<div id="loadingTip">加载数据,请稍候...</div>').appendTo(document.body);
$.getJSON('/owntracks/get_datas?date={{ date }}', function (d) {
if (!d || !d.length) {
$("#loadingTip").text("没有数据...")
return;
}
$('#loadingTip').remove();
pathSimplifierIns.setData(d);
//initRoutesContainer(d);
function onload() {
pathSimplifierIns.renderLater();
}
function onerror(e) {
alert('图片加载失败!');
}
d.forEach(function (item, index) {
var navg1 = pathSimplifierIns.createPathNavigator(index, {
loop: true,
speed: 1000,
});
navg1.start();
})
});
});
</script>
</body>
</html>

@ -1,3 +0,0 @@
{{ object.title }}
{{ object.author.username }}
{{ object.body }}

@ -1,66 +0,0 @@
{% extends 'share_layout/base.html' %}
{% load blog_tags %}
{% block header %}
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
<meta name="description" content="{{ SITE_SEO_DESCRIPTION }}"/>
<meta name="keywords" content="{{ SITE_KEYWORDS }}"/>
<meta property="og:type" content="blog"/>
<meta property="og:title" content="{{ SITE_NAME }}"/>
<meta property="og:description" content="{{ SITE_DESCRIPTION }}"/>
<meta property="og:url" content="{{ SITE_BASE_URL }}"/>
<meta property="og:site_name" content="{{ SITE_NAME }}"/>
{% endblock %}
{% block content %}
<div id="primary" class="site-content">
<div id="content" role="main">
{% if query %}
<header class="archive-header">
{% if suggestion %}
<h2 class="archive-title">
已显示<span style="color: red"> “{{ suggestion }}” </span>的搜索结果。&nbsp;&nbsp;
仍然搜索:<a style="text-transform: none;" href="/search/?q={{ query }}&is_suggest=no">{{ query }}</a> <br>
</h2>
{% else %}
<h2 class="archive-title">
搜索:<span style="color: red">{{ query }} </span> &nbsp;&nbsp;
</h2>
{% endif %}
</header><!-- .archive-header -->
{% endif %}
{% if query and page.object_list %}
{% for article in page.object_list %}
{% load_article_detail article.object True user %}
{% endfor %}
{% if page.has_previous or page.has_next %}
<nav id="nav-below" class="navigation" role="navigation">
<h3 class="assistive-text">文章导航</h3>
{% if page.has_previous %}
<div class="nav-previous"><a
href="?q={{ query }}&amp;page={{ page.previous_page_number }}"><span
class="meta-nav">&larr;</span> 早期文章</a></div>
{% endif %}
{% if page.has_next %}
<div class="nav-next"><a href="?q={{ query }}&amp;page={{ page.next_page_number }}">较新文章
<span
class="meta-nav">→</span></a>
</div>
{% endif %}
</nav><!-- .navigation -->
{% endif %}
{% else %}
<header class="archive-header">
<h1 class="archive-title">哎呀,关键字:<span>{{ query }}</span>没有找到结果,要不换个词再试试?</h1>
</header><!-- .archive-header -->
{% endif %}
</div><!-- #content -->
</div><!-- #primary -->
{% endblock %}
{% block sidebar %}
{% load_sidebar request.user 'i' %}
{% endblock %}

@ -1,6 +0,0 @@
<aside id="text-2" class="widget widget_text"><h3 class="widget-title">Google AdSense</h3>
<div class="textwidget">
{{ GOOGLE_ADSENSE_CODES }}
</div>
</aside>

@ -1,157 +0,0 @@
{% load static %}
{% load cache %}
{% load i18n %}
{% load compress %}
<!DOCTYPE html>
<!--[if IE 7]>
<html class="ie ie7" lang="zh-CN"
prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
<![endif]-->
<!--[if IE 8]>
<html class="ie ie8" lang="zh-CN"
prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
<![endif]-->
<!--[if !(IE 7) & !(IE 8)]><!-->
<html lang="zh-CN" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
<!--<![endif]-->
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="format-detection" content="telephone=no"/>
<meta name="theme-color" content="#21759b"/>
{% load blog_tags %}
{% head_meta %}
{% block header %}
<!-- SEO插件会自动生成title、description、keywords等标签 -->
{% endblock %}
<link rel="profile" href="http://gmpg.org/xfn/11"/>
<!-- DNS预解析 -->
<link rel="dns-prefetch" href="//cdn.mathjax.org"/>
<link rel="dns-prefetch" href="//cdn.jsdelivr.net"/>
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin/>
<!--[if lt IE 9]>
<script src="{% static 'blog/js/html5.js' %}" type="text/javascript"></script>
<![endif]-->
<!-- RSS和图标 -->
<link rel="alternate" type="application/rss+xml" title="{{ SITE_NAME }} &raquo; Feed" href="/feed"/>
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"/>
<link rel="icon" href="/favicon.ico" type="image/x-icon"/>
<link rel="apple-touch-icon" href="/favicon.ico"/>
<!-- 本地字体加载 -->
<link rel="stylesheet" href="{% static 'blog/fonts/open-sans.css' %}">
<!-- 新增:阅读进度条样式 -->
<style>
#reading-progress-bar {
position: fixed;
top: 0;
left: 0;
height: 3px;
background-color: #007bff; /* 你可以更改进度条颜色 */
z-index: 9999; /* 确保在最上层 */
width: 0%;
transition: width 0.1s ease; /* 平滑过渡效果 */
}
</style>
{% compress css %}
<link rel='stylesheet' id='twentytwelve-style-css' href='{% static 'blog/css/style.css' %}' type='text/css'
media='all'/>
<link href="{% static 'blog/css/oauth_style.css' %}" rel="stylesheet">
{% comment %}<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>{% endcomment %}
<!--[if lt IE 9]>
<link rel='stylesheet' id='twentytwelve-ie-css' href='{% static 'blog/css/ie.css' %}' type='text/css' media='all' />
<![endif]-->
<link rel="stylesheet" href="{% static 'pygments/default.css' %}"/>
<link rel="stylesheet" href="{% static 'blog/css/nprogress.css' %}">
{% block compress_css %}
{% endblock %}
{% endcompress %}
{% if GLOBAL_HEADER %}
{{ GLOBAL_HEADER|safe }}
{% endif %}
</head>
<body class="home blog custom-font-enabled">
<!-- 新增:阅读进度条 DOM 元素 -->
<div id="reading-progress-bar"></div>
<div id="page" class="hfeed site">
<header id="masthead" class="site-header" role="banner">
<hgroup>
<h1 class="site-title"><a href="/" title="{{ SITE_NAME }}" rel="home">{{ SITE_NAME }}</a>
</h1>
<h2 class="site-description">{{ SITE_DESCRIPTION }}</h2>
</hgroup>
{% load i18n %}
{% include 'share_layout/nav.html' %}
</header><!-- #masthead -->
<div id="main" class="wrapper">
{% block content %}
{% endblock %}
{% block sidebar %}
{% endblock %}
</div><!-- #main .wrapper -->
{% include 'share_layout/footer.html' %}
</div><!-- #page -->
<!-- JavaScript资源 -->
{% compress js %}
<script src="{% static 'blog/js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'blog/js/nprogress.js' %}"></script>
<script src="{% static 'blog/js/blog.js' %}"></script>
<script src="{% static 'blog/js/navigation.js' %}"></script>
{% block compress_js %}
{% endblock %}
{% endcompress %}
<!-- MathJax智能加载器 -->
<script src="{% static 'blog/js/mathjax-loader.js' %}" async defer></script>
<!-- 新增:阅读进度条 JavaScript -->
<script>
(function($) {
$(document).ready(function() {
var $progressBar = $('#reading-progress-bar');
var $articleContent = $('#article-content'); // 假设文章内容容器的ID是 'article-content'
// 仅在文章详情页执行
if ($articleContent.length > 0) {
var articleTop = $articleContent.offset().top;
var articleHeight = $articleContent.outerHeight();
var windowHeight = $(window).height();
var scrollableDistance = articleHeight - windowHeight;
$(window).on('scroll', function() {
var scrollPosition = $(window).scrollTop();
// 计算阅读进度百分比
var scrollPercent = (scrollPosition - articleTop) / scrollableDistance;
// 确保百分比在 0 到 1 之间
scrollPercent = Math.max(0, Math.min(1, scrollPercent));
// 更新进度条宽度
$progressBar.css('width', (scrollPercent * 100) + '%');
});
// 初始化时触发一次滚动事件,以设置初始状态
$(window).trigger('scroll');
}
});
})(jQuery);
</script>
{% block footer %}
{% endblock %}
</body>
</html>

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html>
<head>
{% load static %}
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="../../favicon.ico">
<meta name="robots" content="noindex">
<title>{{ SITE_NAME }} | {{ SITE_DESCRIPTION }}</title>
<link href="{% static 'account/css/account.css' %}" rel="stylesheet">
{% load compress %}
{% compress css %}
<!-- Bootstrap core CSS -->
<link href="{% static 'assets/css/bootstrap.min.css' %}" rel="stylesheet">
<link href="{% static 'blog/css/oauth_style.css' %}" rel="stylesheet">
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<link href="{% static 'assets/css/ie10-viewport-bug-workaround.css' %}" rel="stylesheet">
<!-- TODC Bootstrap core CSS -->
<link href="{% static 'assets/css/todc-bootstrap.min.css' %}" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="{% static 'assets/css/signin.css' %}" rel="stylesheet">
{% endcompress %}
{% compress js %}
<script src="{% static 'assets/js/ie10-viewport-bug-workaround.js' %}"></script>
<script src="{% static 'assets/js/ie-emulation-modes-warning.js' %}"></script>
{% endcompress %}
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
{% block content %}
{% endblock %}
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
</body>
<script type="text/javascript" src="{% static 'blog/js/jquery-3.6.0.min.js' %}"></script>
<script src="{% static 'account/js/account.js' %}" type="text/javascript"></script>
</html>

@ -1,56 +0,0 @@
<footer id="colophon" role="contentinfo">
<div class="site-info" style="text-align: center">
Copyright&copy;&nbsp;{{ CURRENT_YEAR }}&nbsp;
<a href="/" target="blank">{{ SITE_NAME }}</a>
&nbsp;|&nbsp;
<a href="/sitemap.xml" title="SiteMap" target="_blank">
SiteMap
</a>
&nbsp;|&nbsp;
<a href="/feed" title="RSS Feed" target="_blank">
RSS Feed
</a>
&nbsp;|&nbsp;
<a href="/links.html" title="友情链接" rel="nofollow" target="_blank">
友情链接
</a>
|&nbsp; Hosting On&nbsp;
<a href="https://www.linode.com/?r=b0d38794d05ef8816b357a929106e89b7c6452f9" target="blank" rel="nofollow">Linode</a>
|&nbsp;
<a href="https://tongji.baidu.com/sc-web/3478620/home/ico?siteId=11261596" target="_blank"
rel="nofollow">百度统计</a>
</div>
<div class="site-info" style="text-align: center">
Powered by
<a href="https://www.djangoproject.com/" rel="nofollow" target="blank">Django</a>
&nbsp;|&nbsp;
<a href="https://github.com/liangliangyy/DjangoBlog" rel="nofollow" target="blank">liangliangyy</a>
|
<a href="https://www.lylinux.net" target="blank">lylinux</a>
|
本页面加载耗时:<!!LOAD_TIMES!!>s
</div>
{% if BEIAN_CODE %}
<div class="site-info" style="text-align: center">
<a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">
<p style=" height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">
{{ BEIAN_CODE }}
</p>
</a>
{% if BEIAN_CODE_GONGAN and SHOW_GONGAN_CODE %}
{{ BEIAN_CODE_GONGAN |safe }}
{% endif %}
</div>
{% endif %}
{% if ANALYTICS_CODE %}
{{ ANALYTICS_CODE| safe }}
{% endif %}
{% if GLOBAL_FOOTER %}
{{ GLOBAL_FOOTER|safe }}
{% endif %}
</footer><!-- #colophon -->

@ -1,131 +0,0 @@
{% load i18n %}
{% load blog_tags %} <!-- 确保 blog_tags 已加载 -->
<nav id="site-navigation" class="main-navigation" role="navigation">
<div class="menu-%e8%8f%9c%e5%8d%95-container">
<ul id="menu-%e8%8f%9c%e5%8d%95" class="nav-menu">
<li id="menu-item-3498"
class="menu-item menu-item-type-custom menu-item-object-custom current-menu-item current_page_item menu-item-home menu-item-3498">
<a href="/">{% trans 'index' %}</a></li>
{% query nav_category_list parent_category=None as root_categorys %}
{% for node in root_categorys %}
{% include 'share_layout/nav_node.html' %}
{% endfor %}
{% if nav_pages %}
{% for node in nav_pages %}
<li id="menu-item-{{ node.pk }}"
class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-{{ node.pk }}">
<a href="{{ node.get_absolute_url }}">{{ node.title }}</a>
</li>
{% endfor %}
{% endif %}
<li class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children">
<a href="{% url "blog:archives" %}">{% trans 'Article archive' %}</a>
</li>
<!-- 新增:用户菜单 -->
<li class="menu-item menu-item-type-custom menu-item-object-custom menu-item-has-children user-menu">
{% if user.is_authenticated %}
<a href="javascript:void(0);">
{% if user.profile.avatar %}
<img src="{{ user.profile.avatar.url }}" alt="{{ user.username }}" class="avatar">
{% else %}
<i class="fas fa-user"></i>
{% endif %}
{{ user.username }}
</a>
<ul class="sub-menu">
<li class="menu-item">
<a href="{% url 'blog:user_profile' username=user.username %}">
<i class="fas fa-id-card"></i> {% trans 'My Profile' %}
</a>
</li>
<li class="menu-item">
<a href="{% url 'blog:user_profile_update' %}">
<i class="fas fa-edit"></i> {% trans 'Edit Profile' %}
</a>
</li>
<li class="menu-item">
<a href="{% url 'account:logout' %}"> <!-- 假设你的登出URL名称是 account:logout -->
<i class="fas fa-sign-out-alt"></i> {% trans 'Logout' %}
</a>
</li>
</ul>
{% else %}
<a href="{% url 'account:login' %}"> <!-- 假设你的登录URL名称是 account:login -->
<i class="fas fa-sign-in-alt"></i> {% trans 'Login / Register' %}
</a>
{% endif %}
</li>
<!-- 用户菜单结束 -->
</ul>
</div>
</nav><!-- #site-navigation -->
<!-- 新增:引入 Font Awesome 用于显示图标 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
<!-- 新增:一些简单的 CSS 来美化用户菜单 -->
<style>
/* 让用户菜单靠右浮动 */
.nav-menu .user-menu {
float: right;
position: relative;
}
/* 美化头像 */
.nav-menu .user-menu .avatar {
width: 24px;
height: 24px;
border-radius: 50%;
vertical-align: middle;
margin-right: 8px;
}
/* 子菜单样式 */
.nav-menu .user-menu .sub-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
background: #fff;
min-width: 180px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 10px 0;
z-index: 1000;
border-radius: 4px;
}
.nav-menu .user-menu:hover .sub-menu {
display: block;
}
.nav-menu .user-menu .sub-menu li {
padding: 0;
margin: 0;
list-style: none;
}
.nav-menu .user-menu .sub-menu a {
display: block;
padding: 10px 20px;
color: #333;
text-decoration: none;
white-space: nowrap;
}
.nav-menu .user-menu .sub-menu a:hover {
background-color: #f5f5f5;
color: #007bff;
}
.nav-menu .user-menu .sub-menu i {
width: 20px; /* 图标宽度固定,使文字对齐 */
text-align: center;
margin-right: 8px;
}
</style>

@ -1,19 +0,0 @@
<li id="menu-item-{{ node.pk }}"
class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-{{ node.pk }}">
<a href="{{ node.get_absolute_url }}">{{ node.name }}</a>
{% load blog_tags %}
{% query nav_category_list parent_category=node as child_categorys %}
{% if child_categorys %}
<ul class="sub-menu">
{% for child in child_categorys %}
{% with node=child template_name="share_layout/nav_node.html" %}
{% include template_name %}
{% endwith %}
{% endfor %}
</ul>
{% endif %}
</li>
Loading…
Cancel
Save