项目

常规

个人资料

操作

插件教程 » 历史记录 » 修订版104

上一页 | 修订版104/119 (差异) | 下一页
Go MAEDA, 2018-01-09 10:15
移除了“不可加载” (#20513) 并修复了一些拼写错误。


插件教程

本教程基于Redmine 2.x。您可以在此查看本教程的Redmine 1.x先前的版本。
假设您熟悉Ruby on Rails框架。

注意:Redmine 3.x (Rails 4) 脚本

此维基在Redmine 2.x (Rails 3)上使用ruby script/rails
在Redmine 3.x (Rails 4)上,您需要使用ruby bin/railsrails

创建新插件

您可能需要设置RAILS_ENV变量才能使用以下命令

$ export RAILS_ENV="production" 

在Windows上

$ set RAILS_ENV=production

可以使用Redmine插件生成器创建新插件。
此生成器的语法是

bundle exec ruby bin/rails generate redmine_plugin <plugin_name>

因此,打开命令提示符并“cd”到您的redmine目录,然后执行以下命令

$ bundle exec ruby script/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/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 'John Smith'
  description 'A plugin for managing polls'
  version '0.0.1'
end

然后重新启动应用程序并将浏览器指向https://127.0.0.1:3000/admin/plugins
登录后,您应该在插件列表中看到您的新插件

注意:对插件中init.rb文件的任何更改都需要重新启动应用程序,因为它不会在每个请求中重新加载。

生成模型

目前插件不存储任何内容。让我们为插件创建一个简单的Poll模型。语法是

   bundle exec ruby script/rails generate redmine_plugin_model <plugin_name> <model_name> [field[:type][:index] field[:type][:index] ...]

因此,转到命令提示符并运行

$ bundle exec ruby script/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/001_create_polls.rb

这创建了一个Poll模型和相应的迁移文件001_create_polls.rbplugins/polls/db/migrate

class CreatePolls < ActiveRecord::Migration
  def change
    create_table :polls do |t|
      t.string :question
      t.integer :yes, :default => 0
      t.integer :no, :default => 0
    end
  end
end

您可以调整您的迁移文件(例如,默认值...),然后使用以下命令迁移数据库

$ bundle exec rake redmine:plugins:migrate

Migrating polls (Polls plugin)...
==  CreatePolls: migrating ====================================================
-- create_table(:polls)
   -> 0.0410s
==  CreatePolls: migrated (0.0420s) ===========================================

请注意,每个插件都有自己的迁移集合。

让我们在控制台中添加一些投票,这样我们就有东西可以工作了。控制台是您可以交互式地工作和检查Redmine环境的地方,非常适合探索。但现阶段我们只需要创建两个投票对象。

bundle exec ruby script/rails console
[rails 3] 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 ruby script/rails generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]

所以回到命令提示符,运行

$ bundle exec ruby script/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

