外掛教學¶
本教學基於 Redmine 4.x 和 3.x 版本。
您可以查看本教學的先前版本。
Redmine 1.x
Redmine 2.x
本教學假設您熟悉 Ruby on Rails 框架。
- 目錄
- 外掛教學
建立新的外掛¶
您可能需要設定 RAILS_ENV 變數才能使用以下指令
$ export RAILS_ENV="production"
在 Windows 上
$ set RAILS_ENV=production
可以使用 Redmine 外掛產生器建立新的外掛。
此產生器的語法為
bundle exec rails generate redmine_plugin <plugin_name>
因此,請打開命令提示字元並「cd」到您的 Redmine 目錄,然後執行以下指令
$ bundle exec rails generate redmine_plugin Polls create plugins/polls/app create plugins/polls/app/controllers create plugins/polls/app/helpers create plugins/polls/app/models create plugins/polls/app/views create plugins/polls/db/migrate create plugins/polls/lib/tasks create plugins/polls/assets/images create plugins/polls/assets/javascripts create plugins/polls/assets/stylesheets create plugins/polls/config/locales create plugins/polls/test create plugins/polls/test/fixtures create plugins/polls/test/unit create plugins/polls/test/functional create plugins/polls/test/integration create plugins/polls/test/system create plugins/polls/README.rdoc create plugins/polls/init.rb create plugins/polls/config/routes.rb create plugins/polls/config/locales/en.yml create plugins/polls/test/test_helper.rb
外掛結構建立在 plugins/polls
中。編輯 plugins/polls/init.rb
以調整外掛資訊(名稱、作者、描述和版本)
Redmine::Plugin.register :polls do
name 'Polls plugin'
author 'Author name'
description 'This is a plugin for Redmine'
version '0.0.1'
url 'http://example.com/path/to/plugin'
author_url 'http://example.com/about'
end
然後重新啟動應用程式,並將您的瀏覽器指向 https://127.0.0.1:3000/admin/plugins。
登入後,您應該會在外掛清單中看到您的新外掛
注意:對外掛的 init.rb
檔案所做的任何變更都需要重新啟動應用程式,因為它不會在每個請求時重新載入。
產生模型¶
目前外掛不會儲存任何東西。讓我們為外掛建立一個簡單的 Poll 模型。語法為
bundle exec rails generate redmine_plugin_model <plugin_name> <model_name> [field[:type][:index] field[:type][:index] ...]
因此,請前往命令提示字元並執行
$ bundle exec rails generate redmine_plugin_model polls poll question:string yes:integer no:integer create plugins/polls/app/models/poll.rb create plugins/polls/test/unit/poll_test.rb create plugins/polls/db/migrate/xxxxxxxxxxxx_create_polls.rb
這會在 plugins/polls/db/migrate
中建立 Poll 模型和對應的移轉檔案 xxxxxxxxxxxx_create_polls.rb
class CreatePolls < ActiveRecord::Migration[5.2]
def change
create_table :polls do |t|
t.string :question
t.integer :yes, default: 0
t.integer :no, default: 0
end
end
end
注意:對於 Redmine 3.x,class CreatePolls < ActiveRecord::Migration[5.2]
為 class CreatePolls < ActiveRecord::Migration
。
您可以調整移轉檔案(例如預設值...),然後使用以下指令移轉資料庫
$ bundle exec rake redmine:plugins:migrate Migrating polls (Polls plugin)... == CreatePolls: migrating ==================================================== -- create_table(:polls) -> 0.0410s == CreatePolls: migrated (0.0420s) ===========================================
請注意,每個外掛都有自己的一組移轉。
讓我們在控制台中新增一些 Polls,以便我們有一些東西可以使用。控制台是您可以互動地工作和檢查 Redmine 環境的地方,並且非常適合用來測試。但現在我們只需要建立兩個 Poll 物件
bundle exec rails console >> Poll.create(question: "Can you see this poll") >> Poll.create(question: "And can you see this other poll") >> exit
在您的外掛目錄中編輯 plugins/polls/app/models/poll.rb
,以新增將從控制器呼叫的 #vote 方法
class Poll < ActiveRecord::Base
def vote(answer)
increment(answer == 'yes' ? :yes : :no)
end
end
產生控制器¶
目前,外掛不會執行任何動作。因此,讓我們為外掛建立一個控制器。
我們可以使用插件控制器生成器。語法如下:
bundle exec rails generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]
回到命令提示字元並執行:
$ bundle exec rails generate redmine_plugin_controller Polls polls index vote create plugins/polls/app/controllers/polls_controller.rb create plugins/polls/app/helpers/polls_helper.rb create plugins/polls/test/functional/polls_controller_test.rb create plugins/polls/app/views/polls/index.html.erb create plugins/polls/app/views/polls/vote.html.erb
這會建立一個帶有 2 個動作(#index
和 #vote
)的控制器 PollsController
。
編輯 plugins/polls/app/controllers/polls_controller.rb
來實作這 2 個動作。
class PollsController < ApplicationController
def index
@polls = Poll.all
end
def vote
poll = Poll.find(params[:id])
poll.vote(params[:answer])
if poll.save
flash[:notice] = 'Vote saved.'
end
redirect_to polls_path(project_id: params[:project_id])
end
end
然後編輯 plugins/polls/app/views/polls/index.html.erb
,它將顯示現有的投票。
<h2>Polls</h2>
<% @polls.each do |poll| %>
<p>
<%= poll.question %>?
<%= link_to 'Yes', { action: 'vote', id: poll[:id], answer: 'yes', project_id: @project }, method: :post %> <%= poll.yes %> /
<%= link_to 'No', { action: 'vote', id: poll[:id], answer: 'no', project_id: @project }, method: :post %> <%= poll.no %>
</p>
<% end %>
您可以移除 plugins/polls/app/views/polls/vote.html.erb
,因為 #vote
動作不執行任何渲染。
新增路由¶
Redmine 不提供預設的萬用字元路由(':controller/:action/:id'
)。插件必須在其適當的 config/routes.rb
檔案中宣告所需的路由。因此,請編輯 plugins/polls/config/routes.rb
,為這 2 個動作新增 2 個路由。
get 'polls', to: 'polls#index'
post 'post/:id/vote', to: 'polls#vote'
您可以在這裡找到有關 Rails 路由的更多資訊:https://rails-guides.dev.org.tw/routing.html。
現在,重新啟動應用程式並將您的瀏覽器指向 https://127.0.0.1:3000/polls。
您應該會看到 2 個投票,並且應該可以為它們投票。
國際化¶
翻譯檔必須儲存在 config/locales 中,例如 plugins/polls/config/locales/
。
擴展選單¶
我們的控制器運作良好,但使用者必須知道網址才能看到投票。使用 Redmine 插件 API,您可以擴展標準選單。
因此,讓我們在應用程式選單中新增一個新項目。
擴展應用程式選單¶
編輯插件目錄根目錄下的 plugins/polls/init.rb
,在插件註冊區塊的末尾新增以下行:
Redmine::Plugin.register :redmine_polls do
[...]
menu :application_menu, :polls, { controller: 'polls', action: 'index' }, caption: 'Polls'
end
語法如下:
menu(menu_name, item_name, url, options={})
您可以擴展五個選單:
:top_menu
- 左上角的選單:account_menu
- 右上角包含登入/登出連結的選單:application_menu
- 使用者不在專案內時顯示的主選單:project_menu
- 使用者在專案內時顯示的主選單:admin_menu
- 管理頁面上顯示的選單(只能插入「設定」之後、「插件」之前)
可用的選項有:
:param
- 用於專案 ID 的參數鍵(預設為:id
):if
- 在渲染項目之前呼叫的 Proc,僅當它返回 true 時才會顯示該項目:caption
- 選單標題,可以是:- 本地化的字串符號
- 字串
- 可以將專案作為參數的 Proc
:before
、:after
- 指定應插入選單項目的位置(例如after: :activity
):first
、:last
- 如果設定為 true,則該項目將保留在選單的開頭/結尾(例如last: true
):html
- 傳遞給link_to
的 html 選項雜湊,用於渲染選單項目
在我們的範例中,我們在應用程式選單中新增了一個預設為空的項目。
重新啟動應用程式並前往 https://127.0.0.1:3000/projects
現在,您可以透過點擊當使用者不在專案內時出現的「投票」標籤來存取投票。
擴展專案選單¶
現在,讓我們考慮投票是在專案層級定義的(即使在我們的範例投票模型中並非如此)。因此,我們希望將「投票」標籤新增到專案選單中。
開啟 init.rb
並將剛才新增的行替換為以下 2 行:
Redmine::Plugin.register :redmine_polls do
[...]
permission :polls, { polls: [:index, :vote] }, public: true
menu :project_menu, :polls, { controller: 'polls', action: 'index' }, caption: 'Polls', after: :activity, param: :project_id
end
第二行將我們的「投票」標籤添加到專案選單中,緊鄰活動標籤之後。 第一行是必需的,它聲明 PollsController
中的 2 個動作是公開的。我們稍後會詳細解釋這一點。 重新啟動應用程式並轉到您的一個專案
如果您點選「投票」標籤(位於第三個位置),您應該會注意到專案選單不再顯示。
要顯示專案選單,您必須初始化控制器的實例變數 @project
。
編輯您的 PollsController 來執行此操作
def index
@project = Project.find(params[:project_id])
@polls = Poll.all # @project.polls
end
def vote
poll = Poll.find(params[:id])
poll.vote(params[:answer])
if poll.save
flash[:notice] = 'Vote saved.'
end
redirect_to :action => 'index', project_id: params[:project_id]
end
新增您的 routes.rb 來執行此操作
post 'projects/:project_id/post/:id/vote', to: 'polls#vote', as: 'vote_poll'
編輯您的 views/polls/index.heml.erb 來執行此操作
<h2>Polls</h2>
<% @polls.each do |poll| %>
<p>
<%= poll.question %>?
<%= link_to 'Yes', { action: 'vote', id: poll.id, answer: 'yes', project_id: @project.id }, method: :post %> (<%= poll.yes %>) /
<%= link_to 'No', { action: 'vote', id: poll.id, answer: 'no', project_id: @project.id }, method: :post %> (<%= poll.no %>)
</p>
<% end %>
專案 ID 在 :project_id
參數中可用,因為上述選單項目聲明中使用了 param: :project_id
選項。
現在,您應該在檢視投票時看到專案選單
移除選單中的項目¶
要移除選單中的項目,您可以使用 delete_menu_item
,如下例所示
Redmine::Plugin.register :redmine_polls do
[...]
delete_menu_item :top_menu, :my_page
delete_menu_item :top_menu, :help
delete_menu_item :project_menu, :overview
delete_menu_item :project_menu, :activity
delete_menu_item :project_menu, :news
end
新增新權限¶
目前,任何人都可以為投票投票。 讓我們通過更改權限聲明使其更具可配置性。
我們將聲明 2 個基於專案的權限,一個用於檢視投票,另一個用於投票。這些權限不再是公開的(已移除 public: true
選項)。
編輯 plugins/polls/init.rb
以使用這兩行替換之前的權限聲明
permission :view_polls, polls: :index
permission :vote_polls, polls: :vote
重新啟動應用程式並轉到 https://127.0.0.1:3000/roles/permissions
您現在可以將這些權限授予您現有的角色。
當然,需要在 PollsController 中添加一些程式碼,以便根據當前用戶的權限實際保護操作。為此,我們只需要附加 :authorize
過濾器,並確保在呼叫此過濾器之前正確設置了 Herve Harster 實例變數。
以下是 #index
操作的外觀
class PollsController < ApplicationController
before_action :find_project, :authorize, only: [:index, :vote]
[...]
def index
@polls = Poll.all # @project.polls
end
[...]
private
def find_project
# @project variable must be set before calling the authorize filter
@project = Project.find(params[:project_id])
end
end
可以使用類似的方式在 #vote
操作之前檢索當前專案。
在此之後,只有管理員用戶或在專案中具有適當角色的用戶才能檢視和投票。
如果您想以多語言方式顯示權限的符號,則需要在語言文件中添加必要的文字標籤。
只需在 plugins/polls/config/locales
中建立一個 *.yml(例如 en.yml
)文件,並使用如下標籤填充它
"en":
permission_view_polls: View Polls
permission_vote_polls: Vote Polls
在此範例中,建立的文件名為 en.yml
,但也支援所有其他語言文件。
如您在上面的範例中所見,標籤由權限符號 :view_polls
和 :vote_polls
組成,前面附加了一個額外的 permission_
。
重新啟動您的應用程式並指向權限部分。
建立專案模組¶
目前,投票功能已添加到您的所有專案中。 但是您可能只想為某些專案啟用投票。
那麼,讓我們建立一個「投票」專案模組。 這是通過在對 #project_module
的呼叫中包裝權限聲明來完成的。
編輯 init.rb
並更改權限聲明
project_module :polls do
permission :view_polls, polls: :index
permission :vote_polls, polls: :vote
end
重新啟動應用程式並轉到您的一個專案設定。
點選「模組」標籤。 您應該會在模組列表的末尾看到「投票」模組(預設情況下處於停用狀態)
您現在可以在專案級別啟用/停用投票。
改進外掛檢視¶
新增樣式表¶
讓我們從向外掛檢視添加樣式表開始。
在 plugins/polls/assets/stylesheets
目錄中建立一個名為 voting.css
的文件
a.vote { font-size: 120%; }
a.vote.yes { color: green; }
a.vote.no { color: red; }
啟動應用程式時,插件資源會自動複製到 public/plugin_assets/polls/
,以便透過網路伺服器取得。因此,若要變更插件樣式表或 JavaScript,需要重新啟動應用程式。
連結需要使用引入的類別。因此,請在 plugins/polls/app/views/polls/index.html.erb
檔案中將連結宣告變更為
<%= link_to 'Yes', {action: 'vote', id: poll[:id], answer: 'yes', project_id: @project }, method: :post, class: 'vote yes' %> (<%= poll.yes %>)
<%= link_to 'No', {action: 'vote', id: poll[:id], answer: 'no', project_id: @project }, method: :post, class: 'vote no' %> (<%= poll.no %>)
然後,在 index.html.erb
的末尾附加以下幾行,以便 Redmine 將您的樣式表包含在頁面標題中
<% content_for :header_tags do %>
<%= stylesheet_link_tag 'voting', plugin: 'polls' %>
<% end %>
請注意,呼叫 stylesheet_link_tag
輔助函式時,需要使用 plugin: 'polls'
選項。
可以使用 javascript_include_tag
輔助函式,以相同的方式在插件視圖中包含 JavaScript。
設定頁面標題¶
您可以使用 html_title
輔助函式,從視圖內部設定 HTML 標題。
範例
<% html_title "Polls" %>
使用掛鉤¶
視圖中的掛鉤¶
Redmine 視圖中的掛鉤可讓您將自訂內容插入常規 Redmine 視圖中。例如,查看 source:tags/2.0.0/app/views/projects/show.html.erb#L52 顯示有 2 個可用的掛鉤:一個名為 :view_projects_show_left
,用於將內容添加到左側;另一個名為 :view_projects_show_right
,用於將內容添加到視圖的右側。
若要在視圖中使用一個或多個掛鉤,您需要建立一個繼承自 Redmine::Hook::ViewListener
的類別,並使用您要使用的掛鉤名稱來實作方法。若要將一些內容附加到專案總覽,請將一個類別添加到您的插件中,並在 init.rb
中引用它,然後實作名稱與掛鉤名稱相符的方法。
對於我們的插件,請使用以下內容建立一個檔案 plugins/polls/lib/polls_hook_listener.rb
class PollsHookListener < Redmine::Hook::ViewListener
def view_projects_show_left(context = {})
return content_tag("p", "Custom content added to the left")
end
def view_projects_show_right(context = {})
return content_tag("p", "Custom content added to the right")
end
end
將此行添加到 plugins/polls/init.rb
的開頭
require_dependency File.expand_path('../lib/polls_hook_listener', __FILE__)
重新啟動 Redmine 並查看專案的總覽標籤。您應該會在總覽的左側和右側看到字串。
您也可以使用 render_on
輔助函式來渲染部分內容。在我們的插件中,您必須將 plugins/polls/lib/polls_hook_listener.rb
中剛才建立的內容替換為
class PollsHookListener < Redmine::Hook::ViewListener
render_on :view_projects_show_left, partial: "polls/project_overview"
end
透過建立檔案 app/views/polls/_project_overview.html.erb
,將部分內容添加到您的插件中。其內容(使用一些文字,例如「來自掛鉤的訊息!」)將附加到專案總覽的左側。不要忘記重新啟動 Redmine。
控制器中的掛鉤¶
TODO
使您的插件可配置¶
在 Redmine 中註冊的每個插件都會顯示在管理/插件頁面上。設定控制器提供了對基本配置機制的支援。透過在插件的 init.rb 檔案中將「settings」方法添加到插件註冊區塊,即可啟用此功能。
Redmine::Plugin.register :polls do
[ ... ]
settings default: {'empty' => true}, partial: 'settings/poll_settings'
end
添加此方法將完成兩件事。首先,它會在管理/插件列表中為插件的說明區塊添加一個「配置」連結。點擊此連結將載入一個通用的插件配置範本視圖,該視圖將依次渲染 :partial 所引用的部分視圖。呼叫 settings 方法還將在插件的設定模組中添加支援。設定模型將根據插件名稱儲存和檢索序列化雜湊。可以使用設定方法名稱以 plugin_<插件名稱> 的形式存取此雜湊。對於此範例,可以透過呼叫 Setting.plugin_polls 來存取雜湊。
傳遞給 settings 方法的 :partial 雜湊鍵所引用的視圖將作為插件配置視圖中的部分載入。基本頁面佈局受插件配置視圖的限制:會宣告一個表單並產生提交按鈕。部分內容會被拉入表單內部表格 div 中的視圖中。插件的配置設定將會顯示,並且可以透過標準 HTML 表單元素進行修改。
注意:如果兩個插件的設定檔名有部分相同,則第一個插件會覆蓋第二個插件的設定頁面。因此,請確保為您的設定檔名提供唯一的名稱。
當頁面提交時,settings_controller 將採用由 'settings' 引用的參數雜湊,並以序列化格式將其直接儲存在 Setting.plugin_polls 中。每次產生頁面時,Setting.plugin_polls 的當前值將被分配給局部變數 settings。
建立一個名為 plugins/polls/app/views/settings/_poll_settings.erb
的檔案,並在其中填入以下內容
<table>
<tbody>
<tr>
<th>Notification Default Address</th>
<td>
<input type="text" id="settings_notification_default"
value="<%= settings['notification_default'] %>"
name="settings[notification_default]" >
</td>
</tr>
</tbody>
</table>
在上面的範例中,設定表單不是使用 Rails 表單助手建立的。這是因為沒有 @settings 模型,只有 setting 雜湊。表單助手將嘗試使用不存在的模型存取器方法來存取屬性。例如,對 @settings.notification_default 的呼叫將會失敗。此表單設定的值將作為 Setting.plugin_polls['notification_default'] 存取。
最後,settings 方法呼叫中的 :default 是用於註冊一個值,如果設定表中沒有為此插件儲存任何內容,則將從 Setting.plugin_polls 呼叫中返回該值。
測試您的插件¶
plugins/polls/test/test_helper.rb¶
這是我的測試助手檔案的內容
require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
範例測試¶
適用於 Redmine 4.x 的 plugins/polls/test/functional/polls_controller_test.rb
內容
require File.expand_path('../../test_helper', __FILE__)
class PollsControllerTest < ActionController::TestCase
fixtures :projects
def test_index
get :index, params: { project_id: 1 }
assert_response :success
assert_template 'index'
end
end
適用於 Redmine 3.x 的 plugins/polls/test/functional/polls_controller_test.rb
內容
require File.expand_path('../../test_helper', __FILE__)
class PollsControllerTest < ActionController::TestCase
fixtures :projects
def test_index
get :index, project_id: 1
assert_response :success
assert_template 'index'
end
end
執行測試¶
如有必要,初始化測試資料庫
$ RAILS_ENV=test bundle exec rake db:drop db:create db:migrate redmine:plugins:migrate redmine:load_default_data
執行 polls_controller_test.rb
$ RAILS_ENV=test bundle exec rake test TEST=plugins/polls/test/functional/polls_controller_test.rb
使用權限測試¶
如果您的插件需要專案成員資格,請在功能測試的開頭添加以下內容
def test_index
@request.session[:user_id] = 2
...
end
如果您的插件需要特定權限,您可以將其添加到使用者角色中,如下所示(在 fixtures 中查找適合使用者的角色)
def test_index
Role.find(1).add_permission! :my_permission
...
end
您可以啟用/禁用特定模組,如下所示
def test_index
Project.find(1).enabled_module_names = [:mymodule]
...
end
參考檔案階層¶
這裡簡單列出了本教學和鉤子中提到的所有檔案和目錄。這有助於確保使用標準路徑,也有助於新手知道檔案應該放在哪裡。
plugins/PLUGIN/README.rdoc plugins/PLUGIN/init.rb plugins/PLUGIN/app/ plugins/PLUGIN/app/controllers/ plugins/PLUGIN/app/controllers/CONTROLLER_controller.rb plugins/PLUGIN/app/helpers/ plugins/PLUGIN/app/helpers/CONTROLLER_helper.rb plugins/PLUGIN/app/models/ plugins/PLUGIN/app/models/MODEL.rb plugins/PLUGIN/app/views/ plugins/PLUGIN/app/views/CONTROLLER/ plugins/PLUGIN/app/views/CONTROLLER/_PARTIAL.html.erb plugins/PLUGIN/app/views/CONTROLLER/CONTROLLER-ACTION.html.erb plugins/PLUGIN/app/views/hooks/ plugins/PLUGIN/app/views/hooks/_HOOK.html.erb plugins/PLUGIN/app/views/settings/ plugins/PLUGIN/app/views/settings/_MODEL_settings.html.erb plugins/PLUGIN/assets/ plugins/PLUGIN/assets/images/ plugins/PLUGIN/assets/javascripts/ plugins/PLUGIN/assets/stylesheets/ plugins/PLUGIN/assets/stylesheets/voting.css plugins/PLUGIN/config/ plugins/PLUGIN/config/locales/ plugins/PLUGIN/config/locales/en.yml plugins/PLUGIN/config/routes.rb plugins/PLUGIN/db/ plugins/PLUGIN/db/migrate/ plugins/PLUGIN/db/migrate/001_create_MODELs.rb plugins/PLUGIN/lib/ plugins/PLUGIN/lib/PLUGIN_hook_listener.rb plugins/PLUGIN/lib/PLUGIN/ plugins/PLUGIN/lib/PLUGIN/hooks.rb plugins/PLUGIN/lib/PLUGIN/MODEL_patch.rb plugins/PLUGIN/lib/tasks/ plugins/PLUGIN/test/ plugins/PLUGIN/test/test_helper.rb plugins/PLUGIN/test/functional/ plugins/PLUGIN/test/functional/CONTROLLER_controller_test.rb plugins/PLUGIN/test/unit/ plugins/PLUGIN/test/unit/MODEL_test.rb
由 Tomofumi Murata 於 8 個月 前更新 · 119 個修訂版本