@@ -0,0 +1,50 | |||||
|
1 | # Redmine - project management software | |||
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | class CustomFieldValue | |||
|
19 | attr_accessor :custom_field, :customized, :value | |||
|
20 | ||||
|
21 | def custom_field_id | |||
|
22 | custom_field.id | |||
|
23 | end | |||
|
24 | ||||
|
25 | def true? | |||
|
26 | self.value == '1' | |||
|
27 | end | |||
|
28 | ||||
|
29 | def editable? | |||
|
30 | custom_field.editable? | |||
|
31 | end | |||
|
32 | ||||
|
33 | def visible? | |||
|
34 | custom_field.visible? | |||
|
35 | end | |||
|
36 | ||||
|
37 | def required? | |||
|
38 | custom_field.is_required? | |||
|
39 | end | |||
|
40 | ||||
|
41 | def to_s | |||
|
42 | value.to_s | |||
|
43 | end | |||
|
44 | ||||
|
45 | def validate_value | |||
|
46 | custom_field.validate_field_value(value).each do |message| | |||
|
47 | customized.errors.add(:base, custom_field.name + ' ' + message) | |||
|
48 | end | |||
|
49 | end | |||
|
50 | end |
@@ -1,7 +1,7 | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | # |
|
2 | # | |
3 | # Redmine - project management software |
|
3 | # Redmine - project management software | |
4 |
# Copyright (C) 2006-201 |
|
4 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
5 | # |
|
5 | # | |
6 | # This program is free software; you can redistribute it and/or |
|
6 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
7 | # modify it under the terms of the GNU General Public License | |
@@ -61,8 +61,7 module CustomFieldsHelper | |||||
61 | def custom_field_label_tag(name, custom_value) |
|
61 | def custom_field_label_tag(name, custom_value) | |
62 | content_tag "label", h(custom_value.custom_field.name) + |
|
62 | content_tag "label", h(custom_value.custom_field.name) + | |
63 | (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""), |
|
63 | (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>".html_safe : ""), | |
64 |
:for => "#{name}_custom_field_values_#{custom_value.custom_field.id}" |
|
64 | :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}" | |
65 | :class => (custom_value.errors.empty? ? nil : "error" ) |
|
|||
66 | end |
|
65 | end | |
67 |
|
66 | |||
68 | # Return custom field tag with its label tag |
|
67 | # Return custom field tag with its label tag |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -27,7 +27,7 class CustomField < ActiveRecord::Base | |||||
27 | validates_length_of :name, :maximum => 30 |
|
27 | validates_length_of :name, :maximum => 30 | |
28 | validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats |
|
28 | validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats | |
29 |
|
29 | |||
30 |
validate :validate_ |
|
30 | validate :validate_custom_field | |
31 | before_validation :set_searchable |
|
31 | before_validation :set_searchable | |
32 |
|
32 | |||
33 | def initialize(attributes=nil, *args) |
|
33 | def initialize(attributes=nil, *args) | |
@@ -41,7 +41,7 class CustomField < ActiveRecord::Base | |||||
41 | true |
|
41 | true | |
42 | end |
|
42 | end | |
43 |
|
43 | |||
44 |
def validate_ |
|
44 | def validate_custom_field | |
45 | if self.field_format == "list" |
|
45 | if self.field_format == "list" | |
46 | errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? |
|
46 | errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? | |
47 | errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array |
|
47 | errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array | |
@@ -55,10 +55,9 class CustomField < ActiveRecord::Base | |||||
55 | end |
|
55 | end | |
56 | end |
|
56 | end | |
57 |
|
57 | |||
58 |
|
|
58 | unless valid_field_value?(default_value) | |
59 | v = CustomValue.new(:custom_field => self.clone, :value => default_value, :customized => nil) |
|
59 | errors.add(:default_value, :invalid) | |
60 | v.custom_field.is_required = false |
|
60 | end | |
61 | errors.add(:default_value, :invalid) unless v.valid? |
|
|||
62 | end |
|
61 | end | |
63 |
|
62 | |||
64 | def possible_values_options(obj=nil) |
|
63 | def possible_values_options(obj=nil) | |
@@ -161,4 +160,45 class CustomField < ActiveRecord::Base | |||||
161 | def type_name |
|
160 | def type_name | |
162 | nil |
|
161 | nil | |
163 | end |
|
162 | end | |
|
163 | ||||
|
164 | # Returns the error message for the given value | |||
|
165 | # or an empty array if value is a valid value for the custom field | |||
|
166 | def validate_field_value(value) | |||
|
167 | errs = [] | |||
|
168 | if is_required? && value.blank? | |||
|
169 | errs << ::I18n.t('activerecord.errors.messages.blank') | |||
|
170 | end | |||
|
171 | errs += validate_field_value_format(value) | |||
|
172 | errs | |||
|
173 | end | |||
|
174 | ||||
|
175 | # Returns true if value is a valid value for the custom field | |||
|
176 | def valid_field_value?(value) | |||
|
177 | validate_field_value(value).empty? | |||
|
178 | end | |||
|
179 | ||||
|
180 | protected | |||
|
181 | ||||
|
182 | # Returns the error message for the given value regarding its format | |||
|
183 | def validate_field_value_format(value) | |||
|
184 | errs = [] | |||
|
185 | if value.present? | |||
|
186 | errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp) | |||
|
187 | errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length | |||
|
188 | errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length | |||
|
189 | ||||
|
190 | # Format specific validations | |||
|
191 | case field_format | |||
|
192 | when 'int' | |||
|
193 | errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/ | |||
|
194 | when 'float' | |||
|
195 | begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end | |||
|
196 | when 'date' | |||
|
197 | errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end | |||
|
198 | when 'list' | |||
|
199 | errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value) | |||
|
200 | end | |||
|
201 | end | |||
|
202 | errs | |||
|
203 | end | |||
164 | end |
|
204 | end |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -19,8 +19,6 class CustomValue < ActiveRecord::Base | |||||
19 | belongs_to :custom_field |
|
19 | belongs_to :custom_field | |
20 | belongs_to :customized, :polymorphic => true |
|
20 | belongs_to :customized, :polymorphic => true | |
21 |
|
21 | |||
22 | validate :validate_custom_value |
|
|||
23 |
|
||||
24 | def initialize(attributes=nil, *args) |
|
22 | def initialize(attributes=nil, *args) | |
25 | super |
|
23 | super | |
26 | if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?)) |
|
24 | if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?)) | |
@@ -48,27 +46,4 class CustomValue < ActiveRecord::Base | |||||
48 | def to_s |
|
46 | def to_s | |
49 | value.to_s |
|
47 | value.to_s | |
50 | end |
|
48 | end | |
51 |
|
||||
52 | protected |
|
|||
53 | def validate_custom_value |
|
|||
54 | if value.blank? |
|
|||
55 | errors.add(:value, :blank) if custom_field.is_required? and value.blank? |
|
|||
56 | else |
|
|||
57 | errors.add(:value, :invalid) unless custom_field.regexp.blank? or value =~ Regexp.new(custom_field.regexp) |
|
|||
58 | errors.add(:value, :too_short, :count => custom_field.min_length) if custom_field.min_length > 0 and value.length < custom_field.min_length |
|
|||
59 | errors.add(:value, :too_long, :count => custom_field.max_length) if custom_field.max_length > 0 and value.length > custom_field.max_length |
|
|||
60 |
|
||||
61 | # Format specific validations |
|
|||
62 | case custom_field.field_format |
|
|||
63 | when 'int' |
|
|||
64 | errors.add(:value, :not_a_number) unless value =~ /^[+-]?\d+$/ |
|
|||
65 | when 'float' |
|
|||
66 | begin; Kernel.Float(value); rescue; errors.add(:value, :invalid) end |
|
|||
67 | when 'date' |
|
|||
68 | errors.add(:value, :not_a_date) unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end |
|
|||
69 | when 'list' |
|
|||
70 | errors.add(:value, :inclusion) unless custom_field.possible_values.include?(value) |
|
|||
71 | end |
|
|||
72 | end |
|
|||
73 | end |
|
|||
74 | end |
|
49 | end |
@@ -16,36 +16,6 module ActiveRecord | |||||
16 | end |
|
16 | end | |
17 | end |
|
17 | end | |
18 |
|
18 | |||
19 | module ActiveRecord |
|
|||
20 | class Errors |
|
|||
21 | def full_messages(options = {}) |
|
|||
22 | full_messages = [] |
|
|||
23 |
|
||||
24 | @errors.each_key do |attr| |
|
|||
25 | @errors[attr].each do |message| |
|
|||
26 | next unless message |
|
|||
27 |
|
||||
28 | if attr == "base" |
|
|||
29 | full_messages << message |
|
|||
30 | elsif attr == "custom_values" |
|
|||
31 | # Replace the generic "custom values is invalid" |
|
|||
32 | # with the errors on custom values |
|
|||
33 | @base.custom_values.each do |value| |
|
|||
34 | value.errors.each do |attr, msg| |
|
|||
35 | full_messages << value.custom_field.name + ' ' + msg |
|
|||
36 | end |
|
|||
37 | end |
|
|||
38 | else |
|
|||
39 | attr_name = @base.class.human_attribute_name(attr) |
|
|||
40 | full_messages << attr_name + ' ' + message.to_s |
|
|||
41 | end |
|
|||
42 | end |
|
|||
43 | end |
|
|||
44 | full_messages |
|
|||
45 | end |
|
|||
46 | end |
|
|||
47 | end |
|
|||
48 |
|
||||
49 | module ActionView |
|
19 | module ActionView | |
50 | module Helpers |
|
20 | module Helpers | |
51 | module DateHelper |
|
21 | module DateHelper |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -1308,17 +1308,18 class IssuesControllerTest < ActionController::TestCase | |||||
1308 | field.update_attribute(:is_required, true) |
|
1308 | field.update_attribute(:is_required, true) | |
1309 |
|
1309 | |||
1310 | @request.session[:user_id] = 2 |
|
1310 | @request.session[:user_id] = 2 | |
1311 | post :create, :project_id => 1, |
|
1311 | assert_no_difference 'Issue.count' do | |
1312 | :issue => {:tracker_id => 1, |
|
1312 | post :create, :project_id => 1, | |
1313 | :subject => 'This is the test_new issue', |
|
1313 | :issue => {:tracker_id => 1, | |
1314 |
: |
|
1314 | :subject => 'This is the test_new issue', | |
1315 | :priority_id => 5} |
|
1315 | :description => 'This is the description', | |
|
1316 | :priority_id => 5} | |||
|
1317 | end | |||
1316 | assert_response :success |
|
1318 | assert_response :success | |
1317 | assert_template 'new' |
|
1319 | assert_template 'new' | |
1318 | issue = assigns(:issue) |
|
1320 | issue = assigns(:issue) | |
1319 | assert_not_nil issue |
|
1321 | assert_not_nil issue | |
1320 | assert_equal I18n.translate('activerecord.errors.messages.invalid'), |
|
1322 | assert_error_tag :content => /Database can't be blank/ | |
1321 | issue.errors[:custom_values].to_s |
|
|||
1322 | end |
|
1323 | end | |
1323 |
|
1324 | |||
1324 | def test_post_create_with_watchers |
|
1325 | def test_post_create_with_watchers |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -44,6 +44,14 class CustomFieldTest < ActiveSupport::TestCase | |||||
44 | assert field.save |
|
44 | assert field.save | |
45 | end |
|
45 | end | |
46 |
|
46 | |||
|
47 | def test_default_value_should_be_validated | |||
|
48 | field = CustomField.new(:name => 'Test', :field_format => 'int') | |||
|
49 | field.default_value = 'abc' | |||
|
50 | assert !field.valid? | |||
|
51 | field.default_value = '6' | |||
|
52 | assert field.valid? | |||
|
53 | end | |||
|
54 | ||||
47 | def test_possible_values_should_accept_an_array |
|
55 | def test_possible_values_should_accept_an_array | |
48 | field = CustomField.new |
|
56 | field = CustomField.new | |
49 | field.possible_values = ["One value", ""] |
|
57 | field.possible_values = ["One value", ""] | |
@@ -85,4 +93,75 class CustomFieldTest < ActiveSupport::TestCase | |||||
85 | def test_new_subclass_instance_with_non_subclass_name_should_return_nil |
|
93 | def test_new_subclass_instance_with_non_subclass_name_should_return_nil | |
86 | assert_nil CustomField.new_subclass_instance('Project') |
|
94 | assert_nil CustomField.new_subclass_instance('Project') | |
87 | end |
|
95 | end | |
|
96 | ||||
|
97 | def test_string_field_validation_with_blank_value | |||
|
98 | f = CustomField.new(:field_format => 'string') | |||
|
99 | ||||
|
100 | assert f.valid_field_value?(nil) | |||
|
101 | assert f.valid_field_value?('') | |||
|
102 | ||||
|
103 | f.is_required = true | |||
|
104 | assert !f.valid_field_value?(nil) | |||
|
105 | assert !f.valid_field_value?('') | |||
|
106 | end | |||
|
107 | ||||
|
108 | def test_string_field_validation_with_min_and_max_lengths | |||
|
109 | f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5) | |||
|
110 | ||||
|
111 | assert f.valid_field_value?(nil) | |||
|
112 | assert f.valid_field_value?('') | |||
|
113 | assert f.valid_field_value?('a' * 2) | |||
|
114 | assert !f.valid_field_value?('a') | |||
|
115 | assert !f.valid_field_value?('a' * 6) | |||
|
116 | end | |||
|
117 | ||||
|
118 | def test_string_field_validation_with_regexp | |||
|
119 | f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$') | |||
|
120 | ||||
|
121 | assert f.valid_field_value?(nil) | |||
|
122 | assert f.valid_field_value?('') | |||
|
123 | assert f.valid_field_value?('ABC') | |||
|
124 | assert !f.valid_field_value?('abc') | |||
|
125 | end | |||
|
126 | ||||
|
127 | def test_date_field_validation | |||
|
128 | f = CustomField.new(:field_format => 'date') | |||
|
129 | ||||
|
130 | assert f.valid_field_value?(nil) | |||
|
131 | assert f.valid_field_value?('') | |||
|
132 | assert f.valid_field_value?('1975-07-14') | |||
|
133 | assert !f.valid_field_value?('1975-07-33') | |||
|
134 | assert !f.valid_field_value?('abc') | |||
|
135 | end | |||
|
136 | ||||
|
137 | def test_list_field_validation | |||
|
138 | f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2']) | |||
|
139 | ||||
|
140 | assert f.valid_field_value?(nil) | |||
|
141 | assert f.valid_field_value?('') | |||
|
142 | assert f.valid_field_value?('value2') | |||
|
143 | assert !f.valid_field_value?('abc') | |||
|
144 | end | |||
|
145 | ||||
|
146 | def test_int_field_validation | |||
|
147 | f = CustomField.new(:field_format => 'int') | |||
|
148 | ||||
|
149 | assert f.valid_field_value?(nil) | |||
|
150 | assert f.valid_field_value?('') | |||
|
151 | assert f.valid_field_value?('123') | |||
|
152 | assert f.valid_field_value?('+123') | |||
|
153 | assert f.valid_field_value?('-123') | |||
|
154 | assert !f.valid_field_value?('6abc') | |||
|
155 | end | |||
|
156 | ||||
|
157 | def test_float_field_validation | |||
|
158 | f = CustomField.new(:field_format => 'float') | |||
|
159 | ||||
|
160 | assert f.valid_field_value?(nil) | |||
|
161 | assert f.valid_field_value?('') | |||
|
162 | assert f.valid_field_value?('11.2') | |||
|
163 | assert f.valid_field_value?('-6.250') | |||
|
164 | assert f.valid_field_value?('5') | |||
|
165 | assert !f.valid_field_value?('6abc') | |||
|
166 | end | |||
88 | end |
|
167 | end |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -20,92 +20,6 require File.expand_path('../../test_helper', __FILE__) | |||||
20 | class CustomValueTest < ActiveSupport::TestCase |
|
20 | class CustomValueTest < ActiveSupport::TestCase | |
21 | fixtures :custom_fields, :custom_values, :users |
|
21 | fixtures :custom_fields, :custom_values, :users | |
22 |
|
22 | |||
23 | def test_string_field_validation_with_blank_value |
|
|||
24 | f = CustomField.new(:field_format => 'string') |
|
|||
25 | v = CustomValue.new(:custom_field => f) |
|
|||
26 |
|
||||
27 | v.value = nil |
|
|||
28 | assert v.valid? |
|
|||
29 | v.value = '' |
|
|||
30 | assert v.valid? |
|
|||
31 |
|
||||
32 | f.is_required = true |
|
|||
33 | v.value = nil |
|
|||
34 | assert !v.valid? |
|
|||
35 | v.value = '' |
|
|||
36 | assert !v.valid? |
|
|||
37 | end |
|
|||
38 |
|
||||
39 | def test_string_field_validation_with_min_and_max_lengths |
|
|||
40 | f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5) |
|
|||
41 | v = CustomValue.new(:custom_field => f, :value => '') |
|
|||
42 | assert v.valid? |
|
|||
43 | v.value = 'a' |
|
|||
44 | assert !v.valid? |
|
|||
45 | v.value = 'a' * 2 |
|
|||
46 | assert v.valid? |
|
|||
47 | v.value = 'a' * 6 |
|
|||
48 | assert !v.valid? |
|
|||
49 | end |
|
|||
50 |
|
||||
51 | def test_string_field_validation_with_regexp |
|
|||
52 | f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$') |
|
|||
53 | v = CustomValue.new(:custom_field => f, :value => '') |
|
|||
54 | assert v.valid? |
|
|||
55 | v.value = 'abc' |
|
|||
56 | assert !v.valid? |
|
|||
57 | v.value = 'ABC' |
|
|||
58 | assert v.valid? |
|
|||
59 | end |
|
|||
60 |
|
||||
61 | def test_date_field_validation |
|
|||
62 | f = CustomField.new(:field_format => 'date') |
|
|||
63 | v = CustomValue.new(:custom_field => f, :value => '') |
|
|||
64 | assert v.valid? |
|
|||
65 | v.value = 'abc' |
|
|||
66 | assert !v.valid? |
|
|||
67 | v.value = '1975-07-33' |
|
|||
68 | assert !v.valid? |
|
|||
69 | v.value = '1975-07-14' |
|
|||
70 | assert v.valid? |
|
|||
71 | end |
|
|||
72 |
|
||||
73 | def test_list_field_validation |
|
|||
74 | f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2']) |
|
|||
75 | v = CustomValue.new(:custom_field => f, :value => '') |
|
|||
76 | assert v.valid? |
|
|||
77 | v.value = 'abc' |
|
|||
78 | assert !v.valid? |
|
|||
79 | v.value = 'value2' |
|
|||
80 | assert v.valid? |
|
|||
81 | end |
|
|||
82 |
|
||||
83 | def test_int_field_validation |
|
|||
84 | f = CustomField.new(:field_format => 'int') |
|
|||
85 | v = CustomValue.new(:custom_field => f, :value => '') |
|
|||
86 | assert v.valid? |
|
|||
87 | v.value = 'abc' |
|
|||
88 | assert !v.valid? |
|
|||
89 | v.value = '123' |
|
|||
90 | assert v.valid? |
|
|||
91 | v.value = '+123' |
|
|||
92 | assert v.valid? |
|
|||
93 | v.value = '-123' |
|
|||
94 | assert v.valid? |
|
|||
95 | end |
|
|||
96 |
|
||||
97 | def test_float_field_validation |
|
|||
98 | v = CustomValue.new(:customized => User.find(:first), :custom_field => UserCustomField.find_by_name('Money')) |
|
|||
99 | v.value = '11.2' |
|
|||
100 | assert v.save |
|
|||
101 | v.value = '' |
|
|||
102 | assert v.save |
|
|||
103 | v.value = '-6.250' |
|
|||
104 | assert v.save |
|
|||
105 | v.value = '6a' |
|
|||
106 | assert !v.save |
|
|||
107 | end |
|
|||
108 |
|
||||
109 | def test_default_value |
|
23 | def test_default_value | |
110 | field = CustomField.find_by_default_value('Default string') |
|
24 | field = CustomField.find_by_default_value('Default string') | |
111 | assert_not_nil field |
|
25 | assert_not_nil field |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -29,6 +29,8 class IssueTest < ActiveSupport::TestCase | |||||
29 | :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, |
|
29 | :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values, | |
30 | :time_entries |
|
30 | :time_entries | |
31 |
|
31 | |||
|
32 | include Redmine::I18n | |||
|
33 | ||||
32 | def test_create |
|
34 | def test_create | |
33 | issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, |
|
35 | issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, | |
34 | :status_id => 1, :priority => IssuePriority.all.first, |
|
36 | :status_id => 1, :priority => IssuePriority.all.first, | |
@@ -48,6 +50,7 class IssueTest < ActiveSupport::TestCase | |||||
48 | end |
|
50 | end | |
49 |
|
51 | |||
50 | def test_create_with_required_custom_field |
|
52 | def test_create_with_required_custom_field | |
|
53 | set_language_if_valid 'en' | |||
51 | field = IssueCustomField.find_by_name('Database') |
|
54 | field = IssueCustomField.find_by_name('Database') | |
52 | field.update_attribute(:is_required, true) |
|
55 | field.update_attribute(:is_required, true) | |
53 |
|
56 | |||
@@ -57,18 +60,15 class IssueTest < ActiveSupport::TestCase | |||||
57 | assert issue.available_custom_fields.include?(field) |
|
60 | assert issue.available_custom_fields.include?(field) | |
58 | # No value for the custom field |
|
61 | # No value for the custom field | |
59 | assert !issue.save |
|
62 | assert !issue.save | |
60 | assert_equal I18n.translate('activerecord.errors.messages.invalid'), |
|
63 | assert_equal "Database can't be blank", issue.errors[:base].to_s | |
61 | issue.errors[:custom_values].to_s |
|
|||
62 | # Blank value |
|
64 | # Blank value | |
63 | issue.custom_field_values = { field.id => '' } |
|
65 | issue.custom_field_values = { field.id => '' } | |
64 | assert !issue.save |
|
66 | assert !issue.save | |
65 | assert_equal I18n.translate('activerecord.errors.messages.invalid'), |
|
67 | assert_equal "Database can't be blank", issue.errors[:base].to_s | |
66 | issue.errors[:custom_values].to_s |
|
|||
67 | # Invalid value |
|
68 | # Invalid value | |
68 | issue.custom_field_values = { field.id => 'SQLServer' } |
|
69 | issue.custom_field_values = { field.id => 'SQLServer' } | |
69 | assert !issue.save |
|
70 | assert !issue.save | |
70 | assert_equal I18n.translate('activerecord.errors.messages.invalid'), |
|
71 | assert_equal "Database is not included in the list", issue.errors[:base].to_s | |
71 | issue.errors[:custom_values].to_s |
|
|||
72 | # Valid value |
|
72 | # Valid value | |
73 | issue.custom_field_values = { field.id => 'PostgreSQL' } |
|
73 | issue.custom_field_values = { field.id => 'PostgreSQL' } | |
74 | assert issue.save |
|
74 | assert issue.save | |
@@ -327,8 +327,7 class IssueTest < ActiveSupport::TestCase | |||||
327 | attributes['tracker_id'] = '1' |
|
327 | attributes['tracker_id'] = '1' | |
328 | issue = Issue.new(:project => Project.find(1)) |
|
328 | issue = Issue.new(:project => Project.find(1)) | |
329 | issue.attributes = attributes |
|
329 | issue.attributes = attributes | |
330 |
assert_ |
|
330 | assert_equal 'MySQL', issue.custom_field_value(1) | |
331 | assert_equal 'MySQL', issue.custom_value_for(1).value |
|
|||
332 | end |
|
331 | end | |
333 |
|
332 | |||
334 | def test_should_update_issue_with_disabled_tracker |
|
333 | def test_should_update_issue_with_disabled_tracker |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -20,6 +20,8 require File.expand_path('../../test_helper', __FILE__) | |||||
20 | class TimeEntryActivityTest < ActiveSupport::TestCase |
|
20 | class TimeEntryActivityTest < ActiveSupport::TestCase | |
21 | fixtures :enumerations, :time_entries |
|
21 | fixtures :enumerations, :time_entries | |
22 |
|
22 | |||
|
23 | include Redmine::I18n | |||
|
24 | ||||
23 | def test_should_be_an_enumeration |
|
25 | def test_should_be_an_enumeration | |
24 | assert TimeEntryActivity.ancestors.include?(Enumeration) |
|
26 | assert TimeEntryActivity.ancestors.include?(Enumeration) | |
25 | end |
|
27 | end | |
@@ -44,13 +46,13 class TimeEntryActivityTest < ActiveSupport::TestCase | |||||
44 | end |
|
46 | end | |
45 |
|
47 | |||
46 | def test_create_without_required_custom_field_should_fail |
|
48 | def test_create_without_required_custom_field_should_fail | |
|
49 | set_language_if_valid 'en' | |||
47 | field = TimeEntryActivityCustomField.find_by_name('Billable') |
|
50 | field = TimeEntryActivityCustomField.find_by_name('Billable') | |
48 | field.update_attribute(:is_required, true) |
|
51 | field.update_attribute(:is_required, true) | |
49 |
|
52 | |||
50 | e = TimeEntryActivity.new(:name => 'Custom Data') |
|
53 | e = TimeEntryActivity.new(:name => 'Custom Data') | |
51 | assert !e.save |
|
54 | assert !e.save | |
52 | assert_equal I18n.translate('activerecord.errors.messages.invalid'), |
|
55 | assert_equal "Billable can't be blank", e.errors[:base].to_s | |
53 | e.errors[:custom_values].to_s |
|
|||
54 | end |
|
56 | end | |
55 |
|
57 | |||
56 | def test_create_with_required_custom_field_should_succeed |
|
58 | def test_create_with_required_custom_field_should_succeed | |
@@ -62,7 +64,8 class TimeEntryActivityTest < ActiveSupport::TestCase | |||||
62 | assert e.save |
|
64 | assert e.save | |
63 | end |
|
65 | end | |
64 |
|
66 | |||
65 |
def test_update_ |
|
67 | def test_update_with_required_custom_field_change | |
|
68 | set_language_if_valid 'en' | |||
66 | field = TimeEntryActivityCustomField.find_by_name('Billable') |
|
69 | field = TimeEntryActivityCustomField.find_by_name('Billable') | |
67 | field.update_attribute(:is_required, true) |
|
70 | field.update_attribute(:is_required, true) | |
68 |
|
71 | |||
@@ -73,7 +76,7 class TimeEntryActivityTest < ActiveSupport::TestCase | |||||
73 | # Blanking custom field, save should fail |
|
76 | # Blanking custom field, save should fail | |
74 | e.custom_field_values = {field.id => ""} |
|
77 | e.custom_field_values = {field.id => ""} | |
75 | assert !e.save |
|
78 | assert !e.save | |
76 | assert e.errors[:custom_values] |
|
79 | assert_equal "Billable can't be blank", e.errors[:base].to_s | |
77 |
|
80 | |||
78 | # Update custom field to valid value, save should succeed |
|
81 | # Update custom field to valid value, save should succeed | |
79 | e.custom_field_values = {field.id => "0"} |
|
82 | e.custom_field_values = {field.id => "0"} | |
@@ -81,6 +84,5 class TimeEntryActivityTest < ActiveSupport::TestCase | |||||
81 | e.reload |
|
84 | e.reload | |
82 | assert_equal "0", e.custom_value_for(field).value |
|
85 | assert_equal "0", e.custom_value_for(field).value | |
83 | end |
|
86 | end | |
84 |
|
||||
85 | end |
|
87 | end | |
86 |
|
88 |
@@ -1,5 +1,5 | |||||
1 | # Redmine - project management software |
|
1 | # Redmine - project management software | |
2 |
# Copyright (C) 2006-201 |
|
2 | # Copyright (C) 2006-2012 Jean-Philippe Lang | |
3 | # |
|
3 | # | |
4 | # This program is free software; you can redistribute it and/or |
|
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License |
|
5 | # modify it under the terms of the GNU General Public License | |
@@ -30,12 +30,10 module Redmine | |||||
30 | has_many :custom_values, :as => :customized, |
|
30 | has_many :custom_values, :as => :customized, | |
31 | :include => :custom_field, |
|
31 | :include => :custom_field, | |
32 | :order => "#{CustomField.table_name}.position", |
|
32 | :order => "#{CustomField.table_name}.position", | |
33 | :dependent => :delete_all |
|
33 | :dependent => :delete_all, | |
34 | before_validation { |customized| customized.custom_field_values if customized.new_record? } |
|
34 | :validate => false | |
35 | # Trigger validation only if custom values were changed |
|
|||
36 | validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? } |
|
|||
37 | send :include, Redmine::Acts::Customizable::InstanceMethods |
|
35 | send :include, Redmine::Acts::Customizable::InstanceMethods | |
38 | # Save custom values when saving the customized object |
|
36 | validate :validate_custom_field_values | |
39 | after_save :save_custom_field_values |
|
37 | after_save :save_custom_field_values | |
40 | end |
|
38 | end | |
41 | end |
|
39 | end | |
@@ -66,15 +64,28 module Redmine | |||||
66 | # Sets the values of the object's custom fields |
|
64 | # Sets the values of the object's custom fields | |
67 | # values is a hash like {'1' => 'foo', 2 => 'bar'} |
|
65 | # values is a hash like {'1' => 'foo', 2 => 'bar'} | |
68 | def custom_field_values=(values) |
|
66 | def custom_field_values=(values) | |
69 | @custom_field_values_changed = true |
|
|||
70 | values = values.stringify_keys |
|
67 | values = values.stringify_keys | |
71 | custom_field_values.each do |custom_value| |
|
68 | ||
72 | custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s) |
|
69 | custom_field_values.each do |custom_field_value| | |
73 | end if values.is_a?(Hash) |
|
70 | key = custom_field_value.custom_field_id.to_s | |
|
71 | if values.has_key?(key) | |||
|
72 | value = values[key] | |||
|
73 | custom_field_value.value = value | |||
|
74 | end | |||
|
75 | end | |||
|
76 | @custom_field_values_changed = true | |||
74 | end |
|
77 | end | |
75 |
|
78 | |||
76 | def custom_field_values |
|
79 | def custom_field_values | |
77 | @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) } |
|
80 | @custom_field_values ||= available_custom_fields.collect do |field| | |
|
81 | x = CustomFieldValue.new | |||
|
82 | x.custom_field = field | |||
|
83 | x.customized = self | |||
|
84 | cv = custom_values.detect { |v| v.custom_field == field } | |||
|
85 | cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil) | |||
|
86 | x.value = cv.value | |||
|
87 | x | |||
|
88 | end | |||
78 | end |
|
89 | end | |
79 |
|
90 | |||
80 | def visible_custom_field_values |
|
91 | def visible_custom_field_values | |
@@ -90,18 +101,34 module Redmine | |||||
90 | custom_values.detect {|v| v.custom_field_id == field_id } |
|
101 | custom_values.detect {|v| v.custom_field_id == field_id } | |
91 | end |
|
102 | end | |
92 |
|
103 | |||
|
104 | def custom_field_value(c) | |||
|
105 | field_id = (c.is_a?(CustomField) ? c.id : c.to_i) | |||
|
106 | custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value) | |||
|
107 | end | |||
|
108 | ||||
|
109 | def validate_custom_field_values | |||
|
110 | if new_record? || custom_field_values_changed? | |||
|
111 | custom_field_values.each(&:validate_value) | |||
|
112 | end | |||
|
113 | end | |||
|
114 | ||||
93 | def save_custom_field_values |
|
115 | def save_custom_field_values | |
94 |
|
|
116 | target_custom_values = [] | |
95 |
custom_field_values.each |
|
117 | custom_field_values.each do |custom_field_value| | |
|
118 | target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field} | |||
|
119 | target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field) | |||
|
120 | target.value = custom_field_value.value | |||
|
121 | target_custom_values << target | |||
|
122 | end | |||
|
123 | self.custom_values = target_custom_values | |||
|
124 | custom_values.each(&:save) | |||
96 | @custom_field_values_changed = false |
|
125 | @custom_field_values_changed = false | |
97 | @custom_field_values = nil |
|
126 | true | |
98 | end |
|
127 | end | |
99 |
|
128 | |||
100 | def reset_custom_values! |
|
129 | def reset_custom_values! | |
101 | @custom_field_values = nil |
|
130 | @custom_field_values = nil | |
102 | @custom_field_values_changed = true |
|
131 | @custom_field_values_changed = true | |
103 | values = custom_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} |
|
|||
104 | custom_values.each {|cv| cv.destroy unless custom_field_values.include?(cv)} |
|
|||
105 | end |
|
132 | end | |
106 |
|
133 | |||
107 | module ClassMethods |
|
134 | module ClassMethods |
General Comments 0
You need to be logged in to leave comments.
Login now