|
|
#wr 导入必要模块:日志用于记录插件运行信息,Path用于路径处理
|
|
|
#wr Django模板相关模块用于模板渲染及处理模板不存在的异常
|
|
|
import logging
|
|
|
from pathlib import Path
|
|
|
|
|
|
from django.template import TemplateDoesNotExist # wrDjango模板不存在时抛出的异常
|
|
|
from django.template.loader import render_to_string # wrDjango的模板渲染函数
|
|
|
|
|
|
#wr创建当前模块的日志记录器,用于记录插件相关日志
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
class BasePlugin:
|
|
|
"""
|
|
|
wr插件基类,所有自定义插件需继承此类并实现必要的抽象方法。
|
|
|
提供插件元数据管理、位置渲染、模板处理、静态资源管理等核心功能,
|
|
|
实现了插件系统的基础框架。
|
|
|
"""
|
|
|
|
|
|
# ===================== 插件元数据(子类必须定义) =====================
|
|
|
PLUGIN_NAME = None # wr插件名称(例如:"文章推荐插件")
|
|
|
PLUGIN_DESCRIPTION = None # wr插件描述(说明插件功能)
|
|
|
PLUGIN_VERSION = None # wr插件版本(例如:"1.0.0")
|
|
|
PLUGIN_AUTHOR = None #wr 插件作者(可选,可留空)
|
|
|
|
|
|
# ===================== 插件配置(子类可根据需求重写) =====================
|
|
|
SUPPORTED_POSITIONS = [] # wr插件支持的显示位置列表(例如:['sidebar', 'article_bottom'])
|
|
|
DEFAULT_PRIORITY = 100 #wr 默认优先级:数字越小,插件在同位置越靠前显示
|
|
|
POSITION_PRIORITIES = {} #wr 位置特定优先级:为不同位置单独设置优先级(覆盖默认值)
|
|
|
|
|
|
def __init__(self):
|
|
|
"""
|
|
|
wr初始化插件实例。
|
|
|
检查元数据完整性,获取插件路径和标识符,执行初始化逻辑并注册钩子。
|
|
|
"""
|
|
|
#wr 校验插件元数据:名称、描述、版本为必填项,未定义则抛出异常
|
|
|
if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]):
|
|
|
raise ValueError("插件元数据(PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION)必须定义。")
|
|
|
|
|
|
#wr 获取插件所在目录路径和唯一标识符(slug)
|
|
|
self.plugin_dir = self._get_plugin_directory() # 插件目录路径(Path对象)
|
|
|
self.plugin_slug = self._get_plugin_slug() # 插件唯一标识(默认使用目录名)
|
|
|
|
|
|
# wr执行插件初始化逻辑(子类可重写)
|
|
|
self.init_plugin()
|
|
|
# wr注册插件钩子(子类可重写以注册自定义钩子)
|
|
|
self.register_hooks()
|
|
|
|
|
|
def _get_plugin_directory(self):
|
|
|
"""
|
|
|
wr获取插件所在的目录路径。
|
|
|
通过反射获取当前插件类所在的文件路径,进而得到目录路径。
|
|
|
"""
|
|
|
import inspect
|
|
|
# 获取当前类(子类)的定义文件路径
|
|
|
plugin_file = inspect.getfile(self.__class__)
|
|
|
# 返回文件所在的目录路径
|
|
|
return Path(plugin_file).parent
|
|
|
|
|
|
def _get_plugin_slug(self):
|
|
|
"""
|
|
|
wr获取插件的唯一标识符(slug)。
|
|
|
默认使用插件目录的名称作为slug,确保唯一性。
|
|
|
"""
|
|
|
return self.plugin_dir.name
|
|
|
|
|
|
def init_plugin(self):
|
|
|
"""
|
|
|
wr插件初始化逻辑(钩子方法)。
|
|
|
子类可重写此方法实现自定义初始化操作(如加载配置、连接数据库等)。
|
|
|
默认仅记录初始化日志。
|
|
|
"""
|
|
|
logger.info(f'{self.PLUGIN_NAME} 已初始化。')
|
|
|
|
|
|
def register_hooks(self):
|
|
|
"""
|
|
|
wr注册插件钩子(钩子方法)。
|
|
|
子类可重写此方法注册自定义钩子(如监听系统事件、注册URL路由等)。
|
|
|
默认不执行任何操作。
|
|
|
"""
|
|
|
pass
|
|
|
|
|
|
# ===================== 位置渲染系统(核心功能) =====================
|
|
|
def render_position_widget(self, position, context, **kwargs):
|
|
|
"""
|
|
|
wr根据指定位置渲染插件组件,是位置渲染的入口方法。
|
|
|
|
|
|
Args:
|
|
|
position: 位置标识(如'sidebar'表示侧边栏)
|
|
|
context: Django模板上下文(包含页面渲染所需数据)
|
|
|
**kwargs: 额外参数(如文章ID、用户信息等,按需传递)
|
|
|
|
|
|
Returns:
|
|
|
dict: 包含渲染结果的字典({'html': HTML内容, 'priority': 优先级, 'plugin_name': 插件名})
|
|
|
若位置不支持或无需显示,则返回None
|
|
|
"""
|
|
|
#wr 检查当前位置是否在插件支持的位置列表中,不支持则直接返回None
|
|
|
if position not in self.SUPPORTED_POSITIONS:
|
|
|
return None
|
|
|
|
|
|
#wr 检查插件是否应在当前位置显示(调用should_display判断)
|
|
|
if not self.should_display(position, context, **kwargs):
|
|
|
return None
|
|
|
|
|
|
#wr 动态拼接当前位置对应的渲染方法名(如position为'sidebar',则方法名为'render_sidebar_widget')
|
|
|
method_name = f'render_{position}_widget'
|
|
|
#wr 检查当前类是否实现了对应位置的渲染方法
|
|
|
if hasattr(self, method_name):
|
|
|
#wr 调用对应方法获取HTML内容
|
|
|
html = getattr(self, method_name)(context, **kwargs)
|
|
|
#wr 若渲染成功(返回非空HTML),则构造结果字典
|
|
|
if html:
|
|
|
#wr 优先级:优先使用位置特定优先级,否则使用默认优先级
|
|
|
priority = self.POSITION_PRIORITIES.get(position, self.DEFAULT_PRIORITY)
|
|
|
return {
|
|
|
'html': html,
|
|
|
'priority': priority,
|
|
|
'plugin_name': self.PLUGIN_NAME
|
|
|
}
|
|
|
|
|
|
#wr 若未实现对应渲染方法或渲染失败,返回None
|
|
|
return None
|
|
|
|
|
|
def should_display(self, position, context, **kwargs):
|
|
|
"""
|
|
|
wr判断插件是否应在指定位置显示(钩子方法)。
|
|
|
子类可重写此方法实现条件显示逻辑(如仅在特定页面/用户组显示)。
|
|
|
默认返回True(始终显示)。
|
|
|
|
|
|
Args:
|
|
|
position: 位置标识
|
|
|
context: 模板上下文
|
|
|
**kwargs: 额外参数
|
|
|
|
|
|
Returns:
|
|
|
bool: True表示显示,False表示不显示
|
|
|
"""
|
|
|
return True
|
|
|
|
|
|
# ===================== 位置渲染方法(子类需按需重写) =====================
|
|
|
def render_sidebar_widget(self, context, **kwargs):
|
|
|
"""wr渲染侧边栏组件(钩子方法)。子类重写此方法实现侧边栏显示内容。"""
|
|
|
return None
|
|
|
|
|
|
def render_article_bottom_widget(self, context, **kwargs):
|
|
|
"""wr渲染文章底部组件(钩子方法)。子类重写此方法实现文章底部显示内容。"""
|
|
|
return None
|
|
|
|
|
|
def render_article_top_widget(self, context, **kwargs):
|
|
|
"""wr渲染文章顶部组件(钩子方法)。子类重写此方法实现文章顶部显示内容。"""
|
|
|
return None
|
|
|
|
|
|
def render_header_widget(self, context, **kwargs):
|
|
|
"""wr渲染页头组件(钩子方法)。子类重写此方法实现页头显示内容。"""
|
|
|
return None
|
|
|
|
|
|
def render_footer_widget(self, context, **kwargs):
|
|
|
"""wr渲染页脚组件(钩子方法)。子类重写此方法实现页脚显示内容。"""
|
|
|
return None
|
|
|
|
|
|
def render_comment_before_widget(self, context, **kwargs):
|
|
|
"""wr渲染评论前组件(钩子方法)。子类重写此方法实现评论区前显示内容。"""
|
|
|
return None
|
|
|
|
|
|
def render_comment_after_widget(self, context, **kwargs):
|
|
|
"""wr渲染评论后组件(钩子方法)。子类重写此方法实现评论区后显示内容。"""
|
|
|
return None
|
|
|
|
|
|
# ===================== 模板系统(插件模板渲染) =====================
|
|
|
def render_template(self, template_name, context=None):
|
|
|
"""
|
|
|
wr 渲染插件自带的模板文件。
|
|
|
模板路径固定为"plugins/[插件slug]/[模板文件名]"。
|
|
|
|
|
|
Args:
|
|
|
template_name: 模板文件名(如"sidebar.html")
|
|
|
context: 模板上下文(字典类型,默认为空)
|
|
|
|
|
|
Returns:
|
|
|
str: 渲染后的HTML字符串;若模板不存在,返回空字符串并记录警告日志
|
|
|
"""
|
|
|
if context is None:
|
|
|
context = {} # 确保上下文不为None
|
|
|
|
|
|
#wr 构造模板路径:插件模板需放在"plugins/插件slug/"目录下
|
|
|
template_path = f"plugins/{self.plugin_slug}/{template_name}"
|
|
|
|
|
|
try:
|
|
|
#wr 使用Django的render_to_string渲染模板
|
|
|
return render_to_string(template_path, context)
|
|
|
except TemplateDoesNotExist:
|
|
|
#wr 模板不存在时记录警告日志
|
|
|
logger.warning(f"插件模板不存在:{template_path}")
|
|
|
return ""
|
|
|
|
|
|
# ===================== 静态资源系统(CSS/JS等资源管理) =====================
|
|
|
def get_static_url(self, static_file):
|
|
|
"""
|
|
|
wr获取插件静态文件的URL。
|
|
|
静态文件需放在插件目录的"static/[插件slug]/"下(遵循Django静态文件规范)。
|
|
|
|
|
|
Args:
|
|
|
static_file: 静态文件相对路径(如"css/style.css")
|
|
|
|
|
|
Returns:
|
|
|
str: 静态文件的完整URL(如"/static/demo_plugin/css/style.css")
|
|
|
"""
|
|
|
from django.templatetags.static import static # wr导入Django静态文件工具
|
|
|
#wr 构造静态文件路径并生成URL
|
|
|
return static(f"{self.plugin_slug}/static/{self.plugin_slug}/{static_file}")
|
|
|
|
|
|
def get_css_files(self):
|
|
|
"""
|
|
|
wr获取插件所需的CSS文件列表(钩子方法)。
|
|
|
子类重写此方法返回CSS文件路径列表,系统会自动在页面加载这些CSS。
|
|
|
|
|
|
Returns:
|
|
|
list: CSS文件路径列表(如["css/style.css"])
|
|
|
"""
|
|
|
return []
|
|
|
|
|
|
def get_js_files(self):
|
|
|
"""
|
|
|
wr获取插件所需的JavaScript文件列表(钩子方法)。
|
|
|
子类重写此方法返回JS文件路径列表,系统会自动在页面加载这些JS。
|
|
|
|
|
|
Returns:
|
|
|
list: JS文件路径列表(如["js/script.js"])
|
|
|
"""
|
|
|
return []
|
|
|
|
|
|
def get_head_html(self, context=None):
|
|
|
"""
|
|
|
wr获取需要插入到HTML头部(<head>标签内)的内容(钩子方法)。
|
|
|
子类重写此方法返回自定义头部内容(如额外的CSS链接、meta标签等)。
|
|
|
|
|
|
Args:
|
|
|
context: 模板上下文
|
|
|
|
|
|
Returns:
|
|
|
str: 需插入到<head>的HTML字符串
|
|
|
"""
|
|
|
return ""
|
|
|
|
|
|
def get_body_html(self, context=None):
|
|
|
"""
|
|
|
wr获取需要插入到HTML底部(</body>标签前)的内容(钩子方法)。
|
|
|
子类重写此方法返回自定义底部内容(如额外的JS脚本)。
|
|
|
|
|
|
Args:
|
|
|
context: 模板上下文
|
|
|
|
|
|
Returns:
|
|
|
str: 需插入到<body>底部的HTML字符串
|
|
|
"""
|
|
|
return ""
|
|
|
|
|
|
def get_plugin_info(self):
|
|
|
"""
|
|
|
wr获取插件的详细信息(元数据+配置)。
|
|
|
用于插件管理界面展示插件信息。
|
|
|
|
|
|
Returns:
|
|
|
dict: 包含插件信息的字典
|
|
|
"""
|
|
|
return {
|
|
|
'name': self.PLUGIN_NAME, #wr 插件名称
|
|
|
'description': self.PLUGIN_DESCRIPTION, #wr 插件描述
|
|
|
'version': self.PLUGIN_VERSION, #wr 插件版本
|
|
|
'author': self.PLUGIN_AUTHOR, #wr 插件作者
|
|
|
'slug': self.plugin_slug, #wr 插件唯一标识
|
|
|
'directory': str(self.plugin_dir), #wr 插件目录路径
|
|
|
'supported_positions': self.SUPPORTED_POSITIONS, # wr支持的显示位置
|
|
|
'priorities': self.POSITION_PRIORITIES #wr 各位置的优先级
|
|
|
} |