From 1cd6a2aa84db83b127cc037021dc6351877790d0 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Fri, 1 Apr 2011 13:44:58 +0000 Subject: [PATCH] Adds User and Version custom field format that can be used to reference a project member or version in custom fields (#2096). These new field formats are available for project, issue, version and time entry custom fields. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@5272 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/helpers/custom_fields_helper.rb | 8 +- app/models/custom_field.rb | 33 +++++++- app/models/query.rb | 5 +- app/views/custom_fields/_form.rhtml | 10 ++- lib/redmine.rb | 2 + lib/redmine/custom_field_format.rb | 21 +++--- .../custom_fields_controller_test.rb | 32 ++++++-- test/integration/issues_test.rb | 75 ++++++++++++++++++- test/unit/custom_field_user_format_test.rb | 65 ++++++++++++++++ .../lib/acts_as_customizable.rb | 6 +- 10 files changed, 229 insertions(+), 28 deletions(-) create mode 100644 test/unit/custom_field_user_format_test.rb diff --git a/app/helpers/custom_fields_helper.rb b/app/helpers/custom_fields_helper.rb index efe31a415..36f665e18 100644 --- a/app/helpers/custom_fields_helper.rb +++ b/app/helpers/custom_fields_helper.rb @@ -49,7 +49,7 @@ module CustomFieldsHelper blank_option = custom_field.is_required? ? (custom_field.default_value.blank? ? "" : '') : '' - select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id) + select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value), :id => field_id) else text_field_tag(field_name, custom_value.value, :id => field_id) end @@ -83,7 +83,7 @@ module CustomFieldsHelper [l(:general_text_yes), '1'], [l(:general_text_no), '0']]), :id => field_id) when "list" - select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values), :id => field_id) + select_tag(field_name, options_for_select([[l(:label_no_change_option), '']] + custom_field.possible_values_options), :id => field_id) else text_field_tag(field_name, '', :id => field_id) end @@ -101,8 +101,8 @@ module CustomFieldsHelper end # Return an array of custom field formats which can be used in select_tag - def custom_field_formats_for_select - Redmine::CustomFieldFormat.as_select + def custom_field_formats_for_select(custom_field) + Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name) end # Renders the custom_values in api views diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 3aacd4686..8d7927ab1 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -48,6 +48,33 @@ class CustomField < ActiveRecord::Base errors.add(:default_value, :invalid) unless v.valid? end + def possible_values_options(obj=nil) + case field_format + when 'user', 'version' + if obj.respond_to?(:project) + case field_format + when 'user' + obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]} + when 'version' + obj.project.versions.sort.collect {|u| [u.to_s, u.id.to_s]} + end + else + [] + end + else + read_attribute :possible_values + end + end + + def possible_values(obj=nil) + case field_format + when 'user' + possible_values_options(obj).collect(&:last) + else + read_attribute :possible_values + end + end + # Makes possible_values accept a multiline string def possible_values=(arg) if arg.is_a?(Array) @@ -71,6 +98,8 @@ class CustomField < ActiveRecord::Base casted = value.to_i when 'float' casted = value.to_f + when 'user', 'version' + casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i)) end end casted diff --git a/app/models/query.rb b/app/models/query.rb index 3b1b53cc2..6c4d1c7c1 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -636,6 +636,9 @@ class Query < ActiveRecord::Base options = { :type => :date, :order => 20 } when "bool" options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } + when "user", "version" + next unless project + options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20} else options = { :type => :string, :order => 20 } end diff --git a/app/views/custom_fields/_form.rhtml b/app/views/custom_fields/_form.rhtml index 8cbd90371..2900af900 100644 --- a/app/views/custom_fields/_form.rhtml +++ b/app/views/custom_fields/_form.rhtml @@ -40,6 +40,14 @@ function toggle_custom_field_format() { if (p_searchable) Element.hide(p_searchable.parentNode); Element.hide(p_values.parentNode); break; + case "user": + case "version": + Element.hide(p_length.parentNode); + Element.hide(p_regexp.parentNode); + if (p_searchable) Element.hide(p_searchable.parentNode); + Element.hide(p_values.parentNode); + Element.hide(p_default.parentNode); + break; default: Element.show(p_length.parentNode); Element.show(p_regexp.parentNode); @@ -54,7 +62,7 @@ function toggle_custom_field_format() {

<%= f.text_field :name, :required => true %>

-

<%= f.select :field_format, custom_field_formats_for_select, {}, :onchange => "toggle_custom_field_format();", +

<%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :onchange => "toggle_custom_field_format();", :disabled => !@custom_field.new_record? %>

<%= f.text_field :min_length, :size => 5, :no_label => true %> - diff --git a/lib/redmine.rb b/lib/redmine.rb index 87efc72ea..631f10946 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -41,6 +41,8 @@ Redmine::CustomFieldFormat.map do |fields| fields.register Redmine::CustomFieldFormat.new('list', :label => :label_list, :order => 5) fields.register Redmine::CustomFieldFormat.new('date', :label => :label_date, :order => 6) fields.register Redmine::CustomFieldFormat.new('bool', :label => :label_boolean, :order => 7) + fields.register Redmine::CustomFieldFormat.new('user', :label => :label_user, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 8) + fields.register Redmine::CustomFieldFormat.new('version', :label => :label_version, :only => %w(Issue TimeEntry Version Project), :edit_as => 'list', :order => 9) end # Permissions diff --git a/lib/redmine/custom_field_format.rb b/lib/redmine/custom_field_format.rb index 2f12397d5..7c6364f7a 100644 --- a/lib/redmine/custom_field_format.rb +++ b/lib/redmine/custom_field_format.rb @@ -22,12 +22,14 @@ module Redmine cattr_accessor :available @@available = {} - attr_accessor :name, :order, :label + attr_accessor :name, :order, :label, :edit_as, :class_names def initialize(name, options={}) self.name = name self.label = options[:label] self.order = options[:order] + self.edit_as = options[:edit_as] || name + self.class_names = options[:only] end def format(value) @@ -47,12 +49,11 @@ module Redmine return value } end - - # Allow displaying the edit type of another field_format - # - # Example: display a custom field as a list - def edit_as - name + + ['user', 'version'].each do |name| + define_method("format_as_#{name}") {|value| + return value.blank? ? "" : name.classify.constantize.find_by_id(value.to_i).to_s + } end class << self @@ -79,8 +80,10 @@ module Redmine end # Return an array of custom field formats which can be used in select_tag - def as_select - @@available.values.sort {|a,b| + def as_select(class_name=nil) + fields = @@available.values + fields = fields.select {|field| field.class_names.nil? || field.class_names.include?(class_name)} + fields.sort {|a,b| a.order <=> b.order }.collect {|custom_field_format| [ l(custom_field_format.label), custom_field_format.name ] diff --git a/test/functional/custom_fields_controller_test.rb b/test/functional/custom_fields_controller_test.rb index d1e251b5c..5e665ec13 100644 --- a/test/functional/custom_fields_controller_test.rb +++ b/test/functional/custom_fields_controller_test.rb @@ -1,5 +1,5 @@ # Redmine - project management software -# Copyright (C) 2006-2009 Jean-Philippe Lang +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -31,6 +31,31 @@ class CustomFieldsControllerTest < ActionController::TestCase @request.session[:user_id] = 1 end + def test_get_new_issue_custom_field + get :new, :type => 'IssueCustomField' + assert_response :success + assert_template 'new' + assert_tag :select, + :attributes => {:name => 'custom_field[field_format]'}, + :child => { + :tag => 'option', + :attributes => {:value => 'user'}, + :content => 'User' + } + assert_tag :select, + :attributes => {:name => 'custom_field[field_format]'}, + :child => { + :tag => 'option', + :attributes => {:value => 'version'}, + :content => 'Version' + } + end + + def test_get_new_with_invalid_custom_field_class_should_redirect_to_list + get :new, :type => 'UnknownCustomField' + assert_redirected_to '/custom_fields' + end + def test_post_new_list_custom_field assert_difference 'CustomField.count' do post :new, :type => "IssueCustomField", @@ -53,9 +78,4 @@ class CustomFieldsControllerTest < ActionController::TestCase assert_equal ["0.1", "0.2"], field.possible_values assert_equal 1, field.trackers.size end - - def test_invalid_custom_field_class_should_redirect_to_list - get :new, :type => 'UnknownCustomField' - assert_redirected_to '/custom_fields' - end end diff --git a/test/integration/issues_test.rb b/test/integration/issues_test.rb index b6955d252..a640d1c01 100644 --- a/test/integration/issues_test.rb +++ b/test/integration/issues_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -126,4 +126,75 @@ class IssuesTest < ActionController::IntegrationTest :attributes => { :href => '/projects/ecookbook/issues?page=2' } end + + def test_issue_with_user_custom_field + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true, :trackers => Tracker.all) + Role.anonymous.add_permission! :add_issues, :edit_issues + users = Project.find(1).users + tester = users.first + + # Issue form + get '/projects/ecookbook/issues/new' + assert_response :success + assert_tag :select, + :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, + :children => {:count => (users.size + 1)}, # +1 for blank value + :child => { + :tag => 'option', + :attributes => {:value => tester.id.to_s}, + :content => tester.name + } + + # Create issue + assert_difference 'Issue.count' do + post '/projects/ecookbook/issues', + :issue => { + :tracker_id => '1', + :priority_id => '4', + :subject => 'Issue with user custom field', + :custom_field_values => {@field.id.to_s => users.first.id.to_s} + } + end + issue = Issue.first(:order => 'id DESC') + assert_response 302 + + # Issue view + follow_redirect! + assert_tag :th, + :content => /Tester/, + :sibling => { + :tag => 'td', + :content => tester.name + } + assert_tag :select, + :attributes => {:name => "issue[custom_field_values][#{@field.id}]"}, + :children => {:count => (users.size + 1)}, # +1 for blank value + :child => { + :tag => 'option', + :attributes => {:value => tester.id.to_s, :selected => 'selected'}, + :content => tester.name + } + + # Update issue + new_tester = users[1] + assert_difference 'Journal.count' do + put "/issues/#{issue.id}", + :notes => 'Updating custom field', + :issue => { + :custom_field_values => {@field.id.to_s => new_tester.id.to_s} + } + end + assert_response 302 + + # Issue view + follow_redirect! + assert_tag :content => 'Tester', + :ancestor => {:tag => 'ul', :attributes => {:class => /details/}}, + :sibling => { + :content => tester.name, + :sibling => { + :content => new_tester.name + } + } + end end diff --git a/test/unit/custom_field_user_format_test.rb b/test/unit/custom_field_user_format_test.rb new file mode 100644 index 000000000..085e4c73a --- /dev/null +++ b/test/unit/custom_field_user_format_test.rb @@ -0,0 +1,65 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class CustomFieldUserFormatTest < ActiveSupport::TestCase + fixtures :custom_fields, :projects, :members, :users, :member_roles, :trackers, :issues + + def setup + @field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user') + end + + def test_possible_values_with_no_arguments + assert_equal [], @field.possible_values + assert_equal [], @field.possible_values(nil) + end + + def test_possible_values_with_project_resource + project = Project.find(1) + possible_values = @field.possible_values(project.issues.first) + assert possible_values.any? + assert_equal project.users.sort.collect(&:id).map(&:to_s), possible_values + end + + def test_possible_values_options_with_no_arguments + assert_equal [], @field.possible_values_options + assert_equal [], @field.possible_values_options(nil) + end + + def test_possible_values_options_with_project_resource + project = Project.find(1) + possible_values_options = @field.possible_values_options(project.issues.first) + assert possible_values_options.any? + assert_equal project.users.sort.map {|u| [u.name, u.id.to_s]}, possible_values_options + end + + def test_cast_blank_value + assert_equal nil, @field.cast_value(nil) + assert_equal nil, @field.cast_value("") + end + + def test_cast_valid_value + user = @field.cast_value("2") + assert_kind_of User, user + assert_equal User.find(2), user + end + + def test_cast_invalid_value + assert_equal nil, @field.cast_value("187") + end +end diff --git a/vendor/plugins/acts_as_customizable/lib/acts_as_customizable.rb b/vendor/plugins/acts_as_customizable/lib/acts_as_customizable.rb index b99c38c4b..10ba12341 100644 --- a/vendor/plugins/acts_as_customizable/lib/acts_as_customizable.rb +++ b/vendor/plugins/acts_as_customizable/lib/acts_as_customizable.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -75,7 +75,7 @@ module Redmine end def custom_field_values - @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:custom_field => x, :value => nil) } + @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:customized => self, :custom_field => x, :value => nil) } end def visible_custom_field_values -- 2.39.5