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
This commit is contained in:
parent
122ba564b9
commit
1cd6a2aa84
@ -49,7 +49,7 @@ module CustomFieldsHelper
|
|||||||
blank_option = custom_field.is_required? ?
|
blank_option = custom_field.is_required? ?
|
||||||
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
|
(custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
|
||||||
'<option></option>'
|
'<option></option>'
|
||||||
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
|
else
|
||||||
text_field_tag(field_name, custom_value.value, :id => field_id)
|
text_field_tag(field_name, custom_value.value, :id => field_id)
|
||||||
end
|
end
|
||||||
@ -83,7 +83,7 @@ module CustomFieldsHelper
|
|||||||
[l(:general_text_yes), '1'],
|
[l(:general_text_yes), '1'],
|
||||||
[l(:general_text_no), '0']]), :id => field_id)
|
[l(:general_text_no), '0']]), :id => field_id)
|
||||||
when "list"
|
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
|
else
|
||||||
text_field_tag(field_name, '', :id => field_id)
|
text_field_tag(field_name, '', :id => field_id)
|
||||||
end
|
end
|
||||||
@ -101,8 +101,8 @@ module CustomFieldsHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Return an array of custom field formats which can be used in select_tag
|
# Return an array of custom field formats which can be used in select_tag
|
||||||
def custom_field_formats_for_select
|
def custom_field_formats_for_select(custom_field)
|
||||||
Redmine::CustomFieldFormat.as_select
|
Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Renders the custom_values in api views
|
# Renders the custom_values in api views
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# redMine - project management software
|
# Redmine - project management software
|
||||||
# Copyright (C) 2006 Jean-Philippe Lang
|
# Copyright (C) 2006-2011 Jean-Philippe Lang
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# 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?
|
errors.add(:default_value, :invalid) unless v.valid?
|
||||||
end
|
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
|
# Makes possible_values accept a multiline string
|
||||||
def possible_values=(arg)
|
def possible_values=(arg)
|
||||||
if arg.is_a?(Array)
|
if arg.is_a?(Array)
|
||||||
@ -71,6 +98,8 @@ class CustomField < ActiveRecord::Base
|
|||||||
casted = value.to_i
|
casted = value.to_i
|
||||||
when 'float'
|
when 'float'
|
||||||
casted = value.to_f
|
casted = value.to_f
|
||||||
|
when 'user', 'version'
|
||||||
|
casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
casted
|
casted
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Redmine - project management software
|
# 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
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
@ -636,6 +636,9 @@ class Query < ActiveRecord::Base
|
|||||||
options = { :type => :date, :order => 20 }
|
options = { :type => :date, :order => 20 }
|
||||||
when "bool"
|
when "bool"
|
||||||
options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
|
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
|
else
|
||||||
options = { :type => :string, :order => 20 }
|
options = { :type => :string, :order => 20 }
|
||||||
end
|
end
|
||||||
|
@ -40,6 +40,14 @@ function toggle_custom_field_format() {
|
|||||||
if (p_searchable) Element.hide(p_searchable.parentNode);
|
if (p_searchable) Element.hide(p_searchable.parentNode);
|
||||||
Element.hide(p_values.parentNode);
|
Element.hide(p_values.parentNode);
|
||||||
break;
|
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:
|
default:
|
||||||
Element.show(p_length.parentNode);
|
Element.show(p_length.parentNode);
|
||||||
Element.show(p_regexp.parentNode);
|
Element.show(p_regexp.parentNode);
|
||||||
@ -54,7 +62,7 @@ function toggle_custom_field_format() {
|
|||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<p><%= f.text_field :name, :required => true %></p>
|
<p><%= f.text_field :name, :required => true %></p>
|
||||||
<p><%= f.select :field_format, custom_field_formats_for_select, {}, :onchange => "toggle_custom_field_format();",
|
<p><%= f.select :field_format, custom_field_formats_for_select(@custom_field), {}, :onchange => "toggle_custom_field_format();",
|
||||||
:disabled => !@custom_field.new_record? %></p>
|
:disabled => !@custom_field.new_record? %></p>
|
||||||
<p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
|
<p><label for="custom_field_min_length"><%=l(:label_min_max_length)%></label>
|
||||||
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
|
<%= f.text_field :min_length, :size => 5, :no_label => true %> -
|
||||||
|
@ -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('list', :label => :label_list, :order => 5)
|
||||||
fields.register Redmine::CustomFieldFormat.new('date', :label => :label_date, :order => 6)
|
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('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
|
end
|
||||||
|
|
||||||
# Permissions
|
# Permissions
|
||||||
|
@ -22,12 +22,14 @@ module Redmine
|
|||||||
cattr_accessor :available
|
cattr_accessor :available
|
||||||
@@available = {}
|
@@available = {}
|
||||||
|
|
||||||
attr_accessor :name, :order, :label
|
attr_accessor :name, :order, :label, :edit_as, :class_names
|
||||||
|
|
||||||
def initialize(name, options={})
|
def initialize(name, options={})
|
||||||
self.name = name
|
self.name = name
|
||||||
self.label = options[:label]
|
self.label = options[:label]
|
||||||
self.order = options[:order]
|
self.order = options[:order]
|
||||||
|
self.edit_as = options[:edit_as] || name
|
||||||
|
self.class_names = options[:only]
|
||||||
end
|
end
|
||||||
|
|
||||||
def format(value)
|
def format(value)
|
||||||
@ -47,12 +49,11 @@ module Redmine
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allow displaying the edit type of another field_format
|
['user', 'version'].each do |name|
|
||||||
#
|
define_method("format_as_#{name}") {|value|
|
||||||
# Example: display a custom field as a list
|
return value.blank? ? "" : name.classify.constantize.find_by_id(value.to_i).to_s
|
||||||
def edit_as
|
}
|
||||||
name
|
|
||||||
end
|
end
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
@ -79,8 +80,10 @@ module Redmine
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Return an array of custom field formats which can be used in select_tag
|
# Return an array of custom field formats which can be used in select_tag
|
||||||
def as_select
|
def as_select(class_name=nil)
|
||||||
@@available.values.sort {|a,b|
|
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
|
a.order <=> b.order
|
||||||
}.collect {|custom_field_format|
|
}.collect {|custom_field_format|
|
||||||
[ l(custom_field_format.label), custom_field_format.name ]
|
[ l(custom_field_format.label), custom_field_format.name ]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Redmine - project management software
|
# 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
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
@ -31,6 +31,31 @@ class CustomFieldsControllerTest < ActionController::TestCase
|
|||||||
@request.session[:user_id] = 1
|
@request.session[:user_id] = 1
|
||||||
end
|
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
|
def test_post_new_list_custom_field
|
||||||
assert_difference 'CustomField.count' do
|
assert_difference 'CustomField.count' do
|
||||||
post :new, :type => "IssueCustomField",
|
post :new, :type => "IssueCustomField",
|
||||||
@ -53,9 +78,4 @@ class CustomFieldsControllerTest < ActionController::TestCase
|
|||||||
assert_equal ["0.1", "0.2"], field.possible_values
|
assert_equal ["0.1", "0.2"], field.possible_values
|
||||||
assert_equal 1, field.trackers.size
|
assert_equal 1, field.trackers.size
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_invalid_custom_field_class_should_redirect_to_list
|
|
||||||
get :new, :type => 'UnknownCustomField'
|
|
||||||
assert_redirected_to '/custom_fields'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# redMine - project management software
|
# 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
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# 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' }
|
:attributes => { :href => '/projects/ecookbook/issues?page=2' }
|
||||||
|
|
||||||
end
|
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
|
end
|
||||||
|
65
test/unit/custom_field_user_format_test.rb
Normal file
65
test/unit/custom_field_user_format_test.rb
Normal file
@ -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
|
@ -1,5 +1,5 @@
|
|||||||
# redMine - project management software
|
# 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
|
# This program is free software; you can redistribute it and/or
|
||||||
# modify it under the terms of the GNU General Public License
|
# modify it under the terms of the GNU General Public License
|
||||||
@ -75,7 +75,7 @@ module Redmine
|
|||||||
end
|
end
|
||||||
|
|
||||||
def custom_field_values
|
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
|
end
|
||||||
|
|
||||||
def visible_custom_field_values
|
def visible_custom_field_values
|
||||||
|
Loading…
x
Reference in New Issue
Block a user