创建了一个名为 PollsController 的控制器,包含 2 个操作(#index#vote)。

编辑 plugins/polls/app/controllers/polls_controller.rb 来实现这两个操作。

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 :action => 'index'
  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' }, :method => :post %> (<%= poll.yes %>) /
  <%= link_to 'No', { :action => 'vote', :id => poll[:id], :answer => 'no' }, :method => :post %> (<%= poll.no %>)
  </p>
<% end %>

由于 #vote 动作没有进行渲染,所以您可以删除 plugins/polls/app/views/polls/vote.html.erb

添加路由

Redmine 不提供默认的通配符路由(':controller/:action/:id')。插件必须在它们正确的 config/routes.rb 文件中声明所需的路由。所以编辑 plugins/polls/config/routes.rb 以添加这两个操作的 2 个路由。

get 'polls', :to => 'polls#index'
post 'post/:id/vote', :to => 'polls#vote'

您可以在这里找到有关Rails路由的更多信息:https://guides.rubyonrails.net.cn/routing.html

现在,重新启动应用程序,并将您的浏览器指向 https://127.0.0.1:3000/polls
您应该能看到两个投票,并且应该能够为它们投票。

国际化

翻译文件必须存储在 config/locales 中,例如 plugins/polls/config/locales/

扩展菜单

我们的控制器工作得很好,但用户必须知道URL才能查看投票。使用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 - 菜单标题,可以是
    • 本地化字符串Symbol
    • 字符串
    • 可以接受项目作为参数的Proc
  • :before:after - 指定菜单项应插入的位置(例如 :after => :activity
  • :first:last - 如果设置为true,则该项将保持在菜单的起始/结束位置(例如 :last => true
  • :html - 要传递给 link_to 的html选项的hash,用于渲染菜单项

在我们的示例中,我们已向应用程序菜单添加了一个空的项目。
重新启动应用程序并转到 https://127.0.0.1:3000

现在您可以通过点击欢迎屏幕上的投票标签来访问投票。

扩展项目菜单

现在,让我们考虑将投票定义在项目级别(即使在我们示例投票模型中不是这种情况)。因此,我们希望将“投票”标签页添加到项目菜单中。
打开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个操作是公开的。我们稍后会更详细地解释这一点。重新启动应用程序,并前往您的一个项目。

如果您点击“投票”标签页(第3位),应该会注意到项目菜单不再显示。
为了使项目菜单可见,您必须初始化控制器的实例变量@project

编辑您的PollsController以实现这一点

def index
  @project = Project.find(params[:project_id])
  @polls = Poll.find(:all) # @project.polls
end

由于在上面的菜单项声明中使用了:param => :project_id选项,因此项目id在: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

添加新权限

目前,任何人都可以为投票投票。让我们通过更改权限声明使其更具可配置性。
我们将声明两个基于项目的权限,一个用于查看投票,另一个用于投票。这些权限不再是公开的(移除了:public => true选项)。

编辑plugins/polls/init.rb,用以下2行代码替换之前的权限声明

  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_filter :find_project, :authorize, :only => :index

  [...]

  def index
    @polls = Poll.find(: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/,以便通过您的Web服务器使用。因此,对插件样式表或javascripts的任何更改都需要重新启动应用程序。

引入的类需要由链接使用。因此,在文件plugins/polls/app/views/polls/index.html.erb中更改链接声明为

<%= link_to 'Yes', {:action => 'vote', :id => poll[:id], :answer => 'yes' }, :method => :post, :class => 'vote yes' %> (<%= poll.yes %>)
<%= link_to 'No', {:action => 'vote', :id => poll[:id], :answer => 'no' }, :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辅助函数将javascripts包含在插件视图中。

设置页面标题

您可以使用html_title辅助函数在视图中设置HTML标题。
示例

  <% html_title "Polls" %>

使用钩子

视图中的钩子

Redmine视图中的钩子允许您将自定义内容插入常规Redmine视图中。例如,查看源代码: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 'polls_hook_listener'

重新启动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。

控制器中的钩子

待办事项

使您的插件可配置

与Redmine注册的每个插件都会显示在管理员/插件页面。通过Settings控制器提供基本配置机制的支持。通过在插件init.rb文件中添加“settings”方法来启用此功能。

Redmine::Plugin.register :redmine_polls do
  [ ... ]

  settings :default => {'empty' => true}, :partial => 'settings/poll_settings'
end

添加此方法将完成两件事。首先,它将在管理员/插件列表中为插件描述块添加一个“配置”链接。点击此链接将加载通用的插件配置模板视图,该视图将渲染引用的:partial。调用settings方法还将为插件在Setting模块中添加支持。Setting模型将基于插件名称存储和检索序列化哈希。此哈希通过插件方法名访问,形式为plugin_<插件名称>。对于此示例,可以通过调用Setting.plugin_redmine_polls来访问哈希。

通过settings方法传递给settings方法的:partial哈希键引用的视图将作为插件配置视图的一部分加载。基本页面布局受插件配置视图的限制:声明一个表单并生成提交按钮。部分在表单中的表div内部拉入。将显示并可以修改插件的配置设置,通过标准HTML表单元素进行修改。

注意:如果两个插件具有相同的设置部分名称,则第一个将覆盖第二个的设置页面。因此,请确保为您的设置部分提供一个独特的名称。

当页面提交时,settings_controller将取由'settings'引用的参数散列,并将其直接以序列化格式存储在Setting.plugin_redmine_polls中。每次生成页面时,Setting.plugin_redmine_polls的当前值将分配给局部变量settings。

<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模型,只有设置散列。表单辅助程序将尝试使用模型访问器方法访问属性,但这些方法不存在。例如,调用@settings.notification_default将失败。此表单设置的值可以访问为Setting.plugin_redmine_polls['notification_default']。

最后,在settings方法调用中的:default是为了注册一个值,如果为此插件在设置表中没有存储任何内容,则将从Setting.plugin_redmine_polls调用中返回此值。

测试您的插件

test/test_helper.rb

以下是我的测试辅助文件的内容

require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')

示例测试

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

运行测试

如有必要,初始化测试数据库

$ rake db:drop db:create db:migrate redmine:plugins:migrate redmine:load_default_data RAILS_ENV=test

要执行polls_controller_test.rb

$ bundle exec ruby plugins\polls\test\functionals\polls_controller_test.rb

带有权限的测试

如果您的插件需要加入一个项目,请在功能测试的开始处添加以下内容

def test_index
  @request.session[:user_id] = 2
  ...
end

如果您的插件需要特定的权限,您可以将该权限添加到用户角色中,如下所示(在固定值中查找适合用户的角色)

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

更新者 Go MAEDA · 6年前 · 104次修订