diff --git a/plugins/seo_optimizer/plugin.py b/plugins/seo_optimizer/plugin.py index b5b19a3..9a4b6cd 100644 --- a/plugins/seo_optimizer/plugin.py +++ b/plugins/seo_optimizer/plugin.py @@ -1,142 +1,225 @@ +# 导入JSON模块,用于将结构化数据转换为JSON格式 import json +# 导入Django工具函数,用于移除HTML标签(提取纯文本) from django.utils.html import strip_tags +# 导入Django模板过滤器(当前未使用,预留用于文本截断) from django.template.defaultfilters import truncatewords +# 导入插件基类,所有插件需继承此类实现标准化接口 from djangoblog.plugin_manage.base_plugin import BasePlugin +# 导入插件钩子管理模块,用于注册插件功能到指定钩子 from djangoblog.plugin_manage import hooks +# 导入博客数据模型,用于获取文章、分类、标签等数据 from blog.models import Article, Category, Tag +# 导入工具函数,用于获取博客站点的基础配置(如站点名称、关键词等) from djangoblog.utils import get_blog_setting class SeoOptimizerPlugin(BasePlugin): + # 插件名称,用于在插件管理界面展示 PLUGIN_NAME = 'SEO 优化器' + # 插件功能描述,说明插件的核心作用 PLUGIN_DESCRIPTION = '为文章、页面等提供 SEO 优化,动态生成 meta 标签和 JSON-LD 结构化数据。' + # 插件版本号,用于版本管理 PLUGIN_VERSION = '0.2.0' + # 插件作者信息 PLUGIN_AUTHOR = 'liuangliangyy' def register_hooks(self): + """注册插件钩子,将SEO处理逻辑绑定到页面区域的meta标签生成环节""" + # 当系统渲染中的meta标签时,触发dispatch_seo_generation方法 hooks.register('head_meta', self.dispatch_seo_generation) def _get_article_seo_data(self, context, request, blog_setting): + """ + 生成文章详情页的SEO数据(私有方法,仅内部调用) + + 参数: + context:模板上下文,包含当前页面的文章对象等数据 + request:HTTP请求对象,用于构建绝对URL + blog_setting:博客站点配置信息(如站点名称、关键词) + 返回: + 包含SEO相关数据的字典,若上下文无有效文章对象则返回None + """ + # 从上下文获取文章对象 article = context.get('article') + # 校验是否为有效的Article实例,避免非文章页误处理 if not isinstance(article, Article): return None + # 生成描述信息:移除文章内容中的HTML标签,截取前150字符(符合多数搜索引擎的描述长度建议) description = strip_tags(article.body)[:150] + # 生成关键词:优先使用文章标签,若无标签则使用站点默认关键词 keywords = ",".join([tag.name for tag in article.tags.all()]) or blog_setting.site_keywords - + + # 生成Open Graph(OG)协议标签,用于优化社交平台分享效果 meta_tags = f''' - - - - - - - - + + + + + + + + ''' + # 为文章的每个标签添加OG标签 for tag in article.tags.all(): meta_tags += f'' + # 添加站点名称OG标签,关联文章所属站点 meta_tags += f'' + # 生成JSON-LD结构化数据(帮助搜索引擎理解页面内容结构) structured_data = { - "@context": "https://schema.org", - "@type": "Article", - "mainEntityOfPage": {"@type": "WebPage", "@id": request.build_absolute_uri()}, - "headline": article.title, - "description": description, + "@context": "https://schema.org", # 遵循schema.org的结构化数据标准 + "@type": "Article", # 内容类型为文章 + "mainEntityOfPage": { # 声明页面的主体内容 + "@type": "WebPage", + "@id": request.build_absolute_uri() # 页面唯一标识(完整URL) + }, + "headline": article.title, # 文章标题 + "description": description, # 文章描述 + # 文章首图(通过绝对URL访问,增强内容丰富度) "image": request.build_absolute_uri(article.get_first_image_url()), - "datePublished": article.pub_time.isoformat(), - "dateModified": article.last_modify_time.isoformat(), - "author": {"@type": "Person", "name": article.author.username}, - "publisher": {"@type": "Organization", "name": blog_setting.site_name} + "datePublished": article.pub_time.isoformat(), # 发布时间 + "dateModified": article.last_modify_time.isoformat(), # 修改时间 + "author": { # 作者信息 + "@type": "Person", + "name": article.author.username + }, + "publisher": { # 发布机构信息 + "@type": "Organization", + "name": blog_setting.site_name + } } + # 若文章无图片,则移除image字段(避免空值影响结构化数据有效性) if not structured_data.get("image"): del structured_data["image"] + # 返回整合后的文章页SEO数据 return { - "title": f"{article.title} | {blog_setting.site_name}", + "title": f"{article.title} | {blog_setting.site_name}", # 页面标题(文章标题+站点名称,增强品牌关联) "description": description, "keywords": keywords, - "meta_tags": meta_tags, - "json_ld": structured_data + "meta_tags": meta_tags, # 包含OG标签的HTML片段 + "json_ld": structured_data # JSON-LD结构化数据 } def _get_category_seo_data(self, context, request, blog_setting): + """生成分类页面的SEO数据(私有方法)""" + # 从上下文获取分类名称(注:变量名tag_name可能为笔误,实际应为分类名称) category_name = context.get('tag_name') if not category_name: return None - + + # 根据名称查询分类对象 category = Category.objects.filter(name=category_name).first() - if not category: + if not category: # 若分类不存在,返回None return None + # 页面标题:分类名称+站点名称 title = f"{category.name} | {blog_setting.site_name}" + # 描述:使用分类名称或站点默认描述 description = strip_tags(category.name) or blog_setting.site_description + # 关键词:使用分类名称 keywords = category.name - # BreadcrumbList structured data for category page - breadcrumb_items = [{"@type": "ListItem", "position": 1, "name": "首页", "item": request.build_absolute_uri('/')}] - breadcrumb_items.append({"@type": "ListItem", "position": 2, "name": category.name, "item": request.build_absolute_uri()}) - + # 生成面包屑导航的JSON-LD数据(帮助搜索引擎理解页面在站点中的层级位置) + breadcrumb_items = [ + # 首页面包屑项(位置1) + {"@type": "ListItem", "position": 1, "name": "首页", "item": request.build_absolute_uri('/')} + ] + # 当前分类面包屑项(位置2) + breadcrumb_items.append({ + "@type": "ListItem", + "position": 2, + "name": category.name, + "item": request.build_absolute_uri() + }) + structured_data = { "@context": "https://schema.org", - "@type": "BreadcrumbList", - "itemListElement": breadcrumb_items + "@type": "BreadcrumbList", # 类型为面包屑列表 + "itemListElement": breadcrumb_items # 面包屑项列表 } + # 返回分类页SEO数据 return { "title": title, "description": description, "keywords": keywords, - "meta_tags": "", + "meta_tags": "", # 分类页暂无需额外meta标签 "json_ld": structured_data } def _get_default_seo_data(self, context, request, blog_setting): - # Homepage and other default pages + """生成默认页面(如首页、未匹配的页面)的SEO数据(私有方法)""" + # 生成网站级别的JSON-LD数据,包含站点基本信息和搜索功能描述 structured_data = { "@context": "https://schema.org", - "@type": "WebSite", - "url": request.build_absolute_uri('/'), + "@type": "WebSite", # 类型为网站 + "url": request.build_absolute_uri('/'), # 网站首页URL + # 描述站点的搜索功能(帮助搜索引擎识别并支持站内搜索) "potentialAction": { "@type": "SearchAction", + # 搜索结果页URL模板({search_term_string}为搜索关键词占位符) "target": f"{request.build_absolute_uri('/search/')}?q={{search_term_string}}", - "query-input": "required name=search_term_string" + "query-input": "required name=search_term_string" # 声明搜索参数为必填项 } } + # 返回默认页SEO数据 return { - "title": f"{blog_setting.site_name} | {blog_setting.site_description}", - "description": blog_setting.site_description, - "keywords": blog_setting.site_keywords, - "meta_tags": "", + "title": f"{blog_setting.site_name} | {blog_setting.site_description}", # 首页标题(站点名称+描述) + "description": blog_setting.site_description, # 站点描述 + "keywords": blog_setting.site_keywords, # 站点默认关键词 + "meta_tags": "", # 默认页无需额外meta标签 "json_ld": structured_data } def dispatch_seo_generation(self, metas, context): + """ + 分发SEO数据生成逻辑(核心方法):根据当前页面类型调用对应的数据生成方法 + + 参数: + metas:原始的meta标签内容(未使用,预留用于扩展) + context:模板上下文,包含请求对象和页面数据 + 返回: + 生成的完整SEO标签(包含title、meta标签、JSON-LD脚本等) + """ + # 从上下文获取请求对象,用于判断页面类型和构建URL request = context.get('request') - if not request: + if not request: # 若无请求对象,返回原始内容 return metas + # 获取当前视图的名称(通过Django的URL解析器),用于区分页面类型 view_name = request.resolver_match.view_name + # 获取博客站点配置 blog_setting = get_blog_setting() - + + # 根据页面类型(视图名称)生成对应的SEO数据 seo_data = None if view_name == 'blog:detailbyid': + # 文章详情页:调用文章SEO数据生成方法 seo_data = self._get_article_seo_data(context, request, blog_setting) elif view_name == 'blog:category_detail': + # 分类详情页:调用分类SEO数据生成方法 seo_data = self._get_category_seo_data(context, request, blog_setting) - + + # 若未匹配到特定页面类型,使用默认SEO数据 if not seo_data: - seo_data = self._get_default_seo_data(context, request, blog_setting) + seo_data = self._get_default_seo_data(context, request, blog_setting) + # 生成JSON-LD脚本标签:将结构化数据转换为JSON字符串,确保非ASCII字符正常显示 json_ld_script = f'' + # 拼接所有SEO相关标签并返回,最终会被插入到页面的区域 return f""" - {seo_data.get("title", "")} - - - {seo_data.get("meta_tags", "")} - {json_ld_script} + {seo_data.get("title", "")} + + + {seo_data.get("meta_tags", "")} + {json_ld_script} """ -plugin = SeoOptimizerPlugin() + +# 实例化插件,使插件系统能够识别并加载该插件 +plugin = SeoOptimizerPlugin() \ No newline at end of file diff --git a/plugins/view_count/plugin.py b/plugins/view_count/plugin.py index 15e9d94..3e8528e 100644 --- a/plugins/view_count/plugin.py +++ b/plugins/view_count/plugin.py @@ -1,18 +1,44 @@ +# 导入Django博客系统的插件基类,所有自定义插件需继承此类以实现标准化接口 from djangoblog.plugin_manage.base_plugin import BasePlugin +# 导入插件钩子管理模块,用于将插件功能绑定到系统预设的钩子点 from djangoblog.plugin_manage import hooks +# 定义文章浏览次数统计插件类,继承自插件基类BasePlugin class ViewCountPlugin(BasePlugin): + # 插件名称:在插件管理界面展示,用于区分不同插件 PLUGIN_NAME = '文章浏览次数统计' + # 插件功能描述:说明插件的核心作用,方便管理员理解用途 PLUGIN_DESCRIPTION = '统计文章的浏览次数' + # 插件版本号:用于版本管理,便于后续更新和兼容性判断 PLUGIN_VERSION = '0.1.0' + # 插件作者信息:标注开发者,便于维护和沟通 PLUGIN_AUTHOR = 'liangliangyy' def register_hooks(self): + """ + 注册插件钩子:将统计逻辑绑定到系统的特定触发点 + 作用:告诉插件系统“在哪个时机执行当前插件的功能” + """ + # 绑定规则: + # 1. 'after_article_body_get' 是系统预设的钩子名称,代表“文章内容获取完成后”的时机 + # 2. self.record_view 是当前插件的核心方法,即钩子触发时要执行的逻辑 + # 场景:当用户访问文章详情页,系统成功获取文章内容后,自动触发浏览次数统计 hooks.register('after_article_body_get', self.record_view) def record_view(self, article, *args, **kwargs): + """ + 核心统计方法:执行文章浏览次数的记录操作 + 参数说明: + article:钩子传递的文章对象(即当前被访问的文章),必须是Article模型实例 + *args, **kwargs:预留参数,用于接收钩子传递的额外信息(如请求对象等),保证扩展性 + """ + # 调用文章对象的viewed()方法: + # 该方法应由Article模型预先实现(通常逻辑为“将view_count字段+1并保存到数据库”) + # 插件通过调用模型方法实现统计,解耦插件与数据模型的直接操作,符合设计规范 article.viewed() -plugin = ViewCountPlugin() \ No newline at end of file +# 实例化插件类: +# 插件系统会扫描并加载该实例,使上述注册的钩子和功能生效 +plugin = ViewCountPlugin() \ No newline at end of file diff --git a/templates/account/forget_password.html b/templates/account/forget_password.html index 3384531..4729ec3 100644 --- a/templates/account/forget_password.html +++ b/templates/account/forget_password.html @@ -1,29 +1,56 @@ +{# 1. 模板继承:继承基础账户页面模板,复用头部、底部、样式等公共组件 #} +{# base_account.html 通常包含账户相关页面(登录、注册、密码重置)的通用布局 #} {% extends 'share_layout/base_account.html' %} -{% load i18n %} -{% load static %} + +{# 2. 加载Django内置模板标签库 #} +{% load i18n %} {# 加载国际化标签库,用于实现多语言文本(如英文/中文切换) #} +{% load static %} {# 加载静态文件标签库,用于引用CSS、JS、图片等静态资源 #} + +{# 3. 重写父模板的content块:定义当前页面的核心内容 #} +{# 父模板中会预留content块,子模板通过重写该块注入页面专属内容 #} {% block content %} -
+
{# 容器组件:使用Bootstrap等样式框架的容器类,实现内容居中、响应式布局 #} + {# 页面标题:显示“忘记密码”(多语言支持,实际文本由i18n翻译决定) #} + {# 卡片容器:包裹密码重置表单,通过卡片样式提升页面美观度和层次感 #} -

- Home Page - | - login page + {# 辅助链接:提供首页和登录页的跳转入口,提升用户体验 #} +

{# 文本居中显示 #} + Home Page {# 跳转到网站首页(硬编码根路径,也可改用{% url 'home' %}) #} + | {# 分隔符,增强链接可读性 #} + login page {# 跳转到登录页(使用URL反向解析,避免硬编码) #}

diff --git a/templates/account/login.html b/templates/account/login.html index cff8d33..5850bda 100644 --- a/templates/account/login.html +++ b/templates/account/login.html @@ -1,42 +1,74 @@ +{# 1. 模板继承:复用账户相关页面的公共布局(如头部导航、底部信息、基础样式) #} +{# base_account.html 通常是登录、注册、密码重置等页面的父模板,保证风格统一 #} {% extends 'share_layout/base_account.html' %} -{% load static %} -{% load i18n %} + +{# 2. 加载模板标签库:引入必要的功能支持 #} +{% load static %} {# 加载静态文件标签库,用于引用图片、CSS、JS等资源 #} +{% load i18n %} {# 加载国际化标签库,实现多语言文本切换(如中英文) #} + +{# 3. 重写父模板的content块:定义当前登录页的核心内容 #} +{# 父模板预留content块,子模板通过重写注入专属内容,实现布局复用 #} {% block content %} -
+
{# 容器组件:基于Bootstrap等框架,实现内容居中、响应式适配 #} + {# 页面标题:登录页主标题,居中显示,明确页面用途 #} + {# 卡片容器:包裹登录表单,通过卡片样式提升视觉层次和美观度 #} -

+ {# 辅助链接区域:提供注册、首页、密码重置的跳转入口,覆盖用户不同需求 #} +

{# 文本居中,提升页面整洁度 #} + {# 注册链接:跳转到账户注册页,使用国际化标签,支持多语言文本 #} {% trans 'Create Account' %} + | {# 分隔符:视觉上区分不同链接,提升可读性 #} + Home Page {# 首页链接:硬编码根路径(也可改用{% url 'home' %}实现反向解析) #} | - Home Page - | + {# 密码重置链接:跳转到忘记密码页,支持多语言文本 #} {% trans 'Forget Password' %} diff --git a/templates/account/registration_form.html b/templates/account/registration_form.html index 65e7549..412eeda 100644 --- a/templates/account/registration_form.html +++ b/templates/account/registration_form.html @@ -1,28 +1,53 @@ +{# 1. 模板继承:复用账户相关页面的公共布局 #} +{# base_account.html 通常包含登录、注册、密码重置等页面的通用组件(如头部样式、底部信息)#} +{# 继承后只需专注于当前注册页的专属内容,减少重复代码并保证风格统一 #} {% extends 'share_layout/base_account.html' %} -{% load static %} + +{# 2. 加载模板标签库:引入静态资源处理功能 #} +{% load static %} {# 用于引用图片、CSS、JS等静态文件,避免硬编码路径导致的问题 #} + +{# 3. 重写父模板的content块:定义注册页的核心内容 #} +{# 父模板会预留content块,子模板通过重写该块注入当前页面的具体内容 #} {% block content %} -

+
{# 容器组件:基于Bootstrap等样式框架,实现内容居中、响应式适配(适配手机/电脑屏幕) #} + {# 页面标题:明确当前页面为“创建账户”,居中显示以增强视觉引导 #} + {# 卡片容器:包裹注册表单,通过卡片样式(阴影、圆角)提升页面层次感和美观度 #} -

- Sign In + {# 辅助链接:提供登录页跳转入口,方便已有账号的用户切换到登录流程 #} +

{# 文本居中显示,提升页面整洁度 #} + Sign In {# 通过URL反向解析跳转到登录页,避免硬编码URL #}