diff --git a/src/DjangoBlog-master/blog/__pycache__/__init__.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/__init__.cpython-312.pyc index fba8897b..ebe8735f 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/__init__.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/admin.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/admin.cpython-312.pyc index 2d7cc3e6..e9557a1c 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/admin.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/admin.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/apps.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/apps.cpython-312.pyc index e1565c1c..61058d8e 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/apps.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/apps.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/context_processors.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/context_processors.cpython-312.pyc index 3e68c2c9..4b91cadf 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/context_processors.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/context_processors.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/documents.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/documents.cpython-312.pyc index 274bf703..2f08fcf4 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/documents.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/documents.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/middleware.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/middleware.cpython-312.pyc index 4e2300e7..bbe545de 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/middleware.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/middleware.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/models.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/models.cpython-312.pyc index cc6b1631..eeb8a3ba 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/models.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/models.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/search_indexes.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/search_indexes.cpython-312.pyc index 1bb23e31..8d68fc3d 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/search_indexes.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/search_indexes.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/urls.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/urls.cpython-312.pyc index 11038122..7fe62106 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/urls.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/urls.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/__pycache__/views.cpython-312.pyc b/src/DjangoBlog-master/blog/__pycache__/views.cpython-312.pyc index c8ff1989..02b25113 100644 Binary files a/src/DjangoBlog-master/blog/__pycache__/views.cpython-312.pyc and b/src/DjangoBlog-master/blog/__pycache__/views.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/admin.py b/src/DjangoBlog-master/blog/admin.py index 46c34208..a161e216 100644 --- a/src/DjangoBlog-master/blog/admin.py +++ b/src/DjangoBlog-master/blog/admin.py @@ -1,3 +1,4 @@ +#sxc 该模块是 Django 博客系统的后台管理配置文件,核心功能是为博客核心模型(Article、Tag、Category 等)注册 Django Admin 管理界面,并通过自定义 Admin 类、表单和批量操作,实现对博客内容的可视化管理,包括文章发布 / 草稿切换、评论状态控制、分类标签管理等功能 from django import forms from django.contrib import admin from django.contrib.auth import get_user_model @@ -16,23 +17,23 @@ class ArticleForm(forms.ModelForm): model = Article fields = '__all__' - +#sxc 自定义批量操作函数:将选中的文章状态批量更新为 “发布”(status='p') def makr_article_publish(modeladmin, request, queryset): queryset.update(status='p') - +#sxc 自定义批量操作函数:将选中的文章状态批量更新为 “草稿”(status='d') def draft_article(modeladmin, request, queryset): queryset.update(status='d') - +#sxc 自定义批量操作函数:将选中的文章评论状态批量更新为 “关闭”(comment_status='c') def close_article_commentstatus(modeladmin, request, queryset): queryset.update(comment_status='c') - +#sxc 自定义批量操作函数:将选中的文章评论状态批量更新为 “打开”(comment_status='o') def open_article_commentstatus(modeladmin, request, queryset): queryset.update(comment_status='o') - +#sxc 为批量操作函数设置后台显示名称,支持国际化(_() 函数) makr_article_publish.short_description = _('Publish selected articles') draft_article.short_description = _('Draft selected articles') close_article_commentstatus.short_description = _('Close article comments') @@ -40,9 +41,11 @@ open_article_commentstatus.short_description = _('Open article comments') class ArticlelAdmin(admin.ModelAdmin): + #sxc 文章模型的 Admin 管理类,自定义后台文章列表和编辑页的显示与功能 list_per_page = 20 search_fields = ('body', 'title') form = ArticleForm + #sxc 列表页显示的字段,涵盖文章核心信息:ID、标题、作者、分类、创建时间、浏览量、状态、类型、排序 list_display = ( 'id', 'title', @@ -58,6 +61,7 @@ class ArticlelAdmin(admin.ModelAdmin): filter_horizontal = ('tags',) exclude = ('creation_time', 'last_modify_time') view_on_site = True + #sxc 列表页支持的批量操作,关联前面定义的 4 个批量函数 actions = [ makr_article_publish, draft_article, @@ -65,48 +69,69 @@ class ArticlelAdmin(admin.ModelAdmin): open_article_commentstatus] def link_to_category(self, obj): + #sxc 自定义列表字段:将文章所属分类转为可点击链接,点击跳转到分类的编辑页 + #sxc 获取分类模型的 APP 标签和模型名,用于生成 Admin 后台的 URL info = (obj.category._meta.app_label, obj.category._meta.model_name) link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) + #sxc 生成 HTML 链接,format_html 确保内容安全渲染(避免 XSS) return format_html(u'%s' % (link, obj.category.name)) link_to_category.short_description = _('category') def get_form(self, request, obj=None, **kwargs): + #sxc 重写表单获取方法:限制 “作者” 字段的可选范围为超级用户 + #sxc 调用父类方法获取默认表单 form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) + #sxc 筛选作者下拉框选项,仅显示超级用户(避免普通用户被选为作者) form.base_fields['author'].queryset = get_user_model( ).objects.filter(is_superuser=True) return form def save_model(self, request, obj, form, change): + #sxc 重写模型保存方法,可扩展自定义保存逻辑(如自动记录操作日志) + #sxc 此处保留默认保存逻辑,如需扩展可在 super 前后添加代码 super(ArticlelAdmin, self).save_model(request, obj, form, change) def get_view_on_site_url(self, obj=None): if obj: + #sxc 若有文章对象,调用文章模型的 get_full_url 方法生成前台链接 url = obj.get_full_url() return url else: + #sxc 若无对象(如列表页),返回当前站点的域名 from djangoblog.utils import get_current_site site = get_current_site().domain return site class TagAdmin(admin.ModelAdmin): + #sxc 标签模型的 Admin 管理类 + #sxc 编辑页隐藏自动生成的字段:slug(URL 友好标识)、创建时间、最后修改时间 exclude = ('slug', 'last_mod_time', 'creation_time') class CategoryAdmin(admin.ModelAdmin): list_display = ('name', 'parent_category', 'index') + #sxc 列表页显示:分类名、父分类、权重排序 + #sxc 编辑页隐藏自动生成的字段:slug、创建时间、最后修改时间 exclude = ('slug', 'last_mod_time', 'creation_time') class LinksAdmin(admin.ModelAdmin): + #sxc 友情链接模型的 Admin 管理类 + #sxc 编辑页隐藏自动生成的字段:创建时间、最后修改时间 exclude = ('last_mod_time', 'creation_time') class SideBarAdmin(admin.ModelAdmin): + #sxc 侧边栏模型的 Admin 管理类 + #sxc 列表页显示:侧边栏标题、内容、是否启用、排序序号 list_display = ('name', 'content', 'is_enable', 'sequence') + #sxc 编辑页隐藏自动生成的字段:创建时间、最后修改时间 exclude = ('last_mod_time', 'creation_time') class BlogSettingsAdmin(admin.ModelAdmin): + #sxc 网站配置模型的 Admin 管理类 + #sxc 未添加额外配置,使用 Django Admin 默认的显示和操作逻辑(适合字段较少的配置类) pass diff --git a/src/DjangoBlog-master/blog/apps.py b/src/DjangoBlog-master/blog/apps.py index 79305878..fbd9a262 100644 --- a/src/DjangoBlog-master/blog/apps.py +++ b/src/DjangoBlog-master/blog/apps.py @@ -1,5 +1,8 @@ +#sxc 该模块是 Django 博客应用的配置类模块,用于定义博客应用(blog)的基本配置信息,使 Django 能够识别并正确加载该应用 from django.apps import AppConfig class BlogConfig(AppConfig): + #sxc 博客应用的配置类,继承自 Django 的 AppConfig + #sxc 定义应用的名称为 'blog',Django 通过该名称识别并管理该应用 name = 'blog' diff --git a/src/DjangoBlog-master/blog/context_processors.py b/src/DjangoBlog-master/blog/context_processors.py index 73e3088b..18f04429 100644 --- a/src/DjangoBlog-master/blog/context_processors.py +++ b/src/DjangoBlog-master/blog/context_processors.py @@ -1,3 +1,5 @@ +#sxc 该模块是 Django 博客系统的上下文处理器模块,用于为所有模板提供全局可用的 SEO 相关配置和通用数据,避免在每个视图中重复传递这些数据,提升模板复用性和开发效率 +#sxc 集成缓存机制减少数据库查询,包含网站基本设置、导航数据、评论配置等关键信息 import logging from django.utils import timezone @@ -9,35 +11,41 @@ logger = logging.getLogger(__name__) def seo_processor(requests): + #sxc 上下文处理器函数:向所有模板提供全局 SEO 及网站配置数据 + #sxc 使用缓存减少数据库访问,提升性能,缓存键为'seo_processor' key = 'seo_processor' value = cache.get(key) - if value: - return value + if value: + return value #sxc 缓存命中:直接返回缓存的配置数据 else: logger.info('set processor cache.') setting = get_blog_setting() + #sxc 组装需要传递给模板的全局数据字典 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_NAME': setting.site_name, #sxc 网站名称 + 'SHOW_GOOGLE_ADSENSE': setting.show_google_adsense,#sxc 是否显示谷歌广告 + 'GOOGLE_ADSENSE_CODES': setting.google_adsense_codes,#sxc 谷歌广告代码 + 'SITE_SEO_DESCRIPTION': setting.site_seo_description,#sxc 网站 SEO 描述(用于搜索引擎) + 'SITE_DESCRIPTION': setting.site_description,#sxc 网站描述 + 'SITE_KEYWORDS': setting.site_keywords,#sxc 网站关键字(用于 SEO) + #sxc 网站基础 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(), + 'ARTICLE_SUB_LENGTH': setting.article_sub_length,#sxc 文章摘要长度 + 'nav_category_list': Category.objects.all(),#sxc 导航栏分类列表 + #sxc 导航栏页面列表(仅包含已发布的页面类型文章) '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, + 'OPEN_SITE_COMMENT': setting.open_site_comment,#sxc 是否开启网站评论功能 + 'BEIAN_CODE': setting.beian_code,#sxc 网站备案号 + 'ANALYTICS_CODE': setting.analytics_code,#sxc 网站统计代码 + "BEIAN_CODE_GONGAN": setting.gongan_beiancode,#sxc 公安备案号 + "SHOW_GONGAN_CODE": setting.show_gongan_code,#sxc 是否显示公安备案号 + "CURRENT_YEAR": timezone.now().year, #sxc 当前年份(用于页脚版权信息) + "GLOBAL_HEADER": setting.global_header,#sxc 公共头部 HTML 代码 + "GLOBAL_FOOTER": setting.global_footer,#sxc 公共尾部 HTML 代码 + "COMMENT_NEED_REVIEW": setting.comment_need_review,#sxc 评论是否需要审核 } + #sxc 设置缓存,有效期 10 小时(606010 秒) cache.set(key, value, 60 * 60 * 10) return value diff --git a/src/DjangoBlog-master/blog/documents.py b/src/DjangoBlog-master/blog/documents.py index 0f1db7b7..410f19a1 100644 --- a/src/DjangoBlog-master/blog/documents.py +++ b/src/DjangoBlog-master/blog/documents.py @@ -1,15 +1,17 @@ +#sxc 该模块是 Django 博客系统的 Elasticsearch 集成配置模块,核心功能是定义 Elasticsearch 文档模型(性能日志、文章)、实现文档的创建 / 更新 / 删除等管理操作,同时配置 IP 地理信息解析管道,为博客提供高性能的全文搜索和访问性能监控能力 +#sxc 依赖 elasticsearch-dsl 库,支持条件启用(通过 settings.ELASTICSEARCH_DSL 配置),包含文档模型定义、索引管理、数据同步三大核心模块 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 - +#sxc 判断项目是否配置 Elasticsearch,控制后续功能是否启用 ELASTICSEARCH_ENABLED = hasattr(settings, 'ELASTICSEARCH_DSL') if ELASTICSEARCH_ENABLED: + #sxc 建立与 Elasticsearch 的连接,使用项目配置的地址 connections.create_connection( hosts=[settings.ELASTICSEARCH_DSL['default']['hosts']]) from elasticsearch import Elasticsearch @@ -19,8 +21,10 @@ if ELASTICSEARCH_ENABLED: c = IngestClient(es) try: + #sxc 检查是否存在 "geoip" 管道(用于解析 IP 地理信息) c.get_pipeline('geoip') except elasticsearch.exceptions.NotFoundError: + #sxc 若不存在则创建 "geoip" 管道,定义 IP 解析逻辑(从 ip 字段提取地理信息) c.put_pipeline('geoip', body='''{ "description" : "Add geoip info", "processors" : [ @@ -34,28 +38,33 @@ if ELASTICSEARCH_ENABLED: class GeoIp(InnerDoc): - continent_name = Keyword() - country_iso_code = Keyword() - country_name = Keyword() - location = GeoPoint() + #sxc 内部文档类:定义 IP 地理信息的字段结构,用于嵌套在性能日志文档中 + continent_name = Keyword()#sxc 洲名 + country_iso_code = Keyword()#sxc 国家 ISO 代码(如 CN、US) + country_name = Keyword()#sxc 国家名称 + location = GeoPoint()#sxc 地理位置坐标(经纬度) class UserAgentBrowser(InnerDoc): - Family = Keyword() - Version = Keyword() + #sxc 内部文档类:定义用户代理中的浏览器信息结构 + Family = Keyword()#sxc 浏览器家族(如 Chrome、Safari) + Version = Keyword() #sxc 浏览器版本 class UserAgentOS(UserAgentBrowser): + #sxc 内部文档类:定义用户代理中的操作系统信息结构(继承浏览器类字段) pass class UserAgentDevice(InnerDoc): + #sxc 内部文档类:定义用户代理中的设备信息结构 Family = Keyword() Brand = Keyword() Model = Keyword() class UserAgent(InnerDoc): + #sxc 内部文档类:定义完整的用户代理信息结构,嵌套浏览器、OS、设备信息 browser = Object(UserAgentBrowser, required=False) os = Object(UserAgentOS, required=False) device = Object(UserAgentDevice, required=False) @@ -64,6 +73,7 @@ class UserAgent(InnerDoc): class ElapsedTimeDocument(Document): + #sxc Elasticsearch 文档类:定义访问性能日志模型,用于记录页面加载耗时等信息 url = Keyword() time_taken = Long() log_datetime = Date() @@ -72,6 +82,7 @@ class ElapsedTimeDocument(Document): useragent = Object(UserAgent, required=False) class Index: + #sxc 定义索引配置:索引名为 "performance",分片 1 个、副本 0 个(单节点环境优化) name = 'performance' settings = { "number_of_shards": 1, @@ -83,8 +94,10 @@ class ElapsedTimeDocument(Document): class ElaspedTimeDocumentManager: + #sxc 性能日志文档管理器:提供索引创建、删除、日志写入等静态方法 @staticmethod def build_index(): + #sxc 静态方法:创建 "performance" 索引(若不存在) from elasticsearch import Elasticsearch client = Elasticsearch(settings.ELASTICSEARCH_DSL['default']['hosts']) res = client.indices.exists(index="performance") @@ -93,13 +106,16 @@ class ElaspedTimeDocumentManager: @staticmethod def delete_index(): + #sxc 静态方法:删除 "performance" 索引(忽略 400/404 错误:索引不存在或删除失败) 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): + #sxc 静态方法:创建一条性能日志文档,并应用 geoip 管道解析 IP 地理信息 ElaspedTimeDocumentManager.build_index() + #sxc 组装用户代理信息(从传入的 useragent 对象提取字段) ua = UserAgent() ua.browser = UserAgentBrowser() ua.browser.Family = useragent.browser.family @@ -115,7 +131,7 @@ class ElaspedTimeDocumentManager: ua.device.Model = useragent.device.model ua.string = useragent.ua_string ua.is_bot = useragent.is_bot - +#sxc 创建性能日志文档:ID 用时间戳(毫秒级)确保唯一,关联 geoip 管道 doc = ElapsedTimeDocument( meta={ 'id': int( @@ -131,21 +147,26 @@ class ElaspedTimeDocumentManager: class ArticleDocument(Document): + #sxc Elasticsearch 文档类:定义博客文章模型,用于全文搜索 + #sxc 正文和标题使用 IK 分词器(ik_max_word:细粒度分词;ik_smart:粗粒度搜索) body = Text(analyzer='ik_max_word', search_analyzer='ik_smart') title = Text(analyzer='ik_max_word', search_analyzer='ik_smart') + #sxc 作者信息(嵌套 Object:包含昵称和 ID,昵称支持分词) author = Object(properties={ 'nickname': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), 'id': Integer() }) + #sxc 分类信息(嵌套 Object:包含名称和 ID,名称支持分词) category = Object(properties={ 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), 'id': Integer() }) + #sxc 标签信息(嵌套 Object 列表:包含名称和 ID,名称支持分词) tags = Object(properties={ 'name': Text(analyzer='ik_max_word', search_analyzer='ik_smart'), 'id': Integer() }) - +#sxc 文章其他字段:发布时间、状态、评论状态、类型、浏览量、排序 pub_time = Date() status = Text() comment_status = Text() @@ -154,6 +175,7 @@ class ArticleDocument(Document): article_order = Integer() class Index: + #sxc 定义索引配置:索引名为 "blog",分片 1 个、副本 0 个(单节点环境优化) name = 'blog' settings = { "number_of_shards": 1, @@ -163,21 +185,24 @@ class ArticleDocument(Document): class Meta: doc_type = 'Article' - +#sxc 文章文档管理器:提供索引创建、删除、数据同步(重建 / 更新)等实例方法 class ArticleDocumentManager(): def __init__(self): self.create_index() def create_index(self): + #sxc 实例方法:初始化 "blog" 索引(根据 ArticleDocument 定义创建) ArticleDocument.init() def delete_index(self): + #sxc 实例方法:删除 "blog" 索引(忽略 400/404 错误) 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): + #sxc 实例方法:将 Django Article 模型对象列表转换为 Elasticsearch ArticleDocument 列表 return [ ArticleDocument( meta={ @@ -202,7 +227,9 @@ class ArticleDocumentManager(): article_order=article.article_order) for article in articles] def rebuild(self, articles=None): + #sxc 实例方法:重建文章索引(全量同步或指定文章同步) ArticleDocument.init() + #sxc 若未指定文章列表,则同步所有 Article 模型数据 articles = articles if articles else Article.objects.all() docs = self.convert_to_doc(articles) for doc in docs: diff --git a/src/DjangoBlog-master/blog/forms.py b/src/DjangoBlog-master/blog/forms.py index 715be762..350bed6c 100644 --- a/src/DjangoBlog-master/blog/forms.py +++ b/src/DjangoBlog-master/blog/forms.py @@ -1,19 +1,25 @@ +#sxc 该模块是 Django 博客系统的搜索表单模块,基于 Haystack 搜索框架扩展自定义搜索表单,用于接收和验证用户的搜索请求,同时记录搜索关键词日志,确保搜索功能的合法性和可追溯性 import logging - from django import forms -from haystack.forms import SearchForm +from haystack.forms import SearchForm #sxc 导入 Haystack 框架的基础搜索表单类 -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #sxc 初始化日志记录器,用于记录搜索相关日志 class BlogSearchForm(SearchForm): + #sxc 自定义博客搜索表单类,继承自 Haystack 的 SearchForm,扩展搜索功能 + #sxc 定义搜索输入字段:querydata(必填,用于接收用户输入的搜索关键词) querydata = forms.CharField(required=True) def search(self): + #sxc 重写父类 search 方法:执行搜索逻辑,包含表单验证和日志记录 + #sxc 调用父类 search 方法,获取初始搜索结果集 datas = super(BlogSearchForm, self).search() + #sxc 表单验证:若表单数据无效,返回 “无搜索结果” 的默认响应 if not self.is_valid(): return self.no_query_found() - +#sxc 日志记录:若存在有效搜索关键词,记录关键词到日志(便于分析用户搜索行为) if self.cleaned_data['querydata']: logger.info(self.cleaned_data['querydata']) + #sxc 返回搜索结果集(若验证通过且有关键词,返回匹配结果;否则返回空结果) return datas diff --git a/src/DjangoBlog-master/blog/management/__pycache__/__init__.cpython-312.pyc b/src/DjangoBlog-master/blog/management/__pycache__/__init__.cpython-312.pyc index eded7a7c..a7d93081 100644 Binary files a/src/DjangoBlog-master/blog/management/__pycache__/__init__.cpython-312.pyc and b/src/DjangoBlog-master/blog/management/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/management/commands/build_index.py b/src/DjangoBlog-master/blog/management/commands/build_index.py index 3c4acd74..263ed495 100644 --- a/src/DjangoBlog-master/blog/management/commands/build_index.py +++ b/src/DjangoBlog-master/blog/management/commands/build_index.py @@ -1,3 +1,4 @@ +#sxc 该模块定义了 Django 自定义管理命令,用于在 Elasticsearch 中构建和重建博客系统的搜索索引,支持文章数据和耗时统计数据的索引管理,仅在 Elasticsearch 启用时执行操作。 from django.core.management.base import BaseCommand from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedTimeDocumentManager, \ @@ -6,13 +7,18 @@ from blog.documents import ElapsedTimeDocument, ArticleDocumentManager, ElaspedT # TODO 参数化 class Command(BaseCommand): + #sxc 定义命令的帮助信息,使用python manage.py help build_index可查看 help = 'build search index' def handle(self, *args, **options): + #sxc 命令核心执行逻辑:检查 Elasticsearch 是否启用,若启用则执行索引构建操作 if ELASTICSEARCH_ENABLED: + #sxc 构建耗时统计文档的索引(如文章访问耗时等数据) ElaspedTimeDocumentManager.build_index() + #sxc 初始化耗时统计文档的索引结构 manager = ElapsedTimeDocument() - manager.init() + manager.init() #sxc 创建索引映射(mapping) + #sxc 处理文章文档的索引:先删除旧索引,再重建新索引 manager = ArticleDocumentManager() - manager.delete_index() - manager.rebuild() + manager.delete_index() #sxc 删除已存在的文章索引(避免数据冲突) + manager.rebuild() #sxc 从数据库同步文章数据并重建索引 diff --git a/src/DjangoBlog-master/blog/management/commands/build_search_words.py b/src/DjangoBlog-master/blog/management/commands/build_search_words.py index cfe7e0d5..0afe5c93 100644 --- a/src/DjangoBlog-master/blog/management/commands/build_search_words.py +++ b/src/DjangoBlog-master/blog/management/commands/build_search_words.py @@ -1,3 +1,4 @@ +#sxc 该模块定义了 Django 自定义管理命令,用于提取博客系统中所有标签(Tag)和分类(Category)的名称,去重后输出为搜索词列表,可用于搜索提示、自动补全等功能的数据源。 from django.core.management.base import BaseCommand from blog.models import Tag, Category @@ -5,9 +6,15 @@ from blog.models import Tag, Category # TODO 参数化 class Command(BaseCommand): - help = 'build search words' + #sxc 定义命令的帮助信息,通过python manage.py help build_search_words可查看 + help = 'build search words' #sxc 命令功能描述:生成搜索词列表 def handle(self, *args, **options): + #sxc 命令核心执行逻辑:提取标签和分类名称,去重后输出 + #sxc 1. 提取所有标签名称并转换为列表 + #sxc 2. 提取所有分类名称并转换为列表 + #sxc 3. 合并两个列表并通过 set 去重,得到唯一的搜索词集合 datas = set([t.name for t in Tag.objects.all()] + [t.name for t in Category.objects.all()]) + #sxc 按行输出所有搜索词(每行一个词),便于后续处理或导入 print('\n'.join(datas)) diff --git a/src/DjangoBlog-master/blog/management/commands/clear_cache.py b/src/DjangoBlog-master/blog/management/commands/clear_cache.py index 0d66172c..ab78b4df 100644 --- a/src/DjangoBlog-master/blog/management/commands/clear_cache.py +++ b/src/DjangoBlog-master/blog/management/commands/clear_cache.py @@ -1,11 +1,15 @@ +#sxc 该模块定义了 Django 自定义管理命令,用于清空系统缓存,通过调用缓存工具类的清除方法,快速清理所有缓存数据,适用于缓存数据过期或异常时的手动运维操作。 from django.core.management.base import BaseCommand -from djangoblog.utils import cache +from djangoblog.utils import cache #sxc 导入系统缓存工具类 class Command(BaseCommand): - help = 'clear the whole cache' + #sxc 命令帮助信息,执行python manage.py help clear_cache时显示 + help = 'clear the whole cache' #sxc 命令功能描述:清空所有缓存 def handle(self, *args, **options): + #sxc 命令核心执行逻辑:调用缓存工具类的 clear 方法清空所有缓存 cache.clear() + #sxc 输出操作成功的提示信息(绿色成功标识) self.stdout.write(self.style.SUCCESS('Cleared cache\n')) diff --git a/src/DjangoBlog-master/blog/management/commands/create_testdata.py b/src/DjangoBlog-master/blog/management/commands/create_testdata.py index 675d2ba6..fe3346be 100644 --- a/src/DjangoBlog-master/blog/management/commands/create_testdata.py +++ b/src/DjangoBlog-master/blog/management/commands/create_testdata.py @@ -1,3 +1,4 @@ +#sxc 该模块定义了 Django 自定义管理命令,用于快速生成博客系统所需的测试数据,包括测试用户、分类、标签及文章,适用于开发调试和功能验证场景,生成数据后会自动清理缓存确保数据生效。 from django.contrib.auth import get_user_model from django.contrib.auth.hashers import make_password from django.core.management.base import BaseCommand @@ -6,35 +7,44 @@ from blog.models import Article, Tag, Category class Command(BaseCommand): - help = 'create test datas' + #sxc 命令帮助信息,执行python manage.py help create_testdata时显示 + help = 'create test datas' #sxc 命令功能描述:生成测试数据 def handle(self, *args, **options): + #sxc 命令核心执行逻辑:创建测试用户、分类、标签和文章数据 + #sxc 创建或获取测试用户(邮箱:test@test.com,用户名:测试用户,密码:test!q@w#eTYU) user = get_user_model().objects.get_or_create( email='test@test.com', username='测试用户', password=make_password('test!q@w#eTYU'))[0] - +#sxc 创建或获取父分类(名称:我是父类目,无父分类) pcategory = Category.objects.get_or_create( name='我是父类目', parent_category=None)[0] - +#sxc 创建或获取子分类(名称:子类目,父分类为 pcategory) category = Category.objects.get_or_create( name='子类目', parent_category=pcategory)[0] - category.save() + category.save() #sxc 保存子分类 + #sxc 创建基础标签(名称:标签) basetag = Tag() basetag.name = "标签" basetag.save() + #sxc 循环创建 19 篇测试文章(标题和内容带序号区分) for i in range(1, 20): + #sxc 创建或获取取文章(关联子分类、测试用户,标题和内容动态生成) article = Article.objects.get_or_create( category=category, - title='nice title ' + str(i), - body='nice content ' + str(i), + title='nice title ' + str(i), #sxc 文章标题:nice title 1~19 + body='nice content ' + str(i), #sxc 文章内容:nice content 1~19 author=user)[0] + #sxc 为每篇文章创建专属标签(名称:标签 1~19) tag = Tag() tag.name = "标签" + str(i) tag.save() + #sxc 为文章关联专属标签和基础标签 article.tags.add(tag) article.tags.add(basetag) article.save() - +#sxc 清理系统缓存,确保新生成的测试数据能被及时加载 from djangoblog.utils import cache cache.clear() + #sxc 输出操作成功的提示信息(绿色成功标识) self.stdout.write(self.style.SUCCESS('created test datas \n')) diff --git a/src/DjangoBlog-master/blog/management/commands/ping_baidu.py b/src/DjangoBlog-master/blog/management/commands/ping_baidu.py index 2c7fbdd6..09167bb0 100644 --- a/src/DjangoBlog-master/blog/management/commands/ping_baidu.py +++ b/src/DjangoBlog-master/blog/management/commands/ping_baidu.py @@ -1,50 +1,59 @@ +#sxc 该模块定义了 Django 自定义管理命令,用于向百度搜索引擎推送博客系统的 URL(支持文章、标签、分类及全量 URL),助力搜索引擎快速收录内容,提升博客内容的检索曝光率。 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 - +#sxc 获取当前站点的域名(如example.com),用于拼接完整 URL site = get_current_site().domain - class Command(BaseCommand): - help = 'notify baidu url' +#sxc 命令帮助信息,执行python manage.py help ping_baidu时显示 + help = 'notify baidu url' #sxc 命令功能描述:向百度推送 URL def add_arguments(self, parser): + #sxc 为命令添加参数,指定需推送的 URL 类型,限制可选值避免非法输入 parser.add_argument( - 'data_type', + 'data_type', #sxc 参数名:数据类型(需推送的 URL 类别) type=str, choices=[ - 'all', - 'article', - 'tag', - 'category'], - help='article : all article,tag : all tag,category: all category,all: All of these') + 'all', #sxc 选项 1:推送所有类型 URL(文章 + 标签 + 分类) + 'article',#sxc 选项 2:仅推送文章 URL + 'tag',#sxc 选项 3:仅推送标签 URL + 'category'], #sxc 选项 4:仅推送分类 URL + help='article : all article,tag : all tag,category: all category,all: All of these') #sxc 参数说明 def get_full_url(self, path): + #sxc 拼接完整 URL:将相对路径(如 /article/1/)与站点域名组合为绝对 URL 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) + #sxc 命令核心执行逻辑:根据参数筛选 URL → 拼接完整 URL → 推送百度 + type = options['data_type'] #sxc 获取用户指定的 URL 推送类型 + self.stdout.write('start get %s' % type) #sxc 输出日志:开始获取指定类型的 URL - urls = [] + urls = [] #sxc 存储待推送的完整 URL 列表 + #sxc 1. 筛选文章 URL(仅状态为 “已发布(p)” 的文章) if type == 'article' or type == 'all': for article in Article.objects.filter(status='p'): - urls.append(article.get_full_url()) + urls.append(article.get_full_url())#sxc 文章模型自带完整 URL 方法,直接获取 + #sxc 2. 筛选标签 URL(通过相对路径拼接完整 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)) + url = tag.get_absolute_url() #sxc 获取标签的相对路径(如 /tag/python/) + urls.append(self.get_full_url(url)) #sxc 拼接为完整 URL 并添加到列表 + #sxc 3. 筛选分类 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)) - + url = category.get_absolute_url() #sxc 获取分类的相对路径(如 /category/tech/) + urls.append(self.get_full_url(url)) #sxc 拼接为完整 URL 并添加到列表 +#sxc 输出日志:展示待推送的 URL 数量 self.stdout.write( self.style.SUCCESS( 'start notify %d urls' % len(urls))) + #sxc 调用爬虫通知工具类,向百度推送筛选后的 URL 列表 SpiderNotify.baidu_notify(urls) + #sxc 输出成功日志:推送完成 self.stdout.write(self.style.SUCCESS('finish notify')) diff --git a/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py b/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py index d0f46127..f57045c1 100644 --- a/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py +++ b/src/DjangoBlog-master/blog/management/commands/sync_user_avatar.py @@ -1,3 +1,4 @@ +#sxc 该模块定义了 Django 自定义管理命令,用于同步第三方登录用户(OAuthUser)的头像图片。通过检测头像 URL 有效性,自动更新失效头像,支持从第三方平台重新获取或使用默认头像,确保用户头像展示正常。 import requests from django.core.management.base import BaseCommand from django.templatetags.static import static @@ -8,16 +9,20 @@ from oauth.oauthmanager import get_manager_by_type class Command(BaseCommand): - help = 'sync user avatar' + #sxc 命令帮助信息,执行python manage.py help sync_user_avatar时显示 + help = 'sync user avatar' #sxc 命令功能描述:同步用户头像 def test_picture(self, url): + #sxc 检测头像 URL 是否有效(状态码 200) try: + #sxc 发送 GET 请求检测 URL,超时时间 2 秒 if requests.get(url, timeout=2).status_code == 200: return True except: - pass + pass #sxc 捕获请求异常(如超时、连接失败等),视为无效 def handle(self, *args, **options): + #sxc 命令核心执行逻辑:遍历所有第三方用户,检测并同步头像 static_url = static("../") users = OAuthUser.objects.all() self.stdout.write(f'开始同步{len(users)}个用户头像') @@ -25,23 +30,29 @@ class Command(BaseCommand): self.stdout.write(f'开始同步:{u.nickname}') url = u.picture if url: + #sxc 判断头像是否为本地静态文件 if url.startswith(static_url): + #sxc 检测本地头像 URL 有效性 if self.test_picture(url): continue else: if u.metadata: + #sxc 获取对应平台的 OAuth 管理器(如 QQ、GitHub 等) manage = get_manager_by_type(u.type) + #sxc 从元数据中重新获取头像 URL url = manage.get_picture(u.metadata) + #sxc 下载并保存头像,返回本地存储 URL url = save_user_avatar(url) - else: + else: #sxc 无元数据时使用默认头像 url = static('blog/img/avatar.png') else: url = save_user_avatar(url) else: url = static('blog/img/avatar.png') + #sxc 更新用户头像并保存 if url: self.stdout.write( - f'结束同步:{u.nickname}.url:{url}') + f'结束同步:{u.nickname}.url:{url}')#sxc 输出同步结果 u.picture = url - u.save() - self.stdout.write('结束同步') + u.save() #sxc 保存用户头像更新 + self.stdout.write('结束同步') #sxc 输出同步完成日志 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/middleware.py b/src/DjangoBlog-master/blog/middleware.py index 94dd70c9..17130ffe 100644 --- a/src/DjangoBlog-master/blog/middleware.py +++ b/src/DjangoBlog-master/blog/middleware.py @@ -1,42 +1,53 @@ +#sxc 该模块是 Django 博客系统的自定义中间件,核心功能是监控页面渲染耗时、收集访问用户的 IP 和设备信息,并在启用 Elasticsearch 时将这些性能数据写入日志索引,同时支持在页面中替换渲染耗时占位符,实现性能监控和数据统计 import logging import time +from ipware import get_client_ip#sxc 导入 IP 获取工具,用于获取访问者真实 IP +from user_agents import parse #sxc 导入用户代理解析工具,用于识别设备 / 浏览器信息 +from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager #sxc 导入 Elasticsearch 相关配置和管理器 -from ipware import get_client_ip -from user_agents import parse - -from blog.documents import ELASTICSEARCH_ENABLED, ElaspedTimeDocumentManager - -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #sxc 初始化日志记录器,用于记录中间件运行中的错误 class OnlineMiddleware(object): + #sxc 自定义中间件类:用于监控页面性能、收集访问数据 def __init__(self, get_response=None): + #sxc 中间件初始化方法:接收 Django 的 get_response 回调函数,用于传递请求 self.get_response = get_response super().__init__() def __call__(self, request): ''' page render time ''' + #sxc 中间件核心方法:处理请求和响应,计算页面渲染耗时并收集数据,记录请求开始时间(用于计算渲染耗时) start_time = time.time() + #sxc 传递请求到下一个中间件或视图,获取响应对象 response = self.get_response(request) + #sxc 提取请求中的用户代理字符串(用于解析设备信息) http_user_agent = request.META.get('HTTP_USER_AGENT', '') + #sxc 获取访问者的 IP 地址(ipware 工具自动处理代理 IP 场景) ip, _ = get_client_ip(request) + #sxc 非流式响应(如 HTML 页面)才处理性能数据(流式响应如文件下载无需处理) user_agent = parse(http_user_agent) if not response.streaming: try: + #sxc 计算页面渲染总耗时(当前时间 - 请求开始时间) cast_time = time.time() - start_time + #sxc 若启用 Elasticsearch,将性能数据写入 "performance" 索引 if ELASTICSEARCH_ENABLED: time_taken = round((cast_time) * 1000, 2) url = request.path from django.utils import timezone + #sxc 调用管理器创建性能日志文档,包含 URL、耗时、时间、设备、IP 信息 ElaspedTimeDocumentManager.create( url=url, time_taken=time_taken, log_datetime=timezone.now(), useragent=user_agent, ip=ip) + #sxc 替换页面中的 "" 占位符为实际渲染耗时(保留前 5 位字符) response.content = response.content.replace( b'', str.encode(str(cast_time)[:5])) + #sxc 捕获处理过程中的异常,记录错误日志(避免中间件报错影响页面正常响应) except Exception as e: logger.error("Error OnlineMiddleware: %s" % e) - +#sxc 返回处理后的响应对象 return response diff --git a/src/DjangoBlog-master/blog/migrations/0001_initial.py b/src/DjangoBlog-master/blog/migrations/0001_initial.py index 3d391b62..003b99b5 100644 --- a/src/DjangoBlog-master/blog/migrations/0001_initial.py +++ b/src/DjangoBlog-master/blog/migrations/0001_initial.py @@ -1,3 +1,4 @@ +#sxc 该模块是 Django 的数据库迁移文件,用于定义博客系统的数据模型结构及迁移操作 # Generated by Django 4.1.7 on 2023-03-02 07:14 from django.conf import settings @@ -8,32 +9,48 @@ import mdeditor.fields class Migration(migrations.Migration): - +#sxc 标记这是初始迁移 initial = True - +#sxc 依赖项,依赖于 Django 的用户模型 dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] - +#sxc 迁移操作列表,定义了所有模型的创建操作 operations = [ migrations.CreateModel( name='BlogSettings', fields=[ + #sxc 自增主键 ID ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + #sxc 网站名称字段,默认空字符串,最大长度 200 ('sitename', models.CharField(default='', max_length=200, verbose_name='网站名称')), + #sxc 网站描述字段,默认空字符串,最大长度 1000 ('site_description', models.TextField(default='', max_length=1000, verbose_name='网站描述')), + #sxc 网站 SEO 描述字段,用于搜索引擎优化 ('site_seo_description', models.TextField(default='', max_length=1000, verbose_name='网站SEO描述')), + #sxc 网站关键字字段,用于搜索引擎优化 ('site_keywords', models.TextField(default='', max_length=1000, verbose_name='网站关键字')), + #sxc 文章摘要长度设置,默认 300 ('article_sub_length', models.IntegerField(default=300, verbose_name='文章摘要长度')), + #sxc 侧边栏文章数目设置,默认 10 ('sidebar_article_count', models.IntegerField(default=10, verbose_name='侧边栏文章数目')), + #sxc 侧边栏评论数目设置,默认 5 ('sidebar_comment_count', models.IntegerField(default=5, verbose_name='侧边栏评论数目')), + #sxc 文章页面默认显示评论数目,默认 5 ('article_comment_count', models.IntegerField(default=5, verbose_name='文章页面默认显示评论数目')), + #sxc 是否显示谷歌广告的布尔值,默认不显示 ('show_google_adsense', models.BooleanField(default=False, verbose_name='是否显示谷歌广告')), + #sxc 谷歌广告代码内容,可为空 ('google_adsense_codes', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='广告内容')), + #sxc 是否打开网站评论功能,默认打开 ('open_site_comment', models.BooleanField(default=True, verbose_name='是否打开网站评论功能')), + #sxc 网站备案号,可为空 ('beiancode', models.CharField(blank=True, default='', max_length=2000, null=True, verbose_name='备案号')), + #sxc 网站统计代码 ('analyticscode', models.TextField(default='', max_length=1000, verbose_name='网站统计代码')), + #sxc 是否显示公安备案号,默认不显示 ('show_gongan_code', models.BooleanField(default=False, verbose_name='是否显示公安备案号')), + #sxc 公安备案号内容,可为空 ('gongan_beiancode', models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='公安备案号')), ], options={ @@ -41,16 +58,24 @@ class Migration(migrations.Migration): 'verbose_name_plural': '网站配置', }, ), + #sxc 创建友情链接模型 migrations.CreateModel( name='Links', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + #sxc 链接名称,唯一 ('name', models.CharField(max_length=30, unique=True, verbose_name='链接名称')), + #sxc 链接地址,URL 类型 ('link', models.URLField(verbose_name='链接地址')), + #sxc 排序序号,唯一 ('sequence', models.IntegerField(unique=True, verbose_name='排序')), + #sxc 是否显示,默认显示 ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), + #sxc 显示类型,可选首页、列表页、文章页面、全站、友情链接页面 ('show_type', models.CharField(choices=[('i', '首页'), ('l', '列表页'), ('p', '文章页面'), ('a', '全站'), ('s', '友情链接页面')], default='i', max_length=1, verbose_name='显示类型')), + #sxc 创建时间,默认当前时间 ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), + #sxc 最后修改时间,默认当前时间 ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), ], options={ @@ -59,15 +84,20 @@ class Migration(migrations.Migration): 'ordering': ['sequence'], }, ), + #sxc 创建侧边栏模型 migrations.CreateModel( name='SideBar', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + #sxc 侧边栏标题 ('name', models.CharField(max_length=100, verbose_name='标题')), + #sxc 侧边栏内容 ('content', models.TextField(verbose_name='内容')), + #sxc 排序序号,唯一 ('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='创建时间')), + #sxc 是否启用,默认启用 + ('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={ @@ -76,13 +106,16 @@ class Migration(migrations.Migration): 'ordering': ['sequence'], }, ), + #sxc 创建标签模型 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='修改时间')), + #sxc 标签名称,唯一 ('name', models.CharField(max_length=30, unique=True, verbose_name='标签名')), + #sxc URL 友好的标识符,默认 'no-slug' ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), ], options={ @@ -91,15 +124,19 @@ class Migration(migrations.Migration): 'ordering': ['name'], }, ), + #sxc 创建分类模型 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='修改时间')), + #sxc 分类名称,唯一 ('name', models.CharField(max_length=30, unique=True, verbose_name='分类名')), ('slug', models.SlugField(blank=True, default='no-slug', max_length=60)), + #sxc 权重排序,数字越大越靠前,默认 0 ('index', models.IntegerField(default=0, verbose_name='权重排序-越大越靠前')), + #sxc 父级分类,自引用外键,可为空 ('parent_category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='父级分类')), ], options={ @@ -108,28 +145,42 @@ class Migration(migrations.Migration): 'ordering': ['-index'], }, ), + #sxc 创建文章模型 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='修改时间')), + #sxc 文章标题,唯一 ('title', models.CharField(max_length=200, unique=True, verbose_name='标题')), + #sxc 文章正文,使用 markdown 编辑器字段 ('body', mdeditor.fields.MDTextField(verbose_name='正文')), + #sxc 发布时间,默认当前时间 ('pub_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='发布时间')), + #sxc 文章状态,可选草稿或发表,默认发表 ('status', models.CharField(choices=[('d', '草稿'), ('p', '发表')], default='p', max_length=1, verbose_name='文章状态')), + #sxc 评论状态,可选打开或关闭,默认打开 ('comment_status', models.CharField(choices=[('o', '打开'), ('c', '关闭')], default='o', max_length=1, verbose_name='评论状态')), + #sxc 类型,可选文章或页面,默认文章 ('type', models.CharField(choices=[('a', '文章'), ('p', '页面')], default='a', max_length=1, verbose_name='类型')), + #sxc 浏览量,正整数,默认 0 ('views', models.PositiveIntegerField(default=0, verbose_name='浏览量')), + #sxc 排序,数字越大越靠前,默认 0 ('article_order', models.IntegerField(default=0, verbose_name='排序,数字越大越靠前')), + #sxc 是否显示 toc 目录,默认不显示 ('show_toc', models.BooleanField(default=False, verbose_name='是否显示toc目录')), + #sxc 外键关联到用户模型,作者 ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), + #sxc 外键关联到分类模型 ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='分类')), + #sxc 多对多关联到标签模型 ('tags', models.ManyToManyField(blank=True, to='blog.tag', verbose_name='标签集合')), ], options={ 'verbose_name': '文章', 'verbose_name_plural': '文章', + #sxc 先按排序降序,再按发布时间降序 'ordering': ['-article_order', '-pub_time'], 'get_latest_by': 'id', }, diff --git a/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py b/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py index adbaa36b..3386bfa2 100644 --- a/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py +++ b/src/DjangoBlog-master/blog/migrations/0002_blogsettings_global_footer_and_more.py @@ -1,23 +1,28 @@ +#sxc 该模块是 Django 的数据库迁移文件,用于对博客系统的网站配置模型进行扩展,添加公共头部和公共尾部字段 # Generated by Django 4.1.7 on 2023-03-29 06:08 from django.db import migrations, models class Migration(migrations.Migration): - +#sxc 迁移依赖项,依赖于 blog 应用的初始迁移 (0001_initial) dependencies = [ ('blog', '0001_initial'), ] - +#sxc 迁移操作列表,定义了为 BlogSettings 模型添加字段的操作 operations = [ + #sxc 为 BlogSettings 模型添加公共尾部字段 migrations.AddField( - model_name='blogsettings', - name='global_footer', + model_name='blogsettings',#sxc 为 BlogSettings 模型添加公共尾部字段 + name='global_footer',#sxc 新增字段名称 + #sxc 字段类型为文本字段,可为空,默认值为空字符串,verbose_name 为 "公共尾部" field=models.TextField(blank=True, default='', null=True, verbose_name='公共尾部'), ), + #sxc 为 BlogSettings 模型添加公共头部字段 migrations.AddField( - model_name='blogsettings', - name='global_header', + model_name='blogsettings',#sxc 要修改的模型名称 + name='global_header', #sxc 新增字段名称 + #sxc 字段类型为文本字段,可为空,默认值为空字符串,verbose_name 为 "公共头部" field=models.TextField(blank=True, default='', null=True, verbose_name='公共头部'), ), ] diff --git a/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py b/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py index e9f55024..177a78ea 100644 --- a/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py +++ b/src/DjangoBlog-master/blog/migrations/0003_blogsettings_comment_need_review.py @@ -1,17 +1,21 @@ +#sxc 该模块是 Django 的数据库迁移文件,用于在博客系统的网站配置模型中添加评论审核审核设置字段 # Generated by Django 4.2.1 on 2023-05-09 07:45 from django.db import migrations, models class Migration(migrations.Migration): + #sxc 迁移依赖项,依赖于 blog 应用的上一次迁移 (0002_blogsettings_global_footer_and_more) dependencies = [ ('blog', '0002_blogsettings_global_footer_and_more'), ] - +#sxc 迁移操作列表,定义了为 BlogSettings 模型添加评论审核字段的操作 operations = [ + #sxc 为 BlogSettingsSettings 模型添加评论是否需要审核的字段 migrations.AddField( - model_name='blogsettings', - name='comment_need_review', + model_name='blogsettings', #sxc 要修改的模型名称 + name='comment_need_review',#sxc 新增字段名称 + #sxc 字段类型为布尔值,默认值为 False (不需要审核),verbose_name 为 "评论是否需要审核" field=models.BooleanField(default=False, verbose_name='评论是否需要审核'), ), ] diff --git a/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py b/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py index ceb13982..05df0977 100644 --- a/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py +++ b/src/DjangoBlog-master/blog/migrations/0004_rename_analyticscode_blogsettings_analytics_code_and_more.py @@ -1,24 +1,29 @@ +#sxc 该模块是 Django 的数据库迁移文件,用于对博客系统的 BlogSettings 模型中的部分字段进行重命名,统一字段命名风格为下划线命名法 # Generated by Django 4.2.1 on 2023-05-09 07:51 from django.db import migrations class Migration(migrations.Migration): + #sxc 迁移依赖项,依赖于 blog 应用的上一次迁移 (0003_blogsettings_comment_need_review) dependencies = [ ('blog', '0003_blogsettings_comment_need_review'), ] - +#sxc 迁移操作列表,定义了对 BlogSettings 模型字段的重命名操作 operations = [ + #sxc 将 analyticscode 字段重命名为 analytics_code migrations.RenameField( - model_name='blogsettings', - old_name='analyticscode', - new_name='analytics_code', + model_name='blogsettings',#sxc 要修改的模型名称 + old_name='analyticscode',#sxc 原字段名称 + new_name='analytics_code',#sxc 新字段名称 ), + #sxc 将 beiancode 字段重命名为 beian_code migrations.RenameField( model_name='blogsettings', old_name='beiancode', new_name='beian_code', ), + #sxc 将 sitename 字段重命名为 site_name migrations.RenameField( model_name='blogsettings', old_name='sitename', diff --git a/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py b/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py index d08e8534..c19c4940 100644 --- a/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py +++ b/src/DjangoBlog-master/blog/migrations/0005_alter_article_options_alter_category_options_and_more.py @@ -1,3 +1,4 @@ +#sxc 该模块是 Django 的数据库迁移文件,用于对博客系统的多个模型进行批量修改,包括更新模型选项、字段重命名、字段属性调整以及统一将 verbose_name 从中文改为英文 # Generated by Django 4.2.5 on 2023-09-06 13:13 from django.conf import settings @@ -8,290 +9,349 @@ import mdeditor.fields class Migration(migrations.Migration): - +#sxc 迁移依赖项,依赖于用户模型和 blog 应用的上一次迁移 (0004_...) dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('blog', '0004_rename_analyticscode_blogsettings_analytics_code_and_more'), ] - +#sxc 迁移操作列表,包含模型选项修改、字段移除、新增和属性修改等操作 operations = [ + #sxc 修改 Article 模型的元选项,将 verbose_name 改为英文 migrations.AlterModelOptions( name='article', options={'get_latest_by': 'id', 'ordering': ['-article_order', '-pub_time'], 'verbose_name': 'article', 'verbose_name_plural': 'article'}, ), + #sxc 修改 Category 模型的元选项,将 verbose_name 改为英文 migrations.AlterModelOptions( name='category', options={'ordering': ['-index'], 'verbose_name': 'category', 'verbose_name_plural': 'category'}, ), + #sxc 修改 Links 模型的元选项,将 verbose_name 改为英文 migrations.AlterModelOptions( name='links', options={'ordering': ['sequence'], 'verbose_name': 'link', 'verbose_name_plural': 'link'}, ), + #sxc 修改 SideBar 模型的元选项,将 verbose_name 改为英文 migrations.AlterModelOptions( name='sidebar', options={'ordering': ['sequence'], 'verbose_name': 'sidebar', 'verbose_name_plural': 'sidebar'}, ), + #sxc 修改 Tag 模型的元选项,将 verbose_name 改为英文 migrations.AlterModelOptions( name='tag', options={'ordering': ['name'], 'verbose_name': 'tag', 'verbose_name_plural': 'tag'}, ), + #sxc 移除 Article 模型中的 created_time 字段 migrations.RemoveField( model_name='article', name='created_time', ), + #sxc 移除 Article 模型中的 last_mod_time 字段 migrations.RemoveField( model_name='article', name='last_mod_time', ), + #sxc 移除 Category 模型中的 created_time 字段 migrations.RemoveField( model_name='category', name='created_time', ), + #sxc 移除 Category 模型中的 last_mod_time 字段 migrations.RemoveField( model_name='category', name='last_mod_time', ), + #sxc 移除 Links 模型中的 created_time 字段 migrations.RemoveField( model_name='links', name='created_time', ), + #sxc 移除 SideBar 模型中的 created_time 字段 migrations.RemoveField( model_name='sidebar', name='created_time', ), + #sxc 移除 Tag 模型中的 created_time 字段 migrations.RemoveField( model_name='tag', name='created_time', ), + #sxc 移除 Tag 模型中的 last_mod_time 字段 migrations.RemoveField( model_name='tag', name='last_mod_time', ), + #sxc 为 Article 模型添加 creation_time 字段 (替代原 created_time) migrations.AddField( model_name='article', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), ), + #sxc 为 Article 模型添加 last_modify_time 字段 (替代原 last_mod_time) migrations.AddField( model_name='article', name='last_modify_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), ), + #sxc 为 Category 模型添加 creation_time 字段 (替代原 created_time) migrations.AddField( model_name='category', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), ), + #sxc 为 Category 模型添加 last_modify_time 字段 (替代原 last_mod_time) migrations.AddField( model_name='category', name='last_modify_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), ), + #sxc 为 Links 模型添加 creation_time 字段 (替代原 created_time) migrations.AddField( model_name='links', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), ), + #sxc 为 SideBar 模型添加 creation_time 字段 (替代原 created_time) migrations.AddField( model_name='sidebar', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), ), + #sxc 为 Tag 模型添加 creation_time 字段 (替代原 created_time) migrations.AddField( model_name='tag', name='creation_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), ), + #sxc 为 Tag 模型添加 last_modify_time 字段 (替代原 last_mod_time) migrations.AddField( model_name='tag', name='last_modify_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), ), + #sxc 修改 Article 模型的 article_order 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='article_order', field=models.IntegerField(default=0, verbose_name='order'), ), + #sxc 修改 Article 模型的 author 字段,verbose_name 改为英文 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'), ), + #sxc 修改 Article 模型的 body 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='body', field=mdeditor.fields.MDTextField(verbose_name='body'), ), + #sxc 修改 Article 模型的 category 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='category', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category', verbose_name='category'), ), + #sxc 修改 Article 模型的 comment_status 字段,选项和 verbose_name 改为英文 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'), ), + #sxc 修改 Article 模型的 pub_time 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='pub_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='publish time'), ), + #sxc 修改 Article 模型的 show_toc 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='show_toc', field=models.BooleanField(default=False, verbose_name='show toc'), ), + #sxc 修改 Article 模型的 status 字段,选项和 verbose_name 改为英文 migrations.AlterField( model_name='article', name='status', field=models.CharField(choices=[('d', 'Draft'), ('p', 'Published')], default='p', max_length=1, verbose_name='status'), ), + #sxc 修改 Article 模型的 tags 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='tags', field=models.ManyToManyField(blank=True, to='blog.tag', verbose_name='tag'), ), + #sxc 修改 Article 模型的 title 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='title', field=models.CharField(max_length=200, unique=True, verbose_name='title'), ), + #sxc 修改 Article 模型的 type 字段,选项和 verbose_name 改为英文 migrations.AlterField( model_name='article', name='type', field=models.CharField(choices=[('a', 'Article'), ('p', 'Page')], default='a', max_length=1, verbose_name='type'), ), + #sxc 修改 Article 模型的 views 字段,verbose_name 改为英文 migrations.AlterField( model_name='article', name='views', field=models.PositiveIntegerField(default=0, verbose_name='views'), ), + #sxc 修改 BlogSettings 模型的 article_comment_count 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='article_comment_count', field=models.IntegerField(default=5, verbose_name='article comment count'), ), + #sxc 修改 BlogSettings 模型的 article_sub_length 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='article_sub_length', field=models.IntegerField(default=300, verbose_name='article sub length'), ), + #sxc 修改 BlogSettings 模型的 google_adsense_codes 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='google_adsense_codes', field=models.TextField(blank=True, default='', max_length=2000, null=True, verbose_name='adsense code'), ), + #sxc 修改 BlogSettings 模型的 open_site_comment 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='open_site_comment', field=models.BooleanField(default=True, verbose_name='open site comment'), ), + #sxc 修改 BlogSettings 模型的 show_google_adsense 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='show_google_adsense', field=models.BooleanField(default=False, verbose_name='show adsense'), ), + #sxc 修改 BlogSettings 模型的 sidebar_article_count 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='sidebar_article_count', field=models.IntegerField(default=10, verbose_name='sidebar article count'), ), + #sxc 修改 BlogSettings 模型的 sidebar_comment_count 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='sidebar_comment_count', field=models.IntegerField(default=5, verbose_name='sidebar comment count'), ), + #sxc 修改 BlogSettings 模型的 site_description 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='site_description', field=models.TextField(default='', max_length=1000, verbose_name='site description'), ), + #sxc 修改 BlogSettings 模型的 site_keywords 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='site_keywords', field=models.TextField(default='', max_length=1000, verbose_name='site keywords'), ), + #sxc 修改 BlogSettings 模型的 site_name 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='site_name', field=models.CharField(default='', max_length=200, verbose_name='site name'), ), + #sxc 修改 BlogSettings 模型的 site_seo_description 字段,verbose_name 改为英文 migrations.AlterField( model_name='blogsettings', name='site_seo_description', field=models.TextField(default='', max_length=1000, verbose_name='site seo description'), ), + #sxc 修改 Category 模型的 index 字段,verbose_name 改为英文 migrations.AlterField( model_name='category', name='index', field=models.IntegerField(default=0, verbose_name='index'), ), + #sxc 修改 Category 模型的 name 字段,verbose_name 改为英文 migrations.AlterField( model_name='category', name='name', field=models.CharField(max_length=30, unique=True, verbose_name='category name'), ), + #sxc 修改 Category 模型的 parent_category 字段,verbose_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'), ), + #sxc 修改 Links 模型的 is_enable 字段,verbose_name 改为英文 migrations.AlterField( model_name='links', name='is_enable', field=models.BooleanField(default=True, verbose_name='is show'), ), + #sxc 修改 Links 模型的 last_mod_time 字段,verbose_name 改为英文 migrations.AlterField( model_name='links', name='last_mod_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), ), + #sxc 修改 Links 模型的 link 字段,verbose_name 改为英文 migrations.AlterField( model_name='links', name='link', field=models.URLField(verbose_name='link'), ), + #sxc 修改 Links 模型的 name 字段,verbose_name 改为英文 migrations.AlterField( model_name='links', name='name', field=models.CharField(max_length=30, unique=True, verbose_name='link name'), ), + #sxc 修改 Links 模型的 sequence 字段,verbose_name 改为英文 migrations.AlterField( model_name='links', name='sequence', field=models.IntegerField(unique=True, verbose_name='order'), ), + #sxc 修改 Links 模型的 show_type 字段,选项和 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'), ), + #sxc 修改 SideBar 模型的 content 字段,verbose_name 改为英文 migrations.AlterField( model_name='sidebar', name='content', field=models.TextField(verbose_name='content'), ), + #sxc 修改 SideBar 模型的 is_enable 字段,verbose_name 改为英文 migrations.AlterField( model_name='sidebar', name='is_enable', field=models.BooleanField(default=True, verbose_name='is enable'), ), + #sxc 修改 SideBar 模型的 last_mod_time 字段,verbose_name 改为英文 migrations.AlterField( model_name='sidebar', name='last_mod_time', field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='modify time'), ), + #sxc 修改 SideBar 模型的 name 字段,verbose_name 改为英文 migrations.AlterField( model_name='sidebar', name='name', field=models.CharField(max_length=100, verbose_name='title'), ), + #sxc 修改 SideBar 模型的 sequence 字段,verbose_name 改为英文 migrations.AlterField( model_name='sidebar', name='sequence', field=models.IntegerField(unique=True, verbose_name='order'), ), + #sxc 修改 Tag 模型的 name 字段,verbose_name 改为英文 migrations.AlterField( model_name='tag', name='name', diff --git a/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py b/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py index e36feb4c..5faaf0ba 100644 --- a/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py +++ b/src/DjangoBlog-master/blog/migrations/0006_alter_blogsettings_options.py @@ -1,14 +1,15 @@ +#sxc 该模块是 Django 的数据库迁移文件,用于修改博客系统中 BlogSettings 模型的元选项,将其 verbose_name 从中文改为英文 # Generated by Django 4.2.7 on 2024-01-26 02:41 from django.db import migrations class Migration(migrations.Migration): - +#sxc 迁移依赖项,依赖于 blog 应用的上一次迁移 (0005_...) dependencies = [ ('blog', '0005_alter_article_options_alter_category_options_and_more'), ] - +#sxc 迁移操作列表,仅包含修改 BlogSettings 模型选项的操作 operations = [ migrations.AlterModelOptions( name='blogsettings', diff --git a/src/DjangoBlog-master/blog/migrations/__pycache__/0001_initial.cpython-312.pyc b/src/DjangoBlog-master/blog/migrations/__pycache__/0001_initial.cpython-312.pyc index 20ab1a6d..254db7ef 100644 Binary files a/src/DjangoBlog-master/blog/migrations/__pycache__/0001_initial.cpython-312.pyc and b/src/DjangoBlog-master/blog/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-312.pyc b/src/DjangoBlog-master/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-312.pyc index 2f1ec49c..03759f9c 100644 Binary files a/src/DjangoBlog-master/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-312.pyc and b/src/DjangoBlog-master/blog/migrations/__pycache__/0002_blogsettings_global_footer_and_more.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-312.pyc b/src/DjangoBlog-master/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-312.pyc index 4eb3f977..b54392f1 100644 Binary files a/src/DjangoBlog-master/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-312.pyc and b/src/DjangoBlog-master/blog/migrations/__pycache__/0003_blogsettings_comment_need_review.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-312.pyc b/src/DjangoBlog-master/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-312.pyc index 7b29e27a..49d24b95 100644 Binary files a/src/DjangoBlog-master/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-312.pyc and b/src/DjangoBlog-master/blog/migrations/__pycache__/0004_rename_analyticscode_blogsettings_analytics_code_and_more.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-312.pyc b/src/DjangoBlog-master/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-312.pyc index 31567ae7..8c4c4b90 100644 Binary files a/src/DjangoBlog-master/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-312.pyc and b/src/DjangoBlog-master/blog/migrations/__pycache__/0005_alter_article_options_alter_category_options_and_more.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-312.pyc b/src/DjangoBlog-master/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-312.pyc index f627c051..06a2a1ad 100644 Binary files a/src/DjangoBlog-master/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-312.pyc and b/src/DjangoBlog-master/blog/migrations/__pycache__/0006_alter_blogsettings_options.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/migrations/__pycache__/__init__.cpython-312.pyc b/src/DjangoBlog-master/blog/migrations/__pycache__/__init__.cpython-312.pyc index 8ab3c1a1..ac6227ad 100644 Binary files a/src/DjangoBlog-master/blog/migrations/__pycache__/__init__.cpython-312.pyc and b/src/DjangoBlog-master/blog/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/models.py b/src/DjangoBlog-master/blog/models.py index 083788bb..b87b5aa6 100644 --- a/src/DjangoBlog-master/blog/models.py +++ b/src/DjangoBlog-master/blog/models.py @@ -1,3 +1,4 @@ +#sxc 该模块是 Django 博客系统的核心数据模型定义模块,包含文章、分类、标签、友情链接、侧边栏、网站配置 6 大核心模型,以及基础抽象模型和链接显示类型枚举,同时集成缓存、URL 生成、数据验证等功能,支撑博客的内容管理和系统配置 import logging import re from abc import abstractmethod @@ -8,35 +9,41 @@ 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 mdeditor.fields import MDTextField #sxc 导入 Markdown 编辑器字段,用于文章正文 +from uuslug import slugify #sxc 导入 slug 生成工具,用于生成 URL 友好的标识 -from djangoblog.utils import cache_decorator, cache -from djangoblog.utils import get_current_site +from djangoblog.utils import cache_decorator, cache #sxc 导入缓存工具,优化数据查询性能 +from djangoblog.utils import get_current_site #sxc 导入获取当前站点信息的工具 -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__) #sxc 初始化日志记录器,用于记录模型操作相关日志 class LinkShowType(models.TextChoices): - I = ('i', _('index')) - L = ('l', _('list')) - P = ('p', _('post')) - A = ('a', _('all')) - S = ('s', _('slide')) + #sxc 枚举类:定义友情链接的显示位置选项,支持国际化 + I = ('i', _('index'))#sxc 首页显示 + L = ('l', _('list'))#sxc 列表页显示 + P = ('p', _('post'))#sxc 文章页显示 + A = ('a', _('all'))#sxc 全站显示 + S = ('s', _('slide'))#sxc 幻灯片区域显示 class BaseModel(models.Model): - id = models.AutoField(primary_key=True) + #sxc 抽象基础模型:为所有业务模型提供通用字段和方法,避免代码 + id = models.AutoField(primary_key=True) #sxc 自增主键重复 creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('modify time'), default=now) def save(self, *args, **kwargs): + #sxc 重写保存方法:优化文章浏览量更新逻辑,自动生成 slug 字段 + #sxc 判断是否为文章模型的浏览量更新操作(仅更新 views 字段) is_update_views = isinstance( self, Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] if is_update_views: + #sxc 浏览量更新:直接通过 QuerySet 更新,避免触发完整 save 流程 Article.objects.filter(pk=self.pk).update(views=self.views) else: + #sxc 非浏览量更新:若模型有 slug 字段,自动生成 URL 友好的 slug(基于 title 或 name 字段) if 'slug' in self.__dict__: slug = getattr( self, 'title') if 'title' in self.__dict__ else getattr( @@ -55,19 +62,23 @@ class BaseModel(models.Model): @abstractmethod def get_absolute_url(self): + #sxc 抽象方法:要求子类实现获取自身相对 URL 的逻辑,用于路由跳转 pass class Article(BaseModel): """文章""" + #sxc 文章状态选项:草稿(d)、已发布(p) STATUS_CHOICES = ( ('d', _('Draft')), ('p', _('Published')), ) + #sxc 评论状态选项:打开(o)、关闭(c) COMMENT_STATUS = ( ('o', _('Open')), ('c', _('Close')), ) + #sxc 文章类型选项:文章(a)、独立页面(p,如关于页、联系页) TYPE = ( ('a', _('Article')), ('p', _('Page')), @@ -109,6 +120,7 @@ class Article(BaseModel): return self.body def __str__(self): + #sxc 模型实例的字符串表示,返回文章标题 return self.title class Meta: @@ -118,6 +130,7 @@ class Article(BaseModel): get_latest_by = 'id' def get_absolute_url(self): + #sxc 生成文章的相对 URL,用于路由跳转(包含发布日期和文章 ID) return reverse('blog:detailbyid', kwargs={ 'article_id': self.id, 'year': self.creation_time.year, @@ -129,41 +142,45 @@ class Article(BaseModel): def get_category_tree(self): tree = self.category.get_category_tree() names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) - return names def save(self, *args, **kwargs): + #sxc 重写 save 方法:可扩展文章保存时的自定义逻辑(当前仅调用父类方法) super().save(*args, **kwargs) def viewed(self): + #sxc 文章被浏览时调用,增加浏览量并保存(仅更新 views 字段) self.views += 1 self.save(update_fields=['views']) def comment_list(self): + #sxc 获取文章的有效评论列表,结果缓存 100 分钟,减少数据库查询 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: + #sxc 查询已启用的评论,按 ID 降序(最新评论在前) 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): + #sxc 生成文章在 Django Admin 后台的编辑 URL,用于快速跳转管理 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): - # 下一篇 + #sxc 获取当前文章的下一篇(ID 更大、已发布),结果缓存 100 分钟 return Article.objects.filter( id__gt=self.id, status='p').order_by('id').first() @cache_decorator(expiration=60 * 100) def prev_article(self): - # 前一篇 + #sxc 获取当前文章的前一篇(ID 更小、已发布),结果缓存 100 分钟 return Article.objects.filter(id__lt=self.id, status='p').first() def get_first_image_url(self): @@ -178,28 +195,31 @@ class Article(BaseModel): class Category(BaseModel): + #sxc 文章分类模型,支持层级结构(父子分类),用于组织文章归属 """文章分类""" - name = models.CharField(_('category name'), max_length=30, unique=True) + name = models.CharField(_('category name'), max_length=30, unique=True)#sxc 分类名称,唯一不可重复 parent_category = models.ForeignKey( 'self', verbose_name=_('parent category'), blank=True, null=True, - on_delete=models.CASCADE) + on_delete=models.CASCADE)#sxc 父分类,自关联,支持层级;父分类删除时子分类也删除 slug = models.SlugField(default='no-slug', max_length=60, blank=True) - index = models.IntegerField(default=0, verbose_name=_('index')) + index = models.IntegerField(default=0, verbose_name=_('index')) #sxc 排序权重,值越大越靠前 class Meta: - ordering = ['-index'] + ordering = ['-index'] #sxc 默认按权重降序排列 verbose_name = _('category') verbose_name_plural = verbose_name def get_absolute_url(self): + #sxc 生成分类详情页的 URL,基于 slug 参数 return reverse( 'blog:category_detail', kwargs={ 'category_name': self.slug}) def __str__(self): + #sxc 模型实例的字符串表示,返回分类名称 return self.name @cache_decorator(60 * 60 * 10) @@ -211,6 +231,7 @@ class Category(BaseModel): categorys = [] def parse(category): + #sxc 递归函数:将当前分类加入列表,若有父分类则继续解析 categorys.append(category) if category.parent_category: parse(category.parent_category) @@ -225,9 +246,10 @@ class Category(BaseModel): :return: """ categorys = [] - all_categorys = Category.objects.all() + all_categorys = Category.objects.all() #sxc 预加载所有分类,减少数据库查询 def parse(category): + #sxc 递归函数:将当前分类加入列表,再解析其所有子分类 if category not in categorys: categorys.append(category) childs = all_categorys.filter(parent_category=category) @@ -241,18 +263,22 @@ class Category(BaseModel): class Tag(BaseModel): + #sxc 文章标签模型,用于文章的多维度标记和快速检索 """文章标签""" name = models.CharField(_('tag name'), max_length=30, unique=True) slug = models.SlugField(default='no-slug', max_length=60, blank=True) def __str__(self): + #sxc 模型实例的字符串表示,返回标签名称 return self.name def get_absolute_url(self): + #sxc 生成标签详情页的 URL,基于 slug 参数 return reverse('blog:tag_detail', kwargs={'tag_name': self.slug}) @cache_decorator(60 * 60 * 10) def get_article_count(self): + #sxc 获取关联当前标签的文章数量(去重),结果缓存 10 小时 return Article.objects.filter(tags__name=self.name).distinct().count() class Meta: @@ -262,6 +288,7 @@ class Tag(BaseModel): class Links(models.Model): + #sxc 友情链接模型,用于管理博客外部链接的展示 """友情链接""" name = models.CharField(_('link name'), max_length=30, unique=True) @@ -278,15 +305,17 @@ class Links(models.Model): last_mod_time = models.DateTimeField(_('modify time'), default=now) class Meta: - ordering = ['sequence'] + ordering = ['sequence']#sxc 默认按排序序号升序排列 verbose_name = _('link') verbose_name_plural = verbose_name def __str__(self): + #sxc 模型实例的字符串表示,返回链接名称 return self.name class SideBar(models.Model): + #sxc 侧边栏模型,用于展示自定义 HTML 内容,增强页面灵活性 """侧边栏,可以展示一些html内容""" name = models.CharField(_('title'), max_length=100) content = models.TextField(_('content')) @@ -367,10 +396,12 @@ class BlogSettings(models.Model): 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): + # 调用父类的save方法保存对象 super().save(*args, **kwargs) from djangoblog.utils import cache cache.clear() diff --git a/src/DjangoBlog-master/blog/search_indexes.py b/src/DjangoBlog-master/blog/search_indexes.py index 7f1dfac1..5e1bc1cd 100644 --- a/src/DjangoBlog-master/blog/search_indexes.py +++ b/src/DjangoBlog-master/blog/search_indexes.py @@ -1,13 +1,19 @@ -from haystack import indexes +#sxc 该模块是 Django 博客系统基于 Haystack 搜索框架的索引配置模块,用于定义文章模型的搜索索引结构,指定搜索字段和索引范围,为博客提供全文搜索功能的基础数据支持 +from haystack import indexes #sxc 导入 Haystack 框架的索引类,用于定义搜索索引 -from blog.models import Article +from blog.models import Article #sxc 导入博客文章模型,作为索引的数据源 class ArticleIndex(indexes.SearchIndex, indexes.Indexable): + #sxc 文章搜索索引类,继承 Haystack 的搜索索引和索引源接口 + #sxc 定义主搜索字段 text,document=True 表示该字段为主要搜索字段,use_template=True 表示使用模板定义字段内容 text = indexes.CharField(document=True, use_template=True) def get_model(self): + #sxc 实现 Indexable 接口的方法:指定索引关联的模型为 Article return Article def index_queryset(self, using=None): + #sxc 实现 SearchIndex 的方法:定义需要建立索引的数据集 + #sxc 仅对状态为 “已发布”(status='p')的文章建立索引,确保搜索结果为公开内容 return self.get_model().objects.filter(status='p') diff --git a/src/DjangoBlog-master/blog/templatetags/__pycache__/__init__.cpython-312.pyc b/src/DjangoBlog-master/blog/templatetags/__pycache__/__init__.cpython-312.pyc index bf8b10c6..658272c3 100644 Binary files a/src/DjangoBlog-master/blog/templatetags/__pycache__/__init__.cpython-312.pyc and b/src/DjangoBlog-master/blog/templatetags/__pycache__/__init__.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/templatetags/__pycache__/blog_tags.cpython-312.pyc b/src/DjangoBlog-master/blog/templatetags/__pycache__/blog_tags.cpython-312.pyc index 6dc320ad..7f6924fc 100644 Binary files a/src/DjangoBlog-master/blog/templatetags/__pycache__/blog_tags.cpython-312.pyc and b/src/DjangoBlog-master/blog/templatetags/__pycache__/blog_tags.cpython-312.pyc differ diff --git a/src/DjangoBlog-master/blog/templatetags/blog_tags.py b/src/DjangoBlog-master/blog/templatetags/blog_tags.py index d6cd5d5a..afddf98b 100644 --- a/src/DjangoBlog-master/blog/templatetags/blog_tags.py +++ b/src/DjangoBlog-master/blog/templatetags/blog_tags.py @@ -1,3 +1,5 @@ +#sxc 该模块是 Django 博客系统的自定义模板标签库,提供了格式化显示、内容处理、侧边栏加载、面包屑生成等 16 个实用模板标签 / 过滤器,用于在前端模板中复用核心业务逻辑 +#sxc 依赖 Django 模板系统、博客数据模型、缓存工具及第三方头像服务,同时集成插件钩子机制扩展功能 import hashlib import logging import random @@ -21,17 +23,21 @@ from oauth.models import OAuthUser from djangoblog.plugin_manage import hooks logger = logging.getLogger(__name__) - +#sxc 注册模板标签库,使 Django 模板系统可识别该模块中的标签 register = template.Library() @register.simple_tag(takes_context=True) def head_meta(context): + #sxc 执行头部元信息的插件过滤钩子,返回处理后的安全 HTML 内容 + #sxc takes_context=True 表示需要接收模板上下文参数 return mark_safe(hooks.apply_filters('head_meta', '', context)) @register.simple_tag def timeformat(data): + #sxc 简单标签:按项目配置的 TIME_FORMAT 格式化为时间字符串 + #sxc 异常捕获避免时间格式错误导致页面崩溃,错误时返回空字符串 try: return data.strftime(settings.TIME_FORMAT) except Exception as e: @@ -41,6 +47,8 @@ def timeformat(data): @register.simple_tag def datetimeformat(data): + #sxc 简单标签:按项目配置的 DATE_TIME_FORMAT 格式化为日期时间字符串 + #sxc 异常捕获处理,错误时返回空字符串 try: return data.strftime(settings.DATE_TIME_FORMAT) except Exception as e: @@ -51,11 +59,14 @@ def datetimeformat(data): @register.filter() @stringfilter def custom_markdown(content): + #sxc 过滤器:将 markdown 格式的内容转换为 HTML,并标记为安全内容(避免 XSS 过滤) + #sxc @stringfilter 表示仅接收字符串类型输入 return mark_safe(CommonMarkdown.get_markdown(content)) @register.simple_tag def get_markdown_toc(content): + #sxc 简单标签:获取 markdown 内容的目录(TOC),返回安全 HTML 格式 from djangoblog.utils import CommonMarkdown body, toc = CommonMarkdown.get_markdown_with_toc(content) return mark_safe(toc) @@ -64,6 +75,8 @@ def get_markdown_toc(content): @register.filter() @stringfilter def comment_markdown(content): + #sxc 过滤器:处理评论内容的 markdown 转换,先转 HTML 再 sanitize 过滤危险标签 + #sxc 确保评论内容安全,防止恶意 HTML 注入 content = CommonMarkdown.get_markdown(content) return mark_safe(sanitize_html(content)) @@ -76,6 +89,8 @@ def truncatechars_content(content): :param content: :return: """ + #sxc 过滤器:按博客配置的 “文章摘要长度” 截取内容,保留 HTML 标签结构 + #sxc is_safe=True 表示返回的 HTML 内容安全,无需额外转义 from django.template.defaultfilters import truncatechars_html from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() @@ -85,6 +100,7 @@ def truncatechars_content(content): @register.filter(is_safe=True) @stringfilter def truncate(content): + #sxc 过滤器:移除 HTML 标签后截取前 150 个字符,用于纯文本摘要场景 from django.utils.html import strip_tags return strip_tags(content)[:150] @@ -97,6 +113,8 @@ def load_breadcrumb(article): :param article: :return: """ + #sxc 包含标签:加载文章面包屑导航,渲染指定模板并传递数据 + #sxc 从文章获取分类层级树,反转顺序后拼接网站名称,生成面包屑路径 names = article.get_category_tree() from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() @@ -105,9 +123,9 @@ def load_breadcrumb(article): names = names[::-1] return { - 'names': names, - 'title': article.title, - 'count': len(names) + 1 + 'names': names,#sxc 面包屑层级列表,每个元素为 (名称,链接) + 'title': article.title,#sxc 文章标题 + 'count': len(names) + 1#sxc 面包屑节点数量 } @@ -118,6 +136,8 @@ def load_articletags(article): :param article: :return: """ + #sxc 包含标签:加载文章的标签列表,渲染标签展示模板 + #sxc 为每个标签生成链接、文章数量,并随机分配 Bootstrap 颜色 tags = article.tags.all() tags_list = [] for tag in tags: @@ -127,7 +147,7 @@ def load_articletags(article): url, count, tag, random.choice(settings.BOOTSTRAP_COLOR_TYPES) )) return { - 'article_tags_list': tags_list + 'article_tags_list': tags_list#sxc 传递标签数据列表到模板 } @@ -137,28 +157,38 @@ def load_sidebar(user, linktype): 加载侧边栏 :return: """ + #sxc 包含标签:加载博客侧边栏内容,优先从缓存获取,缓存未命中则查询数据库 + #sxc 缓存键包含 linktype(链接显示类型),确保不同页面侧边栏内容正确 value = cache.get("sidebar" + linktype) if value: - value['user'] = user + value['user'] = user#sxc 补充当前用户信息 return value else: logger.info('load sidebar') from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() + #sxc 查询发布状态的最新文章,数量由博客配置决定 recent_articles = Article.objects.filter( status='p')[:blogsetting.sidebar_article_count] + #sxc 查询所有分类 sidebar_categorys = Category.objects.all() + #sxc 查询启用状态的额外侧边栏,按排序号排列 extra_sidebars = SideBar.objects.filter( is_enable=True).order_by('sequence') + #sxc 查询浏览量最高的文章 most_read_articles = Article.objects.filter(status='p').order_by( '-views')[:blogsetting.sidebar_article_count] + #sxc 按月份分组的文章发布日期 dates = Article.objects.datetimes('creation_time', 'month', order='DESC') + #sxc 查询启用状态的链接,筛选当前页面类型或全站显示的链接 links = Links.objects.filter(is_enable=True).filter( Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) + #sxc 查询启用状态的最新评论,数量由博客配置决定 commment_list = Comment.objects.filter(is_enable=True).order_by( '-id')[:blogsetting.sidebar_comment_count] # 标签云 计算字体大小 # 根据总数计算出平均值 大小为 (数目/平均值)*步长 + #sxc 生成标签云:计算标签字体大小(按文章数量比例),随机打乱顺序 increment = 5 tags = Tag.objects.all() sidebar_tags = None @@ -185,6 +215,7 @@ def load_sidebar(user, linktype): 'sidebar_tags': sidebar_tags, 'extra_sidebars': extra_sidebars } + #sxc 设置缓存,有效期 3 小时(60603 秒) cache.set("sidebar" + linktype, value, 60 * 60 * 60 * 3) logger.info('set sidebar cache.key:{key}'.format(key="sidebar" + linktype)) value['user'] = user @@ -206,6 +237,7 @@ def load_article_metas(article, user): @register.inclusion_tag('blog/tags/article_pagination.html') def load_pagination_info(page_obj, page_type, tag_name): + #sxc 包含标签:加载分页信息,根据不同页面类型(首页 / 标签 / 作者 / 分类)生成上下页链接 previous_url = '' next_url = '' if page_type == '': @@ -217,6 +249,7 @@ def load_pagination_info(page_obj, page_type, tag_name): previous_url = reverse( 'blog:index_page', kwargs={ 'page': previous_number}) + #sxc 标签归档分页逻辑:根据标签 slug 生成链接 if page_type == '分类标签归档': tag = get_object_or_404(Tag, name=tag_name) if page_obj.has_next(): @@ -233,6 +266,7 @@ def load_pagination_info(page_obj, page_type, tag_name): kwargs={ 'page': previous_number, 'tag_name': tag.slug}) + #sxc 作者归档分页逻辑:根据作者名生成链接 if page_type == '作者文章归档': if page_obj.has_next(): next_number = page_obj.next_page_number() @@ -248,7 +282,7 @@ def load_pagination_info(page_obj, page_type, tag_name): kwargs={ 'page': previous_number, 'author_name': tag_name}) - +#sxc 分类归档分页逻辑:根据分类 slug 生成链接 if page_type == '分类目录归档': category = get_object_or_404(Category, name=tag_name) if page_obj.has_next(): @@ -281,6 +315,7 @@ def load_article_detail(article, isindex, user): :param isindex:是否列表页,若是列表页只显示摘要 :return: """ + #sxc 包含标签:加载文章详情内容,根据 isindex 判断是否显示摘要(列表页)或完整内容(详情页) from djangoblog.utils import get_blog_setting blogsetting = get_blog_setting() @@ -297,22 +332,26 @@ def load_article_detail(article, isindex, user): @register.filter def gravatar_url(email, size=40): """获得gravatar头像""" + #sxc 过滤器:获取用户 Gravatar 头像 URL,优先使用 OAuth 用户的自定义头像,其次调用 Gravatar 接口 + #sxc 缓存头像 URL,减少重复查询 cachekey = 'gravatat/' + email url = cache.get(cachekey) if url: return url else: + #sxc 查询 OAuth 用户表,获取用户自定义头像 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') - +#sxc 头像默认图(项目静态文件) default = static('blog/img/avatar.png') - +#sxc 拼接 Gravatar URL,包含 MD5 加密邮箱、默认图、尺寸参数 url = "https://www.gravatar.com/avatar/%s?%s" % (hashlib.md5( email.lower()).hexdigest(), urllib.parse.urlencode({'d': default, 's': str(size)})) + #sxc 缓存头像 URL,有效期 10 小时 cache.set(cachekey, url, 60 * 60 * 10) logger.info('set gravatar cache.key:{key}'.format(key=cachekey)) return url @@ -321,6 +360,7 @@ def gravatar_url(email, size=40): @register.filter def gravatar(email, size=40): """获得gravatar头像""" + #sxc 过滤器:生成 Gravatar 头像的 img 标签,直接返回可渲染的 HTML url = gravatar_url(email, size) return mark_safe( '' % diff --git a/src/DjangoBlog-master/blog/tests.py b/src/DjangoBlog-master/blog/tests.py index ee135052..aba6cb57 100644 --- a/src/DjangoBlog-master/blog/tests.py +++ b/src/DjangoBlog-master/blog/tests.py @@ -1,5 +1,5 @@ +#sxc 该模块基于 Django 框架,通过 TestCase 类对博客系统核心功能进行自动化测试,覆盖文章管理、用户交互、支付流程、第三方集成等场景,确保系统各模块功能正常、接口响应符合预期。 import os - from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.core.management import call_command @@ -20,11 +20,13 @@ from oauth.models import OAuthUser, OAuthConfig # Create your tests here. class ArticleTest(TestCase): + #sxc 初始化测试环境,创建客户端(模拟用户请求)和请求工厂(构造自定义请求),为后续测试方法提供基础对象。 def setUp(self): - self.client = Client() - self.factory = RequestFactory() - + self.client = Client() #sxc 模拟用户浏览器,用于发送 HTTP 请求 + self.factory = RequestFactory() #sxc 用于构造自定义请求对象,支持更灵活的请求参数配置 +#sxc 测试文章相关核心功能,包括用户创建、分类 / 标签 / 侧边栏数据初始化、文章增删查、搜索、分页、第三方通知等,覆盖文章生命周期关键流程。 def test_validate_article(self): + #sxc 获取当前站点域名,用于后续 URL 相关操作 site = get_current_site().domain user = BlogUser.objects.get_or_create( email="liangliangyy@gmail.com", @@ -33,27 +35,31 @@ class ArticleTest(TestCase): user.is_staff = True user.is_superuser = True user.save() + #sxc 测试用户个人主页访问,验证响应状态码为 200(正常访问) response = self.client.get(user.get_absolute_url()) self.assertEqual(response.status_code, 200) + #sxc 测试管理员后台邮件发送日志页面访问(无实际业务校验,仅验证页面可达性) response = self.client.get('/admin/servermanager/emailsendlog/') + #sxc 测试管理员后台操作日志页面访问(无实际业务校验,仅验证页面可达性) response = self.client.get('admin/admin/logentry/') + #sxc 创建测试侧边栏数据,设置排序、名称、内容并启用,用于验证侧边栏功能 s = SideBar() - s.sequence = 1 - s.name = 'test' - s.content = 'test content' - s.is_enable = True + s.sequence = 1 #sxc 侧边栏排序序号,决定展示顺序 + s.name = 'test' #sxc 侧边栏名称 + s.content = 'test content' #sxc 侧边栏内容 + s.is_enable = True #sxc 启用侧边栏 s.save() - +#sxc 创建测试分类数据,设置名称和时间戳,用于文章分类关联 category = Category() category.name = "category" - category.creation_time = timezone.now() - category.last_mod_time = timezone.now() + category.creation_time = timezone.now() #sxc 分类创建时间 + category.last_mod_time = timezone.now() #sxc 分类最后修改时间 category.save() - +#sxc 创建测试标签数据,用于文章标签关联 tag = Tag() tag.name = "nicetag" tag.save() - +#sxc 创建测试文章,设置标题、内容、作者、分类、类型(a:普通文章)、状态(p:已发布) article = Article() article.title = "nicetitle" article.body = "nicecontent" @@ -63,15 +69,18 @@ class ArticleTest(TestCase): article.status = 'p' article.save() + #sxc 验证文章初始状态下标签数量为 0(未关联标签) self.assertEqual(0, article.tags.count()) + #sxc 为文章关联标签并重新保存 article.tags.add(tag) article.save() + #sxc 验证文章关联标签后数量为 1(标签关联成功) self.assertEqual(1, article.tags.count()) - +#sxc 循环创建 20 篇测试文章,用于后续分页功能测试(模拟多数据场景) for i in range(20): article = Article() - article.title = "nicetitle" + str(i) - article.body = "nicetitle" + str(i) + article.title = "nicetitle" + str(i) #sxc 动态生成文章标题(带序号) + article.body = "nicetitle" + str(i) #sxc 动态生成文章内容(带序号) article.author = user article.category = category article.type = 'a' @@ -79,56 +88,59 @@ class ArticleTest(TestCase): article.save() article.tags.add(tag) article.save() + #sxc 检查 Elasticsearch 是否启用,若启用则执行索引构建命令,确保搜索功能可用 from blog.documents import ELASTICSEARCH_ENABLED if ELASTICSEARCH_ENABLED: - call_command("build_index") + call_command("build_index") #sxc 执行 Django 自定义命令,构建文章搜索索引 response = self.client.get('/search', {'q': 'nicetitle'}) self.assertEqual(response.status_code, 200) - + #sxc 测试搜索功能,搜索关键词 "nicetitle",验证响应状态码为 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) - + #sxc 测试文章详情页访问,验证响应状态码为 200 response = self.client.get(category.get_absolute_url()) self.assertEqual(response.status_code, 200) - +#sxc 测试搜索不存在的关键词 “django”,验证搜索页面仍能正常响应(状态码 200) response = self.client.get('/search', {'q': 'django'}) self.assertEqual(response.status_code, 200) + #sxc 测试文章标签模板标签load_articletags,验证返回结果非空 s = load_articletags(article) self.assertIsNotNone(s) - +#sxc 模拟用户登录(使用测试账号),用于后续需登录权限的操作 self.client.login(username='liangliangyy', password='liangliangyy') - +#sxc 测试文章归档页面访问,验证响应状态码为 200 response = self.client.get(reverse('blog:archives')) self.assertEqual(response.status_code, 200) - +#sxc 测试所有文章的分页功能,调用check_pagination方法验证分页链接有效性 p = Paginator(Article.objects.all(), settings.PAGINATE_BY) self.check_pagination(p, '', '') - +#sxc 测试指定标签(tag)下文章的分页功能 p = Paginator(Article.objects.filter(tags=tag), settings.PAGINATE_BY) self.check_pagination(p, '分类标签归档', tag.slug) - +#sxc 测试指定作者(liangliangyy)下文章的分页功能 p = Paginator( Article.objects.filter( author__username='liangliangyy'), settings.PAGINATE_BY) self.check_pagination(p, '作者文章归档', 'liangliangyy') - +#sxc 测试指定分类(category)下文章的分页功能 p = Paginator(Article.objects.filter(category=category), settings.PAGINATE_BY) self.check_pagination(p, '分类目录归档', category.slug) - +#sxc 测试博客搜索表单BlogSearchForm的search方法,验证表单功能可执行 f = BlogSearchForm() f.search() # self.client.login(username='liangliangyy', password='liangliangyy') + #sxc 调用百度爬虫通知工具,通知单篇文章的完整 URL from djangoblog.spider_notify import SpiderNotify SpiderNotify.baidu_notify([article.get_full_url()]) - +#sxc 测试头像相关模板标签gravatar_url和gravatar,验证头像 URL 和 HTML 标签生成功能 from blog.templatetags.blog_tags import gravatar_url, gravatar u = gravatar_url('liangliangyy@gmail.com') u = gravatar('liangliangyy@gmail.com') - +#sxc 创建测试友情链接数据,验证友情链接页面功能 link = Links( sequence=1, name="lylinux", @@ -136,39 +148,47 @@ class ArticleTest(TestCase): link.save() response = self.client.get('/links.html') self.assertEqual(response.status_code, 200) - +#sxc 测试 RSS 订阅页面访问,验证响应状态码为 200 response = self.client.get('/feed/') self.assertEqual(response.status_code, 200) - +#sxc 测试站点地图页面访问,验证响应状态码为 200 response = self.client.get('/sitemap.xml') self.assertEqual(response.status_code, 200) - +#sxc 测试管理员删除文章操作,验证页面可达性 self.client.get("/admin/blog/article/1/delete/") + #sxc 测试管理员后台邮件日志和操作日志页面访问,验证后台功能稳定性 self.client.get('/admin/servermanager/emailsendlog/') self.client.get('/admin/admin/logentry/') self.client.get('/admin/admin/logentry/1/change/') - +#sxc 验证分页功能,遍历所有分页页面,确保上一页、下一页链接响应正常 def check_pagination(self, p, type, value): for page in range(1, p.num_pages + 1): + #sxc 调用分页模板标签,获取分页信息 s = load_pagination_info(p.page(page), type, value) self.assertIsNotNone(s) + #sxc 验证上一页链接响应状态 if s['previous_url']: response = self.client.get(s['previous_url']) self.assertEqual(response.status_code, 200) + #sxc 验证下一页链接响应状态 if s['next_url']: response = self.client.get(s['next_url']) self.assertEqual(response.status_code, 200) - +#sxc 测试图片上传功能,包括未授权拦截、授权上传、工具函数调用 def test_image(self): import requests + #sxc 下载测试图片(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) + #sxc 测试未授权图片上传,验证返回 403 rsp = self.client.post('/upload') self.assertEqual(rsp.status_code, 403) + #sxc 生成上传授权签名 sign = get_sha256(get_sha256(settings.SECRET_KEY)) + #sxc 构造授权上传请求,验证上传成功(状态码 200) with open(imagepath, 'rb') as file: imgfile = SimpleUploadedFile( 'python.png', file.read(), content_type='image/jpg') @@ -185,8 +205,9 @@ class ArticleTest(TestCase): def test_errorpage(self): rsp = self.client.get('/eee') self.assertEqual(rsp.status_code, 404) - +#sxc 测试 Django 自定义管理命令,覆盖索引构建、爬虫通知、数据生成等运维功能 def test_commands(self): + #sxc 创建测试超级用户,用于命令执行权限验证 user = BlogUser.objects.get_or_create( email="liangliangyy@gmail.com", username="liangliangyy")[0] @@ -194,13 +215,13 @@ class ArticleTest(TestCase): user.is_staff = True user.is_superuser = True user.save() - +#sxc 创建 QQ 第三方登录配置 c = OAuthConfig() c.type = 'qq' c.appkey = 'appkey' c.appsecret = 'appsecret' c.save() - +#sxc 创建第一个 QQ 第三方用户(关联本地用户 u = OAuthUser() u.type = 'qq' u.openid = 'openid' @@ -211,7 +232,7 @@ class ArticleTest(TestCase): "figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30" }''' u.save() - +#sxc 创建第二个 QQ 第三方用户(不关联本地用 u = OAuthUser() u.type = 'qq' u.openid = 'openid1' @@ -221,10 +242,11 @@ class ArticleTest(TestCase): "figureurl": "https://qzapp.qlogo.cn/qzapp/101513904/C740E30B4113EAA80E0D9918ABC78E82/30" }''' u.save() - +#sxc 若 Elasticsearch 启用,执行索引构建命令 from blog.documents import ELASTICSEARCH_ENABLED if ELASTICSEARCH_ENABLED: call_command("build_index") + #sxc 执行百度爬虫通知、测试数据生成、缓存清理、头像同步、搜索词构建等命令 call_command("ping_baidu", "all") call_command("create_testdata") call_command("clear_cache") diff --git a/src/DjangoBlog-master/blog/urls.py b/src/DjangoBlog-master/blog/urls.py index adf27036..9a67ba1a 100644 --- a/src/DjangoBlog-master/blog/urls.py +++ b/src/DjangoBlog-master/blog/urls.py @@ -1,3 +1,4 @@ +#sxc 该模块是 Django 博客应用的 URL 路由配置模块,定义了博客前台所有页面的访问路径与对应视图的映射关系,包括首页、文章详情页、分类页、标签页等核心页面,同时配置了缓存和分页路由,实现 URL 的规范化管理和页面访问控制 from django.urls import path from django.views.decorators.cache import cache_page @@ -5,56 +6,69 @@ from . import views app_name = "blog" urlpatterns = [ + #sxc 首页路由:映射到 IndexView 视图,名称为 'index' path( r'', views.IndexView.as_view(), name='index'), + #sxc 首页分页路由:接收页码参数,名称为 'index_page' path( r'page//', views.IndexView.as_view(), name='index_page'), + #sxc 文章详情页路由:通过年月日和文章 ID 访问,名称为 'detailbyid' path( r'article////.html', views.ArticleDetailView.as_view(), name='detailbyid'), + #sxc 分类详情页路由:通过分类 slug 访问,名称为 'category_detail' path( r'category/.html', views.CategoryDetailView.as_view(), name='category_detail'), + #sxc 分类详情分页路由:包含分类 slug 和页码,名称为 'category_detail_page' path( r'category//.html', views.CategoryDetailView.as_view(), name='category_detail_page'), + #sxc 作者文章页路由:通过作者名访问,名称为 'author_detail' path( r'author/.html', views.AuthorDetailView.as_view(), name='author_detail'), + #sxc 作者文章分页路由:包含作者名和页码,名称为 'author_detail_page' path( r'author//.html', views.AuthorDetailView.as_view(), name='author_detail_page'), + #sxc 标签详情页路由:通过标签 slug 访问,名称为 'tag_detail' path( r'tag/.html', views.TagDetailView.as_view(), name='tag_detail'), + #sxc 标签详情分页路由:包含标签 slug 和页码,名称为 'tag_detail_page' path( r'tag//.html', views.TagDetailView.as_view(), name='tag_detail_page'), + #sxc 归档页面路由:使用缓存装饰器,缓存 1 小时 (60*60 秒),名称为 'archives' path( 'archives.html', cache_page( 60 * 60)( views.ArchivesView.as_view()), name='archives'), + #sxc 友情链接页面路由:映射到 LinkListView 视图,名称为 'links' path( 'links.html', views.LinkListView.as_view(), name='links'), + #sxc 文件上传路由:用于处理文件上传请求,名称为 'upload' path( r'upload', views.fileupload, name='upload'), + #sxc 缓存清理路由:用于触发缓存清理操作,名称为 'clean' path( r'clean', views.clean_cache_view, diff --git a/src/DjangoBlog-master/blog/views.py b/src/DjangoBlog-master/blog/views.py index d5dc7ec0..18ef53c5 100644 --- a/src/DjangoBlog-master/blog/views.py +++ b/src/DjangoBlog-master/blog/views.py @@ -1,3 +1,4 @@ +#sxc 该模块基于 Django 框架,实现博客系统的各类视图功能,包括文章列表、详情、分类、标签、搜索、文件上传、错误页处理等,同时集成缓存、插件钩子等机制,保障页面渲染和业务逻辑的稳定性与可扩展性。 import logging import os import uuid @@ -26,6 +27,7 @@ logger = logging.getLogger(__name__) class ArticleListView(ListView): # template_name属性用于指定使用哪个模板进行渲染 + #sxc 文章列表视图基类,封装分页、缓存、上下文处理等通用逻辑,为子类(首页、分类、标签等列表)提供基础支持 template_name = 'blog/article_index.html' # context_object_name属性用于给上下文变量取名(在模板中使用该名字) @@ -42,6 +44,7 @@ class ArticleListView(ListView): @property def page_number(self): + #sxc 获取当前分页页码,支持从 URL 参数或 kwargs 中提取 page_kwarg = self.page_kwarg page = self.kwargs.get( page_kwarg) or self.request.GET.get(page_kwarg) or 1 @@ -51,12 +54,14 @@ class ArticleListView(ListView): """ 子类重写.获得queryset的缓存key """ + #sxc 抽象方法,子类需重写以定义查询集的缓存键生成规则,用于缓存数据的标识 raise NotImplementedError() def get_queryset_data(self): """ 子类重写.获取queryset的数据 """ + #sxc 抽象方法,子类需重写以实现具体的查询集数据获取逻辑(如筛选文章条件) raise NotImplementedError() def get_queryset_from_cache(self, cache_key): @@ -65,6 +70,7 @@ class ArticleListView(ListView): :param cache_key: 缓存key :return: ''' + #sxc 从缓存中获取查询集数据,若缓存未命中则调用 get_queryset_data 获取并缓存 value = cache.get(cache_key) if value: logger.info('get view cache.key:{key}'.format(key=cache_key)) @@ -80,6 +86,7 @@ class ArticleListView(ListView): 重写默认,从缓存获取数据 :return: ''' + #sxc 重写父类方法,优先从缓存获取查询集数据,减少数据库查询 key = self.get_queryset_cache_key() value = self.get_queryset_from_cache(key) return value @@ -94,13 +101,16 @@ class IndexView(ArticleListView): 首页 ''' # 友情链接类型 + #sxc 博客首页视图,继承自 ArticleListView 基类,专注于展示所有已发布文章,配置首页专属友情链接类型 link_type = LinkShowType.I def get_queryset_data(self): + #sxc 实现父类抽象方法,获取首页需展示的文章数据:筛选类型为 “普通文章(a)” 且状态为 “已发布(p)” 的文章 article_list = Article.objects.filter(type='a', status='p') return article_list def get_queryset_cache_key(self): + #sxc 实现父类抽象方法,生成首页文章列表的缓存键,格式为 “index_页码”,确保不同分页数据的缓存隔离 cache_key = 'index_{page}'.format(page=self.page_number) return cache_key @@ -109,18 +119,21 @@ class ArticleDetailView(DetailView): ''' 文章详情页面 ''' - template_name = 'blog/article_detail.html' - model = Article - pk_url_kwarg = 'article_id' - context_object_name = "article" + #sxc 文章详情页视图,继承自 Django 的 DetailView,负责单篇文章的完整信息展示,包括内容、评论、上下篇导航等 + template_name = 'blog/article_detail.html' #sxc 指定详情页渲染模板路径 + model = Article #sxc 关联的数据模型为 Article,自动从数据库获取文章数据 + pk_url_kwarg = 'article_id' #sxc 指定 URL 中用于匹配文章主键的参数名为 “article_id” + context_object_name = "article" #sxc 设定模板中访问文章对象的变量名为 “article” def get_context_data(self, **kwargs): + #sxc 重写父类方法,构建详情页所需的完整上下文数据,包括评论表单、评论分页、上下篇文章等 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) + #sxc 处理评论分页页码:从 URL 参数获取,非数字 / 越界时自动修正为合法值 page = self.request.GET.get('comment_page', '1') if not page.isnumeric(): page = 1 @@ -130,31 +143,34 @@ class ArticleDetailView(DetailView): page = 1 if page > paginator.num_pages: page = paginator.num_pages - +#sxc 获取当前页的评论数据,并计算上下页页码 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 - +#sxc 构建评论上下页的跳转 URL,包含锚点 “#commentlist-container” 以定位到评论区 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' + #sxc 向上下文注入评论相关数据:表单、所有评论、当前页评论、评论总数 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 - +#sxc 向上下文注入当前文章的上下篇文章对象,用于导航 kwargs['next_article'] = self.object.next_article kwargs['prev_article'] = self.object.prev_article - +#sxc 调用父类方法获取基础上下文,再扩展插件相关逻辑 context = super(ArticleDetailView, self).get_context_data(**kwargs) article = self.object # Action Hook, 通知插件"文章详情已获取" + #sxc 触发 “文章详情已获取” 的插件动作钩子,通知所有注册插件执行对应逻辑(如统计阅读量 hooks.run_action('after_article_body_get', article=article, request=self.request) # # Filter Hook, 允许插件修改文章正文 + #sxc 触发 “文章内容过滤” 的插件钩子,允许插件修改文章正文(如添加水印、解析特殊语法) article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article, request=self.request) @@ -165,31 +181,36 @@ class CategoryDetailView(ArticleListView): ''' 分类目录列表 ''' + #sxc 分类目录文章列表视图,按 “分类” 维度筛选文章,展示指定分类及下属子分类的所有已发布文章 page_type = "分类目录归档" def get_queryset_data(self): + #sxc 实现父类抽象方法,按分类筛选文章:先获取分类对象,再查询该分类及子分类下的已发布文章 slug = self.kwargs['category_name'] category = get_object_or_404(Category, slug=slug) - +#sxc 获取当前分类及所有子分类的名称列表(用于多分类联合筛选) categoryname = category.name self.categoryname = categoryname categorynames = list( map(lambda c: c.name, category.get_sub_categorys())) + #sxc 筛选 “属于目标分类 / 子分类” 且 “状态为已发布(p)” 的文章 article_list = Article.objects.filter( category__name__in=categorynames, status='p') return article_list def get_queryset_cache_key(self): + #sxc 实现父类抽象方法,生成分类列表的专属缓存键,确保不同分类、不同分页的缓存隔离 slug = self.kwargs['category_name'] category = get_object_or_404(Category, slug=slug) categoryname = category.name self.categoryname = categoryname + #sxc 缓存键格式:category_list_分类名_页码(如 “category_list_技术_2”) cache_key = 'category_list_{categoryname}_{page}'.format( categoryname=categoryname, page=self.page_number) return cache_key def get_context_data(self, **kwargs): - +#sxc 重写父类方法,向模板上下文注入分类相关标识,用于页面个性化渲染 categoryname = self.categoryname try: categoryname = categoryname.split('/')[-1] @@ -204,9 +225,11 @@ class AuthorDetailView(ArticleListView): ''' 作者详情页 ''' + #sxc 作者文章列表视图,按 “作者” 维度筛选文章,展示指定作者发布的所有已发布文章 page_type = '作者文章归档' def get_queryset_cache_key(self): + #sxc 实现父类抽象方法,生成作者列表的专属缓存键,结合作者别名和页码确保缓存隔离 from uuslug import slugify author_name = slugify(self.kwargs['author_name']) cache_key = 'author_{author_name}_{page}'.format( @@ -214,12 +237,15 @@ class AuthorDetailView(ArticleListView): return cache_key def get_queryset_data(self): + #sxc 实现父类抽象方法,按作者筛选文章:查询指定作者发布的已发布文章 author_name = self.kwargs['author_name'] + #sxc 筛选 “作者用户名为目标值”“类型为普通文章(a)”“状态为已发布(p)” 的文章 article_list = Article.objects.filter( author__username=author_name, type='a', status='p') return article_list def get_context_data(self, **kwargs): + #sxc 重写父类方法,向上下文注入作者相关标识,用于页面渲染 author_name = self.kwargs['author_name'] kwargs['page_type'] = AuthorDetailView.page_type kwargs['tag_name'] = author_name @@ -230,6 +256,7 @@ class TagDetailView(ArticleListView): ''' 标签列表页面 ''' + #sxc 标签文章列表视图,按 “标签” 维度筛选文章,展示关联指定标签的所有已发布文章 page_type = '分类标签归档' def get_queryset_data(self): @@ -237,11 +264,13 @@ class TagDetailView(ArticleListView): tag = get_object_or_404(Tag, slug=slug) tag_name = tag.name self.name = tag_name + #sxc 筛选 “关联目标标签”“类型为普通文章(a)”“状态为已发布(p)” 的文章 article_list = Article.objects.filter( tags__name=tag_name, type='a', status='p') return article_list def get_queryset_cache_key(self): + #sxc 实现父类抽象方法,生成标签列表的专属缓存键,确保不同标签、不同分页的缓存隔离 slug = self.kwargs['tag_name'] tag = get_object_or_404(Tag, slug=slug) tag_name = tag.name @@ -251,6 +280,7 @@ class TagDetailView(ArticleListView): return cache_key def get_context_data(self, **kwargs): + #sxc 重写父类方法,向上下文注入标签相关标识,用于页面个性化渲染 # tag_name = self.kwargs['tag_name'] tag_name = self.name kwargs['page_type'] = TagDetailView.page_type @@ -262,29 +292,36 @@ class ArchivesView(ArticleListView): ''' 文章归档页面 ''' + #sxc 文章归档页面视图,展示所有已发布文章的归档信息(按时间等维度聚合),不分页 page_type = '文章归档' paginate_by = None page_kwarg = None template_name = 'blog/article_archives.html' def get_queryset_data(self): + #sxc 获取所有状态为 “已发布(p)” 的文章,用于归档展示 return Article.objects.filter(status='p').all() def get_queryset_cache_key(self): + #sxc 归档页缓存键固定为 'archives'(因不分页,无需页码参数) cache_key = 'archives' return cache_key class LinkListView(ListView): + #sxc 友情链接列表视图,展示所有启用的友情链接 model = Links template_name = 'blog/links_list.html' def get_queryset(self): + #sxc 筛选出所有 “已启用(is_enable=True)” 的友情链接 return Links.objects.filter(is_enable=True) class EsSearchView(SearchView): + #sxc 基于 Elasticsearch 的搜索视图,处理用户搜索请求并返回带分页和拼写建议的结果 def get_context(self): + #sxc 构建搜索结果页面的上下文数据,包含查询词、分页信息和拼写建议 paginator, page = self.build_page() context = { "query": self.query, @@ -293,6 +330,7 @@ class EsSearchView(SearchView): "paginator": paginator, "suggestion": None, } + #sxc 若搜索引擎支持拼写纠错,获取并添加拼写建议 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()) @@ -307,46 +345,57 @@ def fileupload(request): :param request: :return: """ + #sxc 处理文件上传(图床功能),支持图片压缩和签名验证,仅允许 POST 请求 if request.method == 'POST': - sign = request.GET.get('sign', None) + sign = request.GET.get('sign', None) #sxc 获取 URL 中的签名参数(用于权限验证) + #sxc 验证签名合法性:签名不存在或与系统计算值不符则拒绝访问 if not sign: - return HttpResponseForbidden() + return HttpResponseForbidden() #sxc 无签名时返回 403 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)) + return HttpResponseForbidden() #sxc 签名不匹配时返回 403 + response = [] #sxc 存储上传文件的访问 URL 列表 + for filename in request.FILES: #sxc 遍历所有上传的文件 + timestr = timezone.now().strftime('%Y/%m/%d')#sxc 按当前日期生成存储目录(如 2023/10/01) + imgextensions = ['jpg', 'png', 'jpeg', 'bmp'] #sxc 支持的图片文件扩展名 + fname = u''.join(str(filename)) #sxc 处理文件名(确保 Unicode 兼容) + #sxc 判断文件是否为图片(检查扩展名是否在图片格式列表中) isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0 + #sxc 确定文件存储根目录:图片存于 image 文件夹,其他文件存于 files 文件夹 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) + #sxc 生成唯一文件名(UUID + 原文件扩展名),避免重名 savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}")) + #sxc 防止路径遍历攻击:验证保存路径是否在基础目录内 if not savepath.startswith(base_dir): return HttpResponse("only for post") + #sxc 将上传的文件分块写入本地存储 with open(savepath, 'wb+') as wfile: for chunk in request.FILES[filename].chunks(): wfile.write(chunk) + #sxc 若为图片文件,压缩图片质量(优化存储和加载速度) if isimage: from PIL import Image image = Image.open(savepath) - image.save(savepath, quality=20, optimize=True) + image.save(savepath, quality=20, optimize=True)#sxc 质量压缩至 20% + #sxc 生成文件的访问 URL 并添加到响应列表 url = static(savepath) response.append(url) - return HttpResponse(response) + return HttpResponse(response) #sxc 返回所有上传文件的 URL 列表 else: - return HttpResponse("only for post") + return HttpResponse("only for post") #sxc 非 POST 请求直接拒绝 def page_not_found_view( request, exception, template_name='blog/error_page.html'): + #sxc 处理 404 页面未找到错误,记录异常并返回自定义错误页面 if exception: logger.error(exception) url = request.get_full_path() + #sxc 渲染错误页面,传递错误信息和状态码 return render(request, template_name, {'message': _('Sorry, the page you requested is not found, please click the home page to see other?'), @@ -355,6 +404,7 @@ def page_not_found_view( def server_error_view(request, template_name='blog/error_page.html'): + #sxc 处理 500 服务器内部错误,返回自定义错误页面 return render(request, template_name, {'message': _('Sorry, the server is busy, please click the home page to see other?'), @@ -366,6 +416,7 @@ def permission_denied_view( request, exception, template_name='blog/error_page.html'): + #sxc 处理 403 权限拒绝错误,记录异常并返回自定义错误页面 if exception: logger.error(exception) return render( @@ -375,5 +426,6 @@ def permission_denied_view( def clean_cache_view(request): - cache.clear() + #sxc 清理系统缓存的视图,调用缓存清理方法并返回成功响应 + cache.clear() #sxc 清除所有缓存数据 return HttpResponse('ok')