redmine_projects_sorter ver.1.0.0
githubでwatcherが増えてたので、いい加減バージョンを1以上にしよう。
しました。
yunomu/redmine_projects_sorter at v1.0.0 · GitHub
今回はDBを変更するのでmigrateが必要です。
% git clone git://github.com/yunomu/redmine_projects_sorter.git vender/plugins/redmine_projects_sorter % rake db:migrate_plugins
なにこれ
Redmineのプロジェクト一覧とかガントチャートの画面で、同じ階層にあるプロジェクトの並び順がバラバラだったので、そのへんを制御できるようにしようというプラグインです。
プロジェクト設定に"order"って要素を追加して、同じ階層のプロジェクトをorder順に並び替えます。
やったこと
DBや画面の変更を伴うRedmineプラグインの書き方。
DBの変更
migrationを書く。この辺は普通のRailsと同じ。
こんな感じ。
vendor/plugins/redmine_projects_sorter/db/migrate/001_add_order_column.rb
1 class AddOrderToProjects < ActiveRecord::Migration 2 def self.up 3 add_column :projects, :ord, :integer, 4 :null => false, :default => 100 5 end 6 7 def self.down 8 remove_column :projects, :ord 9 end 10 end
ファイル名はRails2.x仕様じゃないとRedmine的にダメらしいです。試してませんが。
で、ordってカラムを追加しました。カラム名はorderだとSQL的にややこしいし、lft,rgtに並ぶからこれでいいかって感じです。デフォはなんとなく100で。最終的にこの値の小さい順に並べます。
ロジック変更
プラグイン内で、今まではソートキーをプロジェクト名にしてたのを、ordにした。
ソースは略。
画面の変更
projectsの設定画面を変更する。そもそもまずRedmineのprojects設定画面のソースがどこにあるのかというと、
config/route.rb
184 map.with_options :controller => 'projects' do |project_mapper| 185 project_mapper.with_options :conditions => {:method => :get} do |project_views| 186 project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings' 187 project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new' 188 end 189 end
このあたり。projects#settingsって感じ。
app/views/projects/settings.html.erb
1 <h2><%=l(:label_settings)%></h2> 2 3 <%= render_tabs project_settings_tabs %> 4 5 <% html_title(l(:label_settings)) -%>
これだけ?
まあ、render_tabsとproject_settings_tabsを見ればいいんだろう。
render_tabsはApplicationHelperに、project_setting_tabsはProjectsHelperにあった。
app/helpers/application_helper.rb
23 module ApplicationHelper (略) 219 # Renders tabs and their content 220 def render_tabs(tabs) 221 if tabs.any? 222 render :partial => 'common/tabs', :locals => {:tabs => tabs} 223 else 224 content_tag 'p', l(:label_no_data), :class => "nodata" 225 end 226 end
app/helpers/projects_helper.rb
20 module ProjectsHelper (略) 26 def project_settings_tabs 27 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural}, 28 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural}, 29 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural}, 30 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural}, 31 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural} , 32 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki}, 33 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository}, 34 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural}, 35 {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities} 36 ] 37 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)} 38 end
Redmineの設定画面はタブがたくさんあっていろんな設定を変更できるようになっているので、たぶんこれはそのタブのディスパッチャ的なもののようです。
見たいのはinfoタブなのでinfoを見ると、":partial => 'projects/edit'"って、まあそのまんまだった。
app/views/projects/_edit.html.erb
1 <% labelled_tabular_form_for @project do |f| %> 2 <%= render :partial => 'form', :locals => { :f => f } %> 3 <%= submit_tag l(:button_save) %> 4 <% end %>
"form"を見ろ。たらい回し感。
app/views/projects/_form.html.erb
16 <p><%= f.text_field :homepage, :size => 60 %></p> 17 <p><%= f.check_box :is_public %></p> 18 <%= wikitoolbar_for 'project_description' %> 19 20 <% @project.custom_field_values.each do |value| %> 21 <p><%= custom_field_tag_with_label :project, value %></p> 22 <% end %> 23 <%= call_hook(:view_projects_form, :project => @project, :form => f) %> 24 </div>
ここらへん、17行目が公開のチェックボックスなので、このあたりに挿し込みたいんだけど……と思ってたらちょうど23行目にhookがあった。
Redmineにはpluginで拡張するためにいろんな所にhookを書けるようになっているらしい。
Hooks - Redmine
このあたりにHookの使い方が書いてある。
model、view、controller、helperのそれぞれにhookがあって、その一覧がこちら。
Hooks List - Redmine
件の"view_projects_form"なんかもありますね。今回はこれを利用します。
これの見方は、
def view_projects_form(context) # context #=> {:project => @project, :form => f} ... end
という感じのメソッドを定義してくれたらcontextにコメントみたいなハッシュを送り込みますよということらしい。パラメータの意味はviewやhelperのソースを参照するとして、全体としてはこう。
1 class ProjectsSorter < Redmine::Hook::ViewListener 2 def view_projects_form(context) 3 project = context[:project] 4 f = context[:form] 5 6 html = "<p>" 7 html << f.text_field(:ord, :value => project.ord, :type => :number) 8 html << "</p>" 9 html 10 end 11 end
ViewListenrというのを継承してやる必要があるらしい。あとはRedmineのフレームワークに則ってhtmlを返すコードを書いておく。
このListenerは、init.rbあたりでロードしておくだけでいいらしい。
vendor/plugins/redmine_projects_sorter/init.rb
(略) require "redmine_projects_sorter_listener" (略)
あとは、i18n。項目のラベルはそれなりのところに書いておかなきゃいけないそうです。
今回は"ja, form_ord"というところに書かなきゃいけないんですが、他の場合にどこに書かなきゃいけないかは、実際に動かしてみてエラーメッセージを見ながら探せばいいんじゃないでしょうか。
今回は具体的にはここ。面倒なので英語と日本語だけ。
vendor/plugins/redmine_projects_sorter/config/locales/en.yml
en:
field_ord:
Order
vendor/plugins/redmine_projects_sorter/config/locales/ja.yml
ja: field_ord: 表示順
これで一見完成なんだけど、まだ値の更新ができない。何故か。
じゃあどこで止まってるのかと思って、だいぶ上の方で出てきたproject_settings_tabsを見てみると、":action => :edit_project"となってるけどProjectsControllerにそんなメソッドは無い。
でもどうせProjectsController#updateとかなんでしょ? と思ってparamsをログに吐くようにしてみると、まあここまでは正常に渡ってきてるっぽい。ちゃんとparams[:project][:ord]に値が入ってる。
それを保存しているようにも見える。(189行目)
app/controllers/projects_controller.rb
188 def update 189 @project.safe_attributes = params[:project] 190 if validate_parent_id && @project.save
と思ったけど、この"safe_attributes"って何だ。
app/models/projects.rb
569 safe_attributes 'name', 570 'description', 571 'homepage', 572 'is_public', 573 'identifier', 574 'custom_field_values', 575 'custom_fields', 576 'tracker_ids', 577 'issue_custom_field_ids' 578 579 safe_attributes 'enabled_module_names', 580 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
なんかチェックっぽい?
色々試してみたところ、要はこのsafe_attributesの中にordも追加してあげると、とりあえず値の更新ができるという事はわかった。
でもこれが結構難しかったので、
vendor/plugins/redmine_projects_sorter/init.rb
(略) Dispatcher.to_prepare :redmine_projects_sorter do require_dependency 'projects_helper' ProjectsHelper.send(:include, Redmine::Plugins::ProjectsSorter::ProjectsHelperPatch) Project.safe_attributes 'ord' end (略)
Dispatcherの中で書き換える。
sqlite3でしかチェックしてないからmysqlでやるとバグるかもしれませんが。