##// END OF EJS Templates
Moved CUSTOM_FIELDS_TABS out of the model....
Jean-Philippe Lang -
r11847:b19b90234529
parent child
Show More
@@ -1,149 +1,170
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2013 Jean-Philippe Lang
4 # Copyright (C) 2006-2013 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
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module CustomFieldsHelper
20 module CustomFieldsHelper
21
21
22 CUSTOM_FIELDS_TABS = [
23 {:name => 'IssueCustomField', :partial => 'custom_fields/index',
24 :label => :label_issue_plural},
25 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
26 :label => :label_spent_time},
27 {:name => 'ProjectCustomField', :partial => 'custom_fields/index',
28 :label => :label_project_plural},
29 {:name => 'VersionCustomField', :partial => 'custom_fields/index',
30 :label => :label_version_plural},
31 {:name => 'UserCustomField', :partial => 'custom_fields/index',
32 :label => :label_user_plural},
33 {:name => 'GroupCustomField', :partial => 'custom_fields/index',
34 :label => :label_group_plural},
35 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
36 :label => TimeEntryActivity::OptionName},
37 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
38 :label => IssuePriority::OptionName},
39 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
40 :label => DocumentCategory::OptionName}
41 ]
42
22 def custom_fields_tabs
43 def custom_fields_tabs
23 CustomField::CUSTOM_FIELDS_TABS
44 CUSTOM_FIELDS_TABS
24 end
45 end
25
46
26 # Return custom field html tag corresponding to its format
47 # Return custom field html tag corresponding to its format
27 def custom_field_tag(name, custom_value)
48 def custom_field_tag(name, custom_value)
28 custom_field = custom_value.custom_field
49 custom_field = custom_value.custom_field
29 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
50 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
30 field_name << "[]" if custom_field.multiple?
51 field_name << "[]" if custom_field.multiple?
31 field_id = "#{name}_custom_field_values_#{custom_field.id}"
52 field_id = "#{name}_custom_field_values_#{custom_field.id}"
32
53
33 tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
54 tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
34
55
35 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
56 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
36 case field_format.try(:edit_as)
57 case field_format.try(:edit_as)
37 when "date"
58 when "date"
38 text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) +
59 text_field_tag(field_name, custom_value.value, tag_options.merge(:size => 10)) +
39 calendar_for(field_id)
60 calendar_for(field_id)
40 when "text"
61 when "text"
41 text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3))
62 text_area_tag(field_name, custom_value.value, tag_options.merge(:rows => 3))
42 when "bool"
63 when "bool"
43 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options)
64 hidden_field_tag(field_name, '0') + check_box_tag(field_name, '1', custom_value.true?, tag_options)
44 when "list"
65 when "list"
45 blank_option = ''.html_safe
66 blank_option = ''.html_safe
46 unless custom_field.multiple?
67 unless custom_field.multiple?
47 if custom_field.is_required?
68 if custom_field.is_required?
48 unless custom_field.default_value.present?
69 unless custom_field.default_value.present?
49 blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
70 blank_option = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---", :value => '')
50 end
71 end
51 else
72 else
52 blank_option = content_tag('option')
73 blank_option = content_tag('option')
53 end
74 end
54 end
75 end
55 s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
76 s = select_tag(field_name, blank_option + options_for_select(custom_field.possible_values_options(custom_value.customized), custom_value.value),
56 tag_options.merge(:multiple => custom_field.multiple?))
77 tag_options.merge(:multiple => custom_field.multiple?))
57 if custom_field.multiple?
78 if custom_field.multiple?
58 s << hidden_field_tag(field_name, '')
79 s << hidden_field_tag(field_name, '')
59 end
80 end
60 s
81 s
61 else
82 else
62 text_field_tag(field_name, custom_value.value, tag_options)
83 text_field_tag(field_name, custom_value.value, tag_options)
63 end
84 end
64 end
85 end
65
86
66 # Return custom field label tag
87 # Return custom field label tag
67 def custom_field_label_tag(name, custom_value, options={})
88 def custom_field_label_tag(name, custom_value, options={})
68 required = options[:required] || custom_value.custom_field.is_required?
89 required = options[:required] || custom_value.custom_field.is_required?
69
90
70 content_tag "label", h(custom_value.custom_field.name) +
91 content_tag "label", h(custom_value.custom_field.name) +
71 (required ? " <span class=\"required\">*</span>".html_safe : ""),
92 (required ? " <span class=\"required\">*</span>".html_safe : ""),
72 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
93 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}"
73 end
94 end
74
95
75 # Return custom field tag with its label tag
96 # Return custom field tag with its label tag
76 def custom_field_tag_with_label(name, custom_value, options={})
97 def custom_field_tag_with_label(name, custom_value, options={})
77 custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
98 custom_field_label_tag(name, custom_value, options) + custom_field_tag(name, custom_value)
78 end
99 end
79
100
80 def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='')
101 def custom_field_tag_for_bulk_edit(name, custom_field, projects=nil, value='')
81 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
102 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
82 field_name << "[]" if custom_field.multiple?
103 field_name << "[]" if custom_field.multiple?
83 field_id = "#{name}_custom_field_values_#{custom_field.id}"
104 field_id = "#{name}_custom_field_values_#{custom_field.id}"
84
105
85 tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
106 tag_options = {:id => field_id, :class => "#{custom_field.field_format}_cf"}
86
107
87 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
108 field_format = Redmine::CustomFieldFormat.find_by_name(custom_field.field_format)
88 case field_format.try(:edit_as)
109 case field_format.try(:edit_as)
89 when "date"
110 when "date"
90 text_field_tag(field_name, value, tag_options.merge(:size => 10)) +
111 text_field_tag(field_name, value, tag_options.merge(:size => 10)) +
91 calendar_for(field_id)
112 calendar_for(field_id)
92 when "text"
113 when "text"
93 text_area_tag(field_name, value, tag_options.merge(:rows => 3))
114 text_area_tag(field_name, value, tag_options.merge(:rows => 3))
94 when "bool"
115 when "bool"
95 select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
116 select_tag(field_name, options_for_select([[l(:label_no_change_option), ''],
96 [l(:general_text_yes), '1'],
117 [l(:general_text_yes), '1'],
97 [l(:general_text_no), '0']], value), tag_options)
118 [l(:general_text_no), '0']], value), tag_options)
98 when "list"
119 when "list"
99 options = []
120 options = []
100 options << [l(:label_no_change_option), ''] unless custom_field.multiple?
121 options << [l(:label_no_change_option), ''] unless custom_field.multiple?
101 options << [l(:label_none), '__none__'] unless custom_field.is_required?
122 options << [l(:label_none), '__none__'] unless custom_field.is_required?
102 options += custom_field.possible_values_options(projects)
123 options += custom_field.possible_values_options(projects)
103 select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?))
124 select_tag(field_name, options_for_select(options, value), tag_options.merge(:multiple => custom_field.multiple?))
104 else
125 else
105 text_field_tag(field_name, value, tag_options)
126 text_field_tag(field_name, value, tag_options)
106 end
127 end
107 end
128 end
108
129
109 # Return a string used to display a custom value
130 # Return a string used to display a custom value
110 def show_value(custom_value)
131 def show_value(custom_value)
111 return "" unless custom_value
132 return "" unless custom_value
112 format_value(custom_value.value, custom_value.custom_field.field_format)
133 format_value(custom_value.value, custom_value.custom_field.field_format)
113 end
134 end
114
135
115 # Return a string used to display a custom value
136 # Return a string used to display a custom value
116 def format_value(value, field_format)
137 def format_value(value, field_format)
117 if value.is_a?(Array)
138 if value.is_a?(Array)
118 value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
139 value.collect {|v| format_value(v, field_format)}.compact.sort.join(', ')
119 else
140 else
120 Redmine::CustomFieldFormat.format_value(value, field_format)
141 Redmine::CustomFieldFormat.format_value(value, field_format)
121 end
142 end
122 end
143 end
123
144
124 # Return an array of custom field formats which can be used in select_tag
145 # Return an array of custom field formats which can be used in select_tag
125 def custom_field_formats_for_select(custom_field)
146 def custom_field_formats_for_select(custom_field)
126 Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
147 Redmine::CustomFieldFormat.as_select(custom_field.class.customized_class.name)
127 end
148 end
128
149
129 # Renders the custom_values in api views
150 # Renders the custom_values in api views
130 def render_api_custom_values(custom_values, api)
151 def render_api_custom_values(custom_values, api)
131 api.array :custom_fields do
152 api.array :custom_fields do
132 custom_values.each do |custom_value|
153 custom_values.each do |custom_value|
133 attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name}
154 attrs = {:id => custom_value.custom_field_id, :name => custom_value.custom_field.name}
134 attrs.merge!(:multiple => true) if custom_value.custom_field.multiple?
155 attrs.merge!(:multiple => true) if custom_value.custom_field.multiple?
135 api.custom_field attrs do
156 api.custom_field attrs do
136 if custom_value.value.is_a?(Array)
157 if custom_value.value.is_a?(Array)
137 api.array :value do
158 api.array :value do
138 custom_value.value.each do |value|
159 custom_value.value.each do |value|
139 api.value value unless value.blank?
160 api.value value unless value.blank?
140 end
161 end
141 end
162 end
142 else
163 else
143 api.value custom_value.value
164 api.value custom_value.value
144 end
165 end
145 end
166 end
146 end
167 end
147 end unless custom_values.empty?
168 end unless custom_values.empty?
148 end
169 end
149 end
170 end
@@ -1,409 +1,386
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class CustomField < ActiveRecord::Base
18 class CustomField < ActiveRecord::Base
19 include Redmine::SubclassFactory
19 include Redmine::SubclassFactory
20
20
21 has_many :custom_values, :dependent => :delete_all
21 has_many :custom_values, :dependent => :delete_all
22 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
22 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}custom_fields_roles#{table_name_suffix}", :foreign_key => "custom_field_id"
23 acts_as_list :scope => 'type = \'#{self.class}\''
23 acts_as_list :scope => 'type = \'#{self.class}\''
24 serialize :possible_values
24 serialize :possible_values
25
25
26 validates_presence_of :name, :field_format
26 validates_presence_of :name, :field_format
27 validates_uniqueness_of :name, :scope => :type
27 validates_uniqueness_of :name, :scope => :type
28 validates_length_of :name, :maximum => 30
28 validates_length_of :name, :maximum => 30
29 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
29 validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
30 validate :validate_custom_field
30 validate :validate_custom_field
31
31
32 before_validation :set_searchable
32 before_validation :set_searchable
33 after_save :handle_multiplicity_change
33 after_save :handle_multiplicity_change
34 after_save do |field|
34 after_save do |field|
35 if field.visible_changed? && field.visible
35 if field.visible_changed? && field.visible
36 field.roles.clear
36 field.roles.clear
37 end
37 end
38 end
38 end
39
39
40 scope :sorted, lambda { order("#{table_name}.position ASC") }
40 scope :sorted, lambda { order("#{table_name}.position ASC") }
41 scope :visible, lambda {|*args|
41 scope :visible, lambda {|*args|
42 user = args.shift || User.current
42 user = args.shift || User.current
43 if user.admin?
43 if user.admin?
44 # nop
44 # nop
45 elsif user.memberships.any?
45 elsif user.memberships.any?
46 where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
46 where("#{table_name}.visible = ? OR #{table_name}.id IN (SELECT DISTINCT cfr.custom_field_id FROM #{Member.table_name} m" +
47 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
47 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
48 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
48 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
49 " WHERE m.user_id = ?)",
49 " WHERE m.user_id = ?)",
50 true, user.id)
50 true, user.id)
51 else
51 else
52 where(:visible => true)
52 where(:visible => true)
53 end
53 end
54 }
54 }
55
55
56 CUSTOM_FIELDS_TABS = [
57 {:name => 'IssueCustomField', :partial => 'custom_fields/index',
58 :label => :label_issue_plural},
59 {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index',
60 :label => :label_spent_time},
61 {:name => 'ProjectCustomField', :partial => 'custom_fields/index',
62 :label => :label_project_plural},
63 {:name => 'VersionCustomField', :partial => 'custom_fields/index',
64 :label => :label_version_plural},
65 {:name => 'UserCustomField', :partial => 'custom_fields/index',
66 :label => :label_user_plural},
67 {:name => 'GroupCustomField', :partial => 'custom_fields/index',
68 :label => :label_group_plural},
69 {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index',
70 :label => TimeEntryActivity::OptionName},
71 {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index',
72 :label => IssuePriority::OptionName},
73 {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index',
74 :label => DocumentCategory::OptionName}
75 ]
76
77 CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]}
78
79 def visible_by?(project, user=User.current)
56 def visible_by?(project, user=User.current)
80 visible? || user.admin?
57 visible? || user.admin?
81 end
58 end
82
59
83 def field_format=(arg)
60 def field_format=(arg)
84 # cannot change format of a saved custom field
61 # cannot change format of a saved custom field
85 super if new_record?
62 super if new_record?
86 end
63 end
87
64
88 def set_searchable
65 def set_searchable
89 # make sure these fields are not searchable
66 # make sure these fields are not searchable
90 self.searchable = false if %w(int float date bool).include?(field_format)
67 self.searchable = false if %w(int float date bool).include?(field_format)
91 # make sure only these fields can have multiple values
68 # make sure only these fields can have multiple values
92 self.multiple = false unless %w(list user version).include?(field_format)
69 self.multiple = false unless %w(list user version).include?(field_format)
93 true
70 true
94 end
71 end
95
72
96 def validate_custom_field
73 def validate_custom_field
97 if self.field_format == "list"
74 if self.field_format == "list"
98 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
75 errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty?
99 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
76 errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array
100 end
77 end
101
78
102 if regexp.present?
79 if regexp.present?
103 begin
80 begin
104 Regexp.new(regexp)
81 Regexp.new(regexp)
105 rescue
82 rescue
106 errors.add(:regexp, :invalid)
83 errors.add(:regexp, :invalid)
107 end
84 end
108 end
85 end
109
86
110 if default_value.present? && !valid_field_value?(default_value)
87 if default_value.present? && !valid_field_value?(default_value)
111 errors.add(:default_value, :invalid)
88 errors.add(:default_value, :invalid)
112 end
89 end
113 end
90 end
114
91
115 def possible_values_options(obj=nil)
92 def possible_values_options(obj=nil)
116 case field_format
93 case field_format
117 when 'user', 'version'
94 when 'user', 'version'
118 if obj.respond_to?(:project) && obj.project
95 if obj.respond_to?(:project) && obj.project
119 case field_format
96 case field_format
120 when 'user'
97 when 'user'
121 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
98 obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]}
122 when 'version'
99 when 'version'
123 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
100 obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]}
124 end
101 end
125 elsif obj.is_a?(Array)
102 elsif obj.is_a?(Array)
126 obj.collect {|o| possible_values_options(o)}.reduce(:&)
103 obj.collect {|o| possible_values_options(o)}.reduce(:&)
127 else
104 else
128 []
105 []
129 end
106 end
130 when 'bool'
107 when 'bool'
131 [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
108 [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']]
132 else
109 else
133 possible_values || []
110 possible_values || []
134 end
111 end
135 end
112 end
136
113
137 def possible_values(obj=nil)
114 def possible_values(obj=nil)
138 case field_format
115 case field_format
139 when 'user', 'version'
116 when 'user', 'version'
140 possible_values_options(obj).collect(&:last)
117 possible_values_options(obj).collect(&:last)
141 when 'bool'
118 when 'bool'
142 ['1', '0']
119 ['1', '0']
143 else
120 else
144 values = super()
121 values = super()
145 if values.is_a?(Array)
122 if values.is_a?(Array)
146 values.each do |value|
123 values.each do |value|
147 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
124 value.force_encoding('UTF-8') if value.respond_to?(:force_encoding)
148 end
125 end
149 end
126 end
150 values || []
127 values || []
151 end
128 end
152 end
129 end
153
130
154 # Makes possible_values accept a multiline string
131 # Makes possible_values accept a multiline string
155 def possible_values=(arg)
132 def possible_values=(arg)
156 if arg.is_a?(Array)
133 if arg.is_a?(Array)
157 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
134 super(arg.compact.collect(&:strip).select {|v| !v.blank?})
158 else
135 else
159 self.possible_values = arg.to_s.split(/[\n\r]+/)
136 self.possible_values = arg.to_s.split(/[\n\r]+/)
160 end
137 end
161 end
138 end
162
139
163 def cast_value(value)
140 def cast_value(value)
164 casted = nil
141 casted = nil
165 unless value.blank?
142 unless value.blank?
166 case field_format
143 case field_format
167 when 'string', 'text', 'list'
144 when 'string', 'text', 'list'
168 casted = value
145 casted = value
169 when 'date'
146 when 'date'
170 casted = begin; value.to_date; rescue; nil end
147 casted = begin; value.to_date; rescue; nil end
171 when 'bool'
148 when 'bool'
172 casted = (value == '1' ? true : false)
149 casted = (value == '1' ? true : false)
173 when 'int'
150 when 'int'
174 casted = value.to_i
151 casted = value.to_i
175 when 'float'
152 when 'float'
176 casted = value.to_f
153 casted = value.to_f
177 when 'user', 'version'
154 when 'user', 'version'
178 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
155 casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i))
179 end
156 end
180 end
157 end
181 casted
158 casted
182 end
159 end
183
160
184 def value_from_keyword(keyword, customized)
161 def value_from_keyword(keyword, customized)
185 possible_values_options = possible_values_options(customized)
162 possible_values_options = possible_values_options(customized)
186 if possible_values_options.present?
163 if possible_values_options.present?
187 keyword = keyword.to_s.downcase
164 keyword = keyword.to_s.downcase
188 if v = possible_values_options.detect {|text, id| text.downcase == keyword}
165 if v = possible_values_options.detect {|text, id| text.downcase == keyword}
189 if v.is_a?(Array)
166 if v.is_a?(Array)
190 v.last
167 v.last
191 else
168 else
192 v
169 v
193 end
170 end
194 end
171 end
195 else
172 else
196 keyword
173 keyword
197 end
174 end
198 end
175 end
199
176
200 # Returns a ORDER BY clause that can used to sort customized
177 # Returns a ORDER BY clause that can used to sort customized
201 # objects by their value of the custom field.
178 # objects by their value of the custom field.
202 # Returns nil if the custom field can not be used for sorting.
179 # Returns nil if the custom field can not be used for sorting.
203 def order_statement
180 def order_statement
204 return nil if multiple?
181 return nil if multiple?
205 case field_format
182 case field_format
206 when 'string', 'text', 'list', 'date', 'bool'
183 when 'string', 'text', 'list', 'date', 'bool'
207 # COALESCE is here to make sure that blank and NULL values are sorted equally
184 # COALESCE is here to make sure that blank and NULL values are sorted equally
208 "COALESCE(#{join_alias}.value, '')"
185 "COALESCE(#{join_alias}.value, '')"
209 when 'int', 'float'
186 when 'int', 'float'
210 # Make the database cast values into numeric
187 # Make the database cast values into numeric
211 # Postgresql will raise an error if a value can not be casted!
188 # Postgresql will raise an error if a value can not be casted!
212 # CustomValue validations should ensure that it doesn't occur
189 # CustomValue validations should ensure that it doesn't occur
213 "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
190 "CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,3))"
214 when 'user', 'version'
191 when 'user', 'version'
215 value_class.fields_for_order_statement(value_join_alias)
192 value_class.fields_for_order_statement(value_join_alias)
216 else
193 else
217 nil
194 nil
218 end
195 end
219 end
196 end
220
197
221 # Returns a GROUP BY clause that can used to group by custom value
198 # Returns a GROUP BY clause that can used to group by custom value
222 # Returns nil if the custom field can not be used for grouping.
199 # Returns nil if the custom field can not be used for grouping.
223 def group_statement
200 def group_statement
224 return nil if multiple?
201 return nil if multiple?
225 case field_format
202 case field_format
226 when 'list', 'date', 'bool', 'int'
203 when 'list', 'date', 'bool', 'int'
227 order_statement
204 order_statement
228 when 'user', 'version'
205 when 'user', 'version'
229 "COALESCE(#{join_alias}.value, '')"
206 "COALESCE(#{join_alias}.value, '')"
230 else
207 else
231 nil
208 nil
232 end
209 end
233 end
210 end
234
211
235 def join_for_order_statement
212 def join_for_order_statement
236 case field_format
213 case field_format
237 when 'user', 'version'
214 when 'user', 'version'
238 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
215 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
239 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
216 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
240 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
217 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
241 " AND #{join_alias}.custom_field_id = #{id}" +
218 " AND #{join_alias}.custom_field_id = #{id}" +
242 " AND (#{visibility_by_project_condition})" +
219 " AND (#{visibility_by_project_condition})" +
243 " AND #{join_alias}.value <> ''" +
220 " AND #{join_alias}.value <> ''" +
244 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
221 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
245 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
222 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
246 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
223 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
247 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
224 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
248 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
225 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
249 " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
226 " ON CAST(CASE #{join_alias}.value WHEN '' THEN '0' ELSE #{join_alias}.value END AS decimal(30,0)) = #{value_join_alias}.id"
250 when 'int', 'float'
227 when 'int', 'float'
251 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
228 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
252 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
229 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
253 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
230 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
254 " AND #{join_alias}.custom_field_id = #{id}" +
231 " AND #{join_alias}.custom_field_id = #{id}" +
255 " AND (#{visibility_by_project_condition})" +
232 " AND (#{visibility_by_project_condition})" +
256 " AND #{join_alias}.value <> ''" +
233 " AND #{join_alias}.value <> ''" +
257 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
234 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
258 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
235 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
259 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
236 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
260 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
237 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
261 when 'string', 'text', 'list', 'date', 'bool'
238 when 'string', 'text', 'list', 'date', 'bool'
262 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
239 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
263 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
240 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
264 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
241 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
265 " AND #{join_alias}.custom_field_id = #{id}" +
242 " AND #{join_alias}.custom_field_id = #{id}" +
266 " AND (#{visibility_by_project_condition})" +
243 " AND (#{visibility_by_project_condition})" +
267 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
244 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
268 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
245 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
269 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
246 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
270 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
247 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)"
271 else
248 else
272 nil
249 nil
273 end
250 end
274 end
251 end
275
252
276 def join_alias
253 def join_alias
277 "cf_#{id}"
254 "cf_#{id}"
278 end
255 end
279
256
280 def value_join_alias
257 def value_join_alias
281 join_alias + "_" + field_format
258 join_alias + "_" + field_format
282 end
259 end
283
260
284 def visibility_by_project_condition(project_key=nil, user=User.current)
261 def visibility_by_project_condition(project_key=nil, user=User.current)
285 if visible? || user.admin?
262 if visible? || user.admin?
286 "1=1"
263 "1=1"
287 elsif user.anonymous?
264 elsif user.anonymous?
288 "1=0"
265 "1=0"
289 else
266 else
290 project_key ||= "#{self.class.customized_class.table_name}.project_id"
267 project_key ||= "#{self.class.customized_class.table_name}.project_id"
291 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
268 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
292 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
269 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
293 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
270 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
294 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
271 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
295 end
272 end
296 end
273 end
297
274
298 def self.visibility_condition
275 def self.visibility_condition
299 if user.admin?
276 if user.admin?
300 "1=1"
277 "1=1"
301 elsif user.anonymous?
278 elsif user.anonymous?
302 "#{table_name}.visible"
279 "#{table_name}.visible"
303 else
280 else
304 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
281 "#{project_key} IN (SELECT DISTINCT m.project_id FROM #{Member.table_name} m" +
305 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
282 " INNER JOIN #{MemberRole.table_name} mr ON mr.member_id = m.id" +
306 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
283 " INNER JOIN #{table_name_prefix}custom_fields_roles#{table_name_suffix} cfr ON cfr.role_id = mr.role_id" +
307 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
284 " WHERE m.user_id = #{user.id} AND cfr.custom_field_id = #{id})"
308 end
285 end
309 end
286 end
310
287
311 def <=>(field)
288 def <=>(field)
312 position <=> field.position
289 position <=> field.position
313 end
290 end
314
291
315 # Returns the class that values represent
292 # Returns the class that values represent
316 def value_class
293 def value_class
317 case field_format
294 case field_format
318 when 'user', 'version'
295 when 'user', 'version'
319 field_format.classify.constantize
296 field_format.classify.constantize
320 else
297 else
321 nil
298 nil
322 end
299 end
323 end
300 end
324
301
325 def self.customized_class
302 def self.customized_class
326 self.name =~ /^(.+)CustomField$/
303 self.name =~ /^(.+)CustomField$/
327 begin; $1.constantize; rescue nil; end
304 begin; $1.constantize; rescue nil; end
328 end
305 end
329
306
330 # to move in project_custom_field
307 # to move in project_custom_field
331 def self.for_all
308 def self.for_all
332 where(:is_for_all => true).order('position').all
309 where(:is_for_all => true).order('position').all
333 end
310 end
334
311
335 def type_name
312 def type_name
336 nil
313 nil
337 end
314 end
338
315
339 # Returns the error messages for the given value
316 # Returns the error messages for the given value
340 # or an empty array if value is a valid value for the custom field
317 # or an empty array if value is a valid value for the custom field
341 def validate_field_value(value)
318 def validate_field_value(value)
342 errs = []
319 errs = []
343 if value.is_a?(Array)
320 if value.is_a?(Array)
344 if !multiple?
321 if !multiple?
345 errs << ::I18n.t('activerecord.errors.messages.invalid')
322 errs << ::I18n.t('activerecord.errors.messages.invalid')
346 end
323 end
347 if is_required? && value.detect(&:present?).nil?
324 if is_required? && value.detect(&:present?).nil?
348 errs << ::I18n.t('activerecord.errors.messages.blank')
325 errs << ::I18n.t('activerecord.errors.messages.blank')
349 end
326 end
350 value.each {|v| errs += validate_field_value_format(v)}
327 value.each {|v| errs += validate_field_value_format(v)}
351 else
328 else
352 if is_required? && value.blank?
329 if is_required? && value.blank?
353 errs << ::I18n.t('activerecord.errors.messages.blank')
330 errs << ::I18n.t('activerecord.errors.messages.blank')
354 end
331 end
355 errs += validate_field_value_format(value)
332 errs += validate_field_value_format(value)
356 end
333 end
357 errs
334 errs
358 end
335 end
359
336
360 # Returns true if value is a valid value for the custom field
337 # Returns true if value is a valid value for the custom field
361 def valid_field_value?(value)
338 def valid_field_value?(value)
362 validate_field_value(value).empty?
339 validate_field_value(value).empty?
363 end
340 end
364
341
365 def format_in?(*args)
342 def format_in?(*args)
366 args.include?(field_format)
343 args.include?(field_format)
367 end
344 end
368
345
369 protected
346 protected
370
347
371 # Returns the error message for the given value regarding its format
348 # Returns the error message for the given value regarding its format
372 def validate_field_value_format(value)
349 def validate_field_value_format(value)
373 errs = []
350 errs = []
374 if value.present?
351 if value.present?
375 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
352 errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp)
376 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
353 errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length
377 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
354 errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length
378
355
379 # Format specific validations
356 # Format specific validations
380 case field_format
357 case field_format
381 when 'int'
358 when 'int'
382 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
359 errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/
383 when 'float'
360 when 'float'
384 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
361 begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end
385 when 'date'
362 when 'date'
386 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
363 errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end
387 when 'list'
364 when 'list'
388 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
365 errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value)
389 end
366 end
390 end
367 end
391 errs
368 errs
392 end
369 end
393
370
394 # Removes multiple values for the custom field after setting the multiple attribute to false
371 # Removes multiple values for the custom field after setting the multiple attribute to false
395 # We kepp the value with the highest id for each customized object
372 # We kepp the value with the highest id for each customized object
396 def handle_multiplicity_change
373 def handle_multiplicity_change
397 if !new_record? && multiple_was && !multiple
374 if !new_record? && multiple_was && !multiple
398 ids = custom_values.
375 ids = custom_values.
399 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
376 where("EXISTS(SELECT 1 FROM #{CustomValue.table_name} cve WHERE cve.custom_field_id = #{CustomValue.table_name}.custom_field_id" +
400 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
377 " AND cve.customized_type = #{CustomValue.table_name}.customized_type AND cve.customized_id = #{CustomValue.table_name}.customized_id" +
401 " AND cve.id > #{CustomValue.table_name}.id)").
378 " AND cve.id > #{CustomValue.table_name}.id)").
402 pluck(:id)
379 pluck(:id)
403
380
404 if ids.any?
381 if ids.any?
405 custom_values.where(:id => ids).delete_all
382 custom_values.where(:id => ids).delete_all
406 end
383 end
407 end
384 end
408 end
385 end
409 end
386 end
General Comments 0
You need to be logged in to leave comments. Login now