一、插件原理
Xadmin 的插件系統架構設計一定程度上借鑑了 wordpress
的設計。 想要了解 Xadmin 的插件系統架構首先需要了解 Xadmin AdminView
的概念。 簡單來說, 就是 Xadmin 系統中每一個頁面都是一個 AdminView
對象返回的 HttpResponse
結果。 Xadmin 的插件系統做的事情其實就是在 AdminView
運行過程中改變其執行的邏輯, 或是改變其返回的結果, 起到修改或增強原有功能的效果。 下面讓我們看看整個插件從製作完成到實際運行的整個過程。
首先需要創建自己的插件類, 插件類繼承 BaseAdminPlugin
:
class HelloWorldPlugin(BaseAdminPlugin):
...
開發好的插件首先要註冊到 Xadmin 中, 示例代碼如下:
# ListAdminView 是 Model 列表頁面
xadmin.site.register_plugin(HelloWorldPlugin, ListAdminView)
其中插件的註冊和使用可以參看 xadmin.sites.AdminSite.register_plugin()
。
當將插件註冊到 Xadmin 後, Xadmin 在創建 AdminView
實例的時候會將該插件放入實例的 plugins
屬性。 當 AdminView
在處理請求 時, 會首先逐個調用 plugins
中插件的 init_request()
方法, 插件在該方法中一般進行初始化的操作並且返回一個 Boolean 值告訴 AdminView
是否需要加載該插件。 當 init_request()
方法返回值爲 False
時, AdminView
不會加載該插件。 實例如下:
class HelloWorldPlugin(BaseAdminPlugin):
say_hello = False
# 初始化方法根據 ``say_hello`` 屬性值返回
def init_request(self, *args, **kwargs):
return bool(self.say_hello)
在以上實例中, 插件根據自身的 say_hello
屬性來決定是否讓自己被加載。 您可能會迷惑, say_hello
屬性看起來一直會是 False
呀, 那樣這個插件不是永遠不會被加載? 其實 Xadmin 在創建插件實例的時候會將 OptionClass
的同名屬性替換插件的屬性。 這樣, 在不同的 OptionClass
下會有不同的插件結果, 實例如下:
class ListAdminView(ModelAdminView):
# 可以被插件截獲或修改的方法使用該裝飾器裝飾
@filter_hook
def get_context(self):
...
使用 filter_hook()
裝飾的方法執行過程中會根據一定原則執行插件中的同名方法, 具體信息查考該裝飾器的文檔內容。
xadmin.views.base.filter_hook(func)
表明 AdminView 的方法可以被插件插入的裝飾器。 執行使用了該裝飾器的方法時, 會按照以下過程執行:
1. 首先將實例的 plugins 屬性取出, 取出含有同樣方法名的插件;
2. 按照插件方法的 priority 屬性排序;
3. 順序執行插件方法, 執行插件方法的規則:
如果插件方法沒有參數,AdminView 方法的返回結果不爲空則拋出異常;
如果插件方法的第一個參數爲 __ , 則 AdminView 方法將作爲第一個參數傳入, 注意, 這時還未執行該方法, 在插件中可以通過 __() 執行, 這樣就可以實現插件在 AdminView 方法執行前實現一些自己的邏輯, 例如:
def get_context(self, __):
c = {'key': 'value'}
c.update(__())
return c
如果插件方法的第一個參數不爲 __ , 則執行 AdminView 方法, 將結果作爲第一個參數傳入;
4. 最終將插件順序執行的結果返回;
根據該裝飾器的執行原則, 如果我們想修改上面示例中 ListAdminView
的 get_context
的返回值, 可以在插件中實現如下代碼:
class HelloWorldPlugin(BaseAdminPlugin):
# 在插件中加入同名方法,修改 ListAdminView 的 get_context 返回的值
def get_context(self, context):
context.update({'hello_target': 'World!!'})
return context
如果我們希望插件在 AdminView
的方法前執行, 或是完全使用自己的方法替代 AdminView
的方法可以這樣:
class HelloWorldPlugin(BaseAdminPlugin):
# 第一個參數爲 __ 。這樣 __ 即爲 ListAdminView 的 get_context 方法本身, 注意, 這時還沒有執行這個方法。
def get_context(self, __):
context = {'hello_target': 'World!!'}
# 我們可以在任何時候執行 AdminView 的方法, 或是根本不執行
context.update(__())
return context
至此, 加入的插件就實現了對 AdminView
方法的完全控制。
模板插件
我們知道, Django 中一個完整的 View 是包含模板的, 模板用來生成 View 最終返回的 HTML 內容。 當然, 插件也可以在模板中插入自己的內容。 我們來看看具體如何實現。
首先讓我們來看看 Xadmin 中的模板代碼示例片段 (change_list.html):
{% load xadmin %}
...
<form id="changelist-form" action="" method="post"{% view_block 'result_list_form' %}>{% csrf_token %}
{% view_block 'results_top' %}
<div class="results">
{% if results %}
...
其中的 view_block Tag 即爲插件的插入點。 插件可以在自己的插件類中使用 block_ + 插入點名稱
方法將 HTML 片段插入到頁面的這個位置, 示例如下:
class HelloWorldPlugin(BaseAdminPlugin):
# context 即爲 TemplateContext, nodes 參數包含了其他插件的返回內容。
# 您可以直接返回 HTML 片段, 或是將內容加入到 nodes 參數中
def block_results_top(self, context, nodes):
return s"<div class='info'>Hello %s</div>" % context['hello_target']
二、插件實例
下面讓我們來看一個 Xadmin 中完整的插件實例:
from django.template import loader
from xadmin.sites import site
from xadmin.views import BaseAdminPlugin, ListAdminView
REFRESH_VAR = '_refresh'
# 該插件實現了一個列表頁面刷新器的效果
class RefreshPlugin(BaseAdminPlugin):
# 用戶可以定製刷新的頻率,可以傳入多個值。該屬性會被 ``OptionClass`` 的同名屬性替換
refresh_times = []
def init_request(self, *args, **kwargs):
# 根據用戶是否制定了刷新器來決定是否啓動該插件
return bool(self.refresh_times)
# 插件攔截了返回 Media 的方法,加入自己需要的 js 文件。
def get_media(self, media):
if self.request.GET.get(REFRESH_VAR):
# 當頁面處於自動刷新狀態時,加入自己的 js 制定刷新邏輯
media.add_js([self.static('xadmin/js/refresh.js')])
return media
# Block Views
# 在頁面中插入 HTML 片段,顯示刷新選項。
def block_top_toolbar(self, context, nodes):
current_refresh = self.request.GET.get(REFRESH_VAR)
context.update({
'has_refresh': bool(current_refresh),
'clean_refresh_url': self.admin_view.get_query_string(remove=(REFRESH_VAR,)),
'current_refresh': current_refresh,
'refresh_times': [{
'time': r,
'url': self.admin_view.get_query_string({REFRESH_VAR: r}),
'selected': str(r) == current_refresh,
} for r in self.refresh_times],
})
# 可以將 HTML 片段加入 nodes 參數中
nodes.append(loader.render_to_string('xadmin/blocks/refresh.html', context_instance=context))
# 註冊插件
site.register_plugin(RefreshPlugin, ListAdminView)
最後不要忘記在適當的地方加載該代碼, 讓其執行。 一般情況下, 你可以將其寫到 adminx.py 文件中, 這樣, 只要您的 APP 加入到 Django Settings 的 INSTALL_APPS 中, Xadmin 就會自動執行 app 下的 adminx.py 文件。
三、插件開發
瞭解了插件的運行原理後我們就可以開發自己的插件了。 首先我們需要了解插件類中的實用方法。 因爲插件是繼承 BaseAdminPlugin
類, 而該類繼承自 BaseAdminObject
, 所以這兩個類的方法都可以在插件中使用。
Xadmin 在創建插件時會自動注入以下屬性到插件實例中:
- request: Http Request
- user: 當前 User 對象
- args: View 方法的 args 參數
- kwargs: View 方法的 kwargs 參數
- admin_view: AdminView 實例
- admin_site: Xadmin 的 admin_site 對象實例
如果 AdminView
是 ModelAdminView
的子類, 還會自動注入以下屬性:
- model: Model 對象
- opts: Model 的 _meta 屬性
接下來您應該考慮打算製作什麼功能的插件了。 不同功能的插件可能需要註冊到不同的 AdminView
上, Xadmin 系統中主要的 AdminView
有:
- BaseAdminView: 所有
AdminView
的基礎類, 註冊在該 View 上的插件可以影響所有的AdminView
; - CommAdminView: 用戶已經登錄後顯示的 View, 也是所有登錄後 View 的基礎類。 該 View主要作用是創建了 Xadmin 的通用元素, 例如: 系統菜單、 用戶信息等。 插件可以通過註冊該 View 來修改這些信息;
- ModelAdminView: 基於 Model 的
AdminView
的基礎類,註冊的插件可以影響所有基於 Model 的 View; - ListAdminView: Model 列表頁面 View;
- ModelFormAdminView: Model 編輯頁面 View;
- CreateAdminView: Model 創建頁面 View;
- UpdateAdminView: Model 修改頁面 View;
- DeleteAdminView: Model 刪除頁面 View;
- DetailAdminView: Model 詳情頁面 View;
選擇好目標 AdminView
後就要在自己的插件中編寫方法來修改或增強這些 AdminView
。 其中每個 AdminView
可以攔截的方法及其介紹請參看各 AdminView
的文檔。
四、插件規範
文檔模板:
"""
Name
======
作者
----
該插件的作者信息
功能
----
描述插件的主要功能
截圖
----
.. image:: /images/plugins/action.png
使用
----
描述插件的使用方法, 以及使用示例.
版本
----
描述插件的版本信息
API
---
.. autoclass:: XXX
"""
【“插件規範” 應該是官網爲了規範開發者製作插件的格式而統一制定的。 不知道理解的對不對, 希望能得到大神的指點。】