##// END OF EJS Templates
Custom fields refactoring: most of code moved from controllers to models (using new module ActsAsCustomizable)....
Jean-Philippe Lang -
r1578:ce6cf66f6c3a
parent child
Show More
@@ -0,0 +1,2
1 require File.dirname(__FILE__) + '/lib/acts_as_customizable'
2 ActiveRecord::Base.send(:include, Redmine::Acts::Customizable)
@@ -0,0 +1,82
1 # redMine - project management software
2 # Copyright (C) 2006-2008 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 module Redmine
19 module Acts
20 module Customizable
21 def self.included(base)
22 base.extend ClassMethods
23 end
24
25 module ClassMethods
26 def acts_as_customizable(options = {})
27 return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
28 cattr_accessor :customizable_options
29 self.customizable_options = options
30 has_many :custom_values, :dependent => :delete_all, :as => :customized
31 before_validation_on_create { |customized| customized.custom_field_values }
32 # Trigger validation only if custom values were changed
33 validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? }
34 send :include, Redmine::Acts::Customizable::InstanceMethods
35 # Save custom values when saving the customized object
36 after_save :save_custom_field_values
37 end
38 end
39
40 module InstanceMethods
41 def self.included(base)
42 base.extend ClassMethods
43 end
44
45 def available_custom_fields
46 CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'",
47 :order => 'position')
48 end
49
50 def custom_field_values=(values)
51 @custom_field_values_changed = true
52 values = values.stringify_keys
53 custom_field_values.each do |custom_value|
54 custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
55 end if values.is_a?(Hash)
56 end
57
58 def custom_field_values
59 @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:custom_field => x, :value => nil) }
60 end
61
62 def custom_field_values_changed?
63 @custom_field_values_changed == true
64 end
65
66 def custom_value_for(c)
67 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
68 custom_values.detect {|v| v.custom_field_id == field_id }
69 end
70
71 def save_custom_field_values
72 custom_field_values.each(&:save)
73 @custom_field_values_changed = false
74 @custom_field_values = nil
75 end
76
77 module ClassMethods
78 end
79 end
80 end
81 end
82 end
@@ -110,17 +110,12 class AccountController < ApplicationController
110 redirect_to(home_url) && return unless Setting.self_registration?
110 redirect_to(home_url) && return unless Setting.self_registration?
111 if request.get?
111 if request.get?
112 @user = User.new(:language => Setting.default_language)
112 @user = User.new(:language => Setting.default_language)
113 @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
114 else
113 else
115 @user = User.new(params[:user])
114 @user = User.new(params[:user])
116 @user.admin = false
115 @user.admin = false
117 @user.login = params[:user][:login]
116 @user.login = params[:user][:login]
118 @user.status = User::STATUS_REGISTERED
117 @user.status = User::STATUS_REGISTERED
119 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
118 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
120 @custom_values = UserCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x,
121 :customized => @user,
122 :value => (params["custom_fields"] ? params["custom_fields"][x.id.to_s] : nil)) }
123 @user.custom_values = @custom_values
124 case Setting.self_registration
119 case Setting.self_registration
125 when '1'
120 when '1'
126 # Email activation
121 # Email activation
@@ -94,7 +94,6 class IssuesController < ApplicationController
94 end
94 end
95
95
96 def show
96 def show
97 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
98 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
97 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
99 @journals.each_with_index {|j,i| j.indice = i+1}
98 @journals.each_with_index {|j,i| j.indice = i+1}
100 @journals.reverse! if User.current.wants_comments_in_reverse_order?
99 @journals.reverse! if User.current.wants_comments_in_reverse_order?
@@ -113,15 +112,17 class IssuesController < ApplicationController
113 # Add a new issue
112 # Add a new issue
114 # The new issue will be created from an existing one if copy_from parameter is given
113 # The new issue will be created from an existing one if copy_from parameter is given
115 def new
114 def new
116 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
115 @issue = Issue.new
116 @issue.copy_from(params[:copy_from]) if params[:copy_from]
117 @issue.project = @project
117 @issue.project = @project
118 @issue.author = User.current
119 @issue.tracker ||= @project.trackers.find(params[:tracker_id] ? params[:tracker_id] : :first)
118 @issue.tracker ||= @project.trackers.find(params[:tracker_id] ? params[:tracker_id] : :first)
120 if @issue.tracker.nil?
119 if @issue.tracker.nil?
121 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
120 flash.now[:error] = 'No tracker is associated to this project. Please check the Project settings.'
122 render :nothing => true, :layout => true
121 render :nothing => true, :layout => true
123 return
122 return
124 end
123 end
124 @issue.attributes = params[:issue]
125 @issue.author = User.current
125
126
126 default_status = IssueStatus.default
127 default_status = IssueStatus.default
127 unless default_status
128 unless default_status
@@ -134,17 +135,10 class IssuesController < ApplicationController
134
135
135 if request.get? || request.xhr?
136 if request.get? || request.xhr?
136 @issue.start_date ||= Date.today
137 @issue.start_date ||= Date.today
137 @custom_values = @issue.custom_values.empty? ?
138 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
139 @issue.custom_values
140 else
138 else
141 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
139 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
142 # Check that the user is allowed to apply the requested status
140 # Check that the user is allowed to apply the requested status
143 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
141 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
144 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x,
145 :customized => @issue,
146 :value => (params[:custom_fields] ? params[:custom_fields][x.id.to_s] : nil)) }
147 @issue.custom_values = @custom_values
148 if @issue.save
142 if @issue.save
149 attach_files(@issue, params[:attachments])
143 attach_files(@issue, params[:attachments])
150 flash[:notice] = l(:notice_successful_create)
144 flash[:notice] = l(:notice_successful_create)
@@ -165,7 +159,6 class IssuesController < ApplicationController
165 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
159 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
166 @activities = Enumeration::get_values('ACTI')
160 @activities = Enumeration::get_values('ACTI')
167 @priorities = Enumeration::get_values('IPRI')
161 @priorities = Enumeration::get_values('IPRI')
168 @custom_values = []
169 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
162 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
170
163
171 @notes = params[:notes]
164 @notes = params[:notes]
@@ -178,14 +171,7 class IssuesController < ApplicationController
178 @issue.attributes = attrs
171 @issue.attributes = attrs
179 end
172 end
180
173
181 if request.get?
174 if request.post?
182 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
183 else
184 # Update custom fields if user has :edit permission
185 if @edit_allowed && params[:custom_fields]
186 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
187 @issue.custom_values = @custom_values
188 end
189 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
175 @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
190 @time_entry.attributes = params[:time_entry]
176 @time_entry.attributes = params[:time_entry]
191 attachments = attach_files(@issue, params[:attachments])
177 attachments = attach_files(@issue, params[:attachments])
@@ -63,21 +63,17 class ProjectsController < ApplicationController
63
63
64 # Add a new project
64 # Add a new project
65 def add
65 def add
66 @custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
66 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 @trackers = Tracker.all
67 @trackers = Tracker.all
68 @root_projects = Project.find(:all,
68 @root_projects = Project.find(:all,
69 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
69 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
70 :order => 'name')
70 :order => 'name')
71 @project = Project.new(params[:project])
71 @project = Project.new(params[:project])
72 if request.get?
72 if request.get?
73 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
74 @project.trackers = Tracker.all
73 @project.trackers = Tracker.all
75 @project.is_public = Setting.default_projects_public?
74 @project.is_public = Setting.default_projects_public?
76 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
75 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
77 else
76 else
78 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
79 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
80 @project.custom_values = @custom_values
81 @project.enabled_module_names = params[:enabled_modules]
77 @project.enabled_module_names = params[:enabled_modules]
82 if @project.save
78 if @project.save
83 flash[:notice] = l(:notice_successful_create)
79 flash[:notice] = l(:notice_successful_create)
@@ -88,7 +84,6 class ProjectsController < ApplicationController
88
84
89 # Show @project
85 # Show @project
90 def show
86 def show
91 @custom_values = @project.custom_values.find(:all, :include => :custom_field, :order => "#{CustomField.table_name}.position")
92 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
87 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
93 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
88 @subprojects = @project.children.find(:all, :conditions => Project.visible_by(User.current))
94 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
89 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
@@ -115,11 +110,10 class ProjectsController < ApplicationController
115 @root_projects = Project.find(:all,
110 @root_projects = Project.find(:all,
116 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
111 :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id],
117 :order => 'name')
112 :order => 'name')
118 @custom_fields = IssueCustomField.find(:all)
113 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
119 @issue_category ||= IssueCategory.new
114 @issue_category ||= IssueCategory.new
120 @member ||= @project.members.new
115 @member ||= @project.members.new
121 @trackers = Tracker.all
116 @trackers = Tracker.all
122 @custom_values ||= ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
123 @repository ||= @project.repository
117 @repository ||= @project.repository
124 @wiki ||= @project.wiki
118 @wiki ||= @project.wiki
125 end
119 end
@@ -127,10 +121,6 class ProjectsController < ApplicationController
127 # Edit @project
121 # Edit @project
128 def edit
122 def edit
129 if request.post?
123 if request.post?
130 if params[:custom_fields]
131 @custom_values = ProjectCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
132 @project.custom_values = @custom_values
133 end
134 @project.attributes = params[:project]
124 @project.attributes = params[:project]
135 if @project.save
125 if @project.save
136 flash[:notice] = l(:notice_successful_update)
126 flash[:notice] = l(:notice_successful_update)
@@ -54,7 +54,7 class TimelogController < ApplicationController
54 }
54 }
55
55
56 # Add list and boolean custom fields as available criterias
56 # Add list and boolean custom fields as available criterias
57 @project.all_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
57 @project.all_issue_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf|
58 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)",
58 @available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)",
59 :format => cf.field_format,
59 :format => cf.field_format,
60 :label => cf.name}
60 :label => cf.name}
@@ -52,14 +52,11 class UsersController < ApplicationController
52 def add
52 def add
53 if request.get?
53 if request.get?
54 @user = User.new(:language => Setting.default_language)
54 @user = User.new(:language => Setting.default_language)
55 @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user) }
56 else
55 else
57 @user = User.new(params[:user])
56 @user = User.new(params[:user])
58 @user.admin = params[:user][:admin] || false
57 @user.admin = params[:user][:admin] || false
59 @user.login = params[:user][:login]
58 @user.login = params[:user][:login]
60 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
59 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless @user.auth_source_id
61 @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
62 @user.custom_values = @custom_values
63 if @user.save
60 if @user.save
64 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
61 Mailer.deliver_account_information(@user, params[:password]) if params[:send_information]
65 flash[:notice] = l(:notice_successful_create)
62 flash[:notice] = l(:notice_successful_create)
@@ -71,16 +68,10 class UsersController < ApplicationController
71
68
72 def edit
69 def edit
73 @user = User.find(params[:id])
70 @user = User.find(params[:id])
74 if request.get?
71 if request.post?
75 @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| @user.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
76 else
77 @user.admin = params[:user][:admin] if params[:user][:admin]
72 @user.admin = params[:user][:admin] if params[:user][:admin]
78 @user.login = params[:user][:login] if params[:user][:login]
73 @user.login = params[:user][:login] if params[:user][:login]
79 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
74 @user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
80 if params[:custom_fields]
81 @custom_values = UserCustomField.find(:all, :order => "#{CustomField.table_name}.position").collect { |x| CustomValue.new(:custom_field => x, :customized => @user, :value => params["custom_fields"][x.id.to_s]) }
82 @user.custom_values = @custom_values
83 end
84 if @user.update_attributes(params[:user])
75 if @user.update_attributes(params[:user])
85 flash[:notice] = l(:notice_successful_update)
76 flash[:notice] = l(:notice_successful_update)
86 # Give a string to redirect_to otherwise it would use status param as the response code
77 # Give a string to redirect_to otherwise it would use status param as the response code
@@ -25,37 +25,40 module CustomFieldsHelper
25 end
25 end
26
26
27 # Return custom field html tag corresponding to its format
27 # Return custom field html tag corresponding to its format
28 def custom_field_tag(custom_value)
28 def custom_field_tag(name, custom_value)
29 custom_field = custom_value.custom_field
29 custom_field = custom_value.custom_field
30 field_name = "custom_fields[#{custom_field.id}]"
30 field_name = "#{name}[custom_field_values][#{custom_field.id}]"
31 field_id = "custom_fields_#{custom_field.id}"
31 field_id = "#{name}_custom_field_values_#{custom_field.id}"
32
32
33 case custom_field.field_format
33 case custom_field.field_format
34 when "date"
34 when "date"
35 text_field('custom_value', 'value', :name => field_name, :id => field_id, :size => 10) +
35 text_field_tag(field_name, custom_value.value, :id => field_id, :size => 10) +
36 calendar_for(field_id)
36 calendar_for(field_id)
37 when "text"
37 when "text"
38 text_area 'custom_value', 'value', :name => field_name, :id => field_id, :rows => 3, :style => 'width:99%'
38 text_area_tag(field_name, custom_value.value, :id => field_id, :rows => 3, :style => 'width:90%')
39 when "bool"
39 when "bool"
40 check_box 'custom_value', 'value', :name => field_name, :id => field_id
40 check_box_tag(field_name, custom_value.value, :id => field_id)
41 when "list"
41 when "list"
42 select 'custom_value', 'value', custom_field.possible_values, { :include_blank => true }, :name => field_name, :id => field_id
42 blank_option = custom_field.is_required? ?
43 (custom_field.default_value.blank? ? "<option value=\"\">--- #{l(:actionview_instancetag_blank_option)} ---</option>" : '') :
44 '<option></option>'
45 select_tag(field_name, blank_option + options_for_select(custom_field.possible_values, custom_value.value), :id => field_id)
43 else
46 else
44 text_field 'custom_value', 'value', :name => field_name, :id => field_id
47 text_field_tag(field_name, custom_value.value, :id => field_id)
45 end
48 end
46 end
49 end
47
50
48 # Return custom field label tag
51 # Return custom field label tag
49 def custom_field_label_tag(custom_value)
52 def custom_field_label_tag(name, custom_value)
50 content_tag "label", custom_value.custom_field.name +
53 content_tag "label", custom_value.custom_field.name +
51 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
54 (custom_value.custom_field.is_required? ? " <span class=\"required\">*</span>" : ""),
52 :for => "custom_fields_#{custom_value.custom_field.id}",
55 :for => "#{name}_custom_field_values_#{custom_value.custom_field.id}",
53 :class => (custom_value.errors.empty? ? nil : "error" )
56 :class => (custom_value.errors.empty? ? nil : "error" )
54 end
57 end
55
58
56 # Return custom field tag with its label tag
59 # Return custom field tag with its label tag
57 def custom_field_tag_with_label(custom_value)
60 def custom_field_tag_with_label(name, custom_value)
58 custom_field_label_tag(custom_value) + custom_field_tag(custom_value)
61 custom_field_label_tag(name, custom_value) + custom_field_tag(name, custom_value)
59 end
62 end
60
63
61 # Return a string used to display a custom value
64 # Return a string used to display a custom value
@@ -149,7 +149,7 module IssuesHelper
149 ]
149 ]
150 # Export project custom fields if project is given
150 # Export project custom fields if project is given
151 # otherwise export custom fields marked as "For all projects"
151 # otherwise export custom fields marked as "For all projects"
152 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_custom_fields
152 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
153 custom_fields.each {|f| headers << f.name}
153 custom_fields.each {|f| headers << f.name}
154 # Description in the last column
154 # Description in the last column
155 headers << l(:field_description)
155 headers << l(:field_description)
@@ -28,13 +28,12 class Issue < ActiveRecord::Base
28 has_many :journals, :as => :journalized, :dependent => :destroy
28 has_many :journals, :as => :journalized, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
29 has_many :attachments, :as => :container, :dependent => :destroy
30 has_many :time_entries, :dependent => :delete_all
30 has_many :time_entries, :dependent => :delete_all
31 has_many :custom_values, :dependent => :delete_all, :as => :customized
32 has_many :custom_fields, :through => :custom_values
33 has_and_belongs_to_many :changesets, :order => "revision ASC"
31 has_and_belongs_to_many :changesets, :order => "revision ASC"
34
32
35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
33 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
34 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
37
35
36 acts_as_customizable
38 acts_as_watchable
37 acts_as_watchable
39 acts_as_searchable :columns => ['subject', "#{table_name}.description"], :include => :project, :with => {:journal => :issue}
38 acts_as_searchable :columns => ['subject', "#{table_name}.description"], :include => :project, :with => {:journal => :issue}
40 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
39 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
@@ -44,7 +43,6 class Issue < ActiveRecord::Base
44 validates_length_of :subject, :maximum => 255
43 validates_length_of :subject, :maximum => 255
45 validates_inclusion_of :done_ratio, :in => 0..100
44 validates_inclusion_of :done_ratio, :in => 0..100
46 validates_numericality_of :estimated_hours, :allow_nil => true
45 validates_numericality_of :estimated_hours, :allow_nil => true
47 validates_associated :custom_values, :on => :update
48
46
49 def after_initialize
47 def after_initialize
50 if new_record?
48 if new_record?
@@ -54,6 +52,11 class Issue < ActiveRecord::Base
54 end
52 end
55 end
53 end
56
54
55 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
56 def available_custom_fields
57 (project && tracker) ? project.all_issue_custom_fields.select {|c| tracker.custom_fields.include? c } : []
58 end
59
57 def copy_from(arg)
60 def copy_from(arg)
58 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
61 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
59 self.attributes = issue.attributes.dup
62 self.attributes = issue.attributes.dup
@@ -168,11 +171,6 class Issue < ActiveRecord::Base
168 end
171 end
169 end
172 end
170
173
171 def custom_value_for(custom_field)
172 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
173 return nil
174 end
175
176 def init_journal(user, notes = "")
174 def init_journal(user, notes = "")
177 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
175 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
178 @issue_before_change = self.clone
176 @issue_before_change = self.clone
@@ -22,7 +22,6 class Project < ActiveRecord::Base
22
22
23 has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
23 has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 has_many :users, :through => :members
24 has_many :users, :through => :members
25 has_many :custom_values, :dependent => :delete_all, :as => :customized
26 has_many :enabled_modules, :dependent => :delete_all
25 has_many :enabled_modules, :dependent => :delete_all
27 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
26 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
28 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
27 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
@@ -38,7 +37,7 class Project < ActiveRecord::Base
38 has_many :changesets, :through => :repository
37 has_many :changesets, :through => :repository
39 has_one :wiki, :dependent => :destroy
38 has_one :wiki, :dependent => :destroy
40 # Custom field for the project issues
39 # Custom field for the project issues
41 has_and_belongs_to_many :custom_fields,
40 has_and_belongs_to_many :issue_custom_fields,
42 :class_name => 'IssueCustomField',
41 :class_name => 'IssueCustomField',
43 :order => "#{CustomField.table_name}.position",
42 :order => "#{CustomField.table_name}.position",
44 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
43 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
@@ -46,6 +45,7 class Project < ActiveRecord::Base
46
45
47 acts_as_tree :order => "name", :counter_cache => true
46 acts_as_tree :order => "name", :counter_cache => true
48
47
48 acts_as_customizable
49 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
49 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
50 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
50 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
51 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}}
51 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}}
@@ -54,7 +54,6 class Project < ActiveRecord::Base
54
54
55 validates_presence_of :name, :identifier
55 validates_presence_of :name, :identifier
56 validates_uniqueness_of :name, :identifier
56 validates_uniqueness_of :name, :identifier
57 validates_associated :custom_values, :on => :update
58 validates_associated :repository, :wiki
57 validates_associated :repository, :wiki
59 validates_length_of :name, :maximum => 30
58 validates_length_of :name, :maximum => 30
60 validates_length_of :homepage, :maximum => 255
59 validates_length_of :homepage, :maximum => 255
@@ -195,12 +194,8 class Project < ActiveRecord::Base
195
194
196 # Returns an array of all custom fields enabled for project issues
195 # Returns an array of all custom fields enabled for project issues
197 # (explictly associated custom fields and custom fields enabled for all projects)
196 # (explictly associated custom fields and custom fields enabled for all projects)
198 def custom_fields_for_issues(tracker)
197 def all_issue_custom_fields
199 all_custom_fields.select {|c| tracker.custom_fields.include? c }
198 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq
200 end
201
202 def all_custom_fields
203 @all_custom_fields ||= (IssueCustomField.for_all + custom_fields).uniq
204 end
199 end
205
200
206 def project
201 def project
@@ -176,7 +176,7 class Query < ActiveRecord::Base
176 unless @project.active_children.empty?
176 unless @project.active_children.empty?
177 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
177 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
178 end
178 end
179 add_custom_fields_filters(@project.all_custom_fields)
179 add_custom_fields_filters(@project.all_issue_custom_fields)
180 else
180 else
181 # global filters for cross project issue list
181 # global filters for cross project issue list
182 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
182 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
@@ -226,7 +226,7 class Query < ActiveRecord::Base
226 return @available_columns if @available_columns
226 return @available_columns if @available_columns
227 @available_columns = Query.available_columns
227 @available_columns = Query.available_columns
228 @available_columns += (project ?
228 @available_columns += (project ?
229 project.all_custom_fields :
229 project.all_issue_custom_fields :
230 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
230 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
231 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
231 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
232 end
232 end
@@ -37,12 +37,13 class User < ActiveRecord::Base
37
37
38 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
38 has_many :memberships, :class_name => 'Member', :include => [ :project, :role ], :conditions => "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}", :order => "#{Project.table_name}.name", :dependent => :delete_all
39 has_many :projects, :through => :memberships
39 has_many :projects, :through => :memberships
40 has_many :custom_values, :dependent => :delete_all, :as => :customized
41 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
40 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
42 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
43 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
44 belongs_to :auth_source
43 belongs_to :auth_source
45
44
45 acts_as_customizable
46
46 attr_accessor :password, :password_confirmation
47 attr_accessor :password, :password_confirmation
47 attr_accessor :last_before_login_on
48 attr_accessor :last_before_login_on
48 # Prevents unauthorized assignments
49 # Prevents unauthorized assignments
@@ -60,7 +61,6 class User < ActiveRecord::Base
60 validates_length_of :mail, :maximum => 60, :allow_nil => true
61 validates_length_of :mail, :maximum => 60, :allow_nil => true
61 validates_length_of :password, :minimum => 4, :allow_nil => true
62 validates_length_of :password, :minimum => 4, :allow_nil => true
62 validates_confirmation_of :password, :allow_nil => true
63 validates_confirmation_of :password, :allow_nil => true
63 validates_associated :custom_values, :on => :update
64
64
65 def before_create
65 def before_create
66 self.mail_notification = false
66 self.mail_notification = false
@@ -280,6 +280,10 class AnonymousUser < User
280 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
280 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
281 end
281 end
282
282
283 def available_custom_fields
284 []
285 end
286
283 # Overrides a few properties
287 # Overrides a few properties
284 def logged?; false end
288 def logged?; false end
285 def admin; false end
289 def admin; false end
@@ -27,8 +27,8
27 <p><label for="user_language"><%=l(:field_language)%></label>
27 <p><label for="user_language"><%=l(:field_language)%></label>
28 <%= select("user", "language", lang_options_for_select) %></p>
28 <%= select("user", "language", lang_options_for_select) %></p>
29
29
30 <% for @custom_value in @custom_values %>
30 <% @user.custom_field_values.each do |value| %>
31 <p><%= custom_field_tag_with_label @custom_value %></p>
31 <p><%= custom_field_tag_with_label :user, value %></p>
32 <% end %>
32 <% end %>
33 <!--[eoform:user]-->
33 <!--[eoform:user]-->
34 </div>
34 </div>
@@ -1,11 +1,12
1 <div class="splitcontentleft">
1 <div class="splitcontentleft">
2 <% i = 1 %>
2 <% i = 1 %>
3 <% for @custom_value in values %>
3 <% split_on = @issue.custom_field_values.size / 2 %>
4 <p><%= custom_field_tag_with_label @custom_value %></p>
4 <% @issue.custom_field_values.each do |value| %>
5 <% if i == values.size / 2 %>
5 <p><%= custom_field_tag_with_label :issue, value %></p>
6 <% if i == split_on -%>
6 </div><div class="splitcontentright">
7 </div><div class="splitcontentright">
7 <% end %>
8 <% end -%>
8 <% i += 1 %>
9 <% i += 1 -%>
9 <% end %>
10 <% end -%>
10 </div>
11 </div>
11 <div style="clear:both;"> </div>
12 <div style="clear:both;"> </div>
@@ -43,9 +43,9
43 <% end %>
43 <% end %>
44 </tr>
44 </tr>
45 <tr>
45 <tr>
46 <% n = 0
46 <% n = 0 -%>
47 for custom_value in @custom_values %>
47 <% @issue.custom_values.each do |value| -%>
48 <td valign="top"><b><%= custom_value.custom_field.name %>:</b></td><td valign="top"><%= simple_format(h(show_value(custom_value))) %></td>
48 <td valign="top"><b><%=h value.custom_field.name %>:</b></td><td valign="top"><%= simple_format(h(show_value(value))) %></td>
49 <% n = n + 1
49 <% n = n + 1
50 if (n > 1)
50 if (n > 1)
51 n = 0 %>
51 n = 0 %>
@@ -17,8 +17,8
17 <p><%= f.check_box :is_public %></p>
17 <p><%= f.check_box :is_public %></p>
18 <%= wikitoolbar_for 'project_description' %>
18 <%= wikitoolbar_for 'project_description' %>
19
19
20 <% for @custom_value in @custom_values %>
20 <% @project.custom_field_values.each do |value| %>
21 <p><%= custom_field_tag_with_label @custom_value %></p>
21 <p><%= custom_field_tag_with_label :project, value %></p>
22 <% end %>
22 <% end %>
23 </div>
23 </div>
24
24
@@ -34,15 +34,15
34 </fieldset>
34 </fieldset>
35 <% end %>
35 <% end %>
36
36
37 <% unless @custom_fields.empty? %>
37 <% unless @issue_custom_fields.empty? %>
38 <fieldset class="box"><legend><%=l(:label_custom_field_plural)%></legend>
38 <fieldset class="box"><legend><%=l(:label_custom_field_plural)%></legend>
39 <% for custom_field in @custom_fields %>
39 <% @issue_custom_fields.each do |custom_field| %>
40 <label class="floating">
40 <label class="floating">
41 <%= check_box_tag 'project[custom_field_ids][]', custom_field.id, ((@project.custom_fields.include? custom_field) or custom_field.is_for_all?), (custom_field.is_for_all? ? {:disabled => "disabled"} : {}) %>
41 <%= check_box_tag 'project[issue_custom_field_ids][]', custom_field.id, (@project.all_issue_custom_fields.include? custom_field), (custom_field.is_for_all? ? {:disabled => "disabled"} : {}) %>
42 <%= custom_field.name %>
42 <%= custom_field.name %>
43 </label>
43 </label>
44 <% end %>
44 <% end %>
45 <%= hidden_field_tag 'project[custom_field_ids][]', '' %>
45 <%= hidden_field_tag 'project[issue_custom_field_ids][]', '' %>
46 </fieldset>
46 </fieldset>
47 <% end %>
47 <% end %>
48 <!--[eoform:project]-->
48 <!--[eoform:project]-->
@@ -10,7 +10,7
10 <% if @project.parent %>
10 <% if @project.parent %>
11 <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
11 <li><%=l(:field_parent)%>: <%= link_to h(@project.parent.name), :controller => 'projects', :action => 'show', :id => @project.parent %></li>
12 <% end %>
12 <% end %>
13 <% for custom_value in @custom_values %>
13 <% @project.custom_values.each do |custom_value| %>
14 <% if !custom_value.value.empty? %>
14 <% if !custom_value.value.empty? %>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
15 <li><%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li>
16 <% end %>
16 <% end %>
@@ -8,9 +8,9
8 <p><%= f.text_field :mail, :required => true %></p>
8 <p><%= f.text_field :mail, :required => true %></p>
9 <p><%= f.select :language, lang_options_for_select %></p>
9 <p><%= f.select :language, lang_options_for_select %></p>
10
10
11 <% for @custom_value in @custom_values %>
11 <% @user.custom_field_values.each do |value| %>
12 <p><%= custom_field_tag_with_label @custom_value %></p>
12 <p><%= custom_field_tag_with_label :user, value %></p>
13 <% end if @custom_values%>
13 <% end %>
14
14
15 <p><%= f.check_box :admin, :disabled => (@user == User.current) %></p>
15 <p><%= f.check_box :admin, :disabled => (@user == User.current) %></p>
16 </div>
16 </div>
@@ -7,7 +7,10 custom_fields_001:
7 is_filter: true
7 is_filter: true
8 type: IssueCustomField
8 type: IssueCustomField
9 max_length: 0
9 max_length: 0
10 possible_values: MySQL|PostgreSQL|Oracle
10 possible_values:
11 - MySQL
12 - PostgreSQL
13 - Oracle
11 id: 1
14 id: 1
12 is_required: false
15 is_required: false
13 field_format: list
16 field_format: list
@@ -33,7 +36,11 custom_fields_003:
33 is_filter: true
36 is_filter: true
34 type: ProjectCustomField
37 type: ProjectCustomField
35 max_length: 0
38 max_length: 0
36 possible_values: Stable|Beta|Alpha|Planning
39 possible_values:
40 - Stable
41 - Beta
42 - Alpha
43 - Planning
37 id: 3
44 id: 3
38 is_required: true
45 is_required: true
39 field_format: list
46 field_format: list
@@ -170,7 +170,7 class IssuesControllerTest < Test::Unit::TestCase
170 assert_response :success
170 assert_response :success
171 assert_template 'new'
171 assert_template 'new'
172
172
173 assert_tag :tag => 'input', :attributes => { :name => 'custom_fields[2]',
173 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
174 :value => 'Default string' }
174 :value => 'Default string' }
175 end
175 end
176
176
@@ -203,8 +203,8 class IssuesControllerTest < Test::Unit::TestCase
203 :subject => 'This is the test_new issue',
203 :subject => 'This is the test_new issue',
204 :description => 'This is the description',
204 :description => 'This is the description',
205 :priority_id => 5,
205 :priority_id => 5,
206 :estimated_hours => ''},
206 :estimated_hours => '',
207 :custom_fields => {'2' => 'Value for field 2'}
207 :custom_field_values => {'2' => 'Value for field 2'}}
208 assert_redirected_to 'issues/show'
208 assert_redirected_to 'issues/show'
209
209
210 issue = Issue.find_by_subject('This is the test_new issue')
210 issue = Issue.find_by_subject('This is the test_new issue')
@@ -226,6 +226,50 class IssuesControllerTest < Test::Unit::TestCase
226 assert_redirected_to 'issues/show'
226 assert_redirected_to 'issues/show'
227 end
227 end
228
228
229 def test_post_new_with_required_custom_field_and_without_custom_fields_param
230 field = IssueCustomField.find_by_name('Database')
231 field.update_attribute(:is_required, true)
232
233 @request.session[:user_id] = 2
234 post :new, :project_id => 1,
235 :issue => {:tracker_id => 1,
236 :subject => 'This is the test_new issue',
237 :description => 'This is the description',
238 :priority_id => 5}
239 assert_response :success
240 assert_template 'new'
241 issue = assigns(:issue)
242 assert_not_nil issue
243 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
244 end
245
246 def test_post_should_preserve_fields_values_on_validation_failure
247 @request.session[:user_id] = 2
248 post :new, :project_id => 1,
249 :issue => {:tracker_id => 1,
250 :subject => 'This is the test_new issue',
251 # empty description
252 :description => '',
253 :priority_id => 6,
254 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
255 assert_response :success
256 assert_template 'new'
257
258 assert_tag :input, :attributes => { :name => 'issue[subject]',
259 :value => 'This is the test_new issue' }
260 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
261 :child => { :tag => 'option', :attributes => { :selected => 'selected',
262 :value => '6' },
263 :content => 'High' }
264 # Custom fields
265 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
266 :child => { :tag => 'option', :attributes => { :selected => 'selected',
267 :value => 'Oracle' },
268 :content => 'Oracle' }
269 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
270 :value => 'Value for field 2'}
271 end
272
229 def test_copy_issue
273 def test_copy_issue
230 @request.session[:user_id] = 2
274 @request.session[:user_id] = 2
231 get :new, :project_id => 1, :copy_from => 1
275 get :new, :project_id => 1, :copy_from => 1
@@ -280,18 +324,28 class IssuesControllerTest < Test::Unit::TestCase
280 assert_select_rjs :show, "update"
324 assert_select_rjs :show, "update"
281 end
325 end
282
326
283 def test_post_edit
327 def test_post_edit_without_custom_fields_param
284 @request.session[:user_id] = 2
328 @request.session[:user_id] = 2
285 ActionMailer::Base.deliveries.clear
329 ActionMailer::Base.deliveries.clear
286
330
287 issue = Issue.find(1)
331 issue = Issue.find(1)
332 assert_equal '125', issue.custom_value_for(2).value
288 old_subject = issue.subject
333 old_subject = issue.subject
289 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
334 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
290
335
291 post :edit, :id => 1, :issue => {:subject => new_subject}
336 assert_difference('Journal.count') do
337 assert_difference('JournalDetail.count', 2) do
338 post :edit, :id => 1, :issue => {:subject => new_subject,
339 :priority_id => '6',
340 :category_id => '1' # no change
341 }
342 end
343 end
292 assert_redirected_to 'issues/show/1'
344 assert_redirected_to 'issues/show/1'
293 issue.reload
345 issue.reload
294 assert_equal new_subject, issue.subject
346 assert_equal new_subject, issue.subject
347 # Make sure custom fields were not cleared
348 assert_equal '125', issue.custom_value_for(2).value
295
349
296 mail = ActionMailer::Base.deliveries.last
350 mail = ActionMailer::Base.deliveries.last
297 assert_kind_of TMail::Mail, mail
351 assert_kind_of TMail::Mail, mail
@@ -299,6 +353,29 class IssuesControllerTest < Test::Unit::TestCase
299 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
353 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
300 end
354 end
301
355
356 def test_post_edit_with_custom_field_change
357 @request.session[:user_id] = 2
358 issue = Issue.find(1)
359 assert_equal '125', issue.custom_value_for(2).value
360
361 assert_difference('Journal.count') do
362 assert_difference('JournalDetail.count', 3) do
363 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
364 :priority_id => '6',
365 :category_id => '1', # no change
366 :custom_field_values => { '2' => 'New custom value' }
367 }
368 end
369 end
370 assert_redirected_to 'issues/show/1'
371 issue.reload
372 assert_equal 'New custom value', issue.custom_value_for(2).value
373
374 mail = ActionMailer::Base.deliveries.last
375 assert_kind_of TMail::Mail, mail
376 assert mail.body.include?("Searchable field changed from 125 to New custom value")
377 end
378
302 def test_post_edit_with_status_and_assignee_change
379 def test_post_edit_with_status_and_assignee_change
303 issue = Issue.find(1)
380 issue = Issue.find(1)
304 assert_equal 1, issue.status_id
381 assert_equal 1, issue.status_id
@@ -83,7 +83,7 class ProjectsControllerTest < Test::Unit::TestCase
83 def test_edit
83 def test_edit
84 @request.session[:user_id] = 2 # manager
84 @request.session[:user_id] = 2 # manager
85 post :edit, :id => 1, :project => {:name => 'Test changed name',
85 post :edit, :id => 1, :project => {:name => 'Test changed name',
86 :custom_field_ids => ['']}
86 :issue_custom_field_ids => ['']}
87 assert_redirected_to 'projects/settings/ecookbook'
87 assert_redirected_to 'projects/settings/ecookbook'
88 project = Project.find(1)
88 project = Project.find(1)
89 assert_equal 'Test changed name', project.name
89 assert_equal 'Test changed name', project.name
@@ -48,8 +48,9 class AdminTest < ActionController::IntegrationTest
48 post "projects/add", :project => { :name => "blog",
48 post "projects/add", :project => { :name => "blog",
49 :description => "weblog",
49 :description => "weblog",
50 :identifier => "blog",
50 :identifier => "blog",
51 :is_public => 1 },
51 :is_public => 1,
52 'custom_fields[3]' => 'Beta'
52 :custom_field_values => { '3' => 'Beta' }
53 }
53 assert_redirected_to "admin/projects"
54 assert_redirected_to "admin/projects"
54 assert_equal 'Successful creation.', flash[:notice]
55 assert_equal 'Successful creation.', flash[:notice]
55
56
@@ -18,7 +18,13
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class IssueTest < Test::Unit::TestCase
20 class IssueTest < Test::Unit::TestCase
21 fixtures :projects, :users, :members, :trackers, :projects_trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :time_entries
21 fixtures :projects, :users, :members,
22 :trackers, :projects_trackers,
23 :issue_statuses, :issue_categories,
24 :enumerations,
25 :issues,
26 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
27 :time_entries
22
28
23 def test_create
29 def test_create
24 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
30 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
@@ -27,6 +33,76 class IssueTest < Test::Unit::TestCase
27 assert_equal 1.5, issue.estimated_hours
33 assert_equal 1.5, issue.estimated_hours
28 end
34 end
29
35
36 def test_create_with_required_custom_field
37 field = IssueCustomField.find_by_name('Database')
38 field.update_attribute(:is_required, true)
39
40 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
41 assert issue.available_custom_fields.include?(field)
42 # No value for the custom field
43 assert !issue.save
44 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
45 # Blank value
46 issue.custom_field_values = { field.id => '' }
47 assert !issue.save
48 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
49 # Invalid value
50 issue.custom_field_values = { field.id => 'SQLServer' }
51 assert !issue.save
52 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
53 # Valid value
54 issue.custom_field_values = { field.id => 'PostgreSQL' }
55 assert issue.save
56 issue.reload
57 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
58 end
59
60 def test_update_issue_with_required_custom_field
61 field = IssueCustomField.find_by_name('Database')
62 field.update_attribute(:is_required, true)
63
64 issue = Issue.find(1)
65 assert_nil issue.custom_value_for(field)
66 assert issue.available_custom_fields.include?(field)
67 # No change to custom values, issue can be saved
68 assert issue.save
69 # Blank value
70 issue.custom_field_values = { field.id => '' }
71 assert !issue.save
72 # Valid value
73 issue.custom_field_values = { field.id => 'PostgreSQL' }
74 assert issue.save
75 issue.reload
76 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
77 end
78
79 def test_should_not_update_attributes_if_custom_fields_validation_fails
80 issue = Issue.find(1)
81 field = IssueCustomField.find_by_name('Database')
82 assert issue.available_custom_fields.include?(field)
83
84 issue.custom_field_values = { field.id => 'Invalid' }
85 issue.subject = 'Should be not be saved'
86 assert !issue.save
87
88 issue.reload
89 assert_equal "Can't print recipes", issue.subject
90 end
91
92 def test_should_not_recreate_custom_values_objects_on_update
93 field = IssueCustomField.find_by_name('Database')
94
95 issue = Issue.find(1)
96 issue.custom_field_values = { field.id => 'PostgreSQL' }
97 assert issue.save
98 custom_value = issue.custom_value_for(field)
99 issue.reload
100 issue.custom_field_values = { field.id => 'MySQL' }
101 assert issue.save
102 issue.reload
103 assert_equal custom_value.id, issue.custom_value_for(field).id
104 end
105
30 def test_category_based_assignment
106 def test_category_based_assignment
31 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
107 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
32 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
108 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
General Comments 0
You need to be logged in to leave comments. Login now