diff --git a/djangoblog/plugin_manage/base_plugin.py b/djangoblog/plugin_manage/base_plugin.py index df1ce0b4..71c51aac 100644 --- a/djangoblog/plugin_manage/base_plugin.py +++ b/djangoblog/plugin_manage/base_plugin.py @@ -1,84 +1,99 @@ -import logging -from pathlib import Path +import logging # 用于日志记录 +from pathlib import Path # 用于文件路径处理 -from django.template import TemplateDoesNotExist -from django.template.loader import render_to_string +from django.template import TemplateDoesNotExist # Django模板不存在异常 +from django.template.loader import render_to_string # 用于渲染模板为字符串 +# 创建日志记录器,用于记录插件相关日志 logger = logging.getLogger(__name__) class BasePlugin: - # 插件元数据 - PLUGIN_NAME = None - PLUGIN_DESCRIPTION = None - PLUGIN_VERSION = None - PLUGIN_AUTHOR = None - - # 插件配置 - SUPPORTED_POSITIONS = [] # 支持的显示位置 - DEFAULT_PRIORITY = 100 # 默认优先级(数字越小优先级越高) - POSITION_PRIORITIES = {} # 各位置的优先级 {'sidebar': 50, 'article_bottom': 80} + """ + 插件基类,所有自定义插件需继承此类并实现特定方法。 + 提供插件元数据管理、位置渲染、模板渲染、静态资源处理等基础功能。 + """ + + # 插件元数据(子类必须重写这些属性) + PLUGIN_NAME = None # 插件名称(如"天气插件") + PLUGIN_DESCRIPTION = None # 插件描述(功能说明) + PLUGIN_VERSION = None # 插件版本(如"1.0.0") + PLUGIN_AUTHOR = None # 插件作者 + + # 插件配置(子类可根据需求重写) + SUPPORTED_POSITIONS = [] # 支持的显示位置(如['sidebar', 'footer']表示支持侧边栏和页脚) + DEFAULT_PRIORITY = 100 # 默认优先级(数字越小优先级越高,用于多个插件在同一位置排序) + POSITION_PRIORITIES = {} # 各位置的优先级(覆盖默认值,如{'sidebar': 50}表示侧边栏优先级为50) def __init__(self): + """初始化插件,验证元数据并设置基础属性""" + # 校验必须的元数据是否完整,不完整则抛出异常 if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): - raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") + raise ValueError("插件元数据(PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION)必须定义。") - # 设置插件路径 - self.plugin_dir = self._get_plugin_directory() - self.plugin_slug = self._get_plugin_slug() + # 设置插件路径和唯一标识 + self.plugin_dir = self._get_plugin_directory() # 插件所在目录路径 + self.plugin_slug = self._get_plugin_slug() # 插件唯一标识(默认使用目录名) + # 初始化插件并注册钩子 self.init_plugin() self.register_hooks() def _get_plugin_directory(self): - """获取插件目录路径""" + """获取插件所在的目录路径(内部方法)""" import inspect + # 通过inspect模块获取当前类的定义文件路径,再获取其所在目录 plugin_file = inspect.getfile(self.__class__) return Path(plugin_file).parent def _get_plugin_slug(self): - """获取插件标识符(目录名)""" + """获取插件的唯一标识符(默认使用插件目录名,内部方法)""" return self.plugin_dir.name def init_plugin(self): """ - 插件初始化逻辑 - 子类可以重写此方法来实现特定的初始化操作 + 插件初始化逻辑(钩子方法) + 子类可重写此方法实现自定义初始化操作(如加载配置、连接数据库等) """ - logger.info(f'{self.PLUGIN_NAME} initialized.') + logger.info(f'{self.PLUGIN_NAME} 初始化完成。') def register_hooks(self): """ - 注册插件钩子 - 子类可以重写此方法来注册特定的钩子 + 注册插件钩子(钩子方法) + 子类可重写此方法注册自定义钩子(如响应Django信号、注册URL路由等) """ pass # === 位置渲染系统 === def render_position_widget(self, position, context, **kwargs): """ - 根据位置渲染插件组件 - + 根据指定位置渲染插件组件(核心方法) + Args: - position: 位置标识 - context: 模板上下文 - **kwargs: 额外参数 - + position: 位置标识(如'sidebar'表示侧边栏) + context: 模板上下文(包含当前请求、用户等信息) + **kwargs: 额外参数(如文章ID、页面类型等) + Returns: - dict: {'html': 'HTML内容', 'priority': 优先级} 或 None + dict: 包含渲染结果的字典,格式为: + {'html': 'HTML内容', 'priority': 优先级, 'plugin_name': 插件名} + 若不支持该位置或不满足显示条件,返回None """ + # 检查当前位置是否在插件支持的位置列表中 if position not in self.SUPPORTED_POSITIONS: return None - # 检查条件显示 + # 检查是否满足显示条件(调用should_display方法) if not self.should_display(position, context, **kwargs): return None - # 调用具体的位置渲染方法 + # 动态调用对应位置的渲染方法(如position为'sidebar'则调用render_sidebar_widget) method_name = f'render_{position}_widget' if hasattr(self, method_name): + # 调用具体位置的渲染方法获取HTML内容 html = getattr(self, method_name)(context, **kwargs) - if html: + if html: # 若渲染成功(有HTML内容) + # 确定当前位置的优先级(优先使用POSITION_PRIORITIES,否则用默认值) priority = self.POSITION_PRIORITIES.get(position, self.DEFAULT_PRIORITY) return { 'html': html, @@ -90,97 +105,135 @@ class BasePlugin: def should_display(self, position, context, **kwargs): """ - 判断插件是否应该在指定位置显示 - 子类可重写此方法实现条件显示逻辑 - + 判断插件是否应该在指定位置显示(钩子方法) + 子类可重写此方法实现条件显示逻辑(如只在特定页面/用户组显示) + Args: position: 位置标识 context: 模板上下文 **kwargs: 额外参数 - + Returns: - bool: 是否显示 + bool: True表示显示,False表示不显示 """ - return True + return True # 默认始终显示 - # === 各位置渲染方法 - 子类重写 === + # === 各位置渲染方法 - 子类需根据支持的位置重写 === def render_sidebar_widget(self, context, **kwargs): - """渲染侧边栏组件""" + """渲染侧边栏组件(钩子方法),子类重写此方法实现侧边栏内容""" return None def render_article_bottom_widget(self, context, **kwargs): - """渲染文章底部组件""" + """渲染文章底部组件(钩子方法),子类重写此方法实现文章底部内容""" return None def render_article_top_widget(self, context, **kwargs): - """渲染文章顶部组件""" + """渲染文章顶部组件(钩子方法),子类重写此方法实现文章顶部内容""" return None def render_header_widget(self, context, **kwargs): - """渲染页头组件""" + """渲染页头组件(钩子方法),子类重写此方法实现页头内容""" return None def render_footer_widget(self, context, **kwargs): - """渲染页脚组件""" + """渲染页脚组件(钩子方法),子类重写此方法实现页脚内容""" return None def render_comment_before_widget(self, context, **kwargs): - """渲染评论前组件""" + """渲染评论前组件(钩子方法),子类重写此方法实现评论区前内容""" return None def render_comment_after_widget(self, context, **kwargs): - """渲染评论后组件""" + """渲染评论后组件(钩子方法),子类重写此方法实现评论区后内容""" return None # === 模板系统 === def render_template(self, template_name, context=None): """ - 渲染插件模板 - + 渲染插件自带的模板文件 + Args: - template_name: 模板文件名 - context: 模板上下文 - + template_name: 模板文件名(如"sidebar.html") + context: 模板上下文(传递给模板的变量) + Returns: - HTML字符串 + str: 渲染后的HTML字符串(模板不存在则返回空字符串) """ if context is None: - context = {} + context = {} # 默认为空上下文 + # 构建模板路径:plugins/插件标识/模板名(遵循Django模板查找规则) template_path = f"plugins/{self.plugin_slug}/{template_name}" try: + # 调用Django的render_to_string渲染模板 return render_to_string(template_path, context) except TemplateDoesNotExist: - logger.warning(f"Plugin template not found: {template_path}") + # 模板不存在时记录警告日志 + logger.warning(f"插件模板不存在:{template_path}") return "" # === 静态资源系统 === def get_static_url(self, static_file): - """获取插件静态文件URL""" - from django.templatetags.static import static + """ + 获取插件静态文件的URL(如CSS、JS、图片等) + + Args: + static_file: 静态文件相对路径(如"css/style.css") + + Returns: + str: 静态文件的完整URL(如"/static/myplugin/static/myplugin/css/style.css") + """ + from django.templatetags.static import static # 导入Django的static标签 + # 构建静态文件路径:插件标识/static/插件标识/文件路径(遵循Django静态文件规则) return static(f"{self.plugin_slug}/static/{self.plugin_slug}/{static_file}") def get_css_files(self): - """获取插件CSS文件列表""" + """ + 获取插件需要加载的CSS文件列表(钩子方法) + 子类重写此方法返回CSS文件路径列表,框架会自动在页面加载这些CSS + + Returns: + list: CSS文件路径列表(如["css/style.css"]) + """ return [] def get_js_files(self): - """获取插件JavaScript文件列表""" + """ + 获取插件需要加载的JavaScript文件列表(钩子方法) + 子类重写此方法返回JS文件路径列表,框架会自动在页面加载这些JS + + Returns: + list: JS文件路径列表(如["js/script.js"]) + """ return [] def get_head_html(self, context=None): - """获取需要插入到中的HTML内容""" + """ + 获取需要插入到HTML头部(标签内)的内容(钩子方法) + 子类重写此方法返回自定义HTML(如额外的CSS链接、meta标签等) + + Returns: + str: 要插入的HTML字符串 + """ return "" def get_body_html(self, context=None): - """获取需要插入到底部的HTML内容""" + """ + 获取需要插入到HTML body底部的内容(钩子方法) + 子类重写此方法返回自定义HTML(如额外的JS脚本) + + Returns: + str: 要插入底部的HTML字符串 + """ return "" def get_plugin_info(self): """ - 获取插件信息 - :return: 包含插件元数据的字典 + 获取插件的详细信息(用于插件管理、展示等) + + Returns: + dict: 包含插件元数据和配置的字典 """ return { 'name': self.PLUGIN_NAME, @@ -191,4 +244,4 @@ class BasePlugin: 'directory': str(self.plugin_dir), 'supported_positions': self.SUPPORTED_POSITIONS, 'priorities': self.POSITION_PRIORITIES - } + } \ No newline at end of file