diff --git a/.idea/DjangoBlog-yxy_branch.iml b/.idea/DjangoBlog-yxy_branch.iml
new file mode 100644
index 00000000..9b074581
--- /dev/null
+++ b/.idea/DjangoBlog-yxy_branch.iml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index db8786c0..ecfd11f6 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,7 +1,7 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 3d4e23c6..9d79efe6 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 35eb1ddf..94a25f7f 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/src/DjangoBlog-master/.idea/.gitignore b/djangoblog/.idea/.gitignore
similarity index 100%
rename from src/DjangoBlog-master/.idea/.gitignore
rename to djangoblog/.idea/.gitignore
diff --git a/src/DjangoBlog-master/.idea/inspectionProfiles/profiles_settings.xml b/djangoblog/.idea/inspectionProfiles/profiles_settings.xml
similarity index 100%
rename from src/DjangoBlog-master/.idea/inspectionProfiles/profiles_settings.xml
rename to djangoblog/.idea/inspectionProfiles/profiles_settings.xml
diff --git a/djangoblog/.idea/misc.xml b/djangoblog/.idea/misc.xml
new file mode 100644
index 00000000..db8786c0
--- /dev/null
+++ b/djangoblog/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/djangoblog/.idea/modules.xml b/djangoblog/.idea/modules.xml
new file mode 100644
index 00000000..3d4e23c6
--- /dev/null
+++ b/djangoblog/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/djangoblog/.idea/vcs.xml b/djangoblog/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/djangoblog/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/zyl_django.iml b/djangoblog/.idea/zyl_django.iml
similarity index 100%
rename from .idea/zyl_django.iml
rename to djangoblog/.idea/zyl_django.iml
diff --git a/README.md b/djangoblog/README.md
similarity index 100%
rename from README.md
rename to djangoblog/README.md
diff --git a/doc/DjangoBlog开源代码的泛读报告.docx b/djangoblog/doc/DjangoBlog开源代码的泛读报告.docx
similarity index 100%
rename from doc/DjangoBlog开源代码的泛读报告.docx
rename to djangoblog/doc/DjangoBlog开源代码的泛读报告.docx
diff --git a/djangoblog/src/DjangoBlog-master/.idea/.gitignore b/djangoblog/src/DjangoBlog-master/.idea/.gitignore
new file mode 100644
index 00000000..35410cac
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/.idea/.gitignore
@@ -0,0 +1,8 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/src/DjangoBlog-master/.idea/DjangoBlog-master.iml b/djangoblog/src/DjangoBlog-master/.idea/DjangoBlog-master.iml
similarity index 100%
rename from src/DjangoBlog-master/.idea/DjangoBlog-master.iml
rename to djangoblog/src/DjangoBlog-master/.idea/DjangoBlog-master.iml
diff --git a/djangoblog/src/DjangoBlog-master/.idea/inspectionProfiles/profiles_settings.xml b/djangoblog/src/DjangoBlog-master/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/DjangoBlog-master/.idea/misc.xml b/djangoblog/src/DjangoBlog-master/.idea/misc.xml
similarity index 100%
rename from src/DjangoBlog-master/.idea/misc.xml
rename to djangoblog/src/DjangoBlog-master/.idea/misc.xml
diff --git a/src/DjangoBlog-master/.idea/modules.xml b/djangoblog/src/DjangoBlog-master/.idea/modules.xml
similarity index 100%
rename from src/DjangoBlog-master/.idea/modules.xml
rename to djangoblog/src/DjangoBlog-master/.idea/modules.xml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.coveragerc b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.coveragerc
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.coveragerc
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.coveragerc
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.dockerignore b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.dockerignore
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.dockerignore
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.dockerignore
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.gitattributes b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.gitattributes
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.gitattributes
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.gitattributes
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/ISSUE_TEMPLATE.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/ISSUE_TEMPLATE.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/ISSUE_TEMPLATE.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/ISSUE_TEMPLATE.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/codeql-analysis.yml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/codeql-analysis.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/codeql-analysis.yml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/codeql-analysis.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/django.yml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/django.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/django.yml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/django.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/docker.yml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/docker.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/docker.yml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/docker.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/publish-release.yml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/publish-release.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.github/workflows/publish-release.yml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.github/workflows/publish-release.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/.gitignore b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/.gitignore
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/.gitignore
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/.gitignore
diff --git a/src/DjangoBlog-master/DjangoBlog-master/Dockerfile b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/Dockerfile
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/Dockerfile
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/Dockerfile
diff --git a/src/DjangoBlog-master/DjangoBlog-master/LICENSE b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/LICENSE
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/LICENSE
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/LICENSE
diff --git a/src/DjangoBlog-master/DjangoBlog-master/README.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/README.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/README.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/README.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/admin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/admin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/apps.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/apps.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/forms.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/forms.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/forms.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/forms.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0001_initial.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0001_initial.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/0002_alter_bloguser_options_remove_bloguser_created_time_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/models.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/models.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/templatetags/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/templatetags/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/templatetags/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/tests.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/tests.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/urls.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/urls.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/user_login_backend.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/user_login_backend.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/user_login_backend.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/user_login_backend.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/utils.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/utils.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/utils.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/utils.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/accounts/views.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/accounts/views.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/accounts/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/__init__.py
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/admin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/admin.py
new file mode 100644
index 00000000..8b0d9346
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/admin.py
@@ -0,0 +1,189 @@
+# 导入Django表单模块,用于创建自定义表单
+from django import forms
+# 导入Django admin模块,用于注册模型到后台管理系统
+from django.contrib import admin
+# 导入获取用户模型的函数,用于处理作者关联
+from django.contrib.auth import get_user_model
+# 导入reverse函数,用于生成URL
+from django.urls import reverse
+# 导入format_html,用于在admin中生成HTML代码
+from django.utils.html import format_html
+# 导入国际化工具,用于翻译后台显示文本
+from django.utils.translation import gettext_lazy as _
+
+# 导入当前应用的Article模型
+from .models import Article
+
+
+class ArticleForm(forms.ModelForm):
+ """
+ 自定义文章表单,用于在admin中自定义文章的编辑界面
+
+ 可以在这里添加自定义字段验证、 widgets 或修改表单行为
+ 目前注释掉了pagedown编辑器的配置,如需使用可取消注释
+ """
+
+ # body = forms.CharField(widget=AdminPagedownWidget()) # 富文本编辑器配置
+
+ class Meta:
+ model = Article # 关联的模型
+ fields = '__all__' # 包含模型的所有字段
+
+
+# 自定义批量操作:发布选中的文章
+def makr_article_publish(modeladmin, request, queryset):
+ # 将选中文章的状态更新为'p'(published)
+ queryset.update(status='p')
+
+
+# 自定义批量操作:将选中的文章设为草稿
+def draft_article(modeladmin, request, queryset):
+ # 将选中文章的状态更新为'd'(draft)
+ queryset.update(status='d')
+
+
+# 自定义批量操作:关闭选中文章的评论
+def close_article_commentstatus(modeladmin, request, queryset):
+ # 将选中文章的评论状态更新为'c'(closed)
+ queryset.update(comment_status='c')
+
+
+# 自定义批量操作:开启选中文章的评论
+def open_article_commentstatus(modeladmin, request, queryset):
+ # 将选中文章的评论状态更新为'o'(open)
+ queryset.update(comment_status='o')
+
+
+# 为批量操作设置显示名称(支持国际化)
+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):
+ """
+ 文章模型的Admin配置类,自定义文章在后台的显示和操作方式
+ """
+ list_per_page = 20 # 每页显示20条记录
+ search_fields = ('body', 'title') # 可搜索的字段
+ form = ArticleForm # 使用自定义的表单
+ # 列表页显示的字段
+ list_display = (
+ 'id', # 文章ID
+ 'title', # 标题
+ 'author', # 作者
+ 'link_to_category', # 分类(带链接)
+ 'creation_time', # 创建时间
+ 'views', # 浏览量
+ 'status', # 状态
+ 'type', # 类型(文章/页面)
+ 'article_order' # 排序序号
+ )
+ # 列表页可点击跳转编辑的字段
+ list_display_links = ('id', 'title')
+ # 可筛选的字段(右侧过滤器)
+ list_filter = ('status', 'type', 'category')
+ # 多对多字段的水平选择器
+ filter_horizontal = ('tags',)
+ # 编辑页排除的字段(这些字段通常自动生成,不需要手动编辑)
+ exclude = ('creation_time', 'last_modify_time')
+ # 启用"在站点上查看"功能
+ view_on_site = True
+ # 注册批量操作
+ actions = [
+ makr_article_publish,
+ draft_article,
+ close_article_commentstatus,
+ open_article_commentstatus
+ ]
+
+ def link_to_category(self, obj):
+ """
+ 自定义列表字段:显示分类并添加跳转链接到分类编辑页
+
+ Args:
+ obj: 当前文章对象
+
+ Returns:
+ HTML代码:带链接的分类名称
+ """
+ # 获取分类模型的元数据,用于生成URL
+ info = (obj.category._meta.app_label, obj.category._meta.model_name)
+ # 生成分类编辑页的URL
+ link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,))
+ # 返回带链接的HTML
+ return format_html(u'%s' % (link, obj.category.name))
+
+ # 自定义字段的显示名称
+ link_to_category.short_description = _('category')
+
+ def get_form(self, request, obj=None, **kwargs):
+ """
+ 重写表单获取方法,自定义表单字段
+
+ 这里限制了作者只能选择超级用户
+ """
+ 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):
+ """
+ 自定义"在站点上查看"的链接
+
+ Args:
+ obj: 文章对象
+
+ Returns:
+ 文章的前台访问URL或网站首页
+ """
+ if obj:
+ # 如果有文章对象,返回文章的完整URL
+ url = obj.get_full_url()
+ return url
+ else:
+ # 如果没有对象(如在列表页),返回网站首页
+ from djangoblog.utils import get_current_site
+ site = get_current_site().domain
+ return site
+
+
+class TagAdmin(admin.ModelAdmin):
+ """标签模型的Admin配置"""
+ # 编辑页排除的字段(自动生成)
+ exclude = ('slug', 'last_mod_time', 'creation_time')
+
+
+class CategoryAdmin(admin.ModelAdmin):
+ """分类模型的Admin配置"""
+ # 列表页显示的字段
+ list_display = ('name', 'parent_category', 'index')
+ # 编辑页排除的字段
+ exclude = ('slug', 'last_mod_time', 'creation_time')
+
+
+class LinksAdmin(admin.ModelAdmin):
+ """链接模型的Admin配置"""
+ exclude = ('last_mod_time', 'creation_time')
+
+
+class SideBarAdmin(admin.ModelAdmin):
+ """侧边栏模型的Admin配置"""
+ list_display = ('name', 'content', 'is_enable', 'sequence')
+ exclude = ('last_mod_time', 'creation_time')
+
+
+class BlogSettingsAdmin(admin.ModelAdmin):
+ """博客设置模型的Admin配置"""
+ pass # 使用默认配置
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/apps.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/apps.py
new file mode 100644
index 00000000..4bd78485
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/apps.py
@@ -0,0 +1,15 @@
+# 从Django的apps模块导入AppConfig类,用于定义应用的配置
+from django.apps import AppConfig
+
+
+class BlogConfig(AppConfig):
+ """
+ 博客应用(blog)的配置类
+
+ Django通过此类识别和配置应用的基本信息,
+ 包括应用名称、默认自动生成的主键类型等。
+ 当项目启动时,Django会加载每个应用的AppConfig子类。
+ """
+ # 定义应用的名称,必须与应用的实际目录名一致
+ # 这个名称用于Django内部识别应用,例如在INSTALLED_APPS中注册时使用
+ name = 'blog'
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py
new file mode 100644
index 00000000..f2acba47
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py
@@ -0,0 +1,73 @@
+# 导入日志模块,用于记录系统运行时的信息和错误
+import logging
+
+# 从django.utils导入timezone,用于获取当前时间
+from django.utils import timezone
+
+# 导入自定义的缓存工具和获取博客设置的工具函数
+from djangoblog.utils import cache, get_blog_setting
+# 导入当前应用下的Category(分类)和Article(文章)模型
+from .models import Category, Article
+
+# 创建日志记录器,用于记录当前模块的日志信息
+logger = logging.getLogger(__name__)
+
+
+def seo_processor(requests):
+ """
+ 自定义上下文处理器,用于在所有模板中全局共享SEO相关的配置和数据
+
+ 上下文处理器是Django的一个功能,允许你在所有模板中自动添加变量,
+ 无需在每个视图函数中单独传递,特别适合网站全局配置信息的共享。
+
+ Args:
+ requests: Django请求对象,包含当前请求的相关信息(如域名、协议等)
+
+ Returns:
+ dict: 包含网站配置、分类、页面等信息的字典,将被注入到所有模板中
+ """
+ # 定义缓存键,用于标识当前处理器的缓存数据
+ key = 'seo_processor'
+ # 尝试从缓存中获取数据,减少数据库查询和计算开销
+ value = cache.get(key)
+
+ # 如果缓存中存在数据,直接返回缓存内容
+ if value:
+ return value
+ else:
+ # 缓存未命中时,记录日志并重新计算数据
+ logger.info('set processor cache.')
+ # 获取博客的全局设置(从数据库或其他配置源)
+ setting = get_blog_setting()
+
+ # 构建需要传递给模板的全局变量字典
+ value = {
+ 'SITE_NAME': setting.site_name, # 网站名称
+ 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense, # 是否显示谷歌广告
+ 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes, # 谷歌广告代码
+ 'SITE_SEO_DESCRIPTION': setting.site_seo_description, # 网站SEO描述(用于搜索引擎)
+ 'SITE_DESCRIPTION': setting.site_description, # 网站描述
+ 'SITE_KEYWORDS': setting.site_keywords, # 网站关键词(用于SEO)
+ # 网站基础URL(如https://example.com/)
+ 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
+ 'ARTICLE_SUB_LENGTH': setting.article_sub_length, # 文章摘要长度
+ 'nav_category_list': Category.objects.all(), # 导航栏显示的所有分类
+ # 导航栏显示的页面(类型为'p'即page,状态为'p'即published)
+ 'nav_pages': Article.objects.filter(
+ type='p',
+ status='p'),
+ 'OPEN_SITE_COMMENT': setting.open_site_comment, # 是否开启网站评论功能
+ 'BEIAN_CODE': setting.beian_code, # 网站备案号
+ 'ANALYTICS_CODE': setting.analytics_code, # 网站统计代码(如Google Analytics)
+ "BEIAN_CODE_GONGAN": setting.gongan_beiancode, # 公安备案号
+ "SHOW_GONGAN_CODE": setting.show_gongan_code, # 是否显示公安备案号
+ "CURRENT_YEAR": timezone.now().year, # 当前年份(用于页脚版权信息等)
+ "GLOBAL_HEADER": setting.global_header, # 全局页眉代码(如额外的CSS/JS)
+ "GLOBAL_FOOTER": setting.global_footer, # 全局页脚代码
+ "COMMENT_NEED_REVIEW": setting.comment_need_review, # 评论是否需要审核
+ }
+
+ # 将数据存入缓存,有效期为10小时(60秒*60分*10小时)
+ cache.set(key, value, 60 * 60 * 10)
+ # 返回构建的全局变量字典
+ return value
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/documents.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/documents.py
new file mode 100644
index 00000000..c9ba1285
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/documents.py
@@ -0,0 +1,267 @@
+# 导入时间处理模块
+import time
+
+# 导入Elasticsearch客户端相关模块
+import elasticsearch.client
+# 导入Django配置模块
+from django.conf import settings
+# 导入Elasticsearch DSL相关组件,用于定义文档结构
+from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean
+from elasticsearch_dsl.connections import connections
+
+# 导入博客文章模型,用于数据同步
+from blog.models import Article
+
+# 检查是否启用Elasticsearch(通过判断配置中是否存在ELASTICSEARCH_DSL)
+ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL')
+
+# 如果启用了Elasticsearch,则进行初始化配置
+if ELASTICSEARCH_ENABLED:
+ # 创建Elasticsearch连接(从Django配置中获取主机地址)
+ connections.create_connection(
+ hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']])
+ # 导入Elasticsearch客户端并初始化
+ from elasticsearch import Elasticsearch
+
+ es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
+
+ # 初始化IngestClient(用于处理数据预处理管道)
+ from elasticsearch.client import IngestClient
+
+ c = IngestClient(es)
+
+ # 尝试获取名为'geoip'的管道,如果不存在则创建
+ try:
+ c.get_pipeline('geoip')
+ except elasticsearch.exceptions.NotFoundError:
+ # 创建geoip管道:通过ip地址解析地理位置信息
+ c.put_pipeline('geoip', body='''{
+ "description" : "Add geoip info", # 管道描述:添加地理信息
+ "processors" : [
+ {
+ "geoip" : {
+ "field" : "ip" # 基于ip字段解析地理信息
+ }
+ }
+ ]
+ }''')
+
+
+# 定义地理位置信息内部文档(嵌套在主文档中)
+class GeoIp(InnerDoc):
+ continent_name = Keyword() # 大洲名称( Keyword类型:不分词,适合精确查询)
+ country_iso_code = Keyword() # 国家ISO代码(如CN、US)
+ country_name = Keyword() # 国家名称
+ location = GeoPoint() # 经纬度坐标(Elasticsearch地理点类型)
+
+
+# 定义用户代理浏览器信息内部文档
+class UserAgentBrowser(InnerDoc):
+ Family = Keyword() # 浏览器家族(如Chrome、Firefox)
+ Version = Keyword() # 浏览器版本
+
+
+# 定义用户代理操作系统信息内部文档(继承浏览器结构,字段相同)
+class UserAgentOS(UserAgentBrowser):
+ pass
+
+
+# 定义用户代理设备信息内部文档
+class UserAgentDevice(InnerDoc):
+ Family = Keyword() # 设备家族(如iPhone、Windows)
+ Brand = Keyword() # 设备品牌(如Apple、Samsung)
+ Model = Keyword() # 设备型号(如iPhone 13)
+
+
+# 定义用户代理整体信息内部文档(整合浏览器、系统、设备信息)
+class UserAgent(InnerDoc):
+ browser = Object(UserAgentBrowser, required=False) # 浏览器信息(可选)
+ os = Object(UserAgentOS, required=False) # 操作系统信息(可选)
+ device = Object(UserAgentDevice, required=False) # 设备信息(可选)
+ string = Text() # 原始用户代理字符串(如"Mozilla/5.0...")
+ is_bot = Boolean() # 是否为爬虫机器人
+
+
+# 定义性能日志文档(记录访问性能数据)
+class ElapsedTimeDocument(Document):
+ url = Keyword() # 访问的URL(精确匹配)
+ time_taken = Long() # 页面加载耗时(毫秒)
+ log_datetime = Date() # 日志记录时间
+ ip = Keyword() # 访问者IP地址
+ geoip = Object(GeoIp, required=False) # 地理位置信息(由geoip管道生成)
+ useragent = Object(UserAgent, required=False) # 用户代理信息
+
+ # 索引配置
+ class Index:
+ name = 'performance' # 索引名称:performance(性能日志)
+ settings = {
+ "number_of_shards": 1, # 主分片数量
+ "number_of_replicas": 0 # 副本分片数量(单节点环境设为0)
+ }
+
+ # 文档类型配置(Elasticsearch 7+后逐渐废弃,但DSL仍保留兼容)
+ class Meta:
+ doc_type = 'ElapsedTime'
+
+
+# 性能日志文档管理器(处理索引创建、删除、数据写入)
+class ElaspedTimeDocumentManager:
+ @staticmethod
+ def build_index():
+ """创建performance索引(如果不存在)"""
+ from elasticsearch import Elasticsearch
+ # 连接Elasticsearch
+ client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
+ # 检查索引是否存在
+ res = client.indices.exists(index="performance")
+ if not res:
+ # 初始化索引(根据ElapsedTimeDocument的定义创建映射)
+ ElapsedTimeDocument.init()
+
+ @staticmethod
+ def delete_index():
+ """删除performance索引"""
+ from elasticsearch import Elasticsearch
+ es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
+ # 忽略400(索引不存在)和404(请求错误)
+ es.indices.delete(index='performance', ignore=[400, 404])
+
+ @staticmethod
+ def create(url, time_taken, log_datetime, useragent, ip):
+ """创建一条性能日志记录并写入Elasticsearch"""
+ # 确保索引存在
+ ElaspedTimeDocumentManager.build_index()
+
+ # 构建用户代理信息对象
+ ua = UserAgent()
+ ua.browser = UserAgentBrowser()
+ ua.browser.Family = useragent.browser.family # 浏览器家族
+ ua.browser.Version = useragent.browser.version_string # 浏览器版本
+
+ ua.os = UserAgentOS()
+ ua.os.Family = useragent.os.family # 操作系统家族
+ ua.os.Version = useragent.os.version_string # 操作系统版本
+
+ ua.device = UserAgentDevice()
+ ua.device.Family = useragent.device.family # 设备家族
+ ua.device.Brand = useragent.device.brand # 设备品牌
+ ua.device.Model = useragent.device.model # 设备型号
+ ua.string = useragent.ua_string # 原始用户代理字符串
+ ua.is_bot = useragent.is_bot # 是否为爬虫
+
+ # 构建性能日志文档
+ doc = ElapsedTimeDocument(
+ meta={
+ # 用当前时间戳(毫秒)作为文档ID
+ 'id': int(round(time.time() * 1000))
+ },
+ url=url, # 访问URL
+ time_taken=time_taken, # 耗时
+ log_datetime=log_datetime, # 日志时间
+ useragent=ua, # 用户代理信息
+ ip=ip # IP地址
+ )
+ # 保存文档时应用geoip管道(自动解析IP对应的地理位置)
+ doc.save(pipeline="geoip")
+
+
+# 定义文章文档(用于博客文章的搜索索引)
+class ArticleDocument(Document):
+ # 文章内容(使用ik分词器:max_word最大化分词,smart智能分词)
+ body = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
+ # 文章标题(同上分词配置)
+ title = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
+ # 作者信息(嵌套对象)
+ author = Object(properties={
+ 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), # 作者昵称
+ 'id': Integer() # 作者ID
+ })
+ # 分类信息(嵌套对象)
+ category = Object(properties={
+ 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), # 分类名称
+ 'id': Integer() # 分类ID
+ })
+ # 标签信息(嵌套对象列表)
+ tags = Object(properties={
+ 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), # 标签名称
+ 'id': Integer() # 标签ID
+ })
+ pub_time = Date() # 发布时间
+ status = Text() # 文章状态(如发布、草稿)
+ comment_status = Text() # 评论状态(如开启、关闭)
+ type = Text() # 文章类型(如原创、转载)
+ views = Integer() # 浏览量
+ article_order = Integer() # 文章排序权重
+
+ # 索引配置
+ class Index:
+ name = 'blog' # 索引名称:blog(博客文章)
+ settings = {
+ "number_of_shards": 1,
+ "number_of_replicas": 0
+ }
+
+ # 文档类型配置
+ class Meta:
+ doc_type = 'Article'
+
+
+# 文章文档管理器(处理文章索引的创建、更新、重建)
+class ArticleDocumentManager():
+
+ def __init__(self):
+ """初始化时创建索引(如果不存在)"""
+ self.create_index()
+
+ def create_index(self):
+ """创建blog索引(根据ArticleDocument定义初始化映射)"""
+ ArticleDocument.init()
+
+ def delete_index(self):
+ """删除blog索引"""
+ from elasticsearch import Elasticsearch
+ es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
+ es.indices.delete(index='blog', ignore=[400, 404])
+
+ def convert_to_doc(self, articles):
+ """将Django模型对象列表转换为ArticleDocument列表"""
+ return [
+ ArticleDocument(
+ meta={'id': article.id}, # 用文章ID作为文档ID
+ body=article.body, # 文章内容
+ title=article.title, # 文章标题
+ author={
+ 'nickname': article.author.username, # 作者用户名
+ 'id': article.author.id # 作者ID
+ },
+ category={
+ 'name': article.category.name, # 分类名称
+ 'id': article.category.id # 分类ID
+ },
+ # 转换标签列表(多对多关系)
+ tags=[{'name': t.name, 'id': t.id} for t in article.tags.all()],
+ pub_time=article.pub_time, # 发布时间
+ status=article.status, # 文章状态
+ comment_status=article.comment_status, # 评论状态
+ type=article.type, # 文章类型
+ views=article.views, # 浏览量
+ article_order=article.article_order # 排序权重
+ ) for article in articles
+ ]
+
+ def rebuild(self, articles=None):
+ """重建索引(默认同步所有文章,可指定文章列表)"""
+ # 初始化索引结构
+ ArticleDocument.init()
+ # 如果未指定文章,则同步所有文章
+ articles = articles if articles else Article.objects.all()
+ # 转换模型为文档对象
+ docs = self.convert_to_doc(articles)
+ # 批量保存文档
+ for doc in docs:
+ doc.save()
+
+ def update_docs(self, docs):
+ """更新文档列表(批量保存)"""
+ for doc in docs:
+ doc.save()
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/forms.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/forms.py
new file mode 100644
index 00000000..690d1dd5
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/forms.py
@@ -0,0 +1,41 @@
+# 导入日志模块,用于记录搜索相关日志
+import logging
+
+# 导入Django的表单模块,用于构建自定义表单
+from django import forms
+# 导入Haystack的搜索表单基类,用于扩展搜索功能
+from haystack.forms import SearchForm
+
+# 创建日志记录器,使用当前模块名作为日志器名称
+logger = logging.getLogger(__name__)
+
+
+class BlogSearchForm(SearchForm):
+ """
+ 博客搜索表单类,继承自Haystack的SearchForm
+ 用于自定义博客搜索的表单验证和搜索逻辑
+ """
+ # 定义搜索查询字段,required=True表示该字段为必填项
+ # 用户输入的搜索关键词将通过该字段传递
+ querydata = forms.CharField(required=True)
+
+ def search(self):
+ """
+ 重写父类的search方法,实现自定义搜索逻辑
+ 该方法会处理搜索请求并返回搜索结果
+ """
+ # 调用父类的search方法,获取初始搜索结果集
+ # 父类方法会处理Haystack的核心搜索逻辑
+ datas = super(BlogSearchForm, self).search()
+
+ # 检查表单数据是否有效,若无效则返回无查询结果的默认响应
+ if not self.is_valid():
+ return self.no_query_found()
+
+ # 如果表单验证通过且存在查询数据(querydata)
+ if self.cleaned_data['querydata']:
+ # 记录搜索关键词到日志,方便后续分析用户搜索行为
+ logger.info(self.cleaned_data['querydata'])
+
+ # 返回处理后的搜索结果集
+ return datas
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/__init__.py
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py
new file mode 100644
index 00000000..22369c44
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py
@@ -0,0 +1,45 @@
+# 导入Django命令基类,用于创建自定义管理命令
+from django.core.management.base import BaseCommand
+
+# 导入博客相关的Elasticsearch文档管理器和配置
+from blog.documents import (
+ ElapsedTimeDocument, # 耗时统计文档模型
+ ArticleDocumentManager, # 文章文档管理器
+ ElaspedTimeDocumentManager, # 耗时统计文档管理器(注:原拼写可能存在笔误,应为Elapsed)
+ ELASTICSEARCH_ENABLED # Elasticsearch启用状态标记
+)
+
+
+# TODO: 后续可优化为支持参数化(如指定重建的索引类型等)
+class Command(BaseCommand):
+ """
+ Django自定义管理命令:构建Elasticsearch搜索索引
+ 用于初始化或重建文章和耗时统计相关的搜索索引
+ """
+ # 命令的帮助信息(使用python manage.py help build_index时显示)
+ help = 'build search index'
+
+ def handle(self, *args, **options):
+ """
+ 命令核心执行方法
+ 当运行python manage.py build_index时调用
+ """
+ # 仅在Elasticsearch启用时执行索引构建
+ if ELASTICSEARCH_ENABLED:
+ # 构建耗时统计文档的索引
+ ElaspedTimeDocumentManager.build_index()
+
+ # 初始化耗时统计文档的索引结构
+ elapsed_manager = ElapsedTimeDocument()
+ elapsed_manager.init() # 创建索引映射
+
+ # 处理文章文档索引:先删除旧索引,再重建
+ article_manager = ArticleDocumentManager()
+ article_manager.delete_index() # 删除现有文章索引
+ article_manager.rebuild() # 重新创建索引并同步数据
+
+ # 输出成功信息到控制台
+ self.stdout.write(self.style.SUCCESS('Successfully built search indexes'))
+ else:
+ # 当Elasticsearch未启用时,提示用户
+ self.stdout.write(self.style.WARNING('Elasticsearch is not enabled, skipping index build'))
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py
new file mode 100644
index 00000000..b0d807e5
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py
@@ -0,0 +1,32 @@
+# 导入Django命令基类,用于创建自定义管理命令
+from django.core.management.base import BaseCommand
+
+# 导入博客应用中的标签和分类模型
+from blog.models import Tag, Category
+
+
+# TODO: 后续可优化为支持参数化(如指定输出格式、过滤条件等)
+class Command(BaseCommand):
+ """
+ Django自定义管理命令:生成搜索关键词列表
+ 提取所有标签和分类的名称,用于构建搜索提示词或关键词库
+ """
+ # 命令的帮助信息(执行python manage.py help build_search_words时显示)
+ help = 'build search words'
+
+ def handle(self, *args, **options):
+ """
+ 命令核心执行逻辑
+ 当运行python manage.py build_search_words时调用
+ """
+ # 1. 提取所有标签(Tag)的名称并转换为列表
+ # 2. 提取所有分类(Category)的名称并转换为列表
+ # 3. 合并两个列表并通过set去重(确保关键词唯一)
+ datas = set(
+ [tag.name for tag in Tag.objects.all()] + # 标签名称列表
+ [category.name for category in Category.objects.all()] # 分类名称列表
+ )
+
+ # 将去重后的关键词按行打印输出
+ # 格式为每个关键词单独一行,便于后续处理(如写入文件或导入搜索提示库)
+ print('\n'.join(datas))
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py
new file mode 100644
index 00000000..73803c40
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py
@@ -0,0 +1,26 @@
+# 导入Django命令基类,用于创建自定义管理命令
+from django.core.management.base import BaseCommand
+
+# 导入项目自定义的缓存工具(封装自djangoblog.utils)
+from djangoblog.utils import cache
+
+
+class Command(BaseCommand):
+ """
+ Django自定义管理命令:清除系统所有缓存
+ 用于手动触发缓存清理,确保缓存数据与数据库同步
+ """
+ # 命令的帮助信息(执行python manage.py help clear_cache时显示)
+ help = 'clear the whole cache'
+
+ def handle(self, *args, **options):
+ """
+ 命令核心执行逻辑
+ 当运行python manage.py clear_cache时调用
+ """
+ # 调用缓存工具的clear()方法,清除所有缓存数据
+ # 这里的cache是项目自定义的缓存实例(可能封装了Django原生缓存或其他缓存后端)
+ cache.clear()
+
+ # 向控制台输出成功信息(使用Django命令的样式工具,显示绿色成功提示)
+ self.stdout.write(self.style.SUCCESS('Cleared cache\n'))
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py
new file mode 100644
index 00000000..8d472bf6
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py
@@ -0,0 +1,76 @@
+# 导入Django用户模型、密码加密、命令基类
+from django.contrib.auth import get_user_model
+from django.contrib.auth.hashers import make_password
+from django.core.management.base import BaseCommand
+
+# 导入博客应用的核心模型
+from blog.models import Article, Tag, Category
+
+
+class Command(BaseCommand):
+ """
+ Django自定义管理命令:创建测试数据
+ 用于快速生成用户、分类、标签和文章等测试数据,方便开发和测试
+ """
+ # 命令的帮助信息(执行python manage.py help create_testdata时显示)
+ help = 'create test datas'
+
+ def handle(self, *args, **options):
+ """
+ 命令核心执行逻辑
+ 当运行python manage.py create_testdata时调用,生成测试数据
+ """
+ # 1. 创建或获取测试用户
+ # get_or_create:存在则获取,不存在则创建(避免重复生成)
+ # make_password:加密密码(安全存储)
+ user = get_user_model().objects.get_or_create(
+ email='test@test.com', # 测试邮箱
+ username='测试用户', # 用户名
+ password=make_password('test!q@w#eTYU') # 加密后的密码
+ )[0] # [0]取返回元组中的用户对象
+
+ # 2. 创建分类(含层级关系)
+ # 创建父分类(无上级分类)
+ pcategory = Category.objects.get_or_create(
+ name='我是父类目',
+ parent_category=None # 顶级分类
+ )[0]
+
+ # 创建子分类(关联父分类)
+ category = Category.objects.get_or_create(
+ name='子类目',
+ parent_category=pcategory # 关联到父分类
+ )[0]
+ category.save() # 保存子分类
+
+ # 3. 创建基础标签(供所有测试文章共用)
+ basetag = Tag()
+ basetag.name = "标签" # 标签名称
+ basetag.save()
+
+ # 4. 批量创建测试文章(1-19共19篇)
+ for i in range(1, 20):
+ # 创建或获取文章
+ article = Article.objects.get_or_create(
+ category=category, # 关联到子分类
+ title=f'nice title {i}', # 文章标题(带序号)
+ body=f'nice content {i}', # 文章内容(带序号)
+ author=user # 关联到测试用户
+ )[0]
+
+ # 为每篇文章创建专属标签
+ tag = Tag()
+ tag.name = f"标签{i}" # 标签名称(带序号)
+ tag.save()
+
+ # 给文章添加标签(专属标签 + 基础标签)
+ article.tags.add(tag)
+ article.tags.add(basetag)
+ article.save() # 保存文章(更新标签关联)
+
+ # 5. 清除缓存(确保新生成的测试数据能立即生效)
+ from djangoblog.utils import cache
+ cache.clear()
+
+ # 输出成功信息到控制台
+ self.stdout.write(self.style.SUCCESS('created test datas \n'))
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py
new file mode 100644
index 00000000..98addbf4
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py
@@ -0,0 +1,88 @@
+# 导入Django命令基类,用于创建自定义管理命令
+from django.core.management.base import BaseCommand
+
+# 导入搜索引擎推送工具、站点配置和博客模型
+from djangoblog.spider_notify import SpiderNotify # 搜索引擎推送工具
+from djangoblog.utils import get_current_site # 获取当前站点信息
+from blog.models import Article, Tag, Category # 博客核心模型
+
+# 获取当前站点的域名(用于生成完整URL)
+site = get_current_site().domain
+
+
+class Command(BaseCommand):
+ """
+ Django自定义管理命令:向百度搜索引擎推送URL
+ 用于主动告知百度爬虫网站的更新内容,加速收录
+ """
+ # 命令的帮助信息(执行python manage.py help ping_baidu时显示)
+ help = 'notify baidu url'
+
+ def add_arguments(self, parser):
+ """
+ 定义命令参数:指定需要推送的URL类型
+ 通过parser添加命令行参数,限制可选值
+ """
+ parser.add_argument(
+ 'data_type', # 参数名称
+ type=str,
+ choices=[ # 可选参数值
+ 'all', # 推送所有类型(文章、标签、分类)
+ 'article', # 仅推送文章
+ 'tag', # 仅推送标签页
+ 'category' # 仅推送分类页
+ ],
+ help='指定推送类型:article(所有文章)、tag(所有标签)、category(所有分类)、all(全部)'
+ )
+
+ def get_full_url(self, path):
+ """
+ 生成完整的URL(域名+相对路径)
+ :param path: 模型实例的相对路径(如/article/1.html)
+ :return: 完整的URL字符串(如https://example.com/article/1.html)
+ """
+ return f"https://{site}{path}"
+
+ def handle(self, *args, **options):
+ """
+ 命令核心执行逻辑
+ 根据参数类型收集URL,推送给百度搜索引擎
+ """
+ # 获取用户指定的推送类型
+ data_type = options['data_type']
+ self.stdout.write(f'开始收集{data_type}类型的URL...')
+
+ # 存储待推送的URL列表
+ urls = []
+
+ # 1. 收集文章URL(已发布状态)
+ if data_type == 'article' or data_type == 'all':
+ # 筛选所有已发布的文章
+ for article in Article.objects.filter(status='p'):
+ # 调用文章模型的get_full_url方法获取完整URL
+ urls.append(article.get_full_url())
+
+ # 2. 收集标签页URL
+ if data_type == 'tag' or data_type == 'all':
+ for tag in Tag.objects.all():
+ # 获取标签页的相对路径,再生成完整URL
+ relative_url = tag.get_absolute_url()
+ urls.append(self.get_full_url(relative_url))
+
+ # 3. 收集分类页URL
+ if data_type == 'category' or data_type == 'all':
+ for category in Category.objects.all():
+ # 获取分类页的相对路径,再生成完整URL
+ relative_url = category.get_absolute_url()
+ urls.append(self.get_full_url(relative_url))
+
+ # 输出待推送的URL数量
+ self.stdout.write(
+ self.style.SUCCESS(f'准备推送{len(urls)}条URL...')
+ )
+
+ # 调用工具类向百度推送URL
+ SpiderNotify.baidu_notify(urls)
+
+ # 推送完成,输出成功信息
+ self.stdout.write(self.style.SUCCESS('URL推送完成!'))
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py
new file mode 100644
index 00000000..6bade0d6
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py
@@ -0,0 +1,86 @@
+import requests # 用于发送HTTP请求,验证图片URL有效性
+from django.core.management.base import BaseCommand
+from django.templatetags.static import static # 生成静态文件URL
+
+# 导入项目工具和模型:用户头像保存、OAuth用户模型、OAuth管理工具
+from djangoblog.utils import save_user_avatar # 保存用户头像到本地的工具函数
+from oauth.models import OAuthUser # OAuth关联用户模型(存储第三方登录用户信息)
+from oauth.oauthmanager import get_manager_by_type # 根据 OAuth 类型获取对应管理器
+
+
+class Command(BaseCommand):
+ """
+ Django自定义管理命令:同步用户头像
+ 用于检查并更新OAuth用户的头像URL,确保头像可访问(无效则重新获取或使用默认头像)
+ """
+ # 命令的帮助信息(执行python manage.py help sync_user_avatar时显示)
+ help = 'sync user avatar'
+
+ def test_picture(self, url):
+ """
+ 验证图片URL是否有效(可访问且返回200状态码)
+ :param url: 头像图片的URL
+ :return: 有效则返回True,否则返回False
+ """
+ try:
+ # 发送GET请求,超时2秒,检查状态码是否为200
+ if requests.get(url, timeout=2).status_code == 200:
+ return True
+ except:
+ # 任何异常(超时、连接错误等)均视为无效
+ pass
+ return False
+
+ def handle(self, *args, **options):
+ """
+ 命令核心执行逻辑
+ 遍历所有OAuth用户,检查并同步头像URL
+ """
+ # 获取项目静态文件的基础URL(用于判断头像是否为本地静态文件)
+ static_url = static("../")
+
+ # 获取所有OAuth用户
+ users = OAuthUser.objects.all()
+ self.stdout.write(f'开始同步{len(users)}个用户头像')
+
+ # 遍历每个用户处理头像
+ for u in users:
+ self.stdout.write(f'开始同步:{u.nickname}') # 输出当前处理的用户名
+ url = u.picture # 获取用户当前的头像URL
+
+ if url: # 如果用户已有头像URL
+ # 情况1:头像URL是本地静态文件(以static_url开头)
+ if url.startswith(static_url):
+ # 验证本地头像是否有效
+ if self.test_picture(url):
+ self.stdout.write(f' 头像有效,跳过:{url}')
+ continue # 有效则跳过处理
+ else:
+ # 本地头像无效,尝试重新获取
+ self.stdout.write(f' 本地头像无效,尝试重新获取')
+ if u.metadata: # 如果存在第三方平台返回的元数据(可能包含头像信息)
+ # 根据OAuth类型(如qq、weibo)获取对应的管理器
+ manage = get_manager_by_type(u.type)
+ # 从元数据中提取最新头像URL
+ url = manage.get_picture(u.metadata)
+ # 保存头像到本地并返回新的URL
+ url = save_user_avatar(url)
+ else:
+ # 无元数据,使用默认头像
+ url = static('blog/img/avatar.png')
+ else:
+ # 情况2:头像URL是第三方链接(非本地文件),保存到本地
+ self.stdout.write(f' 第三方头像,保存到本地')
+ url = save_user_avatar(url)
+ else:
+ # 情况3:用户无头像URL,使用默认头像
+ self.stdout.write(f' 无头像,使用默认头像')
+ url = static('blog/img/avatar.png')
+
+ # 更新用户头像并保存
+ if url:
+ self.stdout.write(f' 结束同步:{u.nickname}.url:{url}')
+ u.picture = url
+ u.save() # 保存更新后的头像URL
+
+ self.stdout.write('所有用户头像同步完成')
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py
new file mode 100644
index 00000000..6e496e1a
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py
@@ -0,0 +1,90 @@
+# 导入日志模块,用于记录中间件运行过程中的日志信息
+import logging
+# 导入时间模块,用于计算页面渲染耗时
+import time
+
+# 从ipware工具导入获取客户端IP的函数
+from ipware import get_client_ip
+# 从user_agents工具导入解析用户代理的函数
+from user_agents import parse
+
+# 导入博客相关的ES配置和文档管理器(用于记录页面加载时间)
+from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
+
+# 创建当前模块的日志记录器
+logger = logging.getLogger(__name__)
+
+
+class OnlineMiddleware(object):
+ """
+ 自定义Django中间件,用于:
+ 1. 计算页面渲染耗时
+ 2. 收集客户端信息(IP、用户代理)
+ 3. 在启用Elasticsearch时记录访问性能数据
+ 4. 替换响应中的特定标记为实际加载时间
+ """
+
+ def __init__(self, get_response=None):
+ """
+ 中间件初始化方法
+ :param get_response: Django框架传入的处理响应的函数,用于链式调用中间件
+ """
+ self.get_response = get_response
+ # 调用父类初始化方法(Python 2兼容写法,在Python 3中可省略)
+ super().__init__()
+
+ def __call__(self, request):
+ """
+ 中间件核心处理方法,在请求到达视图前和响应返回客户端前执行
+ :param request: Django的请求对象,包含客户端请求信息
+ :return: 处理后的响应对象
+ """
+ # 记录请求处理开始时间(用于计算耗时)
+ start_time = time.time()
+
+ # 调用下一个中间件或视图函数,获取响应对象
+ response = self.get_response(request)
+
+ # 从请求头中获取用户代理字符串(如浏览器型号、系统等信息)
+ http_user_agent = request.META.get('HTTP_USER_AGENT', '')
+ # 获取客户端IP地址(第二个返回值为是否是公开IP,此处暂不使用)
+ ip, _ = get_client_ip(request)
+ # 解析用户代理字符串,转换为可操作的对象(方便提取浏览器、系统等信息)
+ user_agent = parse(http_user_agent)
+
+ # 判断响应是否为非流式响应(流式响应无法修改内容,如文件下载)
+ if not response.streaming:
+ try:
+ # 计算页面渲染总耗时(当前时间 - 开始时间)
+ cast_time = time.time() - start_time
+
+ # 如果启用了Elasticsearch,记录性能数据
+ if ELASTICSEARCH_ENABLED:
+ # 将耗时转换为毫秒并保留2位小数
+ time_taken = round((cast_time) * 1000, 2)
+ # 获取当前请求的URL路径
+ url = request.path
+ # 导入Django的时区工具,用于记录当前时间
+ from django.utils import timezone
+ # 通过文档管理器向Elasticsearch插入一条性能记录
+ ElaspedTimeDocumentManager.create(
+ url=url, # 访问的URL
+ time_taken=time_taken, # 页面加载耗时(毫秒)
+ log_datetime=timezone.now(), # 记录时间(当前时区)
+ useragent=user_agent, # 解析后的用户代理信息
+ ip=ip # 客户端IP地址
+ )
+
+ # 将响应内容中的标记替换为实际耗时(保留前5位字符)
+ # 注:需确保响应内容为bytes类型,因此使用str.encode转换
+ response.content = response.content.replace(
+ b'', str.encode(str(cast_time)[:5])
+ )
+
+ # 捕获所有异常,避免中间件错误导致请求失败
+ except Exception as e:
+ # 记录异常信息到日志
+ logger.error("Error in OnlineMiddleware: %s" % e)
+
+ # 返回处理后的响应对象
+ return response
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py
new file mode 100644
index 00000000..66b3230d
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py
@@ -0,0 +1,202 @@
+# 生成信息:由Django 4.1.7在2023-03-02 07:14自动生成的迁移文件
+# 迁移文件用于定义数据库表结构,通过Django的迁移系统创建或修改数据库表
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion # 用于定义外键删除时的行为
+import django.utils.timezone # 用于处理时间字段的默认值
+import mdeditor.fields # 导入Markdown编辑器字段(用于文章正文)
+
+
+class Migration(migrations.Migration):
+ """
+ 数据库迁移类:定义博客系统初始表结构的迁移操作
+ 所有模型的首次迁移,会创建对应的数据库表
+ """
+ # 标记为初始迁移(首次创建表结构)
+ initial = True
+
+ # 依赖关系:当前迁移依赖于Django用户模型的迁移
+ # 因为Article模型关联了用户表(作者),需确保用户表先创建
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ # 迁移操作:创建所有模型对应的数据库表
+ operations = [
+ # 创建"网站配置"表(BlogSettings)
+ migrations.CreateModel(
+ name='BlogSettings',
+ fields=[
+ # 自增主键(BigAutoField支持更大的数值范围,适合大数据量)
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')), # 网站名称
+ ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')), # 网站描述(用于前端展示)
+ ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')), # 用于搜索引擎优化的描述
+ ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')), # SEO关键字,多个用逗号分隔
+ ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')), # 文章列表页显示的摘要长度
+ ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')), # 侧边栏显示的文章数量
+ ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')), # 侧边栏显示的评论数量
+ ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')), # 文章详情页默认显示的评论数量
+ ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')), # 开关:是否显示谷歌广告
+ ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')), # 谷歌广告代码
+ ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')), # 开关:是否允许评论
+ ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')), # 网站备案号(ICP备案)
+ ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')), # 第三方统计代码(如百度统计)
+ ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')), # 开关:是否显示公安备案号
+ ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')), # 公安备案号
+ ],
+ options={
+ 'verbose_name': '网站配置', # 模型的单数显示名称
+ 'verbose_name_plural': '网站配置', # 模型的复数显示名称(因配置通常只有一条记录,复数同单数)
+ },
+ ),
+
+ # 创建"友情链接"表(Links)
+ migrations.CreateModel(
+ name='Links',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')), # 友情链接名称(唯一)
+ ('link', models.URLField(verbose_name='链接地址')), # 链接的URL地址
+ ('sequence', models.IntegerField(unique=True, verbose_name='排序')), # 排序序号(唯一,控制显示顺序)
+ ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), # 开关:是否在页面显示
+ # 显示类型:指定链接在哪些页面显示
+ ('show_type', models.CharField(
+ choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')],
+ default='i',
+ max_length=1,
+ verbose_name='显示类型'
+ )),
+ ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), # 创建时间
+ ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), # 最后修改时间
+ ],
+ options={
+ 'verbose_name': '友情链接',
+ 'verbose_name_plural': '友情链接',
+ 'ordering': ['sequence'], # 默认按排序序号升序排列
+ },
+ ),
+
+ # 创建"侧边栏"表(SideBar)
+ migrations.CreateModel(
+ name='SideBar',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, verbose_name='标题')), # 侧边栏模块标题
+ ('content', models.TextField(verbose_name='内容')), # 侧边栏内容(支持HTML)
+ ('sequence', models.IntegerField(unique=True, verbose_name='排序')), # 排序序号(控制多个侧边栏的显示顺序)
+ ('is_enable', models.BooleanField(default=True, verbose_name='是否启用')), # 开关:是否显示该侧边栏
+ ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
+ ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
+ ],
+ options={
+ 'verbose_name': '侧边栏',
+ 'verbose_name_plural': '侧边栏',
+ 'ordering': ['sequence'], # 按排序序号升序排列
+ },
+ ),
+
+ # 创建"标签"表(Tag)
+ migrations.CreateModel(
+ name='Tag',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)), # 自增主键
+ ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
+ ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
+ ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')), # 标签名称(唯一)
+ ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), # URL友好的标识符(用于生成标签页URL)
+ ],
+ options={
+ 'verbose_name': '标签',
+ 'verbose_name_plural': '标签',
+ 'ordering': ['name'], # 按标签名升序排列
+ },
+ ),
+
+ # 创建"分类"表(Category)
+ migrations.CreateModel(
+ name='Category',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
+ ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
+ ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')), # 分类名称(唯一)
+ ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), # 用于生成分类页URL的标识符
+ ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')), # 权重值,控制分类在页面的显示优先级
+ # 自关联外键:支持分类层级(父分类->子分类)
+ # on_delete=models.CASCADE表示:若父分类删除,子分类也会被删除
+ ('parent_category', models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to='blog.category',
+ verbose_name='父级分类'
+ )),
+ ],
+ options={
+ 'verbose_name': '分类',
+ 'verbose_name_plural': '分类',
+ 'ordering': ['-index'], # 按权重降序排列(权重越大越靠前)
+ },
+ ),
+
+ # 创建"文章"表(Article)
+ migrations.CreateModel(
+ name='Article',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
+ ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
+ ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')), # 文章标题(唯一)
+ ('body', mdeditor.fields.MDTextField(verbose_name='正文')), # 文章正文(使用Markdown编辑器)
+ ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')), # 发布时间
+ # 文章状态:草稿(d)/发表(p)
+ ('status', models.CharField(
+ choices=[('d', '草稿'), ('p', '发表')],
+ default='p',
+ max_length=1,
+ verbose_name='文章状态'
+ )),
+ # 评论状态:打开(o)/关闭(c)
+ ('comment_status', models.CharField(
+ choices=[('o', '打开'), ('c', '关闭')],
+ default='o',
+ max_length=1,
+ verbose_name='评论状态'
+ )),
+ # 内容类型:文章(a)/页面(p,如关于页、联系页)
+ ('type', models.CharField(
+ choices=[('a', '文章'), ('p', '页面')],
+ default='a',
+ max_length=1,
+ verbose_name='类型'
+ )),
+ ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')), # 浏览量(非负整数)
+ ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')), # 排序值,控制文章在列表中的位置
+ ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')), # 开关:是否显示文章目录
+ # 外键:关联作者(Django用户模型)
+ # on_delete=models.CASCADE表示:若作者账号删除,其文章也会被删除
+ ('author', models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ verbose_name='作者'
+ )),
+ # 外键:关联分类
+ # on_delete=models.CASCADE表示:若分类删除,该分类下的文章也会被删除
+ ('category', models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to='blog.category',
+ verbose_name='分类'
+ )),
+ # 多对多关系:文章与标签(一篇文章可关联多个标签,一个标签可关联多篇文章)
+ ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')),
+ ],
+ options={
+ 'verbose_name': '文章',
+ 'verbose_name_plural': '文章',
+ # 默认排序规则:先按排序值降序(值越大越靠前),再按发布时间降序(最新的在前)
+ 'ordering': ['-article_order', '-pub_time'],
+ 'get_latest_by': 'id', # 按ID获取最新记录(ID自增,越大越新)
+ },
+ ),
+ ]
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py
new file mode 100644
index 00000000..4bb685d9
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py
@@ -0,0 +1,45 @@
+# 生成信息:由Django 4.1.7在2023-03-29 06:08自动生成的迁移文件
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ """
+ 数据库迁移类:为网站配置表添加新字段
+ 用于扩展网站配置功能,支持全局头部和尾部内容的设置
+ """
+
+ # 依赖关系:当前迁移依赖于博客应用的初始迁移(0001_initial)
+ # 确保在初始表结构创建之后再执行此迁移
+ dependencies = [
+ ('blog', '0001_initial'), # 依赖blog应用的第一个迁移文件
+ ]
+
+ # 迁移操作:为BlogSettings模型添加两个新字段
+ operations = [
+ # 为BlogSettings添加"公共尾部"字段
+ migrations.AddField(
+ model_name='blogsettings', # 目标模型:网站配置表
+ name='global_footer', # 新字段名称
+ field=models.TextField(
+ blank=True, # 允许表单提交为空
+ default='', # 默认值为空字符串
+ null=True, # 数据库中允许为NULL
+ verbose_name='公共尾部' # 管理界面显示的字段名称
+ ),
+ # 字段作用:存储网站全局共用的尾部HTML内容(如版权信息、备案号等)
+ # 可在所有页面底部统一显示,避免重复开发
+ ),
+ # 为BlogSettings添加"公共头部"字段
+ migrations.AddField(
+ model_name='blogsettings', # 目标模型:网站配置表
+ name='global_header', # 新字段名称
+ field=models.TextField(
+ blank=True,
+ default='',
+ null=True,
+ verbose_name='公共头部'
+ ),
+ # 字段作用:存储网站全局共用的头部HTML内容(如公共导航、统计代码等)
+ # 可在所有页面顶部统一显示,方便全局修改
+ ),
+ ]
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py
new file mode 100644
index 00000000..eb6e36a3
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py
@@ -0,0 +1,31 @@
+# 生成信息:由Django 4.2.1版本在2023-05-09 07:45自动生成的迁移文件
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ """
+ 数据库迁移类:为网站配置表添加评论审核开关字段
+ 用于控制用户评论是否需要管理员审核后才显示,增强内容管理能力
+ """
+
+ # 依赖关系:当前迁移依赖于博客应用的上一个迁移文件(0002_...)
+ # 确保在之前的表结构变更完成后再执行本次迁移
+ dependencies = [
+ ('blog', '0002_blogsettings_global_footer_and_more'),
+ ]
+
+ # 迁移操作:为BlogSettings模型添加评论审核开关字段
+ operations = [
+ migrations.AddField(
+ model_name='blogsettings', # 目标模型:网站配置表(BlogSettings)
+ name='comment_need_review', # 新字段名称:评论是否需要审核
+ field=models.BooleanField(
+ default=False, # 默认值为False:评论无需审核,提交后直接显示
+ verbose_name='评论是否需要审核' # 管理后台显示的字段名称
+ ),
+ # 字段作用:
+ # - 当值为True时:用户提交的评论需管理员在后台审核通过后才会在前端显示
+ # - 当值为False时:评论提交后立即显示,无需审核
+ # 用于防止垃圾评论或违规内容直接展示,提升网站内容安全性
+ ),
+ ]
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
new file mode 100644
index 00000000..8f8c1ab1
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
@@ -0,0 +1,40 @@
+# 生成信息:由Django 4.2.1版本在2023-05-09 07:51自动生成的迁移文件
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ """
+ 数据库迁移类:重命名BlogSettings模型中的多个字段
+ 目的是统一字段命名规范(采用下划线命名法),提升代码可读性和一致性
+ """
+
+ # 依赖关系:当前迁移依赖于上一个迁移文件(0003_...)
+ # 确保在添加评论审核字段之后执行字段重命名操作
+ dependencies = [
+ ('blog', '0003_blogsettings_comment_need_review'),
+ ]
+
+ # 迁移操作:批量重命名BlogSettings模型的字段
+ operations = [
+ # 重命名"analyticscode"字段为"analytics_code"
+ migrations.RenameField(
+ model_name='blogsettings', # 目标模型:网站配置表
+ old_name='analyticscode', # 旧字段名(驼峰式命名,不规范)
+ new_name='analytics_code', # 新字段名(下划线命名,符合Python规范)
+ # 字段含义:存储网站统计代码(如百度统计、Google Analytics)
+ ),
+ # 重命名"beiancode"字段为"beian_code"
+ migrations.RenameField(
+ model_name='blogsettings',
+ old_name='beiancode', # 旧字段名(连写,不规范)
+ new_name='beian_code', # 新字段名(下划线分隔,更清晰)
+ # 字段含义:存储网站ICP备案号
+ ),
+ # 重命名"sitename"字段为"site_name"
+ migrations.RenameField(
+ model_name='blogsettings',
+ old_name='sitename', # 旧字段名(连写,不规范)
+ new_name='site_name', # 新字段名(下划线分隔,符合命名习惯)
+ # 字段含义:存储网站名称
+ ),
+ ]
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
new file mode 100644
index 00000000..18f8d4f3
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
@@ -0,0 +1,107 @@
+# 生成信息:由Django 4.2.5版本在2023-09-06 13:13自动生成的迁移文件
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion # 外键删除行为处理
+import django.utils.timezone # 时间字段默认值
+import mdeditor.fields # Markdown编辑器字段
+
+
+class Migration(migrations.Migration):
+ """
+ 数据库迁移类:统一模型的字段命名和 verbose_name 为英文
+ 可能是为了国际化适配或代码规范统一,将中文标识改为英文
+ """
+
+ # 依赖关系:依赖用户模型和上一个迁移文件
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'),
+ ]
+
+ operations = [
+ # 1. 修改模型的元数据选项(verbose_name 改为英文)
+ migrations.AlterModelOptions(
+ name='article',
+ options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'],
+ 'verbose_name': 'article', 'verbose_name_plural': 'article'},
+ ),
+ migrations.AlterModelOptions(
+ name='category',
+ options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'},
+ ),
+ migrations.AlterModelOptions(
+ name='links',
+ options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'},
+ ),
+ migrations.AlterModelOptions(
+ name='sidebar',
+ options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'},
+ ),
+ migrations.AlterModelOptions(
+ name='tag',
+ options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'},
+ ),
+
+ # 2. 删除旧的时间字段(中文命名相关)
+ migrations.RemoveField(model_name='article', name='created_time'),
+ migrations.RemoveField(model_name='article', name='last_mod_time'),
+ migrations.RemoveField(model_name='category', name='created_time'),
+ migrations.RemoveField(model_name='category', name='last_mod_time'),
+ migrations.RemoveField(model_name='links', name='created_time'),
+ migrations.RemoveField(model_name='sidebar', name='created_time'),
+ migrations.RemoveField(model_name='tag', name='created_time'),
+ migrations.RemoveField(model_name='tag', name='last_mod_time'),
+
+ # 3. 添加新的时间字段(英文命名)
+ migrations.AddField(
+ model_name='article',
+ name='creation_time', # 新创建时间字段(英文命名)
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
+ ),
+ migrations.AddField(
+ model_name='article',
+ name='last_modify_time', # 新最后修改时间字段
+ field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
+ ),
+ migrations.AddField(model_name='category', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time')),
+ migrations.AddField(model_name='category', name='last_modify_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time')),
+ migrations.AddField(model_name='links', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time')),
+ migrations.AddField(model_name='sidebar', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time')),
+ migrations.AddField(model_name='tag', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time')),
+ migrations.AddField(model_name='tag', name='last_modify_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time')),
+
+ # 4. 修改所有字段的 verbose_name 为英文(仅列举部分代表性字段)
+ migrations.AlterField(
+ model_name='article',
+ name='article_order',
+ field=models.IntegerField(default=0, verbose_name='order'), # 原"排序"改为"order"
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='author',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), # 原"作者"改为"author"
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='body',
+ field=mdeditor.fields.MDTextField(verbose_name='body'), # 原"正文"改为"body"
+ ),
+ migrations.AlterField(
+ model_name='article',
+ name='comment_status',
+ field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'), # 状态选项和描述均改为英文
+ ),
+ # ... 省略其他字段的AlterField(均为verbose_name改为英文)
+
+ # 友情链接模型的显示类型选项修改(中文场景改为英文场景)
+ migrations.AlterField(
+ model_name='links',
+ name='show_type',
+ field=models.CharField(
+ choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')],
+ default='i',
+ max_length=1,
+ verbose_name='show type'
+ ),
+ ),
+ ]
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py
new file mode 100644
index 00000000..eec61592
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py
@@ -0,0 +1,30 @@
+# 生成信息:由Django 4.2.7版本在2024-01-26 02:41自动生成的迁移文件
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ """
+ 数据库迁移类:修改BlogSettings模型的元数据选项
+ 将模型的显示名称从之前的命名(可能为中文或其他语言)统一改为英文,适配国际化需求
+ """
+
+ # 依赖关系:当前迁移依赖于博客应用的上一个迁移文件(0005_...)
+ # 确保在之前的模型结构调整完成后再执行本次元数据修改
+ dependencies = [
+ ('blog', '0005_alter_article_options_alter_category_options_and_more'),
+ ]
+
+ # 迁移操作:修改BlogSettings模型的元数据选项
+ operations = [
+ migrations.AlterModelOptions(
+ name='blogsettings', # 目标模型:网站配置表(BlogSettings)
+ options={
+ 'verbose_name': 'Website configuration', # 模型单数显示名称(改为英文)
+ 'verbose_name_plural': 'Website configuration' # 模型复数显示名称(改为英文,因配置通常为单条记录,复数同单数)
+ },
+ # 修改目的:
+ # 1. 统一模型显示名称为英文,适配国际化场景(如多语言网站后台)
+ # 2. 使模型名称更符合英文开发环境的命名习惯,提升代码一致性
+ # 3. 之前的版本可能使用中文(如"网站配置")或其他命名,此处统一规范化
+ ),
+ ]
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/migrations/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/__init__.py
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/models.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/models.py
new file mode 100644
index 00000000..38a5c161
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/models.py
@@ -0,0 +1,415 @@
+# 导入日志模块,用于记录系统运行日志
+import logging
+# 导入正则表达式模块,用于处理文本中的匹配(如提取图片URL)
+import re
+# 导入抽象基类相关工具,用于定义抽象方法
+from abc import abstractmethod
+
+# 导入Django配置、异常、模型、URL反转等核心功能
+from django.conf import settings
+from django.core.exceptions import ValidationError
+from django.db import models
+from django.urls import reverse
+from django.utils.timezone import now
+from django.utils.translation import gettext_lazy as _ # 国际化翻译工具
+# 导入markdown编辑器字段,用于文章内容编辑
+from mdeditor.fields import MDTextField
+# 导入slug生成工具,用于生成URL友好的标识符
+from uuslug import slugify
+
+# 导入自定义工具:缓存装饰器、缓存操作、获取当前站点信息
+from djangoblog.utils import cache_decorator, cache
+from djangoblog.utils import get_current_site
+
+# 创建当前模块的日志记录器
+logger = logging.getLogger(__name__)
+
+
+class LinkShowType(models.TextChoices):
+ """
+ 链接展示位置的枚举类
+ 定义友情链接在网站中的展示位置选项
+ """
+ I = ('i', _('index')) # 首页展示
+ L = ('l', _('list')) # 列表页展示
+ P = ('p', _('post')) # 文章详情页展示
+ A = ('a', _('all')) # 所有页面展示
+ S = ('s', _('slide')) # 幻灯片展示
+
+
+class BaseModel(models.Model):
+ """
+ 模型基类,所有其他模型的父类
+ 封装通用字段和方法,减少代码重复
+ """
+ # 自增主键ID
+ id = models.AutoField(primary_key=True)
+ # 创建时间字段,默认为当前时间
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ # 最后修改时间字段,默认为当前时间
+ last_modify_time = models.DateTimeField(_('modify time'), default=now)
+
+ def save(self, *args, **kwargs):
+ """
+ 重写保存方法,扩展功能:
+ 1. 单独处理文章阅读量更新(避免更新其他字段)
+ 2. 自动生成slug(URL友好标识符)
+ """
+ # 判断是否是更新文章阅读量的操作
+ is_update_views = isinstance(
+ self,
+ Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
+
+ if is_update_views:
+ # 仅更新阅读量字段(优化性能)
+ Article.objects.filter(pk=self.pk).update(views=self.views)
+ else:
+ # 若模型包含slug字段,则自动生成slug(基于title或name字段)
+ if 'slug' in self.__dict__:
+ # 优先使用title字段,否则使用name字段作为slug源
+ slug_source = getattr(self, 'title') if 'title' in self.__dict__ else getattr(self, 'name')
+ setattr(self, 'slug', slugify(slug_source)) # 生成URL友好的slug
+ # 调用父类保存方法
+ super().save(*args, **kwargs)
+
+ def get_full_url(self):
+ """生成包含域名的完整URL(用于外部链接或分享)"""
+ site_domain = get_current_site().domain # 获取当前站点域名
+ full_url = f"https://{site_domain}{self.get_absolute_url()}"
+ return full_url
+
+ class Meta:
+ abstract = True # 声明为抽象基类,不生成数据库表
+
+ @abstractmethod
+ def get_absolute_url(self):
+ """
+ 抽象方法:获取模型实例的相对URL
+ 子类必须实现,用于生成详情页链接
+ """
+ pass
+
+
+class Article(BaseModel):
+ """
+ 文章模型
+ 存储博客文章的核心信息
+ """
+ # 文章状态选项:草稿/已发布
+ STATUS_CHOICES = (
+ ('d', _('Draft')), # 草稿
+ ('p', _('Published')), # 已发布
+ )
+ # 评论状态选项:开启/关闭
+ COMMENT_STATUS = (
+ ('o', _('Open')), # 允许评论
+ ('c', _('Close')), # 关闭评论
+ )
+ # 内容类型选项:文章/页面(如关于页、联系页)
+ TYPE = (
+ ('a', _('Article')), # 普通文章
+ ('p', _('Page')), # 独立页面
+ )
+
+ title = models.CharField(_('title'), max_length=200, unique=True) # 文章标题(唯一)
+ body = MDTextField(_('body')) # 文章内容(markdown格式)
+ pub_time = models.DateTimeField(
+ _('publish time'), blank=False, null=False, default=now) # 发布时间
+ status = models.CharField(
+ _('status'), max_length=1, choices=STATUS_CHOICES, default='p') # 文章状态
+ comment_status = models.CharField(
+ _('comment status'), max_length=1, choices=COMMENT_STATUS, default='o') # 评论状态
+ type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a') # 内容类型
+ views = models.PositiveIntegerField(_('views'), default=0) # 阅读量
+ author = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ verbose_name=_('author'),
+ blank=False,
+ null=False,
+ on_delete=models.CASCADE # 关联用户,用户删除时文章也删除
+ )
+ article_order = models.IntegerField(
+ _('order'), blank=False, null=False, default=0) # 文章排序权重
+ show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False) # 是否显示目录
+ category = models.ForeignKey(
+ 'Category',
+ verbose_name=_('category'),
+ on_delete=models.CASCADE,
+ blank=False,
+ null=False # 关联分类,分类删除时文章也删除
+ )
+ tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True) # 多对多关联标签
+
+ def body_to_string(self):
+ """将文章内容转换为字符串(用于搜索等场景)"""
+ return self.body
+
+ def __str__(self):
+ """模型实例的字符串表示(用于后台显示)"""
+ return self.title
+
+ class Meta:
+ ordering = ['-article_order', '-pub_time'] # 排序规则:先按排序权重降序,再按发布时间降序
+ verbose_name = _('article') # 模型显示名称(单数)
+ verbose_name_plural = verbose_name # 模型显示名称(复数)
+ get_latest_by = 'id' # 按id获取最新记录
+
+ def get_absolute_url(self):
+ """生成文章详情页的相对URL"""
+ return reverse('blog:detailbyid', kwargs={
+ 'article_id': self.id,
+ 'year': self.creation_time.year,
+ 'month': self.creation_time.month,
+ 'day': self.creation_time.day
+ })
+
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
+ def get_category_tree(self):
+ """获取当前文章所属分类的层级树(含父级分类)"""
+ tree = self.category.get_category_tree()
+ # 转换为 (分类名称, 分类URL) 的列表
+ return [(c.name, c.get_absolute_url()) for c in tree]
+
+ def save(self, *args, **kwargs):
+ """重写保存方法(可扩展,此处直接调用父类方法)"""
+ super().save(*args, **kwargs)
+
+ def viewed(self):
+ """增加阅读量并保存(仅更新views字段)"""
+ self.views += 1
+ self.save(update_fields=['views']) # 只更新views字段,优化性能
+
+ def comment_list(self):
+ """获取当前文章的有效评论列表(带缓存)"""
+ cache_key = f'article_comments_{self.id}'
+ # 尝试从缓存获取
+ cached_comments = cache.get(cache_key)
+ if cached_comments:
+ logger.info(f'从缓存获取文章评论: {self.id}')
+ return cached_comments
+ # 缓存未命中,从数据库查询
+ comments = self.comment_set.filter(is_enable=True).order_by('-id') # 按ID降序(最新在前)
+ cache.set(cache_key, comments, 60 * 100) # 缓存100分钟
+ logger.info(f'缓存文章评论: {self.id}')
+ return comments
+
+ def get_admin_url(self):
+ """获取后台管理编辑页面的URL"""
+ # 自动获取模型的app标签和模型名称
+ info = (self._meta.app_label, self._meta.model_name)
+ return reverse(f'admin:{info[0]}_{info[1]}_change', args=(self.pk,))
+
+ @cache_decorator(expiration=60 * 100) # 缓存100分钟
+ def next_article(self):
+ """获取下一篇文章(ID更大、已发布的第一篇)"""
+ return Article.objects.filter(id__gt=self.id, status='p').order_by('id').first()
+
+ @cache_decorator(expiration=60 * 100) # 缓存100分钟
+ def prev_article(self):
+ """获取上一篇文章(ID更小、已发布的最后一篇)"""
+ return Article.objects.filter(id__lt=self.id, status='p').first()
+
+ def get_first_image_url(self):
+ """从文章内容中提取第一张图片的URL(用于封面图等场景)"""
+ # 正则匹配markdown图片格式: 
+ match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
+ if match:
+ return match.group(1) # 返回匹配到的URL
+ return "" # 无图片时返回空
+
+
+class Category(BaseModel):
+ """
+ 分类模型
+ 用于文章的分类管理,支持层级结构(父分类)
+ """
+ name = models.CharField(_('category name'), max_length=30, unique=True) # 分类名称(唯一)
+ parent_category = models.ForeignKey(
+ 'self',
+ verbose_name=_('parent category'),
+ blank=True,
+ null=True,
+ on_delete=models.CASCADE # 父分类删除时,子分类也删除
+ )
+ slug = models.SlugField(default='no-slug', max_length=60, blank=True) # 分类的URL标识符
+ index = models.IntegerField(default=0, verbose_name=_('index')) # 排序权重(值越大越靠前)
+
+ class Meta:
+ ordering = ['-index'] # 按排序权重降序排列
+ verbose_name = _('category')
+ verbose_name_plural = verbose_name
+
+ def get_absolute_url(self):
+ """生成分类详情页的相对URL"""
+ return reverse('blog:category_detail', kwargs={'category_name': self.slug})
+
+ def __str__(self):
+ return self.name
+
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
+ def get_category_tree(self):
+ """
+ 递归获取当前分类的层级树(含所有父级分类)
+ 例如:子分类 -> 父分类 -> 顶级分类
+ """
+ categorys = []
+
+ def parse(category):
+ categorys.append(category)
+ if category.parent_category: # 若存在父分类,继续递归
+ parse(category.parent_category)
+
+ parse(self)
+ return categorys
+
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
+ def get_sub_categorys(self):
+ """
+ 递归获取当前分类的所有子分类(含多级子分类)
+ """
+ categorys = []
+ all_categorys = Category.objects.all() # 获取所有分类
+
+ def parse(category):
+ if category not in categorys:
+ categorys.append(category)
+ # 获取当前分类的直接子分类
+ childs = all_categorys.filter(parent_category=category)
+ for child in childs:
+ if child not in categorys:
+ categorys.append(child)
+ parse(child) # 递归处理子分类
+
+ parse(self)
+ return categorys
+
+
+class Tag(BaseModel):
+ """
+ 标签模型
+ 用于文章的标签管理(多对多关系)
+ """
+ name = models.CharField(_('tag name'), max_length=30, unique=True) # 标签名称(唯一)
+ slug = models.SlugField(default='no-slug', max_length=60, blank=True) # 标签的URL标识符
+
+ def __str__(self):
+ return self.name
+
+ def get_absolute_url(self):
+ """生成标签详情页的相对URL"""
+ return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
+
+ @cache_decorator(60 * 60 * 10) # 缓存10小时
+ def get_article_count(self):
+ """获取该标签关联的文章数量(去重)"""
+ return Article.objects.filter(tags__name=self.name).distinct().count()
+
+ class Meta:
+ ordering = ['name'] # 按名称升序排列
+ verbose_name = _('tag')
+ verbose_name_plural = verbose_name
+
+
+class Links(models.Model):
+ """
+ 友情链接模型
+ 存储网站的友情链接信息
+ """
+ name = models.CharField(_('link name'), max_length=30, unique=True) # 链接名称(唯一)
+ link = models.URLField(_('link')) # 链接URL
+ sequence = models.IntegerField(_('order'), unique=True) # 排序序号(唯一,用于控制显示顺序)
+ is_enable = models.BooleanField(
+ _('is show'), default=True, blank=False, null=False) # 是否启用(显示)
+ show_type = models.CharField(
+ _('show type'),
+ max_length=1,
+ choices=LinkShowType.choices,
+ default=LinkShowType.I # 默认为首页展示
+ )
+ creation_time = models.DateTimeField(_('creation time'), default=now) # 创建时间
+ last_mod_time = models.DateTimeField(_('modify time'), default=now) # 最后修改时间
+
+ class Meta:
+ ordering = ['sequence'] # 按排序序号升序排列
+ verbose_name = _('link')
+ verbose_name_plural = verbose_name
+
+ def __str__(self):
+ return self.name
+
+
+class SideBar(models.Model):
+ """
+ 侧边栏模型
+ 用于展示网站侧边栏内容(支持HTML)
+ """
+ name = models.CharField(_('title'), max_length=100) # 侧边栏标题
+ content = models.TextField(_('content')) # 侧边栏内容(HTML格式)
+ sequence = models.IntegerField(_('order'), unique=True) # 排序序号(控制显示顺序)
+ is_enable = models.BooleanField(_('is enable'), default=True) # 是否启用
+ creation_time = models.DateTimeField(_('creation time'), default=now)
+ last_mod_time = models.DateTimeField(_('modify time'), default=now)
+
+ class Meta:
+ ordering = ['sequence'] # 按排序序号升序排列
+ verbose_name = _('sidebar')
+ verbose_name_plural = verbose_name
+
+ def __str__(self):
+ return self.name
+
+
+class BlogSettings(models.Model):
+ """
+ 博客配置模型
+ 存储网站的全局设置(单例模式,仅允许一条记录)
+ """
+ site_name = models.CharField(
+ _('site name'), max_length=200, null=False, blank=False, default='') # 网站名称
+ site_description = models.TextField(
+ _('site description'), max_length=1000, null=False, blank=False, default='') # 网站描述
+ site_seo_description = models.TextField(
+ _('site seo description'), max_length=1000, null=False, blank=False, default='') # SEO描述
+ site_keywords = models.TextField(
+ _('site keywords'), max_length=1000, null=False, blank=False, default='') # 网站关键词(SEO)
+ article_sub_length = models.IntegerField(_('article sub length'), default=300) # 文章摘要长度
+ sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10) # 侧边栏文章数量
+ sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5) # 侧边栏评论数量
+ article_comment_count = models.IntegerField(_('article comment count'), default=5) # 文章页显示评论数量
+ show_google_adsense = models.BooleanField(_('show adsense'), default=False) # 是否显示谷歌广告
+ google_adsense_codes = models.TextField(
+ _('adsense code'), max_length=2000, null=True, blank=True, default='') # 谷歌广告代码
+ open_site_comment = models.BooleanField(_('open site comment'), default=True) # 是否开启全站评论
+ global_header = models.TextField("公共头部", null=True, blank=True, default='') # 全局头部HTML代码
+ global_footer = models.TextField("公共尾部", null=True, blank=True, default='') # 全局尾部HTML代码
+ beian_code = models.CharField(
+ '备案号', max_length=2000, null=True, blank=True, default='') # 网站备案号
+ analytics_code = models.TextField(
+ "网站统计代码", max_length=1000, null=False, blank=False, default='') # 统计代码(如百度统计)
+ show_gongan_code = models.BooleanField(
+ '是否显示公安备案号', default=False, null=False) # 是否显示公安备案号
+ gongan_beiancode = models.TextField(
+ '公安备案号', max_length=2000, null=True, blank=True, default='') # 公安备案号
+ comment_need_review = models.BooleanField(
+ '评论是否需要审核', default=False, null=False) # 评论是否需要审核后显示
+
+ class Meta:
+ verbose_name = _('Website configuration')
+ verbose_name_plural = verbose_name
+
+ def __str__(self):
+ return self.site_name
+
+ def clean(self):
+ """
+ 数据验证:确保仅存在一条配置记录
+ 在保存前调用,用于防止创建多条配置
+ """
+ if BlogSettings.objects.exclude(id=self.id).count():
+ raise ValidationError(_('There can only be one configuration')) # 仅允许一个配置记录
+
+ def save(self, *args, **kwargs):
+ """保存配置后清除缓存(确保配置实时生效)"""
+ super().save(*args, **kwargs)
+ from djangoblog.utils import cache
+ cache.clear() # 清除所有缓存
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py
new file mode 100644
index 00000000..70c4e08e
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py
@@ -0,0 +1,32 @@
+# 导入Haystack的索引模块,用于定义搜索索引
+from haystack import indexes
+
+# 导入博客文章模型,作为搜索索引的数据源
+from blog.models import Article
+
+
+class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
+ """
+ 文章搜索索引类,用于配置Haystack搜索的索引规则
+ 继承自Haystack的SearchIndex(搜索索引基类)和Indexable(可索引接口)
+ """
+ # 定义主搜索字段:
+ # - document=True:标记为主要搜索字段(Haystack默认以此字段作为全文检索的基础)
+ # - use_template=True:指定使用模板来构建索引内容(模板通常存放于templates/search/indexes/[app名]/[模型名]_text.txt)
+ text = indexes.CharField(document=True, use_template=True)
+
+ def get_model(self):
+ """
+ 必须实现的方法:指定该索引对应的模型
+ 返回值为需要被索引的Django模型类
+ """
+ return Article
+
+ def index_queryset(self, using=None):
+ """
+ 定义需要被索引的数据集
+ 筛选出状态为"已发布"(status='p')的文章,仅对这些文章建立搜索索引
+ :param using: 可选参数,指定搜索引擎(多引擎场景下使用)
+ :return: 查询集(QuerySet),包含需要被索引的模型实例
+ """
+ return self.get_model().objects.filter(status='p')
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/__init__.py
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py
new file mode 100644
index 00000000..683a9d34
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py
@@ -0,0 +1,408 @@
+import hashlib # 用于Gravatar头像的MD5哈希计算
+import logging # 日志记录
+import random # 随机选择样式
+import urllib # URL编码处理
+
+from django import template # 模板标签核心模块
+from django.conf import settings # 项目配置
+from django.db.models import Q # 数据库查询条件
+from django.shortcuts import get_object_or_404 # 获取对象或返回404
+from django.template.defaultfilters import stringfilter # 字符串过滤器装饰器
+from django.templatetags.static import static # 静态文件URL生成
+from django.urls import reverse # URL反向解析
+from django.utils.safestring import mark_safe # 标记安全HTML字符串
+
+# 导入项目模型
+from blog.models import Article, Category, Tag, Links, SideBar, LinkShowType
+from comments.models import Comment
+# 导入工具类和插件
+from djangoblog.utils import CommonMarkdown, sanitize_html # Markdown处理和HTML净化
+from djangoblog.utils import cache # 缓存工具
+from djangoblog.utils import get_current_site # 获取当前站点信息
+from oauth.models import OAuthUser # OAuth用户模型
+from djangoblog.plugin_manage import hooks # 插件钩子
+
+# 日志配置
+logger = logging.getLogger(__name__)
+
+# 注册模板标签库
+register = template.Library()
+
+
+@register.simple_tag(takes_context=True)
+def head_meta(context):
+ """
+ 页面头部元信息标签(通过插件钩子扩展)
+ 用于动态生成SEO相关的meta标签(如title、keywords等)
+ :param context: 模板上下文
+ :return: 经过插件处理的安全HTML字符串
+ """
+ return mark_safe(hooks.apply_filters('head_meta', '', context))
+
+
+@register.simple_tag
+def timeformat(data):
+ """
+ 时间格式化标签
+ 将 datetime 对象格式化为 settings.TIME_FORMAT 定义的样式
+ :param data: datetime对象
+ :return: 格式化后的时间字符串,失败返回空
+ """
+ try:
+ return data.strftime(settings.TIME_FORMAT)
+ except Exception as e:
+ logger.error(e)
+ return ""
+
+
+@register.simple_tag
+def datetimeformat(data):
+ """
+ 日期时间格式化标签
+ 将 datetime 对象格式化为 settings.DATE_TIME_FORMAT 定义的样式
+ :param data: datetime对象
+ :return: 格式化后的日期时间字符串,失败返回空
+ """
+ try:
+ return data.strftime(settings.DATE_TIME_FORMAT)
+ except Exception as e:
+ logger.error(e)
+ return ""
+
+
+@register.filter()
+@stringfilter
+def custom_markdown(content):
+ """
+ Markdown渲染过滤器
+ 将Markdown格式的文本转换为HTML并标记为安全
+ :param content: Markdown文本
+ :return: 安全的HTML字符串
+ """
+ return mark_safe(CommonMarkdown.get_markdown(content))
+
+
+@register.simple_tag
+def get_markdown_toc(content):
+ """
+ 获取Markdown内容的目录(TOC)
+ 用于生成文章目录导航
+ :param content: Markdown文本
+ :return: 目录的HTML字符串
+ """
+ from djangoblog.utils import CommonMarkdown
+ body, toc = CommonMarkdown.get_markdown_with_toc(content)
+ return mark_safe(toc)
+
+
+@register.filter()
+@stringfilter
+def comment_markdown(content):
+ """
+ 评论内容的Markdown渲染过滤器
+ 先转换为HTML,再通过sanitize_html净化(过滤危险标签)
+ :param content: 评论的Markdown文本
+ :return: 安全的HTML字符串
+ """
+ content = CommonMarkdown.get_markdown(content)
+ return mark_safe(sanitize_html(content))
+
+
+@register.filter(is_safe=True)
+@stringfilter
+def truncatechars_content(content):
+ """
+ 文章内容摘要过滤器
+ 根据网站配置的摘要长度截断HTML内容(保留标签结构)
+ :param content: 文章HTML内容
+ :return: 截断后的安全HTML字符串
+ """
+ from django.template.defaultfilters import truncatechars_html
+ from djangoblog.utils import get_blog_setting
+ blogsetting = get_blog_setting() # 获取网站配置
+ return truncatechars_html(content, blogsetting.article_sub_length)
+
+
+@register.filter(is_safe=True)
+@stringfilter
+def truncate(content):
+ """
+ 简单截断过滤器(纯文本)
+ 去除HTML标签后截断前150个字符
+ :param content: 带HTML的文本
+ :return: 截断后的纯文本
+ """
+ from django.utils.html import strip_tags
+ return strip_tags(content)[:150]
+
+
+@register.inclusion_tag('blog/tags/breadcrumb.html')
+def load_breadcrumb(article):
+ """
+ 面包屑导航标签
+ 生成文章的分类层级导航(如:首页 > 技术 > Python > 文章标题)
+ :param article: 文章对象
+ :return: 包含导航层级和标题的上下文
+ """
+ names = article.get_category_tree() # 获取分类层级列表
+ from djangoblog.utils import get_blog_setting
+ blogsetting = get_blog_setting()
+ site = get_current_site().domain
+ names.append((blogsetting.site_name, '/')) # 添加首页
+ names = names[::-1] # 反转层级顺序(从顶级到当前)
+
+ return {
+ 'names': names,
+ 'title': article.title,
+ 'count': len(names) + 1
+ }
+
+
+@register.inclusion_tag('blog/tags/article_tag_list.html')
+def load_articletags(article):
+ """
+ 文章标签列表标签
+ 生成文章关联的标签列表,包含标签URL、文章数量和随机样式
+ :param article: 文章对象
+ :return: 包含标签信息的上下文
+ """
+ tags = article.tags.all()
+ tags_list = []
+ for tag in tags:
+ url = tag.get_absolute_url() # 标签页URL
+ count = tag.get_article_count() # 标签关联的文章数
+ # 随机选择Bootstrap样式(如primary、success等)
+ tags_list.append((
+ url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES)
+ ))
+ return {'article_tags_list': tags_list}
+
+
+@register.inclusion_tag('blog/tags/sidebar.html')
+def load_sidebar(user, linktype):
+ """
+ 侧边栏内容标签
+ 加载侧边栏所需数据(热门文章、分类、标签云等),并使用缓存优化性能
+ :param user: 当前用户
+ :param linktype: 链接显示类型(控制友情链接显示场景)
+ :return: 侧边栏数据上下文
+ """
+ # 缓存键:区分不同链接类型的侧边栏
+ cachekey = "sidebar" + linktype
+ value = cache.get(cachekey)
+ if value: # 命中缓存直接返回
+ value['user'] = user
+ return value
+ else: # 未命中缓存,重新计算并缓存
+ logger.info('load sidebar')
+ from djangoblog.utils import get_blog_setting
+ blogsetting = get_blog_setting() # 网站配置
+
+ # 侧边栏数据查询
+ recent_articles = Article.objects.filter(status='p')[:blogsetting.sidebar_article_count] # 最新文章
+ sidebar_categorys = Category.objects.all() # 所有分类
+ extra_sidebars = SideBar.objects.filter(is_enable=True).order_by('sequence') # 自定义侧边栏
+ most_read_articles = Article.objects.filter(status='p').order_by('-views')[
+ :blogsetting.sidebar_article_count] # 热门文章
+ dates = Article.objects.datetimes('creation_time', 'month', order='DESC') # 文章归档日期
+ # 符合显示类型的友情链接
+ links = Links.objects.filter(is_enable=True).filter(
+ Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)
+ )
+ # 最新评论
+ commment_list = Comment.objects.filter(is_enable=True).order_by('-id')[:blogsetting.sidebar_comment_count]
+
+ # 标签云(根据文章数量计算字体大小)
+ sidebar_tags = None
+ tags = Tag.objects.all()
+ if tags and len(tags) > 0:
+ # 过滤有文章的标签
+ tag_with_count = [(t, t.get_article_count()) for t in tags if t.get_article_count()]
+ if tag_with_count:
+ total = sum([t[1] for t in tag_with_count])
+ avg = total / len(tag_with_count) # 平均文章数
+ increment = 5 # 字体大小增量
+ # 计算每个标签的字体大小(与平均数量成正比)
+ sidebar_tags = [
+ (t[0], t[1], (t[1] / avg) * increment + 10)
+ for t in tag_with_count
+ ]
+ random.shuffle(sidebar_tags) # 随机排序
+
+ # 组装侧边栏数据
+ value = {
+ 'recent_articles': recent_articles,
+ 'sidebar_categorys': sidebar_categorys,
+ 'most_read_articles': most_read_articles,
+ 'article_dates': dates,
+ 'sidebar_comments': commment_list,
+ 'sidabar_links': links,
+ 'show_google_adsense': blogsetting.show_google_adsense,
+ 'google_adsense_codes': blogsetting.google_adsense_codes,
+ 'open_site_comment': blogsetting.open_site_comment,
+ 'show_gongan_code': blogsetting.show_gongan_code,
+ 'sidebar_tags': sidebar_tags,
+ 'extra_sidebars': extra_sidebars
+ }
+ # 缓存3小时
+ cache.set(cachekey, value, 60 * 60 * 3)
+ logger.info(f'set sidebar cache.key:{cachekey}')
+ value['user'] = user
+ return value
+
+
+@register.inclusion_tag('blog/tags/article_meta_info.html')
+def load_article_metas(article, user):
+ """
+ 文章元信息标签
+ 加载文章的元数据(作者、发布时间、分类等)
+ :param article: 文章对象
+ :param user: 当前用户
+ :return: 包含文章和用户的上下文
+ """
+ return {'article': article, 'user': user}
+
+
+@register.inclusion_tag('blog/tags/article_pagination.html')
+def load_pagination_info(page_obj, page_type, tag_name):
+ """
+ 分页导航标签
+ 根据不同页面类型(首页、标签页、分类页等)生成上一页/下一页链接
+ :param page_obj: Django分页对象
+ :param page_type: 页面类型(如分类标签归档、作者文章归档等)
+ :param tag_name: 标签/分类/作者名称(用于URL参数)
+ :return: 包含分页链接的上下文
+ """
+ previous_url = ''
+ next_url = ''
+
+ # 首页分页
+ if page_type == '':
+ if page_obj.has_next():
+ next_url = reverse('blog:index_page', kwargs={'page': page_obj.next_page_number()})
+ if page_obj.has_previous():
+ previous_url = reverse('blog:index_page', kwargs={'page': page_obj.previous_page_number()})
+
+ # 标签页分页
+ elif page_type == '分类标签归档':
+ tag = get_object_or_404(Tag, name=tag_name)
+ if page_obj.has_next():
+ next_url = reverse('blog:tag_detail_page',
+ kwargs={'page': page_obj.next_page_number(), 'tag_name': tag.slug})
+ if page_obj.has_previous():
+ previous_url = reverse('blog:tag_detail_page',
+ kwargs={'page': page_obj.previous_page_number(), 'tag_name': tag.slug})
+
+ # 作者文章分页
+ elif page_type == '作者文章归档':
+ if page_obj.has_next():
+ next_url = reverse('blog:author_detail_page',
+ kwargs={'page': page_obj.next_page_number(), 'author_name': tag_name})
+ if page_obj.has_previous():
+ previous_url = reverse('blog:author_detail_page',
+ kwargs={'page': page_obj.previous_page_number(), 'author_name': tag_name})
+
+ # 分类页分页
+ elif page_type == '分类目录归档':
+ category = get_object_or_404(Category, name=tag_name)
+ if page_obj.has_next():
+ next_url = reverse('blog:category_detail_page',
+ kwargs={'page': page_obj.next_page_number(), 'category_name': category.slug})
+ if page_obj.has_previous():
+ previous_url = reverse('blog:category_detail_page',
+ kwargs={'page': page_obj.previous_page_number(), 'category_name': category.slug})
+
+ return {
+ 'previous_url': previous_url,
+ 'next_url': next_url,
+ 'page_obj': page_obj
+ }
+
+
+@register.inclusion_tag('blog/tags/article_info.html')
+def load_article_detail(article, isindex, user):
+ """
+ 文章详情标签
+ 加载文章详情页或列表页的展示内容(列表页显示摘要,详情页显示完整内容)
+ :param article: 文章对象
+ :param isindex: 是否为列表页(True/False)
+ :param user: 当前用户
+ :return: 包含文章展示信息的上下文
+ """
+ from djangoblog.utils import get_blog_setting
+ blogsetting = get_blog_setting()
+ return {
+ 'article': article,
+ 'isindex': isindex,
+ 'user': user,
+ 'open_site_comment': blogsetting.open_site_comment, # 是否允许评论
+ }
+
+
+@register.filter
+def gravatar_url(email, size=40):
+ """
+ Gravatar头像URL过滤器
+ 生成用户的Gravatar头像URL(优先使用OAuth用户的头像)
+ :param email: 用户邮箱
+ :param size: 头像尺寸
+ :return: 头像URL字符串
+ """
+ cachekey = f'gravatat/{email}'
+ url = cache.get(cachekey)
+ if url: # 缓存命中
+ return url
+ else: # 缓存未命中
+ # 优先使用OAuth用户的头像
+ oauth_users = OAuthUser.objects.filter(email=email)
+ if oauth_users:
+ valid_avatars = [user for user in oauth_users if user.picture]
+ if valid_avatars:
+ return valid_avatars[0].picture
+
+ # 生成Gravatar URL(邮箱MD5哈希 + 尺寸 + 默认头像)
+ email = email.encode('utf-8')
+ default_avatar = static('blog/img/avatar.png') # 本地默认头像
+ url = f"https://www.gravatar.com/avatar/{hashlib.md5(email.lower()).hexdigest()}?{urllib.parse.urlencode({'d': default_avatar, 's': str(size)})}"
+
+ # 缓存10小时
+ cache.set(cachekey, url, 60 * 60 * 10)
+ logger.info(f'set gravatar cache.key:{cachekey}')
+ return url
+
+
+@register.filter
+def gravatar(email, size=40):
+ """
+ Gravatar头像标签
+ 生成包含头像图片的HTML标签
+ :param email: 用户邮箱
+ :param size: 头像尺寸
+ :return: 安全的img标签HTML字符串
+ """
+ url = gravatar_url(email, size)
+ return mark_safe(f'
')
+
+
+@register.simple_tag
+def query(qs, **kwargs):
+ """
+ 查询集过滤标签
+ 在模板中对查询集进行过滤(如{% query books author=author as mybooks %})
+ :param qs: Django查询集
+ :param kwargs: 过滤条件(键值对)
+ :return: 过滤后的查询集
+ """
+ return qs.filter(**kwargs)
+
+
+@register.filter
+def addstr(arg1, arg2):
+ """
+ 字符串拼接过滤器
+ 将两个参数转换为字符串并拼接
+ :param arg1: 第一个参数
+ :param arg2: 第二个参数
+ :return: 拼接后的字符串
+ """
+ return str(arg1) + str(arg2)
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/tests.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/tests.py
new file mode 100644
index 00000000..8eb99a81
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/tests.py
@@ -0,0 +1,331 @@
+import os
+import requests
+
+# 导入Django核心模块:配置、文件上传、命令调用、分页、静态文件、测试工具、URL反转、时区
+from django.conf import settings
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.core.management import call_command
+from django.core.paginator import Paginator
+from django.templatetags.static import static
+from django.test import Client, RequestFactory, TestCase
+from django.urls import reverse
+from django.utils import timezone
+
+# 导入项目相关模型和工具:用户、博客模型、表单、模板标签、工具函数、OAuth相关
+from accounts.models import BlogUser
+from blog.forms import BlogSearchForm
+from blog.models import Article, Category, Tag, SideBar, Links
+from blog.templatetags.blog_tags import load_pagination_info, load_articletags
+from djangoblog.utils import get_current_site, get_sha256
+from oauth.models import OAuthUser, OAuthConfig
+
+
+class ArticleTest(TestCase):
+ """
+ 博客核心功能测试类
+ 测试文章、分类、标签、搜索、权限等核心业务逻辑
+ """
+
+ def setUp(self):
+ """
+ 测试前的初始化方法
+ 创建测试客户端和请求工厂,用于模拟HTTP请求
+ """
+ self.client = Client() # 模拟用户浏览器的客户端
+ self.factory = RequestFactory() # 用于构造请求对象的工厂
+
+ def test_validate_article(self):
+ """
+ 测试文章相关核心功能:
+ - 用户模型操作
+ - 分类、标签、侧边栏、链接等模型CRUD
+ - 文章发布、分页、搜索、评论等流程
+ - 页面访问状态码验证
+ """
+ # 获取当前站点域名
+ site = get_current_site().domain
+
+ # 创建或获取测试用户(管理员)
+ user = BlogUser.objects.get_or_create(
+ email="liangliangyy@gmail.com",
+ username="liangliangyy")[0]
+ user.set_password("liangliangyy") # 设置密码
+ user.is_staff = True # 允许登录admin
+ user.is_superuser = True # 超级管理员权限
+ user.save()
+
+ # 测试用户个人页面访问
+ response = self.client.get(user.get_absolute_url())
+ self.assertEqual(response.status_code, 200) # 验证页面正常访问
+
+ # 测试admin后台页面访问(未登录状态,实际会跳转登录页)
+ self.client.get('/admin/servermanager/emailsendlog/')
+ self.client.get('admin/admin/logentry/')
+
+ # 创建测试侧边栏
+ s = SideBar()
+ s.sequence = 1 # 排序序号
+ s.name = 'test' # 名称
+ s.content = 'test content' # 内容
+ s.is_enable = True # 启用
+ s.save()
+
+ # 创建测试分类
+ category = Category()
+ category.name = "category" # 分类名称
+ category.creation_time = timezone.now()
+ category.last_mod_time = timezone.now()
+ category.save()
+
+ # 创建测试标签
+ tag = Tag()
+ tag.name = "nicetag" # 标签名称
+ tag.save()
+
+ # 创建测试文章
+ article = Article()
+ article.title = "nicetitle" # 标题
+ article.body = "nicecontent" # 内容
+ article.author = user # 作者
+ article.category = category # 所属分类
+ article.type = 'a' # 类型为文章
+ article.status = 'p' # 状态为已发布
+ article.save()
+
+ # 验证标签关联(初始无标签)
+ self.assertEqual(0, article.tags.count())
+ # 关联标签并验证
+ article.tags.add(tag)
+ article.save()
+ self.assertEqual(1, article.tags.count())
+
+ # 批量创建20篇测试文章(用于测试分页)
+ for i in range(20):
+ article = Article()
+ article.title = f"nicetitle{i}"
+ article.body = f"nicetitle{i}"
+ article.author = user
+ article.category = category
+ article.type = 'a'
+ article.status = 'p'
+ article.save()
+ article.tags.add(tag)
+ article.save()
+
+ # 测试Elasticsearch搜索(如果启用)
+ from blog.documents import ELASTICSEARCH_ENABLED
+ if ELASTICSEARCH_ENABLED:
+ call_command("build_index") # 构建搜索索引
+ response = self.client.get('/search', {'q': 'nicetitle'}) # 执行搜索
+ self.assertEqual(response.status_code, 200) # 验证搜索页正常
+
+ # 测试文章详情页访问
+ response = self.client.get(article.get_absolute_url())
+ self.assertEqual(response.status_code, 200)
+
+ # 测试搜索引擎推送功能
+ from djangoblog.spider_notify import SpiderNotify
+ SpiderNotify.notify(article.get_absolute_url()) # 推送文章URL到搜索引擎
+
+ # 测试标签页访问
+ response = self.client.get(tag.get_absolute_url())
+ self.assertEqual(response.status_code, 200)
+
+ # 测试分类页访问
+ response = self.client.get(category.get_absolute_url())
+ self.assertEqual(response.status_code, 200)
+
+ # 测试搜索功能(无结果场景)
+ response = self.client.get('/search', {'q': 'django'})
+ self.assertEqual(response.status_code, 200)
+
+ # 测试文章标签模板标签
+ s = load_articletags(article)
+ self.assertIsNotNone(s) # 验证模板标签返回结果
+
+ # 登录测试用户
+ self.client.login(username='liangliangyy', password='liangliangyy')
+
+ # 测试归档页访问
+ response = self.client.get(reverse('blog:archives'))
+ self.assertEqual(response.status_code, 200)
+
+ # 测试各种场景下的分页功能
+ # 1. 所有文章分页
+ p = Paginator(Article.objects.all(), settings.PAGINATE_BY)
+ self.check_pagination(p, '', '')
+
+ # 2. 标签筛选分页
+ p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY)
+ self.check_pagination(p, '分类标签归档', tag.slug)
+
+ # 3. 作者筛选分页
+ p = Paginator(
+ Article.objects.filter(author__username='liangliangyy'),
+ settings.PAGINATE_BY
+ )
+ self.check_pagination(p, '作者文章归档', 'liangliangyy')
+
+ # 4. 分类筛选分页
+ p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY)
+ self.check_pagination(p, '分类目录归档', category.slug)
+
+ # 测试搜索表单
+ f = BlogSearchForm()
+ f.search() # 调用搜索方法
+
+ # 测试百度搜索引擎推送
+ SpiderNotify.baidu_notify([article.get_full_url()])
+
+ # 测试头像相关模板标签
+ from blog.templatetags.blog_tags import gravatar_url, gravatar
+ u = gravatar_url('liangliangyy@gmail.com') # 获取头像URL
+ u = gravatar('liangliangyy@gmail.com') # 生成头像HTML
+
+ # 测试友情链接
+ link = Links(
+ sequence=1,
+ name="lylinux",
+ link='https://wwww.lylinux.net'
+ )
+ link.save()
+ response = self.client.get('/links.html') # 访问友情链接页
+ self.assertEqual(response.status_code, 200)
+
+ # 测试RSS订阅
+ response = self.client.get('/feed/')
+ self.assertEqual(response.status_code, 200)
+
+ # 测试站点地图
+ response = self.client.get('/sitemap.xml')
+ self.assertEqual(response.status_code, 200)
+
+ # 测试admin后台操作(删除文章、访问日志)
+ self.client.get("/admin/blog/article/1/delete/")
+ self.client.get('/admin/servermanager/emailsendlog/')
+ self.client.get('/admin/admin/logentry/')
+ self.client.get('/admin/admin/logentry/1/change/')
+
+ def check_pagination(self, p, type, value):
+ """
+ 测试分页功能的辅助方法
+ 验证分页控件生成的URL是否可正常访问
+ """
+ # 遍历所有分页页面
+ for page in range(1, p.num_pages + 1):
+ # 获取分页信息(通过模板标签)
+ s = load_pagination_info(p.page(page), type, value)
+ self.assertIsNotNone(s) # 验证分页信息生成正常
+
+ # 测试上一页链接
+ if s['previous_url']:
+ response = self.client.get(s['previous_url'])
+ self.assertEqual(response.status_code, 200)
+
+ # 测试下一页链接
+ if s['next_url']:
+ response = self.client.get(s['next_url'])
+ self.assertEqual(response.status_code, 200)
+
+ def test_image(self):
+ """
+ 测试图片上传功能:
+ - 未授权上传
+ - 授权上传
+ - 头像保存工具函数
+ - 邮件发送工具函数
+ """
+ # 下载测试图片(Python官方logo)
+ rsp = requests.get('https://www.python.org/static/img/python-logo.png')
+ imagepath = os.path.join(settings.BASE_DIR, 'python.png') # 保存路径
+ with open(imagepath, 'wb') as file:
+ file.write(rsp.content)
+
+ # 测试未授权上传(预期403禁止访问)
+ rsp = self.client.post('/upload')
+ self.assertEqual(rsp.status_code, 403)
+
+ # 生成上传签名(基于SECRET_KEY的双重SHA256加密)
+ sign = get_sha256(get_sha256(settings.SECRET_KEY))
+
+ # 测试授权上传
+ with open(imagepath, 'rb') as file:
+ # 构造上传文件对象
+ imgfile = SimpleUploadedFile(
+ 'python.png', file.read(), content_type='image/jpg'
+ )
+ form_data = {'python.png': imgfile}
+ # 带签名上传
+ rsp = self.client.post(
+ f'/upload?sign={sign}', form_data, follow=True
+ )
+ self.assertEqual(rsp.status_code, 200) # 验证上传成功
+
+ # 清理测试文件
+ os.remove(imagepath)
+
+ # 测试用户头像保存和邮件发送工具函数
+ from djangoblog.utils import save_user_avatar, send_email
+ send_email(['qq@qq.com'], 'testTitle', 'testContent') # 测试发送邮件
+ save_user_avatar('https://www.python.org/static/img/python-logo.png') # 测试保存头像
+
+ def test_errorpage(self):
+ """测试错误页面(404页面)"""
+ rsp = self.client.get('/eee') # 访问不存在的URL
+ self.assertEqual(rsp.status_code, 404) # 验证返回404
+
+ def test_commands(self):
+ """
+ 测试Django自定义命令:
+ - 索引构建、缓存清理、数据同步等
+ """
+ # 创建测试用户
+ user = BlogUser.objects.get_or_create(
+ email="liangliangyy@gmail.com",
+ username="liangliangyy")[0]
+ user.set_password("liangliangyy")
+ user.is_staff = True
+ user.is_superuser = True
+ user.save()
+
+ # 创建OAuth配置
+ c = OAuthConfig()
+ c.type = 'qq' # QQ登录
+ c.appkey = 'appkey'
+ c.appsecret = 'appsecret'
+ c.save()
+
+ # 创建关联用户的OAuth账号
+ u = OAuthUser()
+ u.type = 'qq'
+ u.openid = 'openid'
+ u.user = user # 关联本地用户
+ u.picture = static("/blog/img/avatar.png") # 头像
+ u.metadata = '''
+{
+"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
+}''' # 第三方平台返回的元数据
+ u.save()
+
+ # 创建未关联本地用户的OAuth账号
+ u = OAuthUser()
+ u.type = 'qq'
+ u.openid = 'openid1'
+ u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30'
+ u.metadata = '''
+ {
+ "figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
+ }'''
+ u.save()
+
+ # 测试Elasticsearch索引构建命令
+ from blog.documents import ELASTICSEARCH_ENABLED
+ if ELASTICSEARCH_ENABLED:
+ call_command("build_index")
+
+ # 测试其他自定义命令
+ call_command("ping_baidu", "all") # 百度链接推送
+ call_command("create_testdata") # 创建测试数据
+ call_command("clear_cache") # 清理缓存
+ call_command("sync_user_avatar") # 同步用户头像
+ call_command("build_search_words") # 构建搜索关键词
\ No newline at end of file
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/urls.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/urls.py
new file mode 100644
index 00000000..f4314778
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/urls.py
@@ -0,0 +1,98 @@
+# 导入Django URL路径处理和缓存装饰器
+from django.urls import path
+from django.views.decorators.cache import cache_page
+
+# 导入当前应用的视图模块
+from . import views
+
+# 定义应用命名空间,用于模板中URL反向解析(如{% url 'blog:index' %})
+app_name = "blog"
+
+# URL路由配置列表,映射URL路径到对应的视图
+urlpatterns = [
+ # 首页路由
+ path(
+ r'', # 匹配根路径(如域名/)
+ views.IndexView.as_view(), # 关联首页视图(基于类的视图)
+ name='index' # 路由名称,用于反向解析
+ ),
+ # 首页分页路由(带页码参数)
+ path(
+ r'page//', # 匹配带页码的路径(如/page/2/)
+ views.IndexView.as_view(), # 复用首页视图处理分页
+ name='index_page' # 路由名称
+ ),
+ # 文章详情页路由(按日期和ID)
+ path(
+ r'article////.html',
+ # 匹配路径格式:article/年/月/日/文章ID.html(如article/2023/10/01/1.html)
+ views.ArticleDetailView.as_view(), # 关联文章详情视图
+ name='detailbyid' # 路由名称
+ ),
+ # 分类详情页路由
+ path(
+ r'category/.html',
+ # 匹配路径:category/分类别名.html(如category/tech.html),slug表示URL友好的字符串
+ views.CategoryDetailView.as_view(), # 关联分类详情视图
+ name='category_detail' # 路由名称
+ ),
+ # 分类详情页分页路由
+ path(
+ r'category//.html',
+ # 匹配带页码的分类路径(如category/tech/2.html)
+ views.CategoryDetailView.as_view(), # 复用分类视图处理分页
+ name='category_detail_page' # 路由名称
+ ),
+ # 作者文章列表路由
+ path(
+ r'author/.html',
+ # 匹配路径:author/用户名.html(如author/admin.html)
+ views.AuthorDetailView.as_view(), # 关联作者文章列表视图
+ name='author_detail' # 路由名称
+ ),
+ # 作者文章列表分页路由
+ path(
+ r'author//.html',
+ # 匹配带页码的作者路径(如author/admin/2.html)
+ views.AuthorDetailView.as_view(), # 复用作者视图处理分页
+ name='author_detail_page' # 路由名称
+ ),
+ # 标签详情页路由
+ path(
+ r'tag/.html',
+ # 匹配路径:tag/标签别名.html(如tag/python.html)
+ views.TagDetailView.as_view(), # 关联标签详情视图
+ name='tag_detail' # 路由名称
+ ),
+ # 标签详情页分页路由
+ path(
+ r'tag//.html',
+ # 匹配带页码的标签路径(如tag/python/2.html)
+ views.TagDetailView.as_view(), # 复用标签视图处理分页
+ name='tag_detail_page' # 路由名称
+ ),
+ # 文章归档页路由(带缓存)
+ path(
+ 'archives.html', # 匹配路径:archives.html
+ cache_page(60 * 60)(views.ArchivesView.as_view()), # 缓存60分钟(60秒*60)
+ name='archives' # 路由名称
+ ),
+ # 友情链接页路由
+ path(
+ 'links.html', # 匹配路径:links.html
+ views.LinkListView.as_view(), # 关联友情链接视图
+ name='links' # 路由名称
+ ),
+ # 文件上传接口路由
+ path(
+ r'upload', # 匹配路径:upload
+ views.fileupload, # 关联文件上传视图函数(基于函数的视图)
+ name='upload' # 路由名称
+ ),
+ # 清理缓存接口路由
+ path(
+ r'clean', # 匹配路径:clean
+ views.clean_cache_view, # 关联清理缓存视图函数
+ name='clean' # 路由名称
+ ),
+]
diff --git a/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/views.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/views.py
new file mode 100644
index 00000000..09c7c520
--- /dev/null
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/blog/views.py
@@ -0,0 +1,498 @@
+import logging
+import os
+import uuid # 用于生成唯一文件名
+
+# 导入Django核心模块:配置、分页、HTTP响应、视图工具、翻译等
+from django.conf import settings
+from django.core.paginator import Paginator
+from django.http import HttpResponse, HttpResponseForbidden
+from django.shortcuts import get_object_or_404, render
+from django.templatetags.static import static # 生成静态文件URL
+from django.utils import timezone # 处理时间
+from django.utils.translation import gettext_lazy as _ # 国际化翻译
+from django.views.decorators.csrf import csrf_exempt # 豁免CSRF验证(用于文件上传)
+from django.views.generic.detail import DetailView # 详情页通用视图
+from django.views.generic.list import ListView # 列表页通用视图
+from haystack.views import SearchView # 搜索视图
+
+# 导入项目模型、表单、工具和插件
+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 # 文章内容钩子常量
+from djangoblog.utils import cache, get_blog_setting, get_sha256 # 缓存、配置和加密工具
+
+# 创建当前模块的日志记录器
+logger = logging.getLogger(__name__)
+
+
+class ArticleListView(ListView):
+ """
+ 文章列表基类视图
+ 封装文章列表页的通用逻辑(分页、缓存、上下文处理)
+ 被首页、分类、标签、作者等列表页继承
+ """
+ # 模板路径:所有文章列表页共用此模板
+ template_name = 'blog/article_index.html'
+
+ # 上下文变量名:模板中用{{ article_list }}访问列表数据
+ context_object_name = 'article_list'
+
+ # 页面类型描述(如"分类目录归档"),子类需重写
+ page_type = ''
+ # 分页大小:从配置中获取
+ paginate_by = settings.PAGINATE_BY
+ # 分页参数名:URL中页码的参数名(如?page=2)
+ page_kwarg = 'page'
+ # 友情链接显示类型:默认为列表页(L)
+ link_type = LinkShowType.L
+
+ def get_view_cache_key(self):
+ """获取视图缓存的key(未实际使用,预留扩展)"""
+ return self.request.get['pages']
+
+ @property
+ def page_number(self):
+ """获取当前页码(从URL参数或kwargs中提取)"""
+ page_kwarg = self.page_kwarg
+ # 优先从URL路径参数获取,再从GET参数获取,默认1
+ page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
+ return page
+
+ def get_queryset_cache_key(self):
+ """
+ 抽象方法:获取查询集的缓存key
+ 子类必须实现,用于区分不同页面的缓存
+ """
+ raise NotImplementedError()
+
+ def get_queryset_data(self):
+ """
+ 抽象方法:获取查询集数据
+ 子类必须实现,定义具体的文章筛选逻辑
+ """
+ raise NotImplementedError()
+
+ def get_queryset_from_cache(self, cache_key):
+ """
+ 从缓存获取或生成查询集数据
+ :param cache_key: 缓存唯一标识
+ :return: 文章查询集
+ """
+ # 尝试从缓存获取
+ value = cache.get(cache_key)
+ if value:
+ logger.info(f'从缓存获取数据,key: {cache_key}')
+ return value
+ else:
+ # 缓存未命中,执行查询并缓存
+ article_list = self.get_queryset_data()
+ cache.set(cache_key, article_list)
+ logger.info(f'设置缓存,key: {cache_key}')
+ return article_list
+
+ def get_queryset(self):
+ """
+ 重写父类方法:从缓存获取查询集
+ 优化性能,减少数据库查询
+ """
+ cache_key = self.get_queryset_cache_key()
+ return self.get_queryset_from_cache(cache_key)
+
+ def get_context_data(self, **kwargs):
+ """
+ 扩展上下文数据:添加友情链接显示类型
+ """
+ kwargs['linktype'] = self.link_type
+ return super().get_context_data(** kwargs)
+
+
+class IndexView(ArticleListView):
+ """
+ 首页视图
+ 继承文章列表基类,展示所有已发布的文章
+ """
+ # 友情链接显示类型:首页(I)
+ link_type = LinkShowType.I
+
+ def get_queryset_data(self):
+ """获取首页文章列表:已发布的普通文章(type='a')"""
+ return Article.objects.filter(type='a', status='p')
+
+ def get_queryset_cache_key(self):
+ """生成首页缓存key,包含页码"""
+ return f'index_{self.page_number}'
+
+
+class ArticleDetailView(DetailView):
+ """
+ 文章详情页视图
+ 展示单篇文章的详细内容、评论等
+ """
+ template_name = 'blog/article_detail.html' # 详情页模板
+ model = Article # 关联的模型
+ pk_url_kwarg = 'article_id' # URL中主键的参数名
+ context_object_name = "article" # 模板中文章对象的变量名
+
+ def get_context_data(self, **kwargs):
+ """
+ 构建详情页上下文数据:
+ - 评论表单
+ - 评论分页
+ - 上下篇文章
+ - 插件钩子处理
+ """
+ # 初始化评论表单
+ comment_form = CommentForm()
+
+ # 获取当前文章的所有有效评论
+ article_comments = self.object.comment_list()
+ # 筛选顶级评论(无父评论)
+ parent_comments = article_comments.filter(parent_comment=None)
+
+ # 获取博客配置(评论分页大小)
+ blog_setting = get_blog_setting()
+ # 初始化评论分页器
+ paginator = Paginator(parent_comments, blog_setting.article_comment_count)
+
+ # 处理评论页码参数
+ page = self.request.GET.get('comment_page', '1')
+ if not page.isnumeric():
+ page = 1
+ else:
+ page = int(page)
+ page = max(1, min(page, paginator.num_pages)) # 限制页码范围
+
+ # 获取当前页的评论
+ p_comments = paginator.page(page)
+
+ # 生成上下页评论的URL
+ next_page = p_comments.next_page_number() if p_comments.has_next() else None
+ prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None
+
+ if next_page:
+ kwargs['comment_next_page_url'] = f'{self.object.get_absolute_url()}?comment_page={next_page}#commentlist-container'
+ if prev_page:
+ kwargs['comment_prev_page_url'] = f'{self.object.get_absolute_url()}?comment_page={prev_page}#commentlist-container'
+
+ # 添加上下文数据
+ kwargs['form'] = comment_form # 评论表单
+ kwargs['article_comments'] = article_comments # 所有评论
+ kwargs['p_comments'] = p_comments # 当前页评论
+ kwargs['comment_count'] = len(article_comments) if article_comments else 0 # 评论总数
+
+ # 上下篇文章
+ kwargs['next_article'] = self.object.next_article
+ kwargs['prev_article'] = self.object.prev_article
+
+ # 调用父类方法获取基础上下文
+ context = super().get_context_data(**kwargs)
+
+ # 获取当前文章对象
+ article = self.object
+
+ # 执行插件动作钩子:通知插件"文章详情已获取"
+ hooks.run_action('after_article_body_get', article=article, request=self.request)
+
+ # 执行插件过滤钩子:允许插件修改文章正文(如添加水印、解析特殊标签等)
+ article.body = hooks.apply_filters(
+ ARTICLE_CONTENT_HOOK_NAME,
+ article.body,
+ article=article,
+ request=self.request
+ )
+
+ return context
+
+
+class CategoryDetailView(ArticleListView):
+ """
+ 分类详情页视图
+ 展示指定分类及子分类下的所有文章
+ """
+ page_type = "分类目录归档" # 页面类型描述
+
+ def get_queryset_data(self):
+ """
+ 获取分类下的文章列表:
+ 1. 根据URL中的分类slug获取分类对象
+ 2. 包含所有子分类的文章
+ 3. 仅展示已发布状态
+ """
+ slug = self.kwargs['category_name']
+ category = get_object_or_404(Category, slug=slug) # 获取分类,不存在则404
+
+ # 记录分类名称(用于上下文)
+ self.categoryname = category.name
+ # 获取当前分类及所有子分类的名称列表
+ categorynames = [c.name for c in category.get_sub_categorys()]
+
+ # 筛选属于这些分类且已发布的文章
+ return Article.objects.filter(category__name__in=categorynames, status='p')
+
+ def get_queryset_cache_key(self):
+ """生成分类页面的缓存key,包含分类名和页码"""
+ slug = self.kwargs['category_name']
+ category = get_object_or_404(Category, slug=slug)
+ self.categoryname = category.name
+ return f'category_list_{self.categoryname}_{self.page_number}'
+
+ def get_context_data(self, **kwargs):
+ """扩展上下文:添加页面类型和分类名称"""
+ # 处理分类名称(去除路径前缀,仅保留最后一级)
+ try:
+ categoryname = self.categoryname.split('/')[-1]
+ except:
+ categoryname = self.categoryname
+
+ kwargs['page_type'] = self.page_type
+ kwargs['tag_name'] = categoryname # 模板中统一用tag_name显示当前分类/标签/作者名
+ return super().get_context_data(** kwargs)
+
+
+class AuthorDetailView(ArticleListView):
+ """
+ 作者详情页视图
+ 展示指定作者发布的所有文章
+ """
+ page_type = '作者文章归档' # 页面类型描述
+
+ def get_queryset_cache_key(self):
+ """生成作者页面的缓存key,包含作者名和页码"""
+ from uuslug import slugify # 确保作者名URL友好
+ author_name = slugify(self.kwargs['author_name'])
+ return f'author_{author_name}_{self.page_number}'
+
+ def get_queryset_data(self):
+ """获取指定作者的已发布文章"""
+ author_name = self.kwargs['author_name']
+ return Article.objects.filter(author__username=author_name, type='a', status='p')
+
+ def get_context_data(self, **kwargs):
+ """扩展上下文:添加页面类型和作者名"""
+ kwargs['page_type'] = self.page_type
+ kwargs['tag_name'] = self.kwargs['author_name']
+ return super().get_context_data(** kwargs)
+
+
+class TagDetailView(ArticleListView):
+ """
+ 标签详情页视图
+ 展示指定标签关联的所有文章
+ """
+ page_type = '分类标签归档' # 页面类型描述
+
+ def get_queryset_data(self):
+ """获取指定标签的已发布文章"""
+ slug = self.kwargs['tag_name']
+ tag = get_object_or_404(Tag, slug=slug) # 获取标签,不存在则404
+ self.name = tag.name # 记录标签名
+ return Article.objects.filter(tags__name=self.name, type='a', status='p')
+
+ def get_queryset_cache_key(self):
+ """生成标签页面的缓存key,包含标签名和页码"""
+ slug = self.kwargs['tag_name']
+ tag = get_object_or_404(Tag, slug=slug)
+ self.name = tag.name
+ return f'tag_{self.name}_{self.page_number}'
+
+ def get_context_data(self, **kwargs):
+ """扩展上下文:添加页面类型和标签名"""
+ kwargs['page_type'] = self.page_type
+ kwargs['tag_name'] = self.name
+ return super().get_context_data(** kwargs)
+
+
+class ArchivesView(ArticleListView):
+ """
+ 文章归档页面视图
+ 展示所有已发布文章的归档列表(按时间分组)
+ """
+ page_type = '文章归档'
+ paginate_by = None # 归档页不分页
+ page_kwarg = None # 无需页码参数
+ template_name = 'blog/article_archives.html' # 归档页专用模板
+
+ def get_queryset_data(self):
+ """获取所有已发布文章(用于归档)"""
+ return Article.objects.filter(status='p').all()
+
+ def get_queryset_cache_key(self):
+ """归档页缓存key(固定值,因不分页)"""
+ return 'archives'
+
+
+class LinkListView(ListView):
+ """
+ 友情链接页面视图
+ 展示所有启用的友情链接
+ """
+ model = Links # 关联链接模型
+ template_name = 'blog/links_list.html' # 链接页模板
+
+ def get_queryset(self):
+ """仅获取启用的友情链接"""
+ return Links.objects.filter(is_enable=True)
+
+
+class EsSearchView(SearchView):
+ """
+ 搜索视图(基于Haystack)
+ 处理全文搜索请求并返回结果
+ """
+ def get_context(self):
+ """构建搜索结果页面的上下文数据"""
+ # 构建分页器和当前页数据
+ paginator, page = self.build_page()
+ context = {
+ "query": self.query, # 搜索关键词
+ "form": self.form, # 搜索表单
+ "page": page, # 当前页结果
+ "paginator": paginator, # 分页器
+ "suggestion": None, # 搜索建议(默认无)
+ }
+
+ # 如果搜索引擎支持拼写建议,添加建议内容
+ if hasattr(self.results, "query") and self.results.query.backend.include_spelling:
+ context["suggestion"] = self.results.query.get_spelling_suggestion()
+
+ # 添加额外上下文
+ context.update(self.extra_context())
+ return context
+
+
+@csrf_exempt # 豁免CSRF验证(用于外部调用上传)
+def fileupload(request):
+ """
+ 文件上传接口(图床功能)
+ 仅允许POST请求,且需验证签名
+ """
+ if request.method == 'POST':
+ # 获取签名参数
+ sign = request.GET.get('sign', None)
+ if not sign:
+ return HttpResponseForbidden() # 无签名则禁止
+
+ # 验证签名(双重SHA256加密,基于SECRET_KEY)
+ if sign != get_sha256(get_sha256(settings.SECRET_KEY)):
+ return HttpResponseForbidden() # 签名无效则禁止
+
+ # 存储上传文件的URL
+ response = []
+
+ # 处理每个上传的文件
+ for filename in request.FILES:
+ # 生成时间目录(按年/月/日)
+ timestr = timezone.now().strftime('%Y/%m/%d')
+ # 图片文件扩展名
+ imgextensions = ['jpg', 'png', 'jpeg', 'bmp']
+ # 检查是否为图片
+ fname = str(filename)
+ isimage = any(ext in fname.lower() for ext in imgextensions)
+
+ # 确定存储目录(图片和普通文件分开)
+ base_dir = os.path.join(
+ settings.STATICFILES,
+ "image" if isimage else "files",
+ timestr
+ )
+ # 确保目录存在
+ if not os.path.exists(base_dir):
+ os.makedirs(base_dir)
+
+ # 生成唯一文件名(UUID+原扩展名)
+ file_ext = os.path.splitext(filename)[-1]
+ savepath = os.path.normpath(
+ os.path.join(base_dir, f"{uuid.uuid4().hex}{file_ext}")
+ )
+
+ # 安全检查:防止路径穿越
+ if not savepath.startswith(base_dir):
+ return HttpResponse("Invalid path")
+
+ # 保存文件
+ with open(savepath, 'wb+') as wfile:
+ for chunk in request.FILES[filename].chunks():
+ wfile.write(chunk)
+
+ # 压缩图片(如果是图片文件)
+ if isimage:
+ from PIL import Image
+ try:
+ with Image.open(savepath) as image:
+ # 优化图片质量(20%质量,启用优化)
+ image.save(savepath, quality=20, optimize=True)
+ except Exception as e:
+ logger.error(f"图片压缩失败: {e}")
+
+ # 生成文件的访问URL
+ url = static(savepath)
+ response.append(url)
+
+ # 返回所有上传文件的URL
+ return HttpResponse(response)
+ else:
+ # 仅允许POST请求
+ return HttpResponse("only for post")
+
+
+def page_not_found_view(request, exception, template_name='blog/error_page.html'):
+ """
+ 404错误页面视图
+ 处理页面未找到的情况
+ """
+ if exception:
+ logger.error(exception) # 记录错误详情
+ url = request.get_full_path() # 获取请求的URL
+ return render(
+ request,
+ template_name,
+ {
+ 'message': _('Sorry, the page you requested is not found. Please click the home page to see others.'),
+ 'statuscode': '404'
+ },
+ status=404
+ )
+
+
+def server_error_view(request, template_name='blog/error_page.html'):
+ """
+ 500错误页面视图
+ 处理服务器内部错误
+ """
+ return render(
+ request,
+ template_name,
+ {
+ 'message': _('Sorry, the server is busy. Please click the home page to see others.'),
+ 'statuscode': '500'
+ },
+ status=500
+ )
+
+
+def permission_denied_view(request, exception, template_name='blog/error_page.html'):
+ """
+ 403错误页面视图
+ 处理权限不足的情况
+ """
+ if exception:
+ logger.error(exception) # 记录错误详情
+ return render(
+ request,
+ template_name,
+ {
+ 'message': _('Sorry, you do not have permission to access this page.'),
+ 'statuscode': '403'
+ },
+ status=403
+ )
+
+
+def clean_cache_view(request):
+ """
+ 清理缓存接口
+ 调用后清除所有缓存数据
+ """
+ cache.clear()
+ return HttpResponse('ok')
\ No newline at end of file
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/admin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/admin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/apps.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/apps.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/forms.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/forms.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/forms.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/forms.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0001_initial.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0001_initial.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/migrations/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/models.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/models.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/comments_tags.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/comments_tags.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/comments_tags.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/templatetags/comments_tags.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/tests.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/tests.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/urls.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/urls.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/utils.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/utils.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/utils.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/utils.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/comments/views.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/comments/views.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/comments/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.es.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.yml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.yml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.yml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/docker-compose/docker-compose.yml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/entrypoint.sh b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/entrypoint.sh
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/entrypoint.sh
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/entrypoint.sh
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/configmap.yaml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/configmap.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/configmap.yaml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/configmap.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/deployment.yaml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/deployment.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/deployment.yaml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/deployment.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/gateway.yaml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/gateway.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/gateway.yaml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/gateway.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pv.yaml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pv.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pv.yaml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pv.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pvc.yaml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pvc.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pvc.yaml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/pvc.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/service.yaml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/service.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/service.yaml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/service.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/storageclass.yaml b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/storageclass.yaml
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/storageclass.yaml
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/k8s/storageclass.yaml
diff --git a/src/DjangoBlog-master/DjangoBlog-master/deploy/nginx.conf b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/nginx.conf
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/deploy/nginx.conf
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/deploy/nginx.conf
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/admin_site.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/blog_signals.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/elasticsearch_backend.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/feeds.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/logentryadmin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hooks.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hooks.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hooks.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/hooks.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/loader.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/loader.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/loader.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/plugin_manage/loader.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
similarity index 99%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
index beece6c3..7158989b 100644
--- a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
+++ b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/settings.py
@@ -111,7 +111,7 @@ DATABASES = {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djangoblog',
'USER': 'root',
- 'PASSWORD': 'Zyl123456789',
+ 'PASSWORD': 'yanxinyi2252',
'HOST': '127.0.0.1',
'PORT': int(
3306),
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/sitemap.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/spider_notify.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/utils.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/whoosh_cn_backend.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/djangoblog/wsgi.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/README-en.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/README-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/README-en.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/README-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/config-en.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/config-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/config-en.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/config-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/config.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/config.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/config.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/config.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/docker-en.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/docker-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/docker-en.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/docker-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/docker.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/docker.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/docker.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/docker.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/es.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/es.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/es.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/es.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/alipay.jpg b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/alipay.jpg
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/imgs/alipay.jpg
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/alipay.jpg
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/pycharm_logo.png b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/pycharm_logo.png
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/imgs/pycharm_logo.png
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/pycharm_logo.png
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/wechat.jpg b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/wechat.jpg
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/imgs/wechat.jpg
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/imgs/wechat.jpg
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/k8s-en.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/k8s-en.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/k8s-en.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/k8s-en.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/docs/k8s.md b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/k8s.md
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/docs/k8s.md
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/docs/k8s.md
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.mo
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.po b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.po
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.po
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/en/LC_MESSAGES/django.po
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.mo
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.po b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.po
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.po
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hans/LC_MESSAGES/django.po
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.mo
diff --git a/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/locale/zh_Hant/LC_MESSAGES/django.po
diff --git a/src/DjangoBlog-master/DjangoBlog-master/manage.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/manage.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/manage.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/manage.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/admin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/admin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/apps.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/apps.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/forms.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/forms.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/forms.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/forms.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0001_initial.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0001_initial.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0002_alter_oauthconfig_options_alter_oauthuser_options_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/0003_alter_oauthuser_nickname.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/models.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/models.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/oauthmanager.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/oauthmanager.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/oauthmanager.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/oauthmanager.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/oauth_tags.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/oauth_tags.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/oauth_tags.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/templatetags/oauth_tags.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/tests.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/tests.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/urls.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/urls.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/oauth/views.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/oauth/views.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/oauth/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/admin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/admin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/apps.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/apps.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0001_initial.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0001_initial.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/0002_alter_owntracklog_options_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/models.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/models.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/tests.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/tests.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/urls.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/urls.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/owntracks/views.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/owntracks/views.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/owntracks/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/plugin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/plugin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/article_copyright/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/plugin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/plugin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/external_links/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/plugin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/plugin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/reading_time/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/plugin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/plugin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/seo_optimizer/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/plugin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/plugin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/plugin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/plugins/view_count/plugin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/requirements.txt b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/requirements.txt
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/requirements.txt
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/requirements.txt
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/MemcacheStorage.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/MemcacheStorage.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/MemcacheStorage.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/MemcacheStorage.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/admin.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/admin.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/admin.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/admin.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/api/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/blogapi.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/blogapi.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/api/blogapi.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/blogapi.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/commonapi.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/commonapi.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/api/commonapi.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/api/commonapi.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/apps.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/apps.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/apps.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/apps.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0001_initial.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0001_initial.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0001_initial.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0001_initial.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/0002_alter_emailsendlog_options_and_more.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/__init__.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/__init__.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/__init__.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/migrations/__init__.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/models.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/models.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/models.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/models.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/robot.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/robot.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/robot.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/robot.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/tests.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/tests.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/tests.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/tests.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/urls.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/urls.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/urls.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/urls.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/servermanager/views.py b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/views.py
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/servermanager/views.py
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/servermanager/views.py
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/forget_password.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/forget_password.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/forget_password.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/forget_password.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/login.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/login.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/login.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/login.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/registration_form.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/registration_form.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/registration_form.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/registration_form.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/account/result.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/result.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/account/result.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/account/result.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_archives.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_archives.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_archives.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_archives.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_detail.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_detail.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_detail.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_detail.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_index.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_index.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_index.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/article_index.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/error_page.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/error_page.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/error_page.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/error_page.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/links_list.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/links_list.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/links_list.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/links_list.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_info.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_info.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_info.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_info.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_meta_info.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_meta_info.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_meta_info.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_meta_info.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_pagination.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_pagination.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_pagination.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_pagination.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_tag_list.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_tag_list.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_tag_list.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/article_tag_list.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/breadcrumb.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/breadcrumb.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/breadcrumb.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/breadcrumb.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/sidebar.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/sidebar.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/sidebar.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/blog/tags/sidebar.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item_tree.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item_tree.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item_tree.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_item_tree.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_list.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_list.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_list.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/comment_list.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/post_comment.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/post_comment.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/post_comment.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/comments/tags/post_comment.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/bindsuccess.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/bindsuccess.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/oauth/bindsuccess.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/bindsuccess.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/oauth_applications.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/oauth_applications.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/oauth/oauth_applications.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/oauth_applications.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/require_email.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/require_email.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/oauth/require_email.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/oauth/require_email.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_log_dates.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_log_dates.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_log_dates.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_log_dates.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_maps.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_maps.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_maps.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/owntracks/show_maps.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/search/indexes/blog/article_text.txt b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/search/indexes/blog/article_text.txt
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/search/indexes/blog/article_text.txt
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/search/indexes/blog/article_text.txt
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/search/search.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/search/search.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/search/search.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/search/search.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/adsense.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/adsense.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/adsense.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/adsense.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base_account.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base_account.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base_account.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/base_account.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/footer.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/footer.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/footer.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/footer.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav_node.html b/djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav_node.html
similarity index 100%
rename from src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav_node.html
rename to djangoblog/src/DjangoBlog-master/DjangoBlog-master/templates/share_layout/nav_node.html
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/admin.py b/src/DjangoBlog-master/DjangoBlog-master/blog/admin.py
deleted file mode 100644
index 46c34208..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/admin.py
+++ /dev/null
@@ -1,112 +0,0 @@
-from django import forms
-from django.contrib import admin
-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 _
-
-# Register your models here.
-from .models import Article
-
-
-class ArticleForm(forms.ModelForm):
- # body = forms.CharField(widget=AdminPagedownWidget())
-
- class Meta:
- model = Article
- fields = '__all__'
-
-
-def makr_article_publish(modeladmin, request, queryset):
- queryset.update(status='p')
-
-
-def draft_article(modeladmin, request, queryset):
- queryset.update(status='d')
-
-
-def close_article_commentstatus(modeladmin, request, queryset):
- queryset.update(comment_status='c')
-
-
-def open_article_commentstatus(modeladmin, request, queryset):
- queryset.update(comment_status='o')
-
-
-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',
- 'creation_time',
- 'views',
- 'status',
- 'type',
- 'article_order')
- list_display_links = ('id', 'title')
- list_filter = ('status', 'type', 'category')
- filter_horizontal = ('tags',)
- exclude = ('creation_time', 'last_modify_time')
- view_on_site = True
- actions = [
- makr_article_publish,
- draft_article,
- close_article_commentstatus,
- open_article_commentstatus]
-
- def link_to_category(self, obj):
- 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'%s' % (link, obj.category.name))
-
- link_to_category.short_description = _('category')
-
- def get_form(self, request, obj=None, **kwargs):
- 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:
- url = obj.get_full_url()
- return url
- else:
- from djangoblog.utils import get_current_site
- site = get_current_site().domain
- return site
-
-
-class TagAdmin(admin.ModelAdmin):
- exclude = ('slug', 'last_mod_time', 'creation_time')
-
-
-class CategoryAdmin(admin.ModelAdmin):
- list_display = ('name', 'parent_category', 'index')
- exclude = ('slug', 'last_mod_time', 'creation_time')
-
-
-class LinksAdmin(admin.ModelAdmin):
- exclude = ('last_mod_time', 'creation_time')
-
-
-class SideBarAdmin(admin.ModelAdmin):
- list_display = ('name', 'content', 'is_enable', 'sequence')
- exclude = ('last_mod_time', 'creation_time')
-
-
-class BlogSettingsAdmin(admin.ModelAdmin):
- pass
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/apps.py b/src/DjangoBlog-master/DjangoBlog-master/blog/apps.py
deleted file mode 100644
index 79305878..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/apps.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.apps import AppConfig
-
-
-class BlogConfig(AppConfig):
- name = 'blog'
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py b/src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py
deleted file mode 100644
index 73e3088b..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/context_processors.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import logging
-
-from django.utils import timezone
-
-from djangoblog.utils import cache, get_blog_setting
-from .models import Category, Article
-
-logger = logging.getLogger(__name__)
-
-
-def seo_processor(requests):
- key = 'seo_processor'
- value = cache.get(key)
- if value:
- return value
- else:
- logger.info('set processor cache.')
- setting = get_blog_setting()
- value = {
- 'SITE_NAME': setting.site_name,
- 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,
- 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,
- 'SITE_SEO_DESCRIPTION': setting.site_seo_description,
- 'SITE_DESCRIPTION': setting.site_description,
- 'SITE_KEYWORDS': setting.site_keywords,
- 'SITE_BASE_URL': requests.scheme + '://' + requests.get_host() + '/',
- 'ARTICLE_SUB_LENGTH': setting.article_sub_length,
- 'nav_category_list': Category.objects.all(),
- 'nav_pages': Article.objects.filter(
- type='p',
- status='p'),
- 'OPEN_SITE_COMMENT': setting.open_site_comment,
- 'BEIAN_CODE': setting.beian_code,
- 'ANALYTICS_CODE': setting.analytics_code,
- "BEIAN_CODE_GONGAN": setting.gongan_beiancode,
- "SHOW_GONGAN_CODE": setting.show_gongan_code,
- "CURRENT_YEAR": timezone.now().year,
- "GLOBAL_HEADER": setting.global_header,
- "GLOBAL_FOOTER": setting.global_footer,
- "COMMENT_NEED_REVIEW": setting.comment_need_review,
- }
- cache.set(key, value, 60 * 60 * 10)
- return value
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/documents.py b/src/DjangoBlog-master/DjangoBlog-master/blog/documents.py
deleted file mode 100644
index 0f1db7b7..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/documents.py
+++ /dev/null
@@ -1,213 +0,0 @@
-import time
-
-import elasticsearch.client
-from django.conf import settings
-from elasticsearch_dsl import Document, InnerDoc, Date, Integer, Long, Text, Object, GeoPoint, Keyword, Boolean
-from elasticsearch_dsl.connections import connections
-
-from blog.models import Article
-
-ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL')
-
-if ELASTICSEARCH_ENABLED:
- connections.create_connection(
- hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']])
- from elasticsearch import Elasticsearch
-
- es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
- from elasticsearch.client import IngestClient
-
- c = IngestClient(es)
- try:
- c.get_pipeline('geoip')
- except elasticsearch.exceptions.NotFoundError:
- c.put_pipeline('geoip', body='''{
- "description" : "Add geoip info",
- "processors" : [
- {
- "geoip" : {
- "field" : "ip"
- }
- }
- ]
- }''')
-
-
-class GeoIp(InnerDoc):
- continent_name = Keyword()
- country_iso_code = Keyword()
- country_name = Keyword()
- location = GeoPoint()
-
-
-class UserAgentBrowser(InnerDoc):
- Family = Keyword()
- Version = Keyword()
-
-
-class UserAgentOS(UserAgentBrowser):
- pass
-
-
-class UserAgentDevice(InnerDoc):
- Family = Keyword()
- Brand = Keyword()
- Model = Keyword()
-
-
-class UserAgent(InnerDoc):
- browser = Object(UserAgentBrowser, required=False)
- os = Object(UserAgentOS, required=False)
- device = Object(UserAgentDevice, required=False)
- string = Text()
- is_bot = Boolean()
-
-
-class ElapsedTimeDocument(Document):
- url = Keyword()
- time_taken = Long()
- log_datetime = Date()
- ip = Keyword()
- geoip = Object(GeoIp, required=False)
- useragent = Object(UserAgent, required=False)
-
- class Index:
- name = 'performance'
- settings = {
- "number_of_shards": 1,
- "number_of_replicas": 0
- }
-
- class Meta:
- doc_type = 'ElapsedTime'
-
-
-class ElaspedTimeDocumentManager:
- @staticmethod
- def build_index():
- from elasticsearch import Elasticsearch
- client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
- res = client.indices.exists(index="performance")
- if not res:
- ElapsedTimeDocument.init()
-
- @staticmethod
- def delete_index():
- from elasticsearch import Elasticsearch
- es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
- es.indices.delete(index='performance', ignore=[400, 404])
-
- @staticmethod
- def create(url, time_taken, log_datetime, useragent, ip):
- ElaspedTimeDocumentManager.build_index()
- ua = UserAgent()
- ua.browser = UserAgentBrowser()
- ua.browser.Family = useragent.browser.family
- ua.browser.Version = useragent.browser.version_string
-
- ua.os = UserAgentOS()
- ua.os.Family = useragent.os.family
- ua.os.Version = useragent.os.version_string
-
- ua.device = UserAgentDevice()
- ua.device.Family = useragent.device.family
- ua.device.Brand = useragent.device.brand
- ua.device.Model = useragent.device.model
- ua.string = useragent.ua_string
- ua.is_bot = useragent.is_bot
-
- doc = ElapsedTimeDocument(
- meta={
- 'id': int(
- round(
- time.time() *
- 1000))
- },
- url=url,
- time_taken=time_taken,
- log_datetime=log_datetime,
- useragent=ua, ip=ip)
- doc.save(pipeline="geoip")
-
-
-class ArticleDocument(Document):
- body = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
- title = Text(analyzer='ik_max_word', search_analyzer='ik_smart')
- author = Object(properties={
- 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
- 'id': Integer()
- })
- category = Object(properties={
- 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
- 'id': Integer()
- })
- tags = Object(properties={
- 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'),
- 'id': Integer()
- })
-
- pub_time = Date()
- status = Text()
- comment_status = Text()
- type = Text()
- views = Integer()
- article_order = Integer()
-
- class Index:
- name = 'blog'
- settings = {
- "number_of_shards": 1,
- "number_of_replicas": 0
- }
-
- class Meta:
- doc_type = 'Article'
-
-
-class ArticleDocumentManager():
-
- def __init__(self):
- self.create_index()
-
- def create_index(self):
- ArticleDocument.init()
-
- def delete_index(self):
- from elasticsearch import Elasticsearch
- es = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts'])
- es.indices.delete(index='blog', ignore=[400, 404])
-
- def convert_to_doc(self, articles):
- return [
- ArticleDocument(
- meta={
- 'id': article.id},
- body=article.body,
- title=article.title,
- author={
- 'nickname': article.author.username,
- 'id': article.author.id},
- category={
- 'name': article.category.name,
- 'id': article.category.id},
- tags=[
- {
- 'name': t.name,
- 'id': t.id} for t in article.tags.all()],
- pub_time=article.pub_time,
- status=article.status,
- comment_status=article.comment_status,
- type=article.type,
- views=article.views,
- article_order=article.article_order) for article in articles]
-
- def rebuild(self, articles=None):
- ArticleDocument.init()
- articles = articles if articles else Article.objects.all()
- docs = self.convert_to_doc(articles)
- for doc in docs:
- doc.save()
-
- def update_docs(self, docs):
- for doc in docs:
- doc.save()
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/forms.py b/src/DjangoBlog-master/DjangoBlog-master/blog/forms.py
deleted file mode 100644
index 715be762..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/forms.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import logging
-
-from django import forms
-from haystack.forms import SearchForm
-
-logger = logging.getLogger(__name__)
-
-
-class BlogSearchForm(SearchForm):
- querydata = forms.CharField(required=True)
-
- def search(self):
- datas = super(BlogSearchForm, self).search()
- if not self.is_valid():
- return self.no_query_found()
-
- if self.cleaned_data['querydata']:
- logger.info(self.cleaned_data['querydata'])
- return datas
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py b/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py
deleted file mode 100644
index 3c4acd74..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_index.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \
- ELASTICSEARCH_ENABLED
-
-
-# TODO 参数化
-class Command(BaseCommand):
- help = 'build search index'
-
- def handle(self, *args, **options):
- if ELASTICSEARCH_ENABLED:
- ElaspedTimeDocumentManager.build_index()
- manager = ElapsedTimeDocument()
- manager.init()
- manager = ArticleDocumentManager()
- manager.delete_index()
- manager.rebuild()
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py b/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py
deleted file mode 100644
index cfe7e0d5..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/build_search_words.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from blog.models import Tag, Category
-
-
-# TODO 参数化
-class Command(BaseCommand):
- help = 'build search words'
-
- def handle(self, *args, **options):
- datas = set([t.name for t in Tag.objects.all()] +
- [t.name for t in Category.objects.all()])
- print('\n'.join(datas))
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py b/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py
deleted file mode 100644
index 0d66172c..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/clear_cache.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from djangoblog.utils import cache
-
-
-class Command(BaseCommand):
- help = 'clear the whole cache'
-
- def handle(self, *args, **options):
- cache.clear()
- self.stdout.write(self.style.SUCCESS('Cleared cache\n'))
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py b/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py
deleted file mode 100644
index 675d2ba6..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/create_testdata.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from django.contrib.auth import get_user_model
-from django.contrib.auth.hashers import make_password
-from django.core.management.base import BaseCommand
-
-from blog.models import Article, Tag, Category
-
-
-class Command(BaseCommand):
- help = 'create test datas'
-
- def handle(self, *args, **options):
- user = get_user_model().objects.get_or_create(
- email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0]
-
- pcategory = Category.objects.get_or_create(
- name='我是父类目', parent_category=None)[0]
-
- category = Category.objects.get_or_create(
- name='子类目', parent_category=pcategory)[0]
-
- category.save()
- basetag = Tag()
- basetag.name = "标签"
- basetag.save()
- for i in range(1, 20):
- article = Article.objects.get_or_create(
- category=category,
- title='nice title ' + str(i),
- body='nice content ' + str(i),
- author=user)[0]
- tag = Tag()
- tag.name = "标签" + str(i)
- tag.save()
- article.tags.add(tag)
- article.tags.add(basetag)
- article.save()
-
- from djangoblog.utils import cache
- cache.clear()
- self.stdout.write(self.style.SUCCESS('created test datas \n'))
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py b/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py
deleted file mode 100644
index 2c7fbdd6..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/ping_baidu.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from django.core.management.base import BaseCommand
-
-from djangoblog.spider_notify import SpiderNotify
-from djangoblog.utils import get_current_site
-from blog.models import Article, Tag, Category
-
-site = get_current_site().domain
-
-
-class Command(BaseCommand):
- help = 'notify baidu url'
-
- def add_arguments(self, parser):
- parser.add_argument(
- 'data_type',
- type=str,
- choices=[
- 'all',
- 'article',
- 'tag',
- 'category'],
- help='article : all article,tag : all tag,category: all category,all: All of these')
-
- def get_full_url(self, path):
- url = "https://{site}{path}".format(site=site, path=path)
- return url
-
- def handle(self, *args, **options):
- type = options['data_type']
- self.stdout.write('start get %s' % type)
-
- urls = []
- if type == 'article' or type == 'all':
- for article in Article.objects.filter(status='p'):
- urls.append(article.get_full_url())
- if type == 'tag' or type == 'all':
- for tag in Tag.objects.all():
- url = tag.get_absolute_url()
- urls.append(self.get_full_url(url))
- if type == 'category' or type == 'all':
- for category in Category.objects.all():
- url = category.get_absolute_url()
- urls.append(self.get_full_url(url))
-
- self.stdout.write(
- self.style.SUCCESS(
- 'start notify %d urls' %
- len(urls)))
- SpiderNotify.baidu_notify(urls)
- self.stdout.write(self.style.SUCCESS('finish notify'))
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py b/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py
deleted file mode 100644
index d0f46127..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/management/commands/sync_user_avatar.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import requests
-from django.core.management.base import BaseCommand
-from django.templatetags.static import static
-
-from djangoblog.utils import save_user_avatar
-from oauth.models import OAuthUser
-from oauth.oauthmanager import get_manager_by_type
-
-
-class Command(BaseCommand):
- help = 'sync user avatar'
-
- def test_picture(self, url):
- try:
- if requests.get(url, timeout=2).status_code == 200:
- return True
- except:
- pass
-
- def handle(self, *args, **options):
- static_url = static("../")
- users = OAuthUser.objects.all()
- self.stdout.write(f'开始同步{len(users)}个用户头像')
- for u in users:
- self.stdout.write(f'开始同步:{u.nickname}')
- url = u.picture
- if url:
- if url.startswith(static_url):
- if self.test_picture(url):
- continue
- else:
- if u.metadata:
- manage = get_manager_by_type(u.type)
- url = manage.get_picture(u.metadata)
- url = save_user_avatar(url)
- else:
- url = static('blog/img/avatar.png')
- else:
- url = save_user_avatar(url)
- else:
- url = static('blog/img/avatar.png')
- if url:
- self.stdout.write(
- f'结束同步:{u.nickname}.url:{url}')
- u.picture = url
- u.save()
- self.stdout.write('结束同步')
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py b/src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py
deleted file mode 100644
index 94dd70c9..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/middleware.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import logging
-import time
-
-from ipware import get_client_ip
-from user_agents import parse
-
-from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager
-
-logger = logging.getLogger(__name__)
-
-
-class OnlineMiddleware(object):
- def __init__(self, get_response=None):
- self.get_response = get_response
- super().__init__()
-
- def __call__(self, request):
- ''' page render time '''
- start_time = time.time()
- response = self.get_response(request)
- http_user_agent = request.META.get('HTTP_USER_AGENT', '')
- ip, _ = get_client_ip(request)
- user_agent = parse(http_user_agent)
- if not response.streaming:
- try:
- cast_time = time.time() - start_time
- if ELASTICSEARCH_ENABLED:
- time_taken = round((cast_time) * 1000, 2)
- url = request.path
- from django.utils import timezone
- ElaspedTimeDocumentManager.create(
- url=url,
- time_taken=time_taken,
- log_datetime=timezone.now(),
- useragent=user_agent,
- ip=ip)
- response.content = response.content.replace(
- b'', str.encode(str(cast_time)[:5]))
- except Exception as e:
- logger.error("Error OnlineMiddleware: %s" % e)
-
- return response
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py b/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py
deleted file mode 100644
index 3d391b62..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0001_initial.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# Generated by Django 4.1.7 on 2023-03-02 07:14
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import django.utils.timezone
-import mdeditor.fields
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ]
-
- operations = [
- migrations.CreateModel(
- name='BlogSettings',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')),
- ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')),
- ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')),
- ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')),
- ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')),
- ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')),
- ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')),
- ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')),
- ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')),
- ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')),
- ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')),
- ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')),
- ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')),
- ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')),
- ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')),
- ],
- options={
- 'verbose_name': '网站配置',
- 'verbose_name_plural': '网站配置',
- },
- ),
- migrations.CreateModel(
- name='Links',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')),
- ('link', models.URLField(verbose_name='链接地址')),
- ('sequence', models.IntegerField(unique=True, verbose_name='排序')),
- ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')),
- ('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')),
- ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
- ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
- ],
- options={
- 'verbose_name': '友情链接',
- 'verbose_name_plural': '友情链接',
- 'ordering': ['sequence'],
- },
- ),
- migrations.CreateModel(
- name='SideBar',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=100, verbose_name='标题')),
- ('content', models.TextField(verbose_name='内容')),
- ('sequence', models.IntegerField(unique=True, verbose_name='排序')),
- ('is_enable', models.BooleanField(default=True, verbose_name='是否启用')),
- ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
- ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
- ],
- options={
- 'verbose_name': '侧边栏',
- 'verbose_name_plural': '侧边栏',
- 'ordering': ['sequence'],
- },
- ),
- migrations.CreateModel(
- name='Tag',
- fields=[
- ('id', models.AutoField(primary_key=True, serialize=False)),
- ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
- ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
- ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')),
- ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
- ],
- options={
- 'verbose_name': '标签',
- 'verbose_name_plural': '标签',
- 'ordering': ['name'],
- },
- ),
- migrations.CreateModel(
- name='Category',
- fields=[
- ('id', models.AutoField(primary_key=True, serialize=False)),
- ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
- ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
- ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')),
- ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)),
- ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')),
- ('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')),
- ],
- options={
- 'verbose_name': '分类',
- 'verbose_name_plural': '分类',
- 'ordering': ['-index'],
- },
- ),
- migrations.CreateModel(
- name='Article',
- fields=[
- ('id', models.AutoField(primary_key=True, serialize=False)),
- ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')),
- ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')),
- ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')),
- ('body', mdeditor.fields.MDTextField(verbose_name='正文')),
- ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')),
- ('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')),
- ('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')),
- ('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')),
- ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')),
- ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')),
- ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')),
- ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')),
- ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')),
- ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')),
- ],
- options={
- 'verbose_name': '文章',
- 'verbose_name_plural': '文章',
- 'ordering': ['-article_order', '-pub_time'],
- 'get_latest_by': 'id',
- },
- ),
- ]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py
deleted file mode 100644
index adbaa36b..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Generated by Django 4.1.7 on 2023-03-29 06:08
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('blog', '0001_initial'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='blogsettings',
- name='global_footer',
- field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'),
- ),
- migrations.AddField(
- model_name='blogsettings',
- name='global_header',
- field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'),
- ),
- ]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py b/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py
deleted file mode 100644
index e9f55024..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 4.2.1 on 2023-05-09 07:45
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('blog', '0002_blogsettings_global_footer_and_more'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='blogsettings',
- name='comment_need_review',
- field=models.BooleanField(default=False, verbose_name='评论是否需要审核'),
- ),
- ]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
deleted file mode 100644
index ceb13982..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Generated by Django 4.2.1 on 2023-05-09 07:51
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
- dependencies = [
- ('blog', '0003_blogsettings_comment_need_review'),
- ]
-
- operations = [
- migrations.RenameField(
- model_name='blogsettings',
- old_name='analyticscode',
- new_name='analytics_code',
- ),
- migrations.RenameField(
- model_name='blogsettings',
- old_name='beiancode',
- new_name='beian_code',
- ),
- migrations.RenameField(
- model_name='blogsettings',
- old_name='sitename',
- new_name='site_name',
- ),
- ]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
deleted file mode 100644
index d08e8534..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py
+++ /dev/null
@@ -1,300 +0,0 @@
-# Generated by Django 4.2.5 on 2023-09-06 13:13
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-import django.utils.timezone
-import mdeditor.fields
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'),
- ]
-
- operations = [
- migrations.AlterModelOptions(
- name='article',
- options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'},
- ),
- migrations.AlterModelOptions(
- name='category',
- options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'},
- ),
- migrations.AlterModelOptions(
- name='links',
- options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'},
- ),
- migrations.AlterModelOptions(
- name='sidebar',
- options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'},
- ),
- migrations.AlterModelOptions(
- name='tag',
- options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'},
- ),
- migrations.RemoveField(
- model_name='article',
- name='created_time',
- ),
- migrations.RemoveField(
- model_name='article',
- name='last_mod_time',
- ),
- migrations.RemoveField(
- model_name='category',
- name='created_time',
- ),
- migrations.RemoveField(
- model_name='category',
- name='last_mod_time',
- ),
- migrations.RemoveField(
- model_name='links',
- name='created_time',
- ),
- migrations.RemoveField(
- model_name='sidebar',
- name='created_time',
- ),
- migrations.RemoveField(
- model_name='tag',
- name='created_time',
- ),
- migrations.RemoveField(
- model_name='tag',
- name='last_mod_time',
- ),
- migrations.AddField(
- model_name='article',
- name='creation_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
- ),
- migrations.AddField(
- model_name='article',
- name='last_modify_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
- ),
- migrations.AddField(
- model_name='category',
- name='creation_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
- ),
- migrations.AddField(
- model_name='category',
- name='last_modify_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
- ),
- migrations.AddField(
- model_name='links',
- name='creation_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
- ),
- migrations.AddField(
- model_name='sidebar',
- name='creation_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
- ),
- migrations.AddField(
- model_name='tag',
- name='creation_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'),
- ),
- migrations.AddField(
- model_name='tag',
- name='last_modify_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
- ),
- migrations.AlterField(
- model_name='article',
- name='article_order',
- field=models.IntegerField(default=0, verbose_name='order'),
- ),
- migrations.AlterField(
- model_name='article',
- name='author',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'),
- ),
- migrations.AlterField(
- model_name='article',
- name='body',
- field=mdeditor.fields.MDTextField(verbose_name='body'),
- ),
- migrations.AlterField(
- model_name='article',
- name='category',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'),
- ),
- migrations.AlterField(
- model_name='article',
- name='comment_status',
- field=models.CharField(choices=[('o', 'Open'), ('c', 'Close')], default='o', max_length=1, verbose_name='comment status'),
- ),
- migrations.AlterField(
- model_name='article',
- name='pub_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'),
- ),
- migrations.AlterField(
- model_name='article',
- name='show_toc',
- field=models.BooleanField(default=False, verbose_name='show toc'),
- ),
- migrations.AlterField(
- model_name='article',
- name='status',
- field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'),
- ),
- migrations.AlterField(
- model_name='article',
- name='tags',
- field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'),
- ),
- migrations.AlterField(
- model_name='article',
- name='title',
- field=models.CharField(max_length=200, unique=True, verbose_name='title'),
- ),
- migrations.AlterField(
- model_name='article',
- name='type',
- field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'),
- ),
- migrations.AlterField(
- model_name='article',
- name='views',
- field=models.PositiveIntegerField(default=0, verbose_name='views'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='article_comment_count',
- field=models.IntegerField(default=5, verbose_name='article comment count'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='article_sub_length',
- field=models.IntegerField(default=300, verbose_name='article sub length'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='google_adsense_codes',
- field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='open_site_comment',
- field=models.BooleanField(default=True, verbose_name='open site comment'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='show_google_adsense',
- field=models.BooleanField(default=False, verbose_name='show adsense'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='sidebar_article_count',
- field=models.IntegerField(default=10, verbose_name='sidebar article count'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='sidebar_comment_count',
- field=models.IntegerField(default=5, verbose_name='sidebar comment count'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='site_description',
- field=models.TextField(default='', max_length=1000, verbose_name='site description'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='site_keywords',
- field=models.TextField(default='', max_length=1000, verbose_name='site keywords'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='site_name',
- field=models.CharField(default='', max_length=200, verbose_name='site name'),
- ),
- migrations.AlterField(
- model_name='blogsettings',
- name='site_seo_description',
- field=models.TextField(default='', max_length=1000, verbose_name='site seo description'),
- ),
- migrations.AlterField(
- model_name='category',
- name='index',
- field=models.IntegerField(default=0, verbose_name='index'),
- ),
- migrations.AlterField(
- model_name='category',
- name='name',
- field=models.CharField(max_length=30, unique=True, verbose_name='category name'),
- ),
- migrations.AlterField(
- model_name='category',
- name='parent_category',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='parent category'),
- ),
- migrations.AlterField(
- model_name='links',
- name='is_enable',
- field=models.BooleanField(default=True, verbose_name='is show'),
- ),
- migrations.AlterField(
- model_name='links',
- name='last_mod_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
- ),
- migrations.AlterField(
- model_name='links',
- name='link',
- field=models.URLField(verbose_name='link'),
- ),
- migrations.AlterField(
- model_name='links',
- name='name',
- field=models.CharField(max_length=30, unique=True, verbose_name='link name'),
- ),
- migrations.AlterField(
- model_name='links',
- name='sequence',
- field=models.IntegerField(unique=True, verbose_name='order'),
- ),
- migrations.AlterField(
- model_name='links',
- name='show_type',
- field=models.CharField(choices=[('i', 'index'), ('l', 'list'), ('p', 'post'), ('a', 'all'), ('s', 'slide')], default='i', max_length=1, verbose_name='show type'),
- ),
- migrations.AlterField(
- model_name='sidebar',
- name='content',
- field=models.TextField(verbose_name='content'),
- ),
- migrations.AlterField(
- model_name='sidebar',
- name='is_enable',
- field=models.BooleanField(default=True, verbose_name='is enable'),
- ),
- migrations.AlterField(
- model_name='sidebar',
- name='last_mod_time',
- field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'),
- ),
- migrations.AlterField(
- model_name='sidebar',
- name='name',
- field=models.CharField(max_length=100, verbose_name='title'),
- ),
- migrations.AlterField(
- model_name='sidebar',
- name='sequence',
- field=models.IntegerField(unique=True, verbose_name='order'),
- ),
- migrations.AlterField(
- model_name='tag',
- name='name',
- field=models.CharField(max_length=30, unique=True, verbose_name='tag name'),
- ),
- ]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py b/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py
deleted file mode 100644
index e36feb4c..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 4.2.7 on 2024-01-26 02:41
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('blog', '0005_alter_article_options_alter_category_options_and_more'),
- ]
-
- operations = [
- migrations.AlterModelOptions(
- name='blogsettings',
- options={'verbose_name': 'Website configuration', 'verbose_name_plural': 'Website configuration'},
- ),
- ]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/models.py b/src/DjangoBlog-master/DjangoBlog-master/blog/models.py
deleted file mode 100644
index 083788bb..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/models.py
+++ /dev/null
@@ -1,376 +0,0 @@
-import logging
-import re
-from abc import abstractmethod
-
-from django.conf import settings
-from django.core.exceptions import ValidationError
-from django.db import models
-from django.urls import reverse
-from django.utils.timezone import now
-from django.utils.translation import gettext_lazy as _
-from mdeditor.fields import MDTextField
-from uuslug import slugify
-
-from djangoblog.utils import cache_decorator, cache
-from djangoblog.utils import get_current_site
-
-logger = logging.getLogger(__name__)
-
-
-class LinkShowType(models.TextChoices):
- I = ('i', _('index'))
- L = ('l', _('list'))
- P = ('p', _('post'))
- A = ('a', _('all'))
- S = ('s', _('slide'))
-
-
-class BaseModel(models.Model):
- id = models.AutoField(primary_key=True)
- creation_time = models.DateTimeField(_('creation time'), default=now)
- last_modify_time = models.DateTimeField(_('modify time'), default=now)
-
- def save(self, *args, **kwargs):
- is_update_views = isinstance(
- self,
- Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views']
- if is_update_views:
- Article.objects.filter(pk=self.pk).update(views=self.views)
- else:
- if 'slug' in self.__dict__:
- slug = getattr(
- self, 'title') if 'title' in self.__dict__ else getattr(
- self, 'name')
- setattr(self, 'slug', slugify(slug))
- super().save(*args, **kwargs)
-
- def get_full_url(self):
- site = get_current_site().domain
- url = "https://{site}{path}".format(site=site,
- path=self.get_absolute_url())
- return url
-
- class Meta:
- abstract = True
-
- @abstractmethod
- def get_absolute_url(self):
- pass
-
-
-class Article(BaseModel):
- """文章"""
- STATUS_CHOICES = (
- ('d', _('Draft')),
- ('p', _('Published')),
- )
- COMMENT_STATUS = (
- ('o', _('Open')),
- ('c', _('Close')),
- )
- TYPE = (
- ('a', _('Article')),
- ('p', _('Page')),
- )
- title = models.CharField(_('title'), max_length=200, unique=True)
- body = MDTextField(_('body'))
- pub_time = models.DateTimeField(
- _('publish time'), blank=False, null=False, default=now)
- status = models.CharField(
- _('status'),
- max_length=1,
- choices=STATUS_CHOICES,
- default='p')
- comment_status = models.CharField(
- _('comment status'),
- max_length=1,
- choices=COMMENT_STATUS,
- default='o')
- type = models.CharField(_('type'), max_length=1, choices=TYPE, default='a')
- views = models.PositiveIntegerField(_('views'), default=0)
- author = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- verbose_name=_('author'),
- blank=False,
- null=False,
- on_delete=models.CASCADE)
- article_order = models.IntegerField(
- _('order'), blank=False, null=False, default=0)
- show_toc = models.BooleanField(_('show toc'), blank=False, null=False, default=False)
- category = models.ForeignKey(
- 'Category',
- verbose_name=_('category'),
- on_delete=models.CASCADE,
- blank=False,
- null=False)
- tags = models.ManyToManyField('Tag', verbose_name=_('tag'), blank=True)
-
- def body_to_string(self):
- return self.body
-
- def __str__(self):
- return self.title
-
- class Meta:
- ordering = ['-article_order', '-pub_time']
- verbose_name = _('article')
- verbose_name_plural = verbose_name
- get_latest_by = 'id'
-
- def get_absolute_url(self):
- return reverse('blog:detailbyid', kwargs={
- 'article_id': self.id,
- 'year': self.creation_time.year,
- 'month': self.creation_time.month,
- 'day': self.creation_time.day
- })
-
- @cache_decorator(60 * 60 * 10)
- def get_category_tree(self):
- tree = self.category.get_category_tree()
- names = list(map(lambda c: (c.name, c.get_absolute_url()), tree))
-
- return names
-
- def save(self, *args, **kwargs):
- super().save(*args, **kwargs)
-
- def viewed(self):
- self.views += 1
- self.save(update_fields=['views'])
-
- def comment_list(self):
- cache_key = 'article_comments_{id}'.format(id=self.id)
- value = cache.get(cache_key)
- if value:
- logger.info('get article comments:{id}'.format(id=self.id))
- return value
- else:
- comments = self.comment_set.filter(is_enable=True).order_by('-id')
- cache.set(cache_key, comments, 60 * 100)
- logger.info('set article comments:{id}'.format(id=self.id))
- return comments
-
- def get_admin_url(self):
- info = (self._meta.app_label, self._meta.model_name)
- return reverse('admin:%s_%s_change' % info, args=(self.pk,))
-
- @cache_decorator(expiration=60 * 100)
- def next_article(self):
- # 下一篇
- return Article.objects.filter(
- id__gt=self.id, status='p').order_by('id').first()
-
- @cache_decorator(expiration=60 * 100)
- def prev_article(self):
- # 前一篇
- return Article.objects.filter(id__lt=self.id, status='p').first()
-
- def get_first_image_url(self):
- """
- Get the first image url from article.body.
- :return:
- """
- match = re.search(r'!\[.*?\]\((.+?)\)', self.body)
- if match:
- return match.group(1)
- return ""
-
-
-class Category(BaseModel):
- """文章分类"""
- name = models.CharField(_('category name'), max_length=30, unique=True)
- parent_category = models.ForeignKey(
- 'self',
- verbose_name=_('parent category'),
- blank=True,
- null=True,
- on_delete=models.CASCADE)
- slug = models.SlugField(default='no-slug', max_length=60, blank=True)
- index = models.IntegerField(default=0, verbose_name=_('index'))
-
- class Meta:
- ordering = ['-index']
- verbose_name = _('category')
- verbose_name_plural = verbose_name
-
- def get_absolute_url(self):
- return reverse(
- 'blog:category_detail', kwargs={
- 'category_name': self.slug})
-
- def __str__(self):
- return self.name
-
- @cache_decorator(60 * 60 * 10)
- def get_category_tree(self):
- """
- 递归获得分类目录的父级
- :return:
- """
- categorys = []
-
- def parse(category):
- categorys.append(category)
- if category.parent_category:
- parse(category.parent_category)
-
- parse(self)
- return categorys
-
- @cache_decorator(60 * 60 * 10)
- def get_sub_categorys(self):
- """
- 获得当前分类目录所有子集
- :return:
- """
- categorys = []
- all_categorys = Category.objects.all()
-
- def parse(category):
- if category not in categorys:
- categorys.append(category)
- childs = all_categorys.filter(parent_category=category)
- for child in childs:
- if category not in categorys:
- categorys.append(child)
- parse(child)
-
- parse(self)
- return categorys
-
-
-class Tag(BaseModel):
- """文章标签"""
- name = models.CharField(_('tag name'), max_length=30, unique=True)
- slug = models.SlugField(default='no-slug', max_length=60, blank=True)
-
- def __str__(self):
- return self.name
-
- def get_absolute_url(self):
- return reverse('blog:tag_detail', kwargs={'tag_name': self.slug})
-
- @cache_decorator(60 * 60 * 10)
- def get_article_count(self):
- return Article.objects.filter(tags__name=self.name).distinct().count()
-
- class Meta:
- ordering = ['name']
- verbose_name = _('tag')
- verbose_name_plural = verbose_name
-
-
-class Links(models.Model):
- """友情链接"""
-
- name = models.CharField(_('link name'), max_length=30, unique=True)
- link = models.URLField(_('link'))
- sequence = models.IntegerField(_('order'), unique=True)
- is_enable = models.BooleanField(
- _('is show'), default=True, blank=False, null=False)
- show_type = models.CharField(
- _('show type'),
- max_length=1,
- choices=LinkShowType.choices,
- default=LinkShowType.I)
- creation_time = models.DateTimeField(_('creation time'), default=now)
- last_mod_time = models.DateTimeField(_('modify time'), default=now)
-
- class Meta:
- ordering = ['sequence']
- verbose_name = _('link')
- verbose_name_plural = verbose_name
-
- def __str__(self):
- return self.name
-
-
-class SideBar(models.Model):
- """侧边栏,可以展示一些html内容"""
- name = models.CharField(_('title'), max_length=100)
- content = models.TextField(_('content'))
- sequence = models.IntegerField(_('order'), unique=True)
- is_enable = models.BooleanField(_('is enable'), default=True)
- creation_time = models.DateTimeField(_('creation time'), default=now)
- last_mod_time = models.DateTimeField(_('modify time'), default=now)
-
- class Meta:
- ordering = ['sequence']
- verbose_name = _('sidebar')
- verbose_name_plural = verbose_name
-
- def __str__(self):
- return self.name
-
-
-class BlogSettings(models.Model):
- """blog的配置"""
- site_name = models.CharField(
- _('site name'),
- max_length=200,
- null=False,
- blank=False,
- default='')
- site_description = models.TextField(
- _('site description'),
- max_length=1000,
- null=False,
- blank=False,
- default='')
- site_seo_description = models.TextField(
- _('site seo description'), max_length=1000, null=False, blank=False, default='')
- site_keywords = models.TextField(
- _('site keywords'),
- max_length=1000,
- null=False,
- blank=False,
- default='')
- article_sub_length = models.IntegerField(_('article sub length'), default=300)
- sidebar_article_count = models.IntegerField(_('sidebar article count'), default=10)
- sidebar_comment_count = models.IntegerField(_('sidebar comment count'), default=5)
- article_comment_count = models.IntegerField(_('article comment count'), default=5)
- show_google_adsense = models.BooleanField(_('show adsense'), default=False)
- google_adsense_codes = models.TextField(
- _('adsense code'), max_length=2000, null=True, blank=True, default='')
- open_site_comment = models.BooleanField(_('open site comment'), default=True)
- global_header = models.TextField("公共头部", null=True, blank=True, default='')
- global_footer = models.TextField("公共尾部", null=True, blank=True, default='')
- beian_code = models.CharField(
- '备案号',
- max_length=2000,
- null=True,
- blank=True,
- default='')
- analytics_code = models.TextField(
- "网站统计代码",
- max_length=1000,
- null=False,
- blank=False,
- default='')
- show_gongan_code = models.BooleanField(
- '是否显示公安备案号', default=False, null=False)
- gongan_beiancode = models.TextField(
- '公安备案号',
- max_length=2000,
- null=True,
- blank=True,
- default='')
- comment_need_review = models.BooleanField(
- '评论是否需要审核', default=False, null=False)
-
- class Meta:
- verbose_name = _('Website configuration')
- verbose_name_plural = verbose_name
-
- def __str__(self):
- return self.site_name
-
- def clean(self):
- if BlogSettings.objects.exclude(id=self.id).count():
- raise ValidationError(_('There can only be one configuration'))
-
- def save(self, *args, **kwargs):
- super().save(*args, **kwargs)
- from djangoblog.utils import cache
- cache.clear()
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py b/src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py
deleted file mode 100644
index 7f1dfac1..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/search_indexes.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from haystack import indexes
-
-from blog.models import Article
-
-
-class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
- text = indexes.CharField(document=True, use_template=True)
-
- def get_model(self):
- return Article
-
- def index_queryset(self, using=None):
- return self.get_model().objects.filter(status='p')
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py b/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py
deleted file mode 100644
index d6cd5d5a..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/templatetags/blog_tags.py
+++ /dev/null
@@ -1,344 +0,0 @@
-import hashlib
-import logging
-import random
-import urllib
-
-from django import template
-from django.conf import settings
-from django.db.models import Q
-from django.shortcuts import get_object_or_404
-from django.template.defaultfilters import stringfilter
-from django.templatetags.static import static
-from django.urls import reverse
-from django.utils.safestring import mark_safe
-
-from blog.models import Article, Category, Tag, Links, SideBar, LinkShowType
-from comments.models import Comment
-from djangoblog.utils import CommonMarkdown, sanitize_html
-from djangoblog.utils import cache
-from djangoblog.utils import get_current_site
-from oauth.models import OAuthUser
-from djangoblog.plugin_manage import hooks
-
-logger = logging.getLogger(__name__)
-
-register = template.Library()
-
-
-@register.simple_tag(takes_context=True)
-def head_meta(context):
- return mark_safe(hooks.apply_filters('head_meta', '', context))
-
-
-@register.simple_tag
-def timeformat(data):
- try:
- return data.strftime(settings.TIME_FORMAT)
- except Exception as e:
- logger.error(e)
- return ""
-
-
-@register.simple_tag
-def datetimeformat(data):
- try:
- return data.strftime(settings.DATE_TIME_FORMAT)
- except Exception as e:
- logger.error(e)
- return ""
-
-
-@register.filter()
-@stringfilter
-def custom_markdown(content):
- return mark_safe(CommonMarkdown.get_markdown(content))
-
-
-@register.simple_tag
-def get_markdown_toc(content):
- from djangoblog.utils import CommonMarkdown
- body, toc = CommonMarkdown.get_markdown_with_toc(content)
- return mark_safe(toc)
-
-
-@register.filter()
-@stringfilter
-def comment_markdown(content):
- content = CommonMarkdown.get_markdown(content)
- return mark_safe(sanitize_html(content))
-
-
-@register.filter(is_safe=True)
-@stringfilter
-def truncatechars_content(content):
- """
- 获得文章内容的摘要
- :param content:
- :return:
- """
- from django.template.defaultfilters import truncatechars_html
- from djangoblog.utils import get_blog_setting
- blogsetting = get_blog_setting()
- return truncatechars_html(content, blogsetting.article_sub_length)
-
-
-@register.filter(is_safe=True)
-@stringfilter
-def truncate(content):
- from django.utils.html import strip_tags
-
- return strip_tags(content)[:150]
-
-
-@register.inclusion_tag('blog/tags/breadcrumb.html')
-def load_breadcrumb(article):
- """
- 获得文章面包屑
- :param article:
- :return:
- """
- names = article.get_category_tree()
- from djangoblog.utils import get_blog_setting
- blogsetting = get_blog_setting()
- site = get_current_site().domain
- names.append((blogsetting.site_name, '/'))
- names = names[::-1]
-
- return {
- 'names': names,
- 'title': article.title,
- 'count': len(names) + 1
- }
-
-
-@register.inclusion_tag('blog/tags/article_tag_list.html')
-def load_articletags(article):
- """
- 文章标签
- :param article:
- :return:
- """
- tags = article.tags.all()
- tags_list = []
- for tag in tags:
- url = tag.get_absolute_url()
- count = tag.get_article_count()
- tags_list.append((
- url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES)
- ))
- return {
- 'article_tags_list': tags_list
- }
-
-
-@register.inclusion_tag('blog/tags/sidebar.html')
-def load_sidebar(user, linktype):
- """
- 加载侧边栏
- :return:
- """
- value = cache.get("sidebar" + linktype)
- if value:
- value['user'] = user
- return value
- else:
- logger.info('load sidebar')
- from djangoblog.utils import get_blog_setting
- blogsetting = get_blog_setting()
- recent_articles = Article.objects.filter(
- status='p')[:blogsetting.sidebar_article_count]
- sidebar_categorys = Category.objects.all()
- extra_sidebars = SideBar.objects.filter(
- is_enable=True).order_by('sequence')
- most_read_articles = Article.objects.filter(status='p').order_by(
- '-views')[:blogsetting.sidebar_article_count]
- dates = Article.objects.datetimes('creation_time', 'month', order='DESC')
- links = Links.objects.filter(is_enable=True).filter(
- Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A))
- commment_list = Comment.objects.filter(is_enable=True).order_by(
- '-id')[:blogsetting.sidebar_comment_count]
- # 标签云 计算字体大小
- # 根据总数计算出平均值 大小为 (数目/平均值)*步长
- increment = 5
- tags = Tag.objects.all()
- sidebar_tags = None
- if tags and len(tags) > 0:
- s = [t for t in [(t, t.get_article_count()) for t in tags] if t[1]]
- count = sum([t[1] for t in s])
- dd = 1 if (count == 0 or not len(tags)) else count / len(tags)
- import random
- sidebar_tags = list(
- map(lambda x: (x[0], x[1], (x[1] / dd) * increment + 10), s))
- random.shuffle(sidebar_tags)
-
- value = {
- 'recent_articles': recent_articles,
- 'sidebar_categorys': sidebar_categorys,
- 'most_read_articles': most_read_articles,
- 'article_dates': dates,
- 'sidebar_comments': commment_list,
- 'sidabar_links': links,
- 'show_google_adsense': blogsetting.show_google_adsense,
- 'google_adsense_codes': blogsetting.google_adsense_codes,
- 'open_site_comment': blogsetting.open_site_comment,
- 'show_gongan_code': blogsetting.show_gongan_code,
- 'sidebar_tags': sidebar_tags,
- 'extra_sidebars': extra_sidebars
- }
- cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3)
- logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype))
- value['user'] = user
- return value
-
-
-@register.inclusion_tag('blog/tags/article_meta_info.html')
-def load_article_metas(article, user):
- """
- 获得文章meta信息
- :param article:
- :return:
- """
- return {
- 'article': article,
- 'user': user
- }
-
-
-@register.inclusion_tag('blog/tags/article_pagination.html')
-def load_pagination_info(page_obj, page_type, tag_name):
- previous_url = ''
- next_url = ''
- if page_type == '':
- if page_obj.has_next():
- next_number = page_obj.next_page_number()
- next_url = reverse('blog:index_page', kwargs={'page': next_number})
- if page_obj.has_previous():
- previous_number = page_obj.previous_page_number()
- previous_url = reverse(
- 'blog:index_page', kwargs={
- 'page': previous_number})
- if page_type == '分类标签归档':
- tag = get_object_or_404(Tag, name=tag_name)
- if page_obj.has_next():
- next_number = page_obj.next_page_number()
- next_url = reverse(
- 'blog:tag_detail_page',
- kwargs={
- 'page': next_number,
- 'tag_name': tag.slug})
- if page_obj.has_previous():
- previous_number = page_obj.previous_page_number()
- previous_url = reverse(
- 'blog:tag_detail_page',
- kwargs={
- 'page': previous_number,
- 'tag_name': tag.slug})
- if page_type == '作者文章归档':
- if page_obj.has_next():
- next_number = page_obj.next_page_number()
- next_url = reverse(
- 'blog:author_detail_page',
- kwargs={
- 'page': next_number,
- 'author_name': tag_name})
- if page_obj.has_previous():
- previous_number = page_obj.previous_page_number()
- previous_url = reverse(
- 'blog:author_detail_page',
- kwargs={
- 'page': previous_number,
- 'author_name': tag_name})
-
- if page_type == '分类目录归档':
- category = get_object_or_404(Category, name=tag_name)
- if page_obj.has_next():
- next_number = page_obj.next_page_number()
- next_url = reverse(
- 'blog:category_detail_page',
- kwargs={
- 'page': next_number,
- 'category_name': category.slug})
- if page_obj.has_previous():
- previous_number = page_obj.previous_page_number()
- previous_url = reverse(
- 'blog:category_detail_page',
- kwargs={
- 'page': previous_number,
- 'category_name': category.slug})
-
- return {
- 'previous_url': previous_url,
- 'next_url': next_url,
- 'page_obj': page_obj
- }
-
-
-@register.inclusion_tag('blog/tags/article_info.html')
-def load_article_detail(article, isindex, user):
- """
- 加载文章详情
- :param article:
- :param isindex:是否列表页,若是列表页只显示摘要
- :return:
- """
- from djangoblog.utils import get_blog_setting
- blogsetting = get_blog_setting()
-
- return {
- 'article': article,
- 'isindex': isindex,
- 'user': user,
- 'open_site_comment': blogsetting.open_site_comment,
- }
-
-
-# return only the URL of the gravatar
-# TEMPLATE USE: {{ email|gravatar_url:150 }}
-@register.filter
-def gravatar_url(email, size=40):
- """获得gravatar头像"""
- cachekey = 'gravatat/' + email
- url = cache.get(cachekey)
- if url:
- return url
- else:
- usermodels = OAuthUser.objects.filter(email=email)
- if usermodels:
- o = list(filter(lambda x: x.picture is not None, usermodels))
- if o:
- return o[0].picture
- email = email.encode('utf-8')
-
- default = static('blog/img/avatar.png')
-
- url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5(
- email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)}))
- cache.set(cachekey, url, 60 * 60 * 10)
- logger.info('set gravatar cache.key:{key}'.format(key=cachekey))
- return url
-
-
-@register.filter
-def gravatar(email, size=40):
- """获得gravatar头像"""
- url = gravatar_url(email, size)
- return mark_safe(
- '
' %
- (url, size, size))
-
-
-@register.simple_tag
-def query(qs, **kwargs):
- """ template tag which allows queryset filtering. Usage:
- {% query books author=author as mybooks %}
- {% for book in mybooks %}
- ...
- {% endfor %}
- """
- return qs.filter(**kwargs)
-
-
-@register.filter
-def addstr(arg1, arg2):
- """concatenate arg1 & arg2"""
- return str(arg1) + str(arg2)
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/tests.py b/src/DjangoBlog-master/DjangoBlog-master/blog/tests.py
deleted file mode 100644
index ee135052..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/tests.py
+++ /dev/null
@@ -1,232 +0,0 @@
-import os
-
-from django.conf import settings
-from django.core.files.uploadedfile import SimpleUploadedFile
-from django.core.management import call_command
-from django.core.paginator import Paginator
-from django.templatetags.static import static
-from django.test import Client, RequestFactory, TestCase
-from django.urls import reverse
-from django.utils import timezone
-
-from accounts.models import BlogUser
-from blog.forms import BlogSearchForm
-from blog.models import Article, Category, Tag, SideBar, Links
-from blog.templatetags.blog_tags import load_pagination_info, load_articletags
-from djangoblog.utils import get_current_site, get_sha256
-from oauth.models import OAuthUser, OAuthConfig
-
-
-# Create your tests here.
-
-class ArticleTest(TestCase):
- def setUp(self):
- self.client = Client()
- self.factory = RequestFactory()
-
- def test_validate_article(self):
- site = get_current_site().domain
- user = BlogUser.objects.get_or_create(
- email="liangliangyy@gmail.com",
- username="liangliangyy")[0]
- user.set_password("liangliangyy")
- user.is_staff = True
- user.is_superuser = True
- user.save()
- response = self.client.get(user.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- response = self.client.get('/admin/servermanager/emailsendlog/')
- response = self.client.get('admin/admin/logentry/')
- s = SideBar()
- s.sequence = 1
- s.name = 'test'
- s.content = 'test content'
- s.is_enable = True
- s.save()
-
- category = Category()
- category.name = "category"
- category.creation_time = timezone.now()
- category.last_mod_time = timezone.now()
- category.save()
-
- tag = Tag()
- tag.name = "nicetag"
- tag.save()
-
- article = Article()
- article.title = "nicetitle"
- article.body = "nicecontent"
- article.author = user
- article.category = category
- article.type = 'a'
- article.status = 'p'
-
- article.save()
- self.assertEqual(0, article.tags.count())
- article.tags.add(tag)
- article.save()
- self.assertEqual(1, article.tags.count())
-
- for i in range(20):
- article = Article()
- article.title = "nicetitle" + str(i)
- article.body = "nicetitle" + str(i)
- article.author = user
- article.category = category
- article.type = 'a'
- article.status = 'p'
- article.save()
- article.tags.add(tag)
- article.save()
- from blog.documents import ELASTICSEARCH_ENABLED
- if ELASTICSEARCH_ENABLED:
- call_command("build_index")
- response = self.client.get('/search', {'q': 'nicetitle'})
- self.assertEqual(response.status_code, 200)
-
- response = self.client.get(article.get_absolute_url())
- self.assertEqual(response.status_code, 200)
- from djangoblog.spider_notify import SpiderNotify
- SpiderNotify.notify(article.get_absolute_url())
- response = self.client.get(tag.get_absolute_url())
- self.assertEqual(response.status_code, 200)
-
- response = self.client.get(category.get_absolute_url())
- self.assertEqual(response.status_code, 200)
-
- response = self.client.get('/search', {'q': 'django'})
- self.assertEqual(response.status_code, 200)
- s = load_articletags(article)
- self.assertIsNotNone(s)
-
- self.client.login(username='liangliangyy', password='liangliangyy')
-
- response = self.client.get(reverse('blog:archives'))
- self.assertEqual(response.status_code, 200)
-
- p = Paginator(Article.objects.all(), settings.PAGINATE_BY)
- self.check_pagination(p, '', '')
-
- p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY)
- self.check_pagination(p, '分类标签归档', tag.slug)
-
- p = Paginator(
- Article.objects.filter(
- author__username='liangliangyy'), settings.PAGINATE_BY)
- self.check_pagination(p, '作者文章归档', 'liangliangyy')
-
- p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY)
- self.check_pagination(p, '分类目录归档', category.slug)
-
- f = BlogSearchForm()
- f.search()
- # self.client.login(username='liangliangyy', password='liangliangyy')
- from djangoblog.spider_notify import SpiderNotify
- SpiderNotify.baidu_notify([article.get_full_url()])
-
- from blog.templatetags.blog_tags import gravatar_url, gravatar
- u = gravatar_url('liangliangyy@gmail.com')
- u = gravatar('liangliangyy@gmail.com')
-
- link = Links(
- sequence=1,
- name="lylinux",
- link='https://wwww.lylinux.net')
- link.save()
- response = self.client.get('/links.html')
- self.assertEqual(response.status_code, 200)
-
- response = self.client.get('/feed/')
- self.assertEqual(response.status_code, 200)
-
- response = self.client.get('/sitemap.xml')
- self.assertEqual(response.status_code, 200)
-
- self.client.get("/admin/blog/article/1/delete/")
- self.client.get('/admin/servermanager/emailsendlog/')
- self.client.get('/admin/admin/logentry/')
- self.client.get('/admin/admin/logentry/1/change/')
-
- def check_pagination(self, p, type, value):
- for page in range(1, p.num_pages + 1):
- s = load_pagination_info(p.page(page), type, value)
- self.assertIsNotNone(s)
- if s['previous_url']:
- response = self.client.get(s['previous_url'])
- self.assertEqual(response.status_code, 200)
- if s['next_url']:
- response = self.client.get(s['next_url'])
- self.assertEqual(response.status_code, 200)
-
- def test_image(self):
- import requests
- rsp = requests.get(
- 'https://www.python.org/static/img/python-logo.png')
- imagepath = os.path.join(settings.BASE_DIR, 'python.png')
- with open(imagepath, 'wb') as file:
- file.write(rsp.content)
- rsp = self.client.post('/upload')
- self.assertEqual(rsp.status_code, 403)
- sign = get_sha256(get_sha256(settings.SECRET_KEY))
- with open(imagepath, 'rb') as file:
- imgfile = SimpleUploadedFile(
- 'python.png', file.read(), content_type='image/jpg')
- form_data = {'python.png': imgfile}
- rsp = self.client.post(
- '/upload?sign=' + sign, form_data, follow=True)
- self.assertEqual(rsp.status_code, 200)
- os.remove(imagepath)
- from djangoblog.utils import save_user_avatar, send_email
- send_email(['qq@qq.com'], 'testTitle', 'testContent')
- save_user_avatar(
- 'https://www.python.org/static/img/python-logo.png')
-
- def test_errorpage(self):
- rsp = self.client.get('/eee')
- self.assertEqual(rsp.status_code, 404)
-
- def test_commands(self):
- user = BlogUser.objects.get_or_create(
- email="liangliangyy@gmail.com",
- username="liangliangyy")[0]
- user.set_password("liangliangyy")
- user.is_staff = True
- user.is_superuser = True
- user.save()
-
- c = OAuthConfig()
- c.type = 'qq'
- c.appkey = 'appkey'
- c.appsecret = 'appsecret'
- c.save()
-
- u = OAuthUser()
- u.type = 'qq'
- u.openid = 'openid'
- u.user = user
- u.picture = static("/blog/img/avatar.png")
- u.metadata = '''
-{
-"figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
-}'''
- u.save()
-
- u = OAuthUser()
- u.type = 'qq'
- u.openid = 'openid1'
- u.picture = 'https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30'
- u.metadata = '''
- {
- "figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30"
- }'''
- u.save()
-
- from blog.documents import ELASTICSEARCH_ENABLED
- if ELASTICSEARCH_ENABLED:
- call_command("build_index")
- call_command("ping_baidu", "all")
- call_command("create_testdata")
- call_command("clear_cache")
- call_command("sync_user_avatar")
- call_command("build_search_words")
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/urls.py b/src/DjangoBlog-master/DjangoBlog-master/blog/urls.py
deleted file mode 100644
index adf27036..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/urls.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from django.urls import path
-from django.views.decorators.cache import cache_page
-
-from . import views
-
-app_name = "blog"
-urlpatterns = [
- path(
- r'',
- views.IndexView.as_view(),
- name='index'),
- path(
- r'page//',
- views.IndexView.as_view(),
- name='index_page'),
- path(
- r'article////.html',
- views.ArticleDetailView.as_view(),
- name='detailbyid'),
- path(
- r'category/.html',
- views.CategoryDetailView.as_view(),
- name='category_detail'),
- path(
- r'category//.html',
- views.CategoryDetailView.as_view(),
- name='category_detail_page'),
- path(
- r'author/.html',
- views.AuthorDetailView.as_view(),
- name='author_detail'),
- path(
- r'author//.html',
- views.AuthorDetailView.as_view(),
- name='author_detail_page'),
- path(
- r'tag/.html',
- views.TagDetailView.as_view(),
- name='tag_detail'),
- path(
- r'tag//.html',
- views.TagDetailView.as_view(),
- name='tag_detail_page'),
- path(
- 'archives.html',
- cache_page(
- 60 * 60)(
- views.ArchivesView.as_view()),
- name='archives'),
- path(
- 'links.html',
- views.LinkListView.as_view(),
- name='links'),
- path(
- r'upload',
- views.fileupload,
- name='upload'),
- path(
- r'clean',
- views.clean_cache_view,
- name='clean'),
-]
diff --git a/src/DjangoBlog-master/DjangoBlog-master/blog/views.py b/src/DjangoBlog-master/DjangoBlog-master/blog/views.py
deleted file mode 100644
index d5dc7ec0..00000000
--- a/src/DjangoBlog-master/DjangoBlog-master/blog/views.py
+++ /dev/null
@@ -1,379 +0,0 @@
-import logging
-import os
-import uuid
-
-from django.conf import settings
-from django.core.paginator import Paginator
-from django.http import HttpResponse, HttpResponseForbidden
-from django.shortcuts import get_object_or_404
-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 haystack.views import SearchView
-
-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
-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'
-
- # context_object_name属性用于给上下文变量取名(在模板中使用该名字)
- context_object_name = 'article_list'
-
- # 页面类型,分类目录或标签列表等
- page_type = ''
- paginate_by = settings.PAGINATE_BY
- page_kwarg = 'page'
- link_type = LinkShowType.L
-
- def get_view_cache_key(self):
- return self.request.get['pages']
-
- @property
- def page_number(self):
- page_kwarg = self.page_kwarg
- page = self.kwargs.get(
- page_kwarg) or self.request.GET.get(page_kwarg) or 1
- return page
-
- def get_queryset_cache_key(self):
- """
- 子类重写.获得queryset的缓存key
- """
- raise NotImplementedError()
-
- def get_queryset_data(self):
- """
- 子类重写.获取queryset的数据
- """
- raise NotImplementedError()
-
- def get_queryset_from_cache(self, cache_key):
- '''
- 缓存页面数据
- :param cache_key: 缓存key
- :return:
- '''
- value = cache.get(cache_key)
- if value:
- logger.info('get view cache.key:{key}'.format(key=cache_key))
- return value
- else:
- article_list = self.get_queryset_data()
- cache.set(cache_key, article_list)
- logger.info('set view cache.key:{key}'.format(key=cache_key))
- return article_list
-
- def get_queryset(self):
- '''
- 重写默认,从缓存获取数据
- :return:
- '''
- key = self.get_queryset_cache_key()
- value = self.get_queryset_from_cache(key)
- return value
-
- def get_context_data(self, **kwargs):
- kwargs['linktype'] = self.link_type
- return super(ArticleListView, self).get_context_data(**kwargs)
-
-
-class IndexView(ArticleListView):
- '''
- 首页
- '''
- # 友情链接类型
- link_type = LinkShowType.I
-
- def get_queryset_data(self):
- article_list = Article.objects.filter(type='a', status='p')
- return article_list
-
- def get_queryset_cache_key(self):
- cache_key = 'index_{page}'.format(page=self.page_number)
- return cache_key
-
-
-class ArticleDetailView(DetailView):
- '''
- 文章详情页面
- '''
- template_name = 'blog/article_detail.html'
- model = Article
- pk_url_kwarg = 'article_id'
- context_object_name = "article"
-
- def get_context_data(self, **kwargs):
- comment_form = CommentForm()
-
- article_comments = self.object.comment_list()
- parent_comments = article_comments.filter(parent_comment=None)
- blog_setting = get_blog_setting()
- paginator = Paginator(parent_comments, blog_setting.article_comment_count)
- page = self.request.GET.get('comment_page', '1')
- if not page.isnumeric():
- page = 1
- else:
- page = int(page)
- if page < 1:
- page = 1
- if page > paginator.num_pages:
- page = paginator.num_pages
-
- p_comments = paginator.page(page)
- next_page = p_comments.next_page_number() if p_comments.has_next() else None
- prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None
-
- if next_page:
- kwargs[
- 'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container'
- if prev_page:
- kwargs[
- 'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container'
- kwargs['form'] = comment_form
- kwargs['article_comments'] = article_comments
- kwargs['p_comments'] = p_comments
- kwargs['comment_count'] = len(
- article_comments) if article_comments else 0
-
- kwargs['next_article'] = self.object.next_article
- kwargs['prev_article'] = self.object.prev_article
-
- context = super(ArticleDetailView, self).get_context_data(**kwargs)
- article = self.object
- # Action Hook, 通知插件"文章详情已获取"
- hooks.run_action('after_article_body_get', article=article, request=self.request)
- # # Filter Hook, 允许插件修改文章正文
- article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article,
- request=self.request)
-
- return context
-
-
-class CategoryDetailView(ArticleListView):
- '''
- 分类目录列表
- '''
- page_type = "分类目录归档"
-
- def get_queryset_data(self):
- slug = self.kwargs['category_name']
- category = get_object_or_404(Category, slug=slug)
-
- categoryname = category.name
- self.categoryname = categoryname
- categorynames = list(
- map(lambda c: c.name, category.get_sub_categorys()))
- article_list = Article.objects.filter(
- category__name__in=categorynames, status='p')
- return article_list
-
- def get_queryset_cache_key(self):
- slug = self.kwargs['category_name']
- category = get_object_or_404(Category, slug=slug)
- categoryname = category.name
- self.categoryname = categoryname
- cache_key = 'category_list_{categoryname}_{page}'.format(
- categoryname=categoryname, page=self.page_number)
- return cache_key
-
- def get_context_data(self, **kwargs):
-
- categoryname = self.categoryname
- try:
- categoryname = categoryname.split('/')[-1]
- except BaseException:
- pass
- kwargs['page_type'] = CategoryDetailView.page_type
- kwargs['tag_name'] = categoryname
- return super(CategoryDetailView, self).get_context_data(**kwargs)
-
-
-class AuthorDetailView(ArticleListView):
- '''
- 作者详情页
- '''
- page_type = '作者文章归档'
-
- def get_queryset_cache_key(self):
- from uuslug import slugify
- author_name = slugify(self.kwargs['author_name'])
- cache_key = 'author_{author_name}_{page}'.format(
- author_name=author_name, page=self.page_number)
- return cache_key
-
- def get_queryset_data(self):
- author_name = self.kwargs['author_name']
- article_list = Article.objects.filter(
- author__username=author_name, type='a', status='p')
- return article_list
-
- def get_context_data(self, **kwargs):
- author_name = self.kwargs['author_name']
- kwargs['page_type'] = AuthorDetailView.page_type
- kwargs['tag_name'] = author_name
- return super(AuthorDetailView, self).get_context_data(**kwargs)
-
-
-class TagDetailView(ArticleListView):
- '''
- 标签列表页面
- '''
- page_type = '分类标签归档'
-
- def get_queryset_data(self):
- slug = self.kwargs['tag_name']
- tag = get_object_or_404(Tag, slug=slug)
- tag_name = tag.name
- self.name = tag_name
- article_list = Article.objects.filter(
- tags__name=tag_name, type='a', status='p')
- return article_list
-
- def get_queryset_cache_key(self):
- slug = self.kwargs['tag_name']
- tag = get_object_or_404(Tag, slug=slug)
- tag_name = tag.name
- self.name = tag_name
- cache_key = 'tag_{tag_name}_{page}'.format(
- tag_name=tag_name, page=self.page_number)
- return cache_key
-
- def get_context_data(self, **kwargs):
- # tag_name = self.kwargs['tag_name']
- tag_name = self.name
- kwargs['page_type'] = TagDetailView.page_type
- kwargs['tag_name'] = tag_name
- return super(TagDetailView, self).get_context_data(**kwargs)
-
-
-class ArchivesView(ArticleListView):
- '''
- 文章归档页面
- '''
- page_type = '文章归档'
- paginate_by = None
- page_kwarg = None
- template_name = 'blog/article_archives.html'
-
- def get_queryset_data(self):
- return Article.objects.filter(status='p').all()
-
- def get_queryset_cache_key(self):
- cache_key = 'archives'
- return cache_key
-
-
-class LinkListView(ListView):
- model = Links
- template_name = 'blog/links_list.html'
-
- def get_queryset(self):
- return Links.objects.filter(is_enable=True)
-
-
-class EsSearchView(SearchView):
- def get_context(self):
- paginator, page = self.build_page()
- context = {
- "query": self.query,
- "form": self.form,
- "page": page,
- "paginator": paginator,
- "suggestion": None,
- }
- if hasattr(self.results, "query") and self.results.query.backend.include_spelling:
- context["suggestion"] = self.results.query.get_spelling_suggestion()
- context.update(self.extra_context())
-
- return context
-
-
-@csrf_exempt
-def fileupload(request):
- """
- 该方法需自己写调用端来上传图片,该方法仅提供图床功能
- :param request:
- :return:
- """
- if request.method == 'POST':
- sign = request.GET.get('sign', None)
- if not sign:
- return HttpResponseForbidden()
- if not sign == get_sha256(get_sha256(settings.SECRET_KEY)):
- return HttpResponseForbidden()
- response = []
- for filename in request.FILES:
- timestr = timezone.now().strftime('%Y/%m/%d')
- imgextensions = ['jpg', 'png', 'jpeg', 'bmp']
- fname = u''.join(str(filename))
- isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0
- base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr)
- if not os.path.exists(base_dir):
- os.makedirs(base_dir)
- savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}"))
- if not savepath.startswith(base_dir):
- return HttpResponse("only for post")
- with open(savepath, 'wb+') as wfile:
- for chunk in request.FILES[filename].chunks():
- wfile.write(chunk)
- if isimage:
- from PIL import Image
- image = Image.open(savepath)
- image.save(savepath, quality=20, optimize=True)
- url = static(savepath)
- response.append(url)
- return HttpResponse(response)
-
- else:
- return HttpResponse("only for post")
-
-
-def page_not_found_view(
- request,
- exception,
- template_name='blog/error_page.html'):
- if exception:
- logger.error(exception)
- url = request.get_full_path()
- return render(request,
- template_name,
- {'message': _('Sorry, the page you requested is not found, please click the home page to see other?'),
- 'statuscode': '404'},
- status=404)
-
-
-def server_error_view(request, template_name='blog/error_page.html'):
- return render(request,
- template_name,
- {'message': _('Sorry, the server is busy, please click the home page to see other?'),
- 'statuscode': '500'},
- status=500)
-
-
-def permission_denied_view(
- request,
- exception,
- template_name='blog/error_page.html'):
- if exception:
- logger.error(exception)
- return render(
- request, template_name, {
- 'message': _('Sorry, you do not have permission to access this page?'),
- 'statuscode': '403'}, status=403)
-
-
-def clean_cache_view(request):
- cache.clear()
- return HttpResponse('ok')