插件能力

pull/12/head
liangliangyy 7 months ago
parent 3177109778
commit 650e16ea9d

@ -2,6 +2,7 @@ import logging
import os
import uuid
import markdown
from django.conf import settings
from django.core.paginator import Paginator
from django.http import HttpResponse, HttpResponseForbidden
@ -17,6 +18,7 @@ from haystack.views import SearchView
from blog.models import Article, Category, LinkShowType, Links, Tag
from comments.forms import CommentForm
from djangoblog.plugin_manage import hooks
from djangoblog.utils import cache, get_blog_setting, get_sha256
logger = logging.getLogger(__name__)
@ -154,7 +156,19 @@ class ArticleDetailView(DetailView):
kwargs['next_article'] = self.object.next_article
kwargs['prev_article'] = self.object.prev_article
return super(ArticleDetailView, self).get_context_data(**kwargs)
context = super(ArticleDetailView, self).get_context_data(**kwargs)
article = self.object
# Action Hook, 通知插件"文章详情已获取"
hooks.run_action('after_article_body_get', article=article, request=self.request)
# Filter Hook, 允许插件修改文章正文
article.body = hooks.apply_filters('the_content', article.body, article=article, request=self.request)
# toc = markdown.toc
md = markdown.Markdown(extensions=[
'markdown.extensions.extra',
])
article.body = md.convert(article.body)
return context
class CategoryDetailView(ArticleListView):

@ -0,0 +1,41 @@
import logging
logger = logging.getLogger(__name__)
class BasePlugin:
# 插件元数据
PLUGIN_NAME = None
PLUGIN_DESCRIPTION = None
PLUGIN_VERSION = None
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.")
self.init_plugin()
self.register_hooks()
def init_plugin(self):
"""
插件初始化逻辑
子类可以重写此方法来实现特定的初始化操作
"""
logger.info(f'{self.PLUGIN_NAME} initialized.')
def register_hooks(self):
"""
注册插件钩子
子类可以重写此方法来注册特定的钩子
"""
pass
def get_plugin_info(self):
"""
获取插件信息
:return: 包含插件元数据的字典
"""
return {
'name': self.PLUGIN_NAME,
'description': self.PLUGIN_DESCRIPTION,
'version': self.PLUGIN_VERSION
}

@ -0,0 +1,4 @@
ARTICLE_DETAIL_LOAD = 'article_detail_load'
ARTICLE_CREATE = 'article_create'
ARTICLE_UPDATE = 'article_update'
ARTICLE_DELETE = 'article_delete'

@ -0,0 +1,44 @@
import logging
logger = logging.getLogger(__name__)
_hooks = {}
def register(hook_name: str, callback: callable):
"""
注册一个钩子回调
"""
if hook_name not in _hooks:
_hooks[hook_name] = []
_hooks[hook_name].append(callback)
logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'")
def run_action(hook_name: str, *args, **kwargs):
"""
执行一个 Action Hook
它会按顺序执行所有注册到该钩子上的回调函数
"""
if hook_name in _hooks:
logger.debug(f"Running action hook '{hook_name}'")
for callback in _hooks[hook_name]:
try:
callback(*args, **kwargs)
except Exception as e:
logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True)
def apply_filters(hook_name: str, value, *args, **kwargs):
"""
执行一个 Filter Hook
它会把 value 依次传递给所有注册的回调函数进行处理
"""
if hook_name in _hooks:
logger.debug(f"Applying filter hook '{hook_name}'")
for callback in _hooks[hook_name]:
try:
value = callback(value, *args, **kwargs)
except Exception as e:
logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True)
return value

@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
import sys
from pathlib import Path
from django.utils.translation import gettext_lazy as _
@ -20,9 +21,8 @@ def env_to_bool(env, default):
return default if str_val is None else str_val == 'True'
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
@ -330,3 +330,18 @@ if os.environ.get('DJANGO_ELASTICSEARCH_HOST'):
'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine',
},
}
# Plugin System
PLUGINS_DIR = BASE_DIR / 'plugins'
ACTIVE_PLUGINS = [
'article_plugin_test',
]
# 加载插件
for plugin_dir in os.listdir(PLUGINS_DIR):
plugin_path = os.path.join(PLUGINS_DIR, plugin_dir)
if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, '__init__.py')):
try:
__import__(f'plugins.{plugin_dir}.plugin')
except ImportError:
pass

@ -0,0 +1,33 @@
from djangoblog.plugin_manage.base_plugin import BasePlugin
from djangoblog.plugin_manage import hooks
class ArticleCopyrightPlugin(BasePlugin):
# 1. 将插件元数据定义为类属性,以匹配 BasePlugin 的要求
PLUGIN_NAME = '文章结尾版权声明'
PLUGIN_DESCRIPTION = '一个在文章正文末尾添加版权声明的测试插件。'
PLUGIN_VERSION = '0.2.0'
# 也可以添加作者等其他自定义元数据
PLUGIN_AUTHOR = 'liangliangyy'
# 2. 实现 register_hooks 方法,专门用于注册钩子
def register_hooks(self):
# 在这里将插件的方法注册到指定的钩子上
hooks.register('the_content', self.add_copyright_to_content)
def add_copyright_to_content(self, content, *args, **kwargs):
"""
这个方法会被注册到 'the_content' 过滤器钩子上
它接收原始内容并返回添加了版权信息的新内容
"""
article = kwargs.get('article')
if not article:
return content
copyright_info = f"\n<hr><p>本文由 {article.author.username} 原创,转载请注明出处。</p>"
return content + copyright_info
# 3. 实例化插件。
# 这会自动调用 BasePlugin.__init__然后 BasePlugin.__init__ 会调用我们上面定义的 register_hooks 方法。
plugin = ArticleCopyrightPlugin()
Loading…
Cancel
Save