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图片格式: ![描述](URL) + 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')