##// END OF EJS Templates
Allow non admin users to add subprojects (#2963)....
Jean-Philippe Lang -
r2945:534ce51154c5
parent child
Show More
@@ -1,350 +1,350
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 ProjectsController < ApplicationController
18 class ProjectsController < ApplicationController
19 menu_item :overview
19 menu_item :overview
20 menu_item :activity, :only => :activity
20 menu_item :activity, :only => :activity
21 menu_item :roadmap, :only => :roadmap
21 menu_item :roadmap, :only => :roadmap
22 menu_item :files, :only => [:list_files, :add_file]
22 menu_item :files, :only => [:list_files, :add_file]
23 menu_item :settings, :only => :settings
23 menu_item :settings, :only => :settings
24 menu_item :issues, :only => [:changelog]
24 menu_item :issues, :only => [:changelog]
25
25
26 before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
26 before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
27 before_filter :find_optional_project, :only => :activity
27 before_filter :find_optional_project, :only => :activity
28 before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
28 before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
29 before_filter :authorize_global, :only => :add
29 before_filter :authorize_global, :only => :add
30 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
30 before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
31 accept_key_auth :activity
31 accept_key_auth :activity
32
32
33 after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
33 after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
34 if controller.request.post?
34 if controller.request.post?
35 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
35 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
36 end
36 end
37 end
37 end
38
38
39 helper :sort
39 helper :sort
40 include SortHelper
40 include SortHelper
41 helper :custom_fields
41 helper :custom_fields
42 include CustomFieldsHelper
42 include CustomFieldsHelper
43 helper :issues
43 helper :issues
44 helper IssuesHelper
44 helper IssuesHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :repositories
47 helper :repositories
48 include RepositoriesHelper
48 include RepositoriesHelper
49 include ProjectsHelper
49 include ProjectsHelper
50
50
51 # Lists visible projects
51 # Lists visible projects
52 def index
52 def index
53 respond_to do |format|
53 respond_to do |format|
54 format.html {
54 format.html {
55 @projects = Project.visible.find(:all, :order => 'lft')
55 @projects = Project.visible.find(:all, :order => 'lft')
56 }
56 }
57 format.atom {
57 format.atom {
58 projects = Project.visible.find(:all, :order => 'created_on DESC',
58 projects = Project.visible.find(:all, :order => 'created_on DESC',
59 :limit => Setting.feeds_limit.to_i)
59 :limit => Setting.feeds_limit.to_i)
60 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
61 }
61 }
62 end
62 end
63 end
63 end
64
64
65 # Add a new project
65 # Add a new project
66 def add
66 def add
67 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
68 @trackers = Tracker.all
68 @trackers = Tracker.all
69 @project = Project.new(params[:project])
69 @project = Project.new(params[:project])
70 if request.get?
70 if request.get?
71 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
71 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
72 @project.trackers = Tracker.all
72 @project.trackers = Tracker.all
73 @project.is_public = Setting.default_projects_public?
73 @project.is_public = Setting.default_projects_public?
74 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
74 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
75 else
75 else
76 @project.enabled_module_names = params[:enabled_modules]
76 @project.enabled_module_names = params[:enabled_modules]
77 if @project.save
77 if @project.save
78 @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
78 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
79 # Add current user as a project member if he is not admin
79 # Add current user as a project member if he is not admin
80 unless User.current.admin?
80 unless User.current.admin?
81 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
81 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
82 m = Member.new(:user => User.current, :roles => [r])
82 m = Member.new(:user => User.current, :roles => [r])
83 @project.members << m
83 @project.members << m
84 end
84 end
85 flash[:notice] = l(:notice_successful_create)
85 flash[:notice] = l(:notice_successful_create)
86 redirect_to :controller => 'projects', :action => 'settings', :id => @project
86 redirect_to :controller => 'projects', :action => 'settings', :id => @project
87 end
87 end
88 end
88 end
89 end
89 end
90
90
91 def copy
91 def copy
92 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
92 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
93 @trackers = Tracker.all
93 @trackers = Tracker.all
94 @root_projects = Project.find(:all,
94 @root_projects = Project.find(:all,
95 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
95 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
96 :order => 'name')
96 :order => 'name')
97 @source_project = Project.find(params[:id])
97 @source_project = Project.find(params[:id])
98 if request.get?
98 if request.get?
99 @project = Project.copy_from(@source_project)
99 @project = Project.copy_from(@source_project)
100 if @project
100 if @project
101 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
101 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
102 else
102 else
103 redirect_to :controller => 'admin', :action => 'projects'
103 redirect_to :controller => 'admin', :action => 'projects'
104 end
104 end
105 else
105 else
106 @project = Project.new(params[:project])
106 @project = Project.new(params[:project])
107 @project.enabled_module_names = params[:enabled_modules]
107 @project.enabled_module_names = params[:enabled_modules]
108 if @project.copy(@source_project, :only => params[:only])
108 if @project.copy(@source_project, :only => params[:only])
109 @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
109 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
110 flash[:notice] = l(:notice_successful_create)
110 flash[:notice] = l(:notice_successful_create)
111 redirect_to :controller => 'admin', :action => 'projects'
111 redirect_to :controller => 'admin', :action => 'projects'
112 end
112 end
113 end
113 end
114 rescue ActiveRecord::RecordNotFound
114 rescue ActiveRecord::RecordNotFound
115 redirect_to :controller => 'admin', :action => 'projects'
115 redirect_to :controller => 'admin', :action => 'projects'
116 end
116 end
117
117
118 # Show @project
118 # Show @project
119 def show
119 def show
120 if params[:jump]
120 if params[:jump]
121 # try to redirect to the requested menu item
121 # try to redirect to the requested menu item
122 redirect_to_project_menu_item(@project, params[:jump]) && return
122 redirect_to_project_menu_item(@project, params[:jump]) && return
123 end
123 end
124
124
125 @users_by_role = @project.users_by_role
125 @users_by_role = @project.users_by_role
126 @subprojects = @project.children.visible
126 @subprojects = @project.children.visible
127 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
127 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
128 @trackers = @project.rolled_up_trackers
128 @trackers = @project.rolled_up_trackers
129
129
130 cond = @project.project_condition(Setting.display_subprojects_issues?)
130 cond = @project.project_condition(Setting.display_subprojects_issues?)
131
131
132 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
132 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
133 :include => [:project, :status, :tracker],
133 :include => [:project, :status, :tracker],
134 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
134 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
135 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
135 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
136 :include => [:project, :status, :tracker],
136 :include => [:project, :status, :tracker],
137 :conditions => cond)
137 :conditions => cond)
138
138
139 TimeEntry.visible_by(User.current) do
139 TimeEntry.visible_by(User.current) do
140 @total_hours = TimeEntry.sum(:hours,
140 @total_hours = TimeEntry.sum(:hours,
141 :include => :project,
141 :include => :project,
142 :conditions => cond).to_f
142 :conditions => cond).to_f
143 end
143 end
144 @key = User.current.rss_key
144 @key = User.current.rss_key
145 end
145 end
146
146
147 def settings
147 def settings
148 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
148 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
149 @issue_category ||= IssueCategory.new
149 @issue_category ||= IssueCategory.new
150 @member ||= @project.members.new
150 @member ||= @project.members.new
151 @trackers = Tracker.all
151 @trackers = Tracker.all
152 @repository ||= @project.repository
152 @repository ||= @project.repository
153 @wiki ||= @project.wiki
153 @wiki ||= @project.wiki
154 end
154 end
155
155
156 # Edit @project
156 # Edit @project
157 def edit
157 def edit
158 if request.post?
158 if request.post?
159 @project.attributes = params[:project]
159 @project.attributes = params[:project]
160 if @project.save
160 if @project.save
161 @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
161 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
162 flash[:notice] = l(:notice_successful_update)
162 flash[:notice] = l(:notice_successful_update)
163 redirect_to :action => 'settings', :id => @project
163 redirect_to :action => 'settings', :id => @project
164 else
164 else
165 settings
165 settings
166 render :action => 'settings'
166 render :action => 'settings'
167 end
167 end
168 end
168 end
169 end
169 end
170
170
171 def modules
171 def modules
172 @project.enabled_module_names = params[:enabled_modules]
172 @project.enabled_module_names = params[:enabled_modules]
173 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
173 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
174 end
174 end
175
175
176 def archive
176 def archive
177 @project.archive if request.post? && @project.active?
177 @project.archive if request.post? && @project.active?
178 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
178 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
179 end
179 end
180
180
181 def unarchive
181 def unarchive
182 @project.unarchive if request.post? && !@project.active?
182 @project.unarchive if request.post? && !@project.active?
183 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
183 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
184 end
184 end
185
185
186 # Delete @project
186 # Delete @project
187 def destroy
187 def destroy
188 @project_to_destroy = @project
188 @project_to_destroy = @project
189 if request.post? and params[:confirm]
189 if request.post? and params[:confirm]
190 @project_to_destroy.destroy
190 @project_to_destroy.destroy
191 redirect_to :controller => 'admin', :action => 'projects'
191 redirect_to :controller => 'admin', :action => 'projects'
192 end
192 end
193 # hide project in layout
193 # hide project in layout
194 @project = nil
194 @project = nil
195 end
195 end
196
196
197 # Add a new issue category to @project
197 # Add a new issue category to @project
198 def add_issue_category
198 def add_issue_category
199 @category = @project.issue_categories.build(params[:category])
199 @category = @project.issue_categories.build(params[:category])
200 if request.post? and @category.save
200 if request.post? and @category.save
201 respond_to do |format|
201 respond_to do |format|
202 format.html do
202 format.html do
203 flash[:notice] = l(:notice_successful_create)
203 flash[:notice] = l(:notice_successful_create)
204 redirect_to :action => 'settings', :tab => 'categories', :id => @project
204 redirect_to :action => 'settings', :tab => 'categories', :id => @project
205 end
205 end
206 format.js do
206 format.js do
207 # IE doesn't support the replace_html rjs method for select box options
207 # IE doesn't support the replace_html rjs method for select box options
208 render(:update) {|page| page.replace "issue_category_id",
208 render(:update) {|page| page.replace "issue_category_id",
209 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
209 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
210 }
210 }
211 end
211 end
212 end
212 end
213 end
213 end
214 end
214 end
215
215
216 # Add a new version to @project
216 # Add a new version to @project
217 def add_version
217 def add_version
218 @version = @project.versions.build(params[:version])
218 @version = @project.versions.build(params[:version])
219 if request.post? and @version.save
219 if request.post? and @version.save
220 flash[:notice] = l(:notice_successful_create)
220 flash[:notice] = l(:notice_successful_create)
221 redirect_to :action => 'settings', :tab => 'versions', :id => @project
221 redirect_to :action => 'settings', :tab => 'versions', :id => @project
222 end
222 end
223 end
223 end
224
224
225 def add_file
225 def add_file
226 if request.post?
226 if request.post?
227 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
227 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
228 attachments = attach_files(container, params[:attachments])
228 attachments = attach_files(container, params[:attachments])
229 if !attachments.empty? && Setting.notified_events.include?('file_added')
229 if !attachments.empty? && Setting.notified_events.include?('file_added')
230 Mailer.deliver_attachments_added(attachments)
230 Mailer.deliver_attachments_added(attachments)
231 end
231 end
232 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
232 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
233 return
233 return
234 end
234 end
235 @versions = @project.versions.sort
235 @versions = @project.versions.sort
236 end
236 end
237
237
238 def save_activities
238 def save_activities
239 if request.post? && params[:enumerations]
239 if request.post? && params[:enumerations]
240 Project.transaction do
240 Project.transaction do
241 params[:enumerations].each do |id, activity|
241 params[:enumerations].each do |id, activity|
242 @project.update_or_create_time_entry_activity(id, activity)
242 @project.update_or_create_time_entry_activity(id, activity)
243 end
243 end
244 end
244 end
245 end
245 end
246
246
247 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
247 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
248 end
248 end
249
249
250 def reset_activities
250 def reset_activities
251 @project.time_entry_activities.each do |time_entry_activity|
251 @project.time_entry_activities.each do |time_entry_activity|
252 time_entry_activity.destroy(time_entry_activity.parent)
252 time_entry_activity.destroy(time_entry_activity.parent)
253 end
253 end
254 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
254 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
255 end
255 end
256
256
257 def list_files
257 def list_files
258 sort_init 'filename', 'asc'
258 sort_init 'filename', 'asc'
259 sort_update 'filename' => "#{Attachment.table_name}.filename",
259 sort_update 'filename' => "#{Attachment.table_name}.filename",
260 'created_on' => "#{Attachment.table_name}.created_on",
260 'created_on' => "#{Attachment.table_name}.created_on",
261 'size' => "#{Attachment.table_name}.filesize",
261 'size' => "#{Attachment.table_name}.filesize",
262 'downloads' => "#{Attachment.table_name}.downloads"
262 'downloads' => "#{Attachment.table_name}.downloads"
263
263
264 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
264 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
265 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
265 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
266 render :layout => !request.xhr?
266 render :layout => !request.xhr?
267 end
267 end
268
268
269 # Show changelog for @project
269 # Show changelog for @project
270 def changelog
270 def changelog
271 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
271 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
272 retrieve_selected_tracker_ids(@trackers)
272 retrieve_selected_tracker_ids(@trackers)
273 @versions = @project.versions.sort
273 @versions = @project.versions.sort
274 end
274 end
275
275
276 def roadmap
276 def roadmap
277 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
277 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
278 retrieve_selected_tracker_ids(@trackers)
278 retrieve_selected_tracker_ids(@trackers)
279 @versions = @project.versions.sort
279 @versions = @project.versions.sort
280 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
280 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
281 end
281 end
282
282
283 def activity
283 def activity
284 @days = Setting.activity_days_default.to_i
284 @days = Setting.activity_days_default.to_i
285
285
286 if params[:from]
286 if params[:from]
287 begin; @date_to = params[:from].to_date + 1; rescue; end
287 begin; @date_to = params[:from].to_date + 1; rescue; end
288 end
288 end
289
289
290 @date_to ||= Date.today + 1
290 @date_to ||= Date.today + 1
291 @date_from = @date_to - @days
291 @date_from = @date_to - @days
292 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
292 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
293 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
293 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
294
294
295 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
295 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
296 :with_subprojects => @with_subprojects,
296 :with_subprojects => @with_subprojects,
297 :author => @author)
297 :author => @author)
298 @activity.scope_select {|t| !params["show_#{t}"].nil?}
298 @activity.scope_select {|t| !params["show_#{t}"].nil?}
299 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
299 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
300
300
301 events = @activity.events(@date_from, @date_to)
301 events = @activity.events(@date_from, @date_to)
302
302
303 if events.empty? || stale?(:etag => [events.first, User.current])
303 if events.empty? || stale?(:etag => [events.first, User.current])
304 respond_to do |format|
304 respond_to do |format|
305 format.html {
305 format.html {
306 @events_by_day = events.group_by(&:event_date)
306 @events_by_day = events.group_by(&:event_date)
307 render :layout => false if request.xhr?
307 render :layout => false if request.xhr?
308 }
308 }
309 format.atom {
309 format.atom {
310 title = l(:label_activity)
310 title = l(:label_activity)
311 if @author
311 if @author
312 title = @author.name
312 title = @author.name
313 elsif @activity.scope.size == 1
313 elsif @activity.scope.size == 1
314 title = l("label_#{@activity.scope.first.singularize}_plural")
314 title = l("label_#{@activity.scope.first.singularize}_plural")
315 end
315 end
316 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
316 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
317 }
317 }
318 end
318 end
319 end
319 end
320
320
321 rescue ActiveRecord::RecordNotFound
321 rescue ActiveRecord::RecordNotFound
322 render_404
322 render_404
323 end
323 end
324
324
325 private
325 private
326 # Find project of id params[:id]
326 # Find project of id params[:id]
327 # if not found, redirect to project list
327 # if not found, redirect to project list
328 # Used as a before_filter
328 # Used as a before_filter
329 def find_project
329 def find_project
330 @project = Project.find(params[:id])
330 @project = Project.find(params[:id])
331 rescue ActiveRecord::RecordNotFound
331 rescue ActiveRecord::RecordNotFound
332 render_404
332 render_404
333 end
333 end
334
334
335 def find_optional_project
335 def find_optional_project
336 return true unless params[:id]
336 return true unless params[:id]
337 @project = Project.find(params[:id])
337 @project = Project.find(params[:id])
338 authorize
338 authorize
339 rescue ActiveRecord::RecordNotFound
339 rescue ActiveRecord::RecordNotFound
340 render_404
340 render_404
341 end
341 end
342
342
343 def retrieve_selected_tracker_ids(selectable_trackers)
343 def retrieve_selected_tracker_ids(selectable_trackers)
344 if ids = params[:tracker_ids]
344 if ids = params[:tracker_ids]
345 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
345 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
346 else
346 else
347 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
347 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
348 end
348 end
349 end
349 end
350 end
350 end
@@ -1,72 +1,72
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 module ProjectsHelper
18 module ProjectsHelper
19 def link_to_version(version, options = {})
19 def link_to_version(version, options = {})
20 return '' unless version && version.is_a?(Version)
20 return '' unless version && version.is_a?(Version)
21 link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options
21 link_to h(version.name), { :controller => 'versions', :action => 'show', :id => version }, options
22 end
22 end
23
23
24 def project_settings_tabs
24 def project_settings_tabs
25 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
25 tabs = [{:name => 'info', :action => :edit_project, :partial => 'projects/edit', :label => :label_information_plural},
26 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
26 {:name => 'modules', :action => :select_project_modules, :partial => 'projects/settings/modules', :label => :label_module_plural},
27 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
27 {:name => 'members', :action => :manage_members, :partial => 'projects/settings/members', :label => :label_member_plural},
28 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
28 {:name => 'versions', :action => :manage_versions, :partial => 'projects/settings/versions', :label => :label_version_plural},
29 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
29 {:name => 'categories', :action => :manage_categories, :partial => 'projects/settings/issue_categories', :label => :label_issue_category_plural},
30 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
30 {:name => 'wiki', :action => :manage_wiki, :partial => 'projects/settings/wiki', :label => :label_wiki},
31 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
31 {:name => 'repository', :action => :manage_repository, :partial => 'projects/settings/repository', :label => :label_repository},
32 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
32 {:name => 'boards', :action => :manage_boards, :partial => 'projects/settings/boards', :label => :label_board_plural},
33 {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
33 {:name => 'activities', :action => :manage_project_activities, :partial => 'projects/settings/activities', :label => :enumeration_activities}
34 ]
34 ]
35 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
35 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
36 end
36 end
37
37
38 def parent_project_select_tag(project)
38 def parent_project_select_tag(project)
39 options = '<option></option>' + project_tree_options_for_select(project.possible_parents, :selected => project.parent)
39 options = '<option></option>' + project_tree_options_for_select(project.allowed_parents, :selected => project.parent)
40 content_tag('select', options, :name => 'project[parent_id]')
40 content_tag('select', options, :name => 'project[parent_id]')
41 end
41 end
42
42
43 # Renders a tree of projects as a nested set of unordered lists
43 # Renders a tree of projects as a nested set of unordered lists
44 # The given collection may be a subset of the whole project tree
44 # The given collection may be a subset of the whole project tree
45 # (eg. some intermediate nodes are private and can not be seen)
45 # (eg. some intermediate nodes are private and can not be seen)
46 def render_project_hierarchy(projects)
46 def render_project_hierarchy(projects)
47 s = ''
47 s = ''
48 if projects.any?
48 if projects.any?
49 ancestors = []
49 ancestors = []
50 projects.each do |project|
50 projects.each do |project|
51 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
51 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
52 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
52 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
53 else
53 else
54 ancestors.pop
54 ancestors.pop
55 s << "</li>"
55 s << "</li>"
56 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
56 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
57 ancestors.pop
57 ancestors.pop
58 s << "</ul></li>\n"
58 s << "</ul></li>\n"
59 end
59 end
60 end
60 end
61 classes = (ancestors.empty? ? 'root' : 'child')
61 classes = (ancestors.empty? ? 'root' : 'child')
62 s << "<li class='#{classes}'><div class='#{classes}'>" +
62 s << "<li class='#{classes}'><div class='#{classes}'>" +
63 link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
63 link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}")
64 s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
64 s << "<div class='wiki description'>#{textilizable(project.short_description, :project => project)}</div>" unless project.description.blank?
65 s << "</div>\n"
65 s << "</div>\n"
66 ancestors << project
66 ancestors << project
67 end
67 end
68 s << ("</li></ul>\n" * ancestors.size)
68 s << ("</li></ul>\n" * ancestors.size)
69 end
69 end
70 s
70 s
71 end
71 end
72 end
72 end
@@ -1,594 +1,622
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 # Specific overidden Activities
23 # Specific overidden Activities
24 has_many :time_entry_activities do
24 has_many :time_entry_activities do
25 def active
25 def active
26 find(:all, :conditions => {:active => true})
26 find(:all, :conditions => {:active => true})
27 end
27 end
28 end
28 end
29 has_many :members, :include => :user, :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
29 has_many :members, :include => :user, :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
30 has_many :member_principals, :class_name => 'Member',
30 has_many :member_principals, :class_name => 'Member',
31 :include => :principal,
31 :include => :principal,
32 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
32 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
33 has_many :users, :through => :members
33 has_many :users, :through => :members
34 has_many :principals, :through => :member_principals, :source => :principal
34 has_many :principals, :through => :member_principals, :source => :principal
35
35
36 has_many :enabled_modules, :dependent => :delete_all
36 has_many :enabled_modules, :dependent => :delete_all
37 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
37 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
38 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
38 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
39 has_many :issue_changes, :through => :issues, :source => :journals
39 has_many :issue_changes, :through => :issues, :source => :journals
40 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
40 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
41 has_many :time_entries, :dependent => :delete_all
41 has_many :time_entries, :dependent => :delete_all
42 has_many :queries, :dependent => :delete_all
42 has_many :queries, :dependent => :delete_all
43 has_many :documents, :dependent => :destroy
43 has_many :documents, :dependent => :destroy
44 has_many :news, :dependent => :delete_all, :include => :author
44 has_many :news, :dependent => :delete_all, :include => :author
45 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
45 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
46 has_many :boards, :dependent => :destroy, :order => "position ASC"
46 has_many :boards, :dependent => :destroy, :order => "position ASC"
47 has_one :repository, :dependent => :destroy
47 has_one :repository, :dependent => :destroy
48 has_many :changesets, :through => :repository
48 has_many :changesets, :through => :repository
49 has_one :wiki, :dependent => :destroy
49 has_one :wiki, :dependent => :destroy
50 # Custom field for the project issues
50 # Custom field for the project issues
51 has_and_belongs_to_many :issue_custom_fields,
51 has_and_belongs_to_many :issue_custom_fields,
52 :class_name => 'IssueCustomField',
52 :class_name => 'IssueCustomField',
53 :order => "#{CustomField.table_name}.position",
53 :order => "#{CustomField.table_name}.position",
54 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
54 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
55 :association_foreign_key => 'custom_field_id'
55 :association_foreign_key => 'custom_field_id'
56
56
57 acts_as_nested_set :order => 'name', :dependent => :destroy
57 acts_as_nested_set :order => 'name', :dependent => :destroy
58 acts_as_attachable :view_permission => :view_files,
58 acts_as_attachable :view_permission => :view_files,
59 :delete_permission => :manage_files
59 :delete_permission => :manage_files
60
60
61 acts_as_customizable
61 acts_as_customizable
62 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
62 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
63 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
63 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
64 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
64 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
65 :author => nil
65 :author => nil
66
66
67 attr_protected :status, :enabled_module_names
67 attr_protected :status, :enabled_module_names
68
68
69 validates_presence_of :name, :identifier
69 validates_presence_of :name, :identifier
70 validates_uniqueness_of :name, :identifier
70 validates_uniqueness_of :name, :identifier
71 validates_associated :repository, :wiki
71 validates_associated :repository, :wiki
72 validates_length_of :name, :maximum => 30
72 validates_length_of :name, :maximum => 30
73 validates_length_of :homepage, :maximum => 255
73 validates_length_of :homepage, :maximum => 255
74 validates_length_of :identifier, :in => 1..20
74 validates_length_of :identifier, :in => 1..20
75 # donwcase letters, digits, dashes but not digits only
75 # donwcase letters, digits, dashes but not digits only
76 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
76 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
77 # reserved words
77 # reserved words
78 validates_exclusion_of :identifier, :in => %w( new )
78 validates_exclusion_of :identifier, :in => %w( new )
79
79
80 before_destroy :delete_all_members
80 before_destroy :delete_all_members
81
81
82 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
82 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
83 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
83 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
84 named_scope :all_public, { :conditions => { :is_public => true } }
84 named_scope :all_public, { :conditions => { :is_public => true } }
85 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
85 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
86
86
87 def identifier=(identifier)
87 def identifier=(identifier)
88 super unless identifier_frozen?
88 super unless identifier_frozen?
89 end
89 end
90
90
91 def identifier_frozen?
91 def identifier_frozen?
92 errors[:identifier].nil? && !(new_record? || identifier.blank?)
92 errors[:identifier].nil? && !(new_record? || identifier.blank?)
93 end
93 end
94
94
95 def issues_with_subprojects(include_subprojects=false)
95 def issues_with_subprojects(include_subprojects=false)
96 conditions = nil
96 conditions = nil
97 if include_subprojects
97 if include_subprojects
98 ids = [id] + descendants.collect(&:id)
98 ids = [id] + descendants.collect(&:id)
99 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
99 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
100 end
100 end
101 conditions ||= ["#{Project.table_name}.id = ?", id]
101 conditions ||= ["#{Project.table_name}.id = ?", id]
102 # Quick and dirty fix for Rails 2 compatibility
102 # Quick and dirty fix for Rails 2 compatibility
103 Issue.send(:with_scope, :find => { :conditions => conditions }) do
103 Issue.send(:with_scope, :find => { :conditions => conditions }) do
104 Version.send(:with_scope, :find => { :conditions => conditions }) do
104 Version.send(:with_scope, :find => { :conditions => conditions }) do
105 yield
105 yield
106 end
106 end
107 end
107 end
108 end
108 end
109
109
110 # returns latest created projects
110 # returns latest created projects
111 # non public projects will be returned only if user is a member of those
111 # non public projects will be returned only if user is a member of those
112 def self.latest(user=nil, count=5)
112 def self.latest(user=nil, count=5)
113 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
113 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
114 end
114 end
115
115
116 # Returns a SQL :conditions string used to find all active projects for the specified user.
116 # Returns a SQL :conditions string used to find all active projects for the specified user.
117 #
117 #
118 # Examples:
118 # Examples:
119 # Projects.visible_by(admin) => "projects.status = 1"
119 # Projects.visible_by(admin) => "projects.status = 1"
120 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
120 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
121 def self.visible_by(user=nil)
121 def self.visible_by(user=nil)
122 user ||= User.current
122 user ||= User.current
123 if user && user.admin?
123 if user && user.admin?
124 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
124 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
125 elsif user && user.memberships.any?
125 elsif user && user.memberships.any?
126 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
126 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
127 else
127 else
128 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
128 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
129 end
129 end
130 end
130 end
131
131
132 def self.allowed_to_condition(user, permission, options={})
132 def self.allowed_to_condition(user, permission, options={})
133 statements = []
133 statements = []
134 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
134 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
135 if perm = Redmine::AccessControl.permission(permission)
135 if perm = Redmine::AccessControl.permission(permission)
136 unless perm.project_module.nil?
136 unless perm.project_module.nil?
137 # If the permission belongs to a project module, make sure the module is enabled
137 # If the permission belongs to a project module, make sure the module is enabled
138 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
138 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
139 end
139 end
140 end
140 end
141 if options[:project]
141 if options[:project]
142 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
142 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
143 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
143 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
144 base_statement = "(#{project_statement}) AND (#{base_statement})"
144 base_statement = "(#{project_statement}) AND (#{base_statement})"
145 end
145 end
146 if user.admin?
146 if user.admin?
147 # no restriction
147 # no restriction
148 else
148 else
149 statements << "1=0"
149 statements << "1=0"
150 if user.logged?
150 if user.logged?
151 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
151 if Role.non_member.allowed_to?(permission) && !options[:member]
152 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
153 end
152 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
154 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
153 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
155 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
154 elsif Role.anonymous.allowed_to?(permission)
155 # anonymous user allowed on public project
156 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
157 else
156 else
158 # anonymous user is not authorized
157 if Role.anonymous.allowed_to?(permission) && !options[:member]
158 # anonymous user allowed on public project
159 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
160 end
159 end
161 end
160 end
162 end
161 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
163 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
162 end
164 end
163
165
164 # Returns the Systemwide and project specific activities
166 # Returns the Systemwide and project specific activities
165 def activities(include_inactive=false)
167 def activities(include_inactive=false)
166 if include_inactive
168 if include_inactive
167 return all_activities
169 return all_activities
168 else
170 else
169 return active_activities
171 return active_activities
170 end
172 end
171 end
173 end
172
174
173 # Will create a new Project specific Activity or update an existing one
175 # Will create a new Project specific Activity or update an existing one
174 #
176 #
175 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
177 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
176 # does not successfully save.
178 # does not successfully save.
177 def update_or_create_time_entry_activity(id, activity_hash)
179 def update_or_create_time_entry_activity(id, activity_hash)
178 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
180 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
179 self.create_time_entry_activity_if_needed(activity_hash)
181 self.create_time_entry_activity_if_needed(activity_hash)
180 else
182 else
181 activity = project.time_entry_activities.find_by_id(id.to_i)
183 activity = project.time_entry_activities.find_by_id(id.to_i)
182 activity.update_attributes(activity_hash) if activity
184 activity.update_attributes(activity_hash) if activity
183 end
185 end
184 end
186 end
185
187
186 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
188 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
187 #
189 #
188 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
190 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
189 # does not successfully save.
191 # does not successfully save.
190 def create_time_entry_activity_if_needed(activity)
192 def create_time_entry_activity_if_needed(activity)
191 if activity['parent_id']
193 if activity['parent_id']
192
194
193 parent_activity = TimeEntryActivity.find(activity['parent_id'])
195 parent_activity = TimeEntryActivity.find(activity['parent_id'])
194 activity['name'] = parent_activity.name
196 activity['name'] = parent_activity.name
195 activity['position'] = parent_activity.position
197 activity['position'] = parent_activity.position
196
198
197 if Enumeration.overridding_change?(activity, parent_activity)
199 if Enumeration.overridding_change?(activity, parent_activity)
198 project_activity = self.time_entry_activities.create(activity)
200 project_activity = self.time_entry_activities.create(activity)
199
201
200 if project_activity.new_record?
202 if project_activity.new_record?
201 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
203 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
202 else
204 else
203 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
205 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
204 end
206 end
205 end
207 end
206 end
208 end
207 end
209 end
208
210
209 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
211 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
210 #
212 #
211 # Examples:
213 # Examples:
212 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
214 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
213 # project.project_condition(false) => "projects.id = 1"
215 # project.project_condition(false) => "projects.id = 1"
214 def project_condition(with_subprojects)
216 def project_condition(with_subprojects)
215 cond = "#{Project.table_name}.id = #{id}"
217 cond = "#{Project.table_name}.id = #{id}"
216 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
218 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
217 cond
219 cond
218 end
220 end
219
221
220 def self.find(*args)
222 def self.find(*args)
221 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
223 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
222 project = find_by_identifier(*args)
224 project = find_by_identifier(*args)
223 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
225 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
224 project
226 project
225 else
227 else
226 super
228 super
227 end
229 end
228 end
230 end
229
231
230 def to_param
232 def to_param
231 # id is used for projects with a numeric identifier (compatibility)
233 # id is used for projects with a numeric identifier (compatibility)
232 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
234 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
233 end
235 end
234
236
235 def active?
237 def active?
236 self.status == STATUS_ACTIVE
238 self.status == STATUS_ACTIVE
237 end
239 end
238
240
239 # Archives the project and its descendants recursively
241 # Archives the project and its descendants recursively
240 def archive
242 def archive
241 # Archive subprojects if any
243 # Archive subprojects if any
242 children.each do |subproject|
244 children.each do |subproject|
243 subproject.archive
245 subproject.archive
244 end
246 end
245 update_attribute :status, STATUS_ARCHIVED
247 update_attribute :status, STATUS_ARCHIVED
246 end
248 end
247
249
248 # Unarchives the project
250 # Unarchives the project
249 # All its ancestors must be active
251 # All its ancestors must be active
250 def unarchive
252 def unarchive
251 return false if ancestors.detect {|a| !a.active?}
253 return false if ancestors.detect {|a| !a.active?}
252 update_attribute :status, STATUS_ACTIVE
254 update_attribute :status, STATUS_ACTIVE
253 end
255 end
254
256
255 # Returns an array of projects the project can be moved to
257 # Returns an array of projects the project can be moved to
256 def possible_parents
258 # by the current user
257 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
259 def allowed_parents
260 return @allowed_parents if @allowed_parents
261 @allowed_parents = (Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_project, :member => true)) - self_and_descendants)
262 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
263 @allowed_parents << parent
264 end
265 @allowed_parents
266 end
267
268 # Sets the parent of the project with authorization check
269 def set_allowed_parent!(p)
270 unless p.nil? || p.is_a?(Project)
271 if p.to_s.blank?
272 p = nil
273 else
274 p = Project.find_by_id(p)
275 return false unless p
276 end
277 end
278 if p.nil?
279 if !new_record? && allowed_parents.empty?
280 return false
281 end
282 elsif !allowed_parents.include?(p)
283 return false
284 end
285 set_parent!(p)
258 end
286 end
259
287
260 # Sets the parent of the project
288 # Sets the parent of the project
261 # Argument can be either a Project, a String, a Fixnum or nil
289 # Argument can be either a Project, a String, a Fixnum or nil
262 def set_parent!(p)
290 def set_parent!(p)
263 unless p.nil? || p.is_a?(Project)
291 unless p.nil? || p.is_a?(Project)
264 if p.to_s.blank?
292 if p.to_s.blank?
265 p = nil
293 p = nil
266 else
294 else
267 p = Project.find_by_id(p)
295 p = Project.find_by_id(p)
268 return false unless p
296 return false unless p
269 end
297 end
270 end
298 end
271 if p == parent && !p.nil?
299 if p == parent && !p.nil?
272 # Nothing to do
300 # Nothing to do
273 true
301 true
274 elsif p.nil? || (p.active? && move_possible?(p))
302 elsif p.nil? || (p.active? && move_possible?(p))
275 # Insert the project so that target's children or root projects stay alphabetically sorted
303 # Insert the project so that target's children or root projects stay alphabetically sorted
276 sibs = (p.nil? ? self.class.roots : p.children)
304 sibs = (p.nil? ? self.class.roots : p.children)
277 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
305 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
278 if to_be_inserted_before
306 if to_be_inserted_before
279 move_to_left_of(to_be_inserted_before)
307 move_to_left_of(to_be_inserted_before)
280 elsif p.nil?
308 elsif p.nil?
281 if sibs.empty?
309 if sibs.empty?
282 # move_to_root adds the project in first (ie. left) position
310 # move_to_root adds the project in first (ie. left) position
283 move_to_root
311 move_to_root
284 else
312 else
285 move_to_right_of(sibs.last) unless self == sibs.last
313 move_to_right_of(sibs.last) unless self == sibs.last
286 end
314 end
287 else
315 else
288 # move_to_child_of adds the project in last (ie.right) position
316 # move_to_child_of adds the project in last (ie.right) position
289 move_to_child_of(p)
317 move_to_child_of(p)
290 end
318 end
291 true
319 true
292 else
320 else
293 # Can not move to the given target
321 # Can not move to the given target
294 false
322 false
295 end
323 end
296 end
324 end
297
325
298 # Returns an array of the trackers used by the project and its active sub projects
326 # Returns an array of the trackers used by the project and its active sub projects
299 def rolled_up_trackers
327 def rolled_up_trackers
300 @rolled_up_trackers ||=
328 @rolled_up_trackers ||=
301 Tracker.find(:all, :include => :projects,
329 Tracker.find(:all, :include => :projects,
302 :select => "DISTINCT #{Tracker.table_name}.*",
330 :select => "DISTINCT #{Tracker.table_name}.*",
303 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
331 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
304 :order => "#{Tracker.table_name}.position")
332 :order => "#{Tracker.table_name}.position")
305 end
333 end
306
334
307 # Closes open and locked project versions that are completed
335 # Closes open and locked project versions that are completed
308 def close_completed_versions
336 def close_completed_versions
309 Version.transaction do
337 Version.transaction do
310 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
338 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
311 if version.completed?
339 if version.completed?
312 version.update_attribute(:status, 'closed')
340 version.update_attribute(:status, 'closed')
313 end
341 end
314 end
342 end
315 end
343 end
316 end
344 end
317
345
318 # Returns a hash of project users grouped by role
346 # Returns a hash of project users grouped by role
319 def users_by_role
347 def users_by_role
320 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
348 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
321 m.roles.each do |r|
349 m.roles.each do |r|
322 h[r] ||= []
350 h[r] ||= []
323 h[r] << m.user
351 h[r] << m.user
324 end
352 end
325 h
353 h
326 end
354 end
327 end
355 end
328
356
329 # Deletes all project's members
357 # Deletes all project's members
330 def delete_all_members
358 def delete_all_members
331 me, mr = Member.table_name, MemberRole.table_name
359 me, mr = Member.table_name, MemberRole.table_name
332 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
360 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
333 Member.delete_all(['project_id = ?', id])
361 Member.delete_all(['project_id = ?', id])
334 end
362 end
335
363
336 # Users issues can be assigned to
364 # Users issues can be assigned to
337 def assignable_users
365 def assignable_users
338 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
366 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
339 end
367 end
340
368
341 # Returns the mail adresses of users that should be always notified on project events
369 # Returns the mail adresses of users that should be always notified on project events
342 def recipients
370 def recipients
343 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
371 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
344 end
372 end
345
373
346 # Returns an array of all custom fields enabled for project issues
374 # Returns an array of all custom fields enabled for project issues
347 # (explictly associated custom fields and custom fields enabled for all projects)
375 # (explictly associated custom fields and custom fields enabled for all projects)
348 def all_issue_custom_fields
376 def all_issue_custom_fields
349 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
377 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
350 end
378 end
351
379
352 def project
380 def project
353 self
381 self
354 end
382 end
355
383
356 def <=>(project)
384 def <=>(project)
357 name.downcase <=> project.name.downcase
385 name.downcase <=> project.name.downcase
358 end
386 end
359
387
360 def to_s
388 def to_s
361 name
389 name
362 end
390 end
363
391
364 # Returns a short description of the projects (first lines)
392 # Returns a short description of the projects (first lines)
365 def short_description(length = 255)
393 def short_description(length = 255)
366 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
394 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
367 end
395 end
368
396
369 # Return true if this project is allowed to do the specified action.
397 # Return true if this project is allowed to do the specified action.
370 # action can be:
398 # action can be:
371 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
399 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
372 # * a permission Symbol (eg. :edit_project)
400 # * a permission Symbol (eg. :edit_project)
373 def allows_to?(action)
401 def allows_to?(action)
374 if action.is_a? Hash
402 if action.is_a? Hash
375 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
403 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
376 else
404 else
377 allowed_permissions.include? action
405 allowed_permissions.include? action
378 end
406 end
379 end
407 end
380
408
381 def module_enabled?(module_name)
409 def module_enabled?(module_name)
382 module_name = module_name.to_s
410 module_name = module_name.to_s
383 enabled_modules.detect {|m| m.name == module_name}
411 enabled_modules.detect {|m| m.name == module_name}
384 end
412 end
385
413
386 def enabled_module_names=(module_names)
414 def enabled_module_names=(module_names)
387 if module_names && module_names.is_a?(Array)
415 if module_names && module_names.is_a?(Array)
388 module_names = module_names.collect(&:to_s)
416 module_names = module_names.collect(&:to_s)
389 # remove disabled modules
417 # remove disabled modules
390 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
418 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
391 # add new modules
419 # add new modules
392 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
420 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
393 else
421 else
394 enabled_modules.clear
422 enabled_modules.clear
395 end
423 end
396 end
424 end
397
425
398 # Returns an auto-generated project identifier based on the last identifier used
426 # Returns an auto-generated project identifier based on the last identifier used
399 def self.next_identifier
427 def self.next_identifier
400 p = Project.find(:first, :order => 'created_on DESC')
428 p = Project.find(:first, :order => 'created_on DESC')
401 p.nil? ? nil : p.identifier.to_s.succ
429 p.nil? ? nil : p.identifier.to_s.succ
402 end
430 end
403
431
404 # Copies and saves the Project instance based on the +project+.
432 # Copies and saves the Project instance based on the +project+.
405 # Duplicates the source project's:
433 # Duplicates the source project's:
406 # * Wiki
434 # * Wiki
407 # * Versions
435 # * Versions
408 # * Categories
436 # * Categories
409 # * Issues
437 # * Issues
410 # * Members
438 # * Members
411 # * Queries
439 # * Queries
412 #
440 #
413 # Accepts an +options+ argument to specify what to copy
441 # Accepts an +options+ argument to specify what to copy
414 #
442 #
415 # Examples:
443 # Examples:
416 # project.copy(1) # => copies everything
444 # project.copy(1) # => copies everything
417 # project.copy(1, :only => 'members') # => copies members only
445 # project.copy(1, :only => 'members') # => copies members only
418 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
446 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
419 def copy(project, options={})
447 def copy(project, options={})
420 project = project.is_a?(Project) ? project : Project.find(project)
448 project = project.is_a?(Project) ? project : Project.find(project)
421
449
422 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
450 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
423 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
451 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
424
452
425 Project.transaction do
453 Project.transaction do
426 if save
454 if save
427 reload
455 reload
428 to_be_copied.each do |name|
456 to_be_copied.each do |name|
429 send "copy_#{name}", project
457 send "copy_#{name}", project
430 end
458 end
431 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
459 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
432 save
460 save
433 end
461 end
434 end
462 end
435 end
463 end
436
464
437
465
438 # Copies +project+ and returns the new instance. This will not save
466 # Copies +project+ and returns the new instance. This will not save
439 # the copy
467 # the copy
440 def self.copy_from(project)
468 def self.copy_from(project)
441 begin
469 begin
442 project = project.is_a?(Project) ? project : Project.find(project)
470 project = project.is_a?(Project) ? project : Project.find(project)
443 if project
471 if project
444 # clear unique attributes
472 # clear unique attributes
445 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
473 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
446 copy = Project.new(attributes)
474 copy = Project.new(attributes)
447 copy.enabled_modules = project.enabled_modules
475 copy.enabled_modules = project.enabled_modules
448 copy.trackers = project.trackers
476 copy.trackers = project.trackers
449 copy.custom_values = project.custom_values.collect {|v| v.clone}
477 copy.custom_values = project.custom_values.collect {|v| v.clone}
450 copy.issue_custom_fields = project.issue_custom_fields
478 copy.issue_custom_fields = project.issue_custom_fields
451 return copy
479 return copy
452 else
480 else
453 return nil
481 return nil
454 end
482 end
455 rescue ActiveRecord::RecordNotFound
483 rescue ActiveRecord::RecordNotFound
456 return nil
484 return nil
457 end
485 end
458 end
486 end
459
487
460 private
488 private
461
489
462 # Copies wiki from +project+
490 # Copies wiki from +project+
463 def copy_wiki(project)
491 def copy_wiki(project)
464 # Check that the source project has a wiki first
492 # Check that the source project has a wiki first
465 unless project.wiki.nil?
493 unless project.wiki.nil?
466 self.wiki ||= Wiki.new
494 self.wiki ||= Wiki.new
467 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
495 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
468 project.wiki.pages.each do |page|
496 project.wiki.pages.each do |page|
469 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
497 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
470 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
498 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
471 new_wiki_page.content = new_wiki_content
499 new_wiki_page.content = new_wiki_content
472 wiki.pages << new_wiki_page
500 wiki.pages << new_wiki_page
473 end
501 end
474 end
502 end
475 end
503 end
476
504
477 # Copies versions from +project+
505 # Copies versions from +project+
478 def copy_versions(project)
506 def copy_versions(project)
479 project.versions.each do |version|
507 project.versions.each do |version|
480 new_version = Version.new
508 new_version = Version.new
481 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
509 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
482 self.versions << new_version
510 self.versions << new_version
483 end
511 end
484 end
512 end
485
513
486 # Copies issue categories from +project+
514 # Copies issue categories from +project+
487 def copy_issue_categories(project)
515 def copy_issue_categories(project)
488 project.issue_categories.each do |issue_category|
516 project.issue_categories.each do |issue_category|
489 new_issue_category = IssueCategory.new
517 new_issue_category = IssueCategory.new
490 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
518 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
491 self.issue_categories << new_issue_category
519 self.issue_categories << new_issue_category
492 end
520 end
493 end
521 end
494
522
495 # Copies issues from +project+
523 # Copies issues from +project+
496 def copy_issues(project)
524 def copy_issues(project)
497 project.issues.each do |issue|
525 project.issues.each do |issue|
498 new_issue = Issue.new
526 new_issue = Issue.new
499 new_issue.copy_from(issue)
527 new_issue.copy_from(issue)
500 # Reassign fixed_versions by name, since names are unique per
528 # Reassign fixed_versions by name, since names are unique per
501 # project and the versions for self are not yet saved
529 # project and the versions for self are not yet saved
502 if issue.fixed_version
530 if issue.fixed_version
503 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
531 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
504 end
532 end
505 # Reassign the category by name, since names are unique per
533 # Reassign the category by name, since names are unique per
506 # project and the categories for self are not yet saved
534 # project and the categories for self are not yet saved
507 if issue.category
535 if issue.category
508 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
536 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
509 end
537 end
510 self.issues << new_issue
538 self.issues << new_issue
511 end
539 end
512 end
540 end
513
541
514 # Copies members from +project+
542 # Copies members from +project+
515 def copy_members(project)
543 def copy_members(project)
516 project.members.each do |member|
544 project.members.each do |member|
517 new_member = Member.new
545 new_member = Member.new
518 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
546 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
519 new_member.role_ids = member.role_ids.dup
547 new_member.role_ids = member.role_ids.dup
520 new_member.project = self
548 new_member.project = self
521 self.members << new_member
549 self.members << new_member
522 end
550 end
523 end
551 end
524
552
525 # Copies queries from +project+
553 # Copies queries from +project+
526 def copy_queries(project)
554 def copy_queries(project)
527 project.queries.each do |query|
555 project.queries.each do |query|
528 new_query = Query.new
556 new_query = Query.new
529 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
557 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
530 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
558 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
531 new_query.project = self
559 new_query.project = self
532 self.queries << new_query
560 self.queries << new_query
533 end
561 end
534 end
562 end
535
563
536 # Copies boards from +project+
564 # Copies boards from +project+
537 def copy_boards(project)
565 def copy_boards(project)
538 project.boards.each do |board|
566 project.boards.each do |board|
539 new_board = Board.new
567 new_board = Board.new
540 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
568 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
541 new_board.project = self
569 new_board.project = self
542 self.boards << new_board
570 self.boards << new_board
543 end
571 end
544 end
572 end
545
573
546 def allowed_permissions
574 def allowed_permissions
547 @allowed_permissions ||= begin
575 @allowed_permissions ||= begin
548 module_names = enabled_modules.collect {|m| m.name}
576 module_names = enabled_modules.collect {|m| m.name}
549 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
577 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
550 end
578 end
551 end
579 end
552
580
553 def allowed_actions
581 def allowed_actions
554 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
582 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
555 end
583 end
556
584
557 # Returns all the active Systemwide and project specific activities
585 # Returns all the active Systemwide and project specific activities
558 def active_activities
586 def active_activities
559 overridden_activity_ids = self.time_entry_activities.active.collect(&:parent_id)
587 overridden_activity_ids = self.time_entry_activities.active.collect(&:parent_id)
560
588
561 if overridden_activity_ids.empty?
589 if overridden_activity_ids.empty?
562 return TimeEntryActivity.active
590 return TimeEntryActivity.active
563 else
591 else
564 return system_activities_and_project_overrides
592 return system_activities_and_project_overrides
565 end
593 end
566 end
594 end
567
595
568 # Returns all the Systemwide and project specific activities
596 # Returns all the Systemwide and project specific activities
569 # (inactive and active)
597 # (inactive and active)
570 def all_activities
598 def all_activities
571 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
599 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
572
600
573 if overridden_activity_ids.empty?
601 if overridden_activity_ids.empty?
574 return TimeEntryActivity.all
602 return TimeEntryActivity.all
575 else
603 else
576 return system_activities_and_project_overrides(true)
604 return system_activities_and_project_overrides(true)
577 end
605 end
578 end
606 end
579
607
580 # Returns the systemwide active activities merged with the project specific overrides
608 # Returns the systemwide active activities merged with the project specific overrides
581 def system_activities_and_project_overrides(include_inactive=false)
609 def system_activities_and_project_overrides(include_inactive=false)
582 if include_inactive
610 if include_inactive
583 return TimeEntryActivity.all.
611 return TimeEntryActivity.all.
584 find(:all,
612 find(:all,
585 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
613 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
586 self.time_entry_activities
614 self.time_entry_activities
587 else
615 else
588 return TimeEntryActivity.active.
616 return TimeEntryActivity.active.
589 find(:all,
617 find(:all,
590 :conditions => ["id NOT IN (?)", self.time_entry_activities.active.collect(&:parent_id)]) +
618 :conditions => ["id NOT IN (?)", self.time_entry_activities.active.collect(&:parent_id)]) +
591 self.time_entry_activities.active
619 self.time_entry_activities.active
592 end
620 end
593 end
621 end
594 end
622 end
@@ -1,49 +1,49
1 <%= error_messages_for 'project' %>
1 <%= error_messages_for 'project' %>
2
2
3 <div class="box">
3 <div class="box">
4 <!--[form:project]-->
4 <!--[form:project]-->
5 <p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
5 <p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
6
6
7 <% if User.current.admin? && !@project.possible_parents.empty? %>
7 <% unless @project.allowed_parents.empty? %>
8 <p><label><%= l(:field_parent) %></label><%= parent_project_select_tag(@project) %></p>
8 <p><label><%= l(:field_parent) %></label><%= parent_project_select_tag(@project) %></p>
9 <% end %>
9 <% end %>
10
10
11 <p><%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %></p>
11 <p><%= f.text_area :description, :rows => 5, :class => 'wiki-edit' %></p>
12 <p><%= f.text_field :identifier, :required => true, :disabled => @project.identifier_frozen? %>
12 <p><%= f.text_field :identifier, :required => true, :disabled => @project.identifier_frozen? %>
13 <% unless @project.identifier_frozen? %>
13 <% unless @project.identifier_frozen? %>
14 <br /><em><%= l(:text_length_between, :min => 1, :max => 20) %> <%= l(:text_project_identifier_info) %></em>
14 <br /><em><%= l(:text_length_between, :min => 1, :max => 20) %> <%= l(:text_project_identifier_info) %></em>
15 <% end %></p>
15 <% end %></p>
16 <p><%= f.text_field :homepage, :size => 60 %></p>
16 <p><%= f.text_field :homepage, :size => 60 %></p>
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 <% @project.custom_field_values.each do |value| %>
20 <% @project.custom_field_values.each do |value| %>
21 <p><%= custom_field_tag_with_label :project, value %></p>
21 <p><%= custom_field_tag_with_label :project, value %></p>
22 <% end %>
22 <% end %>
23 <%= call_hook(:view_projects_form, :project => @project, :form => f) %>
23 <%= call_hook(:view_projects_form, :project => @project, :form => f) %>
24 </div>
24 </div>
25
25
26 <% unless @trackers.empty? %>
26 <% unless @trackers.empty? %>
27 <fieldset class="box"><legend><%=l(:label_tracker_plural)%></legend>
27 <fieldset class="box"><legend><%=l(:label_tracker_plural)%></legend>
28 <% @trackers.each do |tracker| %>
28 <% @trackers.each do |tracker| %>
29 <label class="floating">
29 <label class="floating">
30 <%= check_box_tag 'project[tracker_ids][]', tracker.id, @project.trackers.include?(tracker) %>
30 <%= check_box_tag 'project[tracker_ids][]', tracker.id, @project.trackers.include?(tracker) %>
31 <%= tracker %>
31 <%= tracker %>
32 </label>
32 </label>
33 <% end %>
33 <% end %>
34 <%= hidden_field_tag 'project[tracker_ids][]', '' %>
34 <%= hidden_field_tag 'project[tracker_ids][]', '' %>
35 </fieldset>
35 </fieldset>
36 <% end %>
36 <% end %>
37
37
38 <% unless @issue_custom_fields.empty? %>
38 <% unless @issue_custom_fields.empty? %>
39 <fieldset class="box"><legend><%=l(:label_custom_field_plural)%></legend>
39 <fieldset class="box"><legend><%=l(:label_custom_field_plural)%></legend>
40 <% @issue_custom_fields.each do |custom_field| %>
40 <% @issue_custom_fields.each do |custom_field| %>
41 <label class="floating">
41 <label class="floating">
42 <%= 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 <%= 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"} : {}) %>
43 <%= custom_field.name %>
43 <%= custom_field.name %>
44 </label>
44 </label>
45 <% end %>
45 <% end %>
46 <%= hidden_field_tag 'project[issue_custom_field_ids][]', '' %>
46 <%= hidden_field_tag 'project[issue_custom_field_ids][]', '' %>
47 </fieldset>
47 </fieldset>
48 <% end %>
48 <% end %>
49 <!--[eoform:project]-->
49 <!--[eoform:project]-->
@@ -1,765 +1,782
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'projects_controller'
19 require 'projects_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class ProjectsController; def rescue_action(e) raise e end; end
22 class ProjectsController; def rescue_action(e) raise e end; end
23
23
24 class ProjectsControllerTest < ActionController::TestCase
24 class ProjectsControllerTest < ActionController::TestCase
25 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
25 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
27 :attachments, :custom_fields, :custom_values, :time_entries
27 :attachments, :custom_fields, :custom_values, :time_entries
28
28
29 def setup
29 def setup
30 @controller = ProjectsController.new
30 @controller = ProjectsController.new
31 @request = ActionController::TestRequest.new
31 @request = ActionController::TestRequest.new
32 @response = ActionController::TestResponse.new
32 @response = ActionController::TestResponse.new
33 @request.session[:user_id] = nil
33 @request.session[:user_id] = nil
34 Setting.default_language = 'en'
34 Setting.default_language = 'en'
35 end
35 end
36
36
37 def test_index_routing
37 def test_index_routing
38 assert_routing(
38 assert_routing(
39 {:method => :get, :path => '/projects'},
39 {:method => :get, :path => '/projects'},
40 :controller => 'projects', :action => 'index'
40 :controller => 'projects', :action => 'index'
41 )
41 )
42 end
42 end
43
43
44 def test_index
44 def test_index
45 get :index
45 get :index
46 assert_response :success
46 assert_response :success
47 assert_template 'index'
47 assert_template 'index'
48 assert_not_nil assigns(:projects)
48 assert_not_nil assigns(:projects)
49
49
50 assert_tag :ul, :child => {:tag => 'li',
50 assert_tag :ul, :child => {:tag => 'li',
51 :descendant => {:tag => 'a', :content => 'eCookbook'},
51 :descendant => {:tag => 'a', :content => 'eCookbook'},
52 :child => { :tag => 'ul',
52 :child => { :tag => 'ul',
53 :descendant => { :tag => 'a',
53 :descendant => { :tag => 'a',
54 :content => 'Child of private child'
54 :content => 'Child of private child'
55 }
55 }
56 }
56 }
57 }
57 }
58
58
59 assert_no_tag :a, :content => /Private child of eCookbook/
59 assert_no_tag :a, :content => /Private child of eCookbook/
60 end
60 end
61
61
62 def test_index_atom_routing
62 def test_index_atom_routing
63 assert_routing(
63 assert_routing(
64 {:method => :get, :path => '/projects.atom'},
64 {:method => :get, :path => '/projects.atom'},
65 :controller => 'projects', :action => 'index', :format => 'atom'
65 :controller => 'projects', :action => 'index', :format => 'atom'
66 )
66 )
67 end
67 end
68
68
69 def test_index_atom
69 def test_index_atom
70 get :index, :format => 'atom'
70 get :index, :format => 'atom'
71 assert_response :success
71 assert_response :success
72 assert_template 'common/feed.atom.rxml'
72 assert_template 'common/feed.atom.rxml'
73 assert_select 'feed>title', :text => 'Redmine: Latest projects'
73 assert_select 'feed>title', :text => 'Redmine: Latest projects'
74 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
74 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
75 end
75 end
76
76
77 def test_add_routing
77 def test_add_routing
78 assert_routing(
78 assert_routing(
79 {:method => :get, :path => '/projects/new'},
79 {:method => :get, :path => '/projects/new'},
80 :controller => 'projects', :action => 'add'
80 :controller => 'projects', :action => 'add'
81 )
81 )
82 assert_recognizes(
82 assert_recognizes(
83 {:controller => 'projects', :action => 'add'},
83 {:controller => 'projects', :action => 'add'},
84 {:method => :post, :path => '/projects/new'}
84 {:method => :post, :path => '/projects/new'}
85 )
85 )
86 assert_recognizes(
86 assert_recognizes(
87 {:controller => 'projects', :action => 'add'},
87 {:controller => 'projects', :action => 'add'},
88 {:method => :post, :path => '/projects'}
88 {:method => :post, :path => '/projects'}
89 )
89 )
90 end
90 end
91
91
92 def test_get_add
92 def test_get_add
93 @request.session[:user_id] = 1
93 @request.session[:user_id] = 1
94 get :add
94 get :add
95 assert_response :success
95 assert_response :success
96 assert_template 'add'
96 assert_template 'add'
97 end
97 end
98
98
99 def test_get_add_by_non_admin
99 def test_get_add_by_non_admin
100 @request.session[:user_id] = 2
100 @request.session[:user_id] = 2
101 get :add
101 get :add
102 assert_response :success
102 assert_response :success
103 assert_template 'add'
103 assert_template 'add'
104 end
104 end
105
105
106 def test_post_add
106 def test_post_add
107 @request.session[:user_id] = 1
107 @request.session[:user_id] = 1
108 post :add, :project => { :name => "blog",
108 post :add, :project => { :name => "blog",
109 :description => "weblog",
109 :description => "weblog",
110 :identifier => "blog",
110 :identifier => "blog",
111 :is_public => 1,
111 :is_public => 1,
112 :custom_field_values => { '3' => 'Beta' }
112 :custom_field_values => { '3' => 'Beta' }
113 }
113 }
114 assert_redirected_to '/projects/blog/settings'
114 assert_redirected_to '/projects/blog/settings'
115
115
116 project = Project.find_by_name('blog')
116 project = Project.find_by_name('blog')
117 assert_kind_of Project, project
117 assert_kind_of Project, project
118 assert_equal 'weblog', project.description
118 assert_equal 'weblog', project.description
119 assert_equal true, project.is_public?
119 assert_equal true, project.is_public?
120 assert_nil project.parent
121 end
122
123 def test_post_add_subproject
124 @request.session[:user_id] = 1
125 post :add, :project => { :name => "blog",
126 :description => "weblog",
127 :identifier => "blog",
128 :is_public => 1,
129 :custom_field_values => { '3' => 'Beta' },
130 :parent_id => 1
131 }
132 assert_redirected_to '/projects/blog/settings'
133
134 project = Project.find_by_name('blog')
135 assert_kind_of Project, project
136 assert_equal Project.find(1), project.parent
120 end
137 end
121
138
122 def test_post_add_by_non_admin
139 def test_post_add_by_non_admin
123 @request.session[:user_id] = 2
140 @request.session[:user_id] = 2
124 post :add, :project => { :name => "blog",
141 post :add, :project => { :name => "blog",
125 :description => "weblog",
142 :description => "weblog",
126 :identifier => "blog",
143 :identifier => "blog",
127 :is_public => 1,
144 :is_public => 1,
128 :custom_field_values => { '3' => 'Beta' }
145 :custom_field_values => { '3' => 'Beta' }
129 }
146 }
130 assert_redirected_to '/projects/blog/settings'
147 assert_redirected_to '/projects/blog/settings'
131
148
132 project = Project.find_by_name('blog')
149 project = Project.find_by_name('blog')
133 assert_kind_of Project, project
150 assert_kind_of Project, project
134 assert_equal 'weblog', project.description
151 assert_equal 'weblog', project.description
135 assert_equal true, project.is_public?
152 assert_equal true, project.is_public?
136
153
137 # User should be added as a project member
154 # User should be added as a project member
138 assert User.find(2).member_of?(project)
155 assert User.find(2).member_of?(project)
139 assert_equal 1, project.members.size
156 assert_equal 1, project.members.size
140 end
157 end
141
158
142 def test_show_routing
159 def test_show_routing
143 assert_routing(
160 assert_routing(
144 {:method => :get, :path => '/projects/test'},
161 {:method => :get, :path => '/projects/test'},
145 :controller => 'projects', :action => 'show', :id => 'test'
162 :controller => 'projects', :action => 'show', :id => 'test'
146 )
163 )
147 end
164 end
148
165
149 def test_show_by_id
166 def test_show_by_id
150 get :show, :id => 1
167 get :show, :id => 1
151 assert_response :success
168 assert_response :success
152 assert_template 'show'
169 assert_template 'show'
153 assert_not_nil assigns(:project)
170 assert_not_nil assigns(:project)
154 end
171 end
155
172
156 def test_show_by_identifier
173 def test_show_by_identifier
157 get :show, :id => 'ecookbook'
174 get :show, :id => 'ecookbook'
158 assert_response :success
175 assert_response :success
159 assert_template 'show'
176 assert_template 'show'
160 assert_not_nil assigns(:project)
177 assert_not_nil assigns(:project)
161 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
178 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
162 end
179 end
163
180
164 def test_show_should_not_fail_when_custom_values_are_nil
181 def test_show_should_not_fail_when_custom_values_are_nil
165 project = Project.find_by_identifier('ecookbook')
182 project = Project.find_by_identifier('ecookbook')
166 project.custom_values.first.update_attribute(:value, nil)
183 project.custom_values.first.update_attribute(:value, nil)
167 get :show, :id => 'ecookbook'
184 get :show, :id => 'ecookbook'
168 assert_response :success
185 assert_response :success
169 assert_template 'show'
186 assert_template 'show'
170 assert_not_nil assigns(:project)
187 assert_not_nil assigns(:project)
171 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
188 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
172 end
189 end
173
190
174 def test_private_subprojects_hidden
191 def test_private_subprojects_hidden
175 get :show, :id => 'ecookbook'
192 get :show, :id => 'ecookbook'
176 assert_response :success
193 assert_response :success
177 assert_template 'show'
194 assert_template 'show'
178 assert_no_tag :tag => 'a', :content => /Private child/
195 assert_no_tag :tag => 'a', :content => /Private child/
179 end
196 end
180
197
181 def test_private_subprojects_visible
198 def test_private_subprojects_visible
182 @request.session[:user_id] = 2 # manager who is a member of the private subproject
199 @request.session[:user_id] = 2 # manager who is a member of the private subproject
183 get :show, :id => 'ecookbook'
200 get :show, :id => 'ecookbook'
184 assert_response :success
201 assert_response :success
185 assert_template 'show'
202 assert_template 'show'
186 assert_tag :tag => 'a', :content => /Private child/
203 assert_tag :tag => 'a', :content => /Private child/
187 end
204 end
188
205
189 def test_settings_routing
206 def test_settings_routing
190 assert_routing(
207 assert_routing(
191 {:method => :get, :path => '/projects/4223/settings'},
208 {:method => :get, :path => '/projects/4223/settings'},
192 :controller => 'projects', :action => 'settings', :id => '4223'
209 :controller => 'projects', :action => 'settings', :id => '4223'
193 )
210 )
194 assert_routing(
211 assert_routing(
195 {:method => :get, :path => '/projects/4223/settings/members'},
212 {:method => :get, :path => '/projects/4223/settings/members'},
196 :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
213 :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
197 )
214 )
198 end
215 end
199
216
200 def test_settings
217 def test_settings
201 @request.session[:user_id] = 2 # manager
218 @request.session[:user_id] = 2 # manager
202 get :settings, :id => 1
219 get :settings, :id => 1
203 assert_response :success
220 assert_response :success
204 assert_template 'settings'
221 assert_template 'settings'
205 end
222 end
206
223
207 def test_edit
224 def test_edit
208 @request.session[:user_id] = 2 # manager
225 @request.session[:user_id] = 2 # manager
209 post :edit, :id => 1, :project => {:name => 'Test changed name',
226 post :edit, :id => 1, :project => {:name => 'Test changed name',
210 :issue_custom_field_ids => ['']}
227 :issue_custom_field_ids => ['']}
211 assert_redirected_to 'projects/ecookbook/settings'
228 assert_redirected_to 'projects/ecookbook/settings'
212 project = Project.find(1)
229 project = Project.find(1)
213 assert_equal 'Test changed name', project.name
230 assert_equal 'Test changed name', project.name
214 end
231 end
215
232
216 def test_add_version_routing
233 def test_add_version_routing
217 assert_routing(
234 assert_routing(
218 {:method => :get, :path => 'projects/64/versions/new'},
235 {:method => :get, :path => 'projects/64/versions/new'},
219 :controller => 'projects', :action => 'add_version', :id => '64'
236 :controller => 'projects', :action => 'add_version', :id => '64'
220 )
237 )
221 assert_routing(
238 assert_routing(
222 #TODO: use PUT
239 #TODO: use PUT
223 {:method => :post, :path => 'projects/64/versions/new'},
240 {:method => :post, :path => 'projects/64/versions/new'},
224 :controller => 'projects', :action => 'add_version', :id => '64'
241 :controller => 'projects', :action => 'add_version', :id => '64'
225 )
242 )
226 end
243 end
227
244
228 def test_add_issue_category_routing
245 def test_add_issue_category_routing
229 assert_routing(
246 assert_routing(
230 {:method => :get, :path => 'projects/test/categories/new'},
247 {:method => :get, :path => 'projects/test/categories/new'},
231 :controller => 'projects', :action => 'add_issue_category', :id => 'test'
248 :controller => 'projects', :action => 'add_issue_category', :id => 'test'
232 )
249 )
233 assert_routing(
250 assert_routing(
234 #TODO: use PUT and update form
251 #TODO: use PUT and update form
235 {:method => :post, :path => 'projects/64/categories/new'},
252 {:method => :post, :path => 'projects/64/categories/new'},
236 :controller => 'projects', :action => 'add_issue_category', :id => '64'
253 :controller => 'projects', :action => 'add_issue_category', :id => '64'
237 )
254 )
238 end
255 end
239
256
240 def test_destroy_routing
257 def test_destroy_routing
241 assert_routing(
258 assert_routing(
242 {:method => :get, :path => '/projects/567/destroy'},
259 {:method => :get, :path => '/projects/567/destroy'},
243 :controller => 'projects', :action => 'destroy', :id => '567'
260 :controller => 'projects', :action => 'destroy', :id => '567'
244 )
261 )
245 assert_routing(
262 assert_routing(
246 #TODO: use DELETE and update form
263 #TODO: use DELETE and update form
247 {:method => :post, :path => 'projects/64/destroy'},
264 {:method => :post, :path => 'projects/64/destroy'},
248 :controller => 'projects', :action => 'destroy', :id => '64'
265 :controller => 'projects', :action => 'destroy', :id => '64'
249 )
266 )
250 end
267 end
251
268
252 def test_get_destroy
269 def test_get_destroy
253 @request.session[:user_id] = 1 # admin
270 @request.session[:user_id] = 1 # admin
254 get :destroy, :id => 1
271 get :destroy, :id => 1
255 assert_response :success
272 assert_response :success
256 assert_template 'destroy'
273 assert_template 'destroy'
257 assert_not_nil Project.find_by_id(1)
274 assert_not_nil Project.find_by_id(1)
258 end
275 end
259
276
260 def test_post_destroy
277 def test_post_destroy
261 @request.session[:user_id] = 1 # admin
278 @request.session[:user_id] = 1 # admin
262 post :destroy, :id => 1, :confirm => 1
279 post :destroy, :id => 1, :confirm => 1
263 assert_redirected_to 'admin/projects'
280 assert_redirected_to 'admin/projects'
264 assert_nil Project.find_by_id(1)
281 assert_nil Project.find_by_id(1)
265 end
282 end
266
283
267 def test_add_file
284 def test_add_file
268 set_tmp_attachments_directory
285 set_tmp_attachments_directory
269 @request.session[:user_id] = 2
286 @request.session[:user_id] = 2
270 Setting.notified_events = ['file_added']
287 Setting.notified_events = ['file_added']
271 ActionMailer::Base.deliveries.clear
288 ActionMailer::Base.deliveries.clear
272
289
273 assert_difference 'Attachment.count' do
290 assert_difference 'Attachment.count' do
274 post :add_file, :id => 1, :version_id => '',
291 post :add_file, :id => 1, :version_id => '',
275 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
292 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
276 end
293 end
277 assert_redirected_to 'projects/ecookbook/files'
294 assert_redirected_to 'projects/ecookbook/files'
278 a = Attachment.find(:first, :order => 'created_on DESC')
295 a = Attachment.find(:first, :order => 'created_on DESC')
279 assert_equal 'testfile.txt', a.filename
296 assert_equal 'testfile.txt', a.filename
280 assert_equal Project.find(1), a.container
297 assert_equal Project.find(1), a.container
281
298
282 mail = ActionMailer::Base.deliveries.last
299 mail = ActionMailer::Base.deliveries.last
283 assert_kind_of TMail::Mail, mail
300 assert_kind_of TMail::Mail, mail
284 assert_equal "[eCookbook] New file", mail.subject
301 assert_equal "[eCookbook] New file", mail.subject
285 assert mail.body.include?('testfile.txt')
302 assert mail.body.include?('testfile.txt')
286 end
303 end
287
304
288 def test_add_file_routing
305 def test_add_file_routing
289 assert_routing(
306 assert_routing(
290 {:method => :get, :path => '/projects/33/files/new'},
307 {:method => :get, :path => '/projects/33/files/new'},
291 :controller => 'projects', :action => 'add_file', :id => '33'
308 :controller => 'projects', :action => 'add_file', :id => '33'
292 )
309 )
293 assert_routing(
310 assert_routing(
294 {:method => :post, :path => '/projects/33/files/new'},
311 {:method => :post, :path => '/projects/33/files/new'},
295 :controller => 'projects', :action => 'add_file', :id => '33'
312 :controller => 'projects', :action => 'add_file', :id => '33'
296 )
313 )
297 end
314 end
298
315
299 def test_add_version_file
316 def test_add_version_file
300 set_tmp_attachments_directory
317 set_tmp_attachments_directory
301 @request.session[:user_id] = 2
318 @request.session[:user_id] = 2
302 Setting.notified_events = ['file_added']
319 Setting.notified_events = ['file_added']
303
320
304 assert_difference 'Attachment.count' do
321 assert_difference 'Attachment.count' do
305 post :add_file, :id => 1, :version_id => '2',
322 post :add_file, :id => 1, :version_id => '2',
306 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
323 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
307 end
324 end
308 assert_redirected_to 'projects/ecookbook/files'
325 assert_redirected_to 'projects/ecookbook/files'
309 a = Attachment.find(:first, :order => 'created_on DESC')
326 a = Attachment.find(:first, :order => 'created_on DESC')
310 assert_equal 'testfile.txt', a.filename
327 assert_equal 'testfile.txt', a.filename
311 assert_equal Version.find(2), a.container
328 assert_equal Version.find(2), a.container
312 end
329 end
313
330
314 def test_list_files
331 def test_list_files
315 get :list_files, :id => 1
332 get :list_files, :id => 1
316 assert_response :success
333 assert_response :success
317 assert_template 'list_files'
334 assert_template 'list_files'
318 assert_not_nil assigns(:containers)
335 assert_not_nil assigns(:containers)
319
336
320 # file attached to the project
337 # file attached to the project
321 assert_tag :a, :content => 'project_file.zip',
338 assert_tag :a, :content => 'project_file.zip',
322 :attributes => { :href => '/attachments/download/8/project_file.zip' }
339 :attributes => { :href => '/attachments/download/8/project_file.zip' }
323
340
324 # file attached to a project's version
341 # file attached to a project's version
325 assert_tag :a, :content => 'version_file.zip',
342 assert_tag :a, :content => 'version_file.zip',
326 :attributes => { :href => '/attachments/download/9/version_file.zip' }
343 :attributes => { :href => '/attachments/download/9/version_file.zip' }
327 end
344 end
328
345
329 def test_list_files_routing
346 def test_list_files_routing
330 assert_routing(
347 assert_routing(
331 {:method => :get, :path => '/projects/33/files'},
348 {:method => :get, :path => '/projects/33/files'},
332 :controller => 'projects', :action => 'list_files', :id => '33'
349 :controller => 'projects', :action => 'list_files', :id => '33'
333 )
350 )
334 end
351 end
335
352
336 def test_changelog_routing
353 def test_changelog_routing
337 assert_routing(
354 assert_routing(
338 {:method => :get, :path => '/projects/44/changelog'},
355 {:method => :get, :path => '/projects/44/changelog'},
339 :controller => 'projects', :action => 'changelog', :id => '44'
356 :controller => 'projects', :action => 'changelog', :id => '44'
340 )
357 )
341 end
358 end
342
359
343 def test_changelog
360 def test_changelog
344 get :changelog, :id => 1
361 get :changelog, :id => 1
345 assert_response :success
362 assert_response :success
346 assert_template 'changelog'
363 assert_template 'changelog'
347 assert_not_nil assigns(:versions)
364 assert_not_nil assigns(:versions)
348 end
365 end
349
366
350 def test_roadmap_routing
367 def test_roadmap_routing
351 assert_routing(
368 assert_routing(
352 {:method => :get, :path => 'projects/33/roadmap'},
369 {:method => :get, :path => 'projects/33/roadmap'},
353 :controller => 'projects', :action => 'roadmap', :id => '33'
370 :controller => 'projects', :action => 'roadmap', :id => '33'
354 )
371 )
355 end
372 end
356
373
357 def test_roadmap
374 def test_roadmap
358 get :roadmap, :id => 1
375 get :roadmap, :id => 1
359 assert_response :success
376 assert_response :success
360 assert_template 'roadmap'
377 assert_template 'roadmap'
361 assert_not_nil assigns(:versions)
378 assert_not_nil assigns(:versions)
362 # Version with no date set appears
379 # Version with no date set appears
363 assert assigns(:versions).include?(Version.find(3))
380 assert assigns(:versions).include?(Version.find(3))
364 # Completed version doesn't appear
381 # Completed version doesn't appear
365 assert !assigns(:versions).include?(Version.find(1))
382 assert !assigns(:versions).include?(Version.find(1))
366 end
383 end
367
384
368 def test_roadmap_with_completed_versions
385 def test_roadmap_with_completed_versions
369 get :roadmap, :id => 1, :completed => 1
386 get :roadmap, :id => 1, :completed => 1
370 assert_response :success
387 assert_response :success
371 assert_template 'roadmap'
388 assert_template 'roadmap'
372 assert_not_nil assigns(:versions)
389 assert_not_nil assigns(:versions)
373 # Version with no date set appears
390 # Version with no date set appears
374 assert assigns(:versions).include?(Version.find(3))
391 assert assigns(:versions).include?(Version.find(3))
375 # Completed version appears
392 # Completed version appears
376 assert assigns(:versions).include?(Version.find(1))
393 assert assigns(:versions).include?(Version.find(1))
377 end
394 end
378
395
379 def test_project_activity_routing
396 def test_project_activity_routing
380 assert_routing(
397 assert_routing(
381 {:method => :get, :path => '/projects/1/activity'},
398 {:method => :get, :path => '/projects/1/activity'},
382 :controller => 'projects', :action => 'activity', :id => '1'
399 :controller => 'projects', :action => 'activity', :id => '1'
383 )
400 )
384 end
401 end
385
402
386 def test_project_activity_atom_routing
403 def test_project_activity_atom_routing
387 assert_routing(
404 assert_routing(
388 {:method => :get, :path => '/projects/1/activity.atom'},
405 {:method => :get, :path => '/projects/1/activity.atom'},
389 :controller => 'projects', :action => 'activity', :id => '1', :format => 'atom'
406 :controller => 'projects', :action => 'activity', :id => '1', :format => 'atom'
390 )
407 )
391 end
408 end
392
409
393 def test_project_activity
410 def test_project_activity
394 get :activity, :id => 1, :with_subprojects => 0
411 get :activity, :id => 1, :with_subprojects => 0
395 assert_response :success
412 assert_response :success
396 assert_template 'activity'
413 assert_template 'activity'
397 assert_not_nil assigns(:events_by_day)
414 assert_not_nil assigns(:events_by_day)
398
415
399 assert_tag :tag => "h3",
416 assert_tag :tag => "h3",
400 :content => /#{2.days.ago.to_date.day}/,
417 :content => /#{2.days.ago.to_date.day}/,
401 :sibling => { :tag => "dl",
418 :sibling => { :tag => "dl",
402 :child => { :tag => "dt",
419 :child => { :tag => "dt",
403 :attributes => { :class => /issue-edit/ },
420 :attributes => { :class => /issue-edit/ },
404 :child => { :tag => "a",
421 :child => { :tag => "a",
405 :content => /(#{IssueStatus.find(2).name})/,
422 :content => /(#{IssueStatus.find(2).name})/,
406 }
423 }
407 }
424 }
408 }
425 }
409 end
426 end
410
427
411 def test_previous_project_activity
428 def test_previous_project_activity
412 get :activity, :id => 1, :from => 3.days.ago.to_date
429 get :activity, :id => 1, :from => 3.days.ago.to_date
413 assert_response :success
430 assert_response :success
414 assert_template 'activity'
431 assert_template 'activity'
415 assert_not_nil assigns(:events_by_day)
432 assert_not_nil assigns(:events_by_day)
416
433
417 assert_tag :tag => "h3",
434 assert_tag :tag => "h3",
418 :content => /#{3.day.ago.to_date.day}/,
435 :content => /#{3.day.ago.to_date.day}/,
419 :sibling => { :tag => "dl",
436 :sibling => { :tag => "dl",
420 :child => { :tag => "dt",
437 :child => { :tag => "dt",
421 :attributes => { :class => /issue/ },
438 :attributes => { :class => /issue/ },
422 :child => { :tag => "a",
439 :child => { :tag => "a",
423 :content => /#{Issue.find(1).subject}/,
440 :content => /#{Issue.find(1).subject}/,
424 }
441 }
425 }
442 }
426 }
443 }
427 end
444 end
428
445
429 def test_global_activity_routing
446 def test_global_activity_routing
430 assert_routing({:method => :get, :path => '/activity'}, :controller => 'projects', :action => 'activity', :id => nil)
447 assert_routing({:method => :get, :path => '/activity'}, :controller => 'projects', :action => 'activity', :id => nil)
431 end
448 end
432
449
433 def test_global_activity
450 def test_global_activity
434 get :activity
451 get :activity
435 assert_response :success
452 assert_response :success
436 assert_template 'activity'
453 assert_template 'activity'
437 assert_not_nil assigns(:events_by_day)
454 assert_not_nil assigns(:events_by_day)
438
455
439 assert_tag :tag => "h3",
456 assert_tag :tag => "h3",
440 :content => /#{5.day.ago.to_date.day}/,
457 :content => /#{5.day.ago.to_date.day}/,
441 :sibling => { :tag => "dl",
458 :sibling => { :tag => "dl",
442 :child => { :tag => "dt",
459 :child => { :tag => "dt",
443 :attributes => { :class => /issue/ },
460 :attributes => { :class => /issue/ },
444 :child => { :tag => "a",
461 :child => { :tag => "a",
445 :content => /#{Issue.find(5).subject}/,
462 :content => /#{Issue.find(5).subject}/,
446 }
463 }
447 }
464 }
448 }
465 }
449 end
466 end
450
467
451 def test_user_activity
468 def test_user_activity
452 get :activity, :user_id => 2
469 get :activity, :user_id => 2
453 assert_response :success
470 assert_response :success
454 assert_template 'activity'
471 assert_template 'activity'
455 assert_not_nil assigns(:events_by_day)
472 assert_not_nil assigns(:events_by_day)
456
473
457 assert_tag :tag => "h3",
474 assert_tag :tag => "h3",
458 :content => /#{3.day.ago.to_date.day}/,
475 :content => /#{3.day.ago.to_date.day}/,
459 :sibling => { :tag => "dl",
476 :sibling => { :tag => "dl",
460 :child => { :tag => "dt",
477 :child => { :tag => "dt",
461 :attributes => { :class => /issue/ },
478 :attributes => { :class => /issue/ },
462 :child => { :tag => "a",
479 :child => { :tag => "a",
463 :content => /#{Issue.find(1).subject}/,
480 :content => /#{Issue.find(1).subject}/,
464 }
481 }
465 }
482 }
466 }
483 }
467 end
484 end
468
485
469 def test_global_activity_atom_routing
486 def test_global_activity_atom_routing
470 assert_routing({:method => :get, :path => '/activity.atom'}, :controller => 'projects', :action => 'activity', :id => nil, :format => 'atom')
487 assert_routing({:method => :get, :path => '/activity.atom'}, :controller => 'projects', :action => 'activity', :id => nil, :format => 'atom')
471 end
488 end
472
489
473 def test_activity_atom_feed
490 def test_activity_atom_feed
474 get :activity, :format => 'atom'
491 get :activity, :format => 'atom'
475 assert_response :success
492 assert_response :success
476 assert_template 'common/feed.atom.rxml'
493 assert_template 'common/feed.atom.rxml'
477 end
494 end
478
495
479 def test_archive_routing
496 def test_archive_routing
480 assert_routing(
497 assert_routing(
481 #TODO: use PUT to project path and modify form
498 #TODO: use PUT to project path and modify form
482 {:method => :post, :path => 'projects/64/archive'},
499 {:method => :post, :path => 'projects/64/archive'},
483 :controller => 'projects', :action => 'archive', :id => '64'
500 :controller => 'projects', :action => 'archive', :id => '64'
484 )
501 )
485 end
502 end
486
503
487 def test_archive
504 def test_archive
488 @request.session[:user_id] = 1 # admin
505 @request.session[:user_id] = 1 # admin
489 post :archive, :id => 1
506 post :archive, :id => 1
490 assert_redirected_to 'admin/projects'
507 assert_redirected_to 'admin/projects'
491 assert !Project.find(1).active?
508 assert !Project.find(1).active?
492 end
509 end
493
510
494 def test_unarchive_routing
511 def test_unarchive_routing
495 assert_routing(
512 assert_routing(
496 #TODO: use PUT to project path and modify form
513 #TODO: use PUT to project path and modify form
497 {:method => :post, :path => '/projects/567/unarchive'},
514 {:method => :post, :path => '/projects/567/unarchive'},
498 :controller => 'projects', :action => 'unarchive', :id => '567'
515 :controller => 'projects', :action => 'unarchive', :id => '567'
499 )
516 )
500 end
517 end
501
518
502 def test_unarchive
519 def test_unarchive
503 @request.session[:user_id] = 1 # admin
520 @request.session[:user_id] = 1 # admin
504 Project.find(1).archive
521 Project.find(1).archive
505 post :unarchive, :id => 1
522 post :unarchive, :id => 1
506 assert_redirected_to 'admin/projects'
523 assert_redirected_to 'admin/projects'
507 assert Project.find(1).active?
524 assert Project.find(1).active?
508 end
525 end
509
526
510 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
527 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
511 CustomField.delete_all
528 CustomField.delete_all
512 parent = nil
529 parent = nil
513 6.times do |i|
530 6.times do |i|
514 p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}")
531 p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}")
515 p.set_parent!(parent)
532 p.set_parent!(parent)
516 get :show, :id => p
533 get :show, :id => p
517 assert_tag :h1, :parent => { :attributes => {:id => 'header'}},
534 assert_tag :h1, :parent => { :attributes => {:id => 'header'}},
518 :children => { :count => [i, 3].min,
535 :children => { :count => [i, 3].min,
519 :only => { :tag => 'a' } }
536 :only => { :tag => 'a' } }
520
537
521 parent = p
538 parent = p
522 end
539 end
523 end
540 end
524
541
525 def test_copy_with_project
542 def test_copy_with_project
526 @request.session[:user_id] = 1 # admin
543 @request.session[:user_id] = 1 # admin
527 get :copy, :id => 1
544 get :copy, :id => 1
528 assert_response :success
545 assert_response :success
529 assert_template 'copy'
546 assert_template 'copy'
530 assert assigns(:project)
547 assert assigns(:project)
531 assert_equal Project.find(1).description, assigns(:project).description
548 assert_equal Project.find(1).description, assigns(:project).description
532 assert_nil assigns(:project).id
549 assert_nil assigns(:project).id
533 end
550 end
534
551
535 def test_copy_without_project
552 def test_copy_without_project
536 @request.session[:user_id] = 1 # admin
553 @request.session[:user_id] = 1 # admin
537 get :copy
554 get :copy
538 assert_response :redirect
555 assert_response :redirect
539 assert_redirected_to :controller => 'admin', :action => 'projects'
556 assert_redirected_to :controller => 'admin', :action => 'projects'
540 end
557 end
541
558
542 def test_jump_should_redirect_to_active_tab
559 def test_jump_should_redirect_to_active_tab
543 get :show, :id => 1, :jump => 'issues'
560 get :show, :id => 1, :jump => 'issues'
544 assert_redirected_to 'projects/ecookbook/issues'
561 assert_redirected_to 'projects/ecookbook/issues'
545 end
562 end
546
563
547 def test_jump_should_not_redirect_to_inactive_tab
564 def test_jump_should_not_redirect_to_inactive_tab
548 get :show, :id => 3, :jump => 'documents'
565 get :show, :id => 3, :jump => 'documents'
549 assert_response :success
566 assert_response :success
550 assert_template 'show'
567 assert_template 'show'
551 end
568 end
552
569
553 def test_jump_should_not_redirect_to_unknown_tab
570 def test_jump_should_not_redirect_to_unknown_tab
554 get :show, :id => 3, :jump => 'foobar'
571 get :show, :id => 3, :jump => 'foobar'
555 assert_response :success
572 assert_response :success
556 assert_template 'show'
573 assert_template 'show'
557 end
574 end
558
575
559 def test_reset_activities_routing
576 def test_reset_activities_routing
560 assert_routing({:method => :delete, :path => 'projects/64/reset_activities'},
577 assert_routing({:method => :delete, :path => 'projects/64/reset_activities'},
561 :controller => 'projects', :action => 'reset_activities', :id => '64')
578 :controller => 'projects', :action => 'reset_activities', :id => '64')
562 end
579 end
563
580
564 def test_reset_activities
581 def test_reset_activities
565 @request.session[:user_id] = 2 # manager
582 @request.session[:user_id] = 2 # manager
566 project_activity = TimeEntryActivity.new({
583 project_activity = TimeEntryActivity.new({
567 :name => 'Project Specific',
584 :name => 'Project Specific',
568 :parent => TimeEntryActivity.find(:first),
585 :parent => TimeEntryActivity.find(:first),
569 :project => Project.find(1),
586 :project => Project.find(1),
570 :active => true
587 :active => true
571 })
588 })
572 assert project_activity.save
589 assert project_activity.save
573 project_activity_two = TimeEntryActivity.new({
590 project_activity_two = TimeEntryActivity.new({
574 :name => 'Project Specific Two',
591 :name => 'Project Specific Two',
575 :parent => TimeEntryActivity.find(:last),
592 :parent => TimeEntryActivity.find(:last),
576 :project => Project.find(1),
593 :project => Project.find(1),
577 :active => true
594 :active => true
578 })
595 })
579 assert project_activity_two.save
596 assert project_activity_two.save
580
597
581 delete :reset_activities, :id => 1
598 delete :reset_activities, :id => 1
582 assert_response :redirect
599 assert_response :redirect
583 assert_redirected_to 'projects/ecookbook/settings/activities'
600 assert_redirected_to 'projects/ecookbook/settings/activities'
584
601
585 assert_nil TimeEntryActivity.find_by_id(project_activity.id)
602 assert_nil TimeEntryActivity.find_by_id(project_activity.id)
586 assert_nil TimeEntryActivity.find_by_id(project_activity_two.id)
603 assert_nil TimeEntryActivity.find_by_id(project_activity_two.id)
587 end
604 end
588
605
589 def test_reset_activities_should_reassign_time_entries_back_to_the_system_activity
606 def test_reset_activities_should_reassign_time_entries_back_to_the_system_activity
590 @request.session[:user_id] = 2 # manager
607 @request.session[:user_id] = 2 # manager
591 project_activity = TimeEntryActivity.new({
608 project_activity = TimeEntryActivity.new({
592 :name => 'Project Specific Design',
609 :name => 'Project Specific Design',
593 :parent => TimeEntryActivity.find(9),
610 :parent => TimeEntryActivity.find(9),
594 :project => Project.find(1),
611 :project => Project.find(1),
595 :active => true
612 :active => true
596 })
613 })
597 assert project_activity.save
614 assert project_activity.save
598 assert TimeEntry.update_all("activity_id = '#{project_activity.id}'", ["project_id = ? AND activity_id = ?", 1, 9])
615 assert TimeEntry.update_all("activity_id = '#{project_activity.id}'", ["project_id = ? AND activity_id = ?", 1, 9])
599 assert 3, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size
616 assert 3, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size
600
617
601 delete :reset_activities, :id => 1
618 delete :reset_activities, :id => 1
602 assert_response :redirect
619 assert_response :redirect
603 assert_redirected_to 'projects/ecookbook/settings/activities'
620 assert_redirected_to 'projects/ecookbook/settings/activities'
604
621
605 assert_nil TimeEntryActivity.find_by_id(project_activity.id)
622 assert_nil TimeEntryActivity.find_by_id(project_activity.id)
606 assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size, "TimeEntries still assigned to project specific activity"
623 assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(project_activity.id, 1).size, "TimeEntries still assigned to project specific activity"
607 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "TimeEntries still assigned to project specific activity"
624 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "TimeEntries still assigned to project specific activity"
608 end
625 end
609
626
610 def test_save_activities_routing
627 def test_save_activities_routing
611 assert_routing({:method => :post, :path => 'projects/64/activities/save'},
628 assert_routing({:method => :post, :path => 'projects/64/activities/save'},
612 :controller => 'projects', :action => 'save_activities', :id => '64')
629 :controller => 'projects', :action => 'save_activities', :id => '64')
613 end
630 end
614
631
615 def test_save_activities_to_override_system_activities
632 def test_save_activities_to_override_system_activities
616 @request.session[:user_id] = 2 # manager
633 @request.session[:user_id] = 2 # manager
617 billable_field = TimeEntryActivityCustomField.find_by_name("Billable")
634 billable_field = TimeEntryActivityCustomField.find_by_name("Billable")
618
635
619 post :save_activities, :id => 1, :enumerations => {
636 post :save_activities, :id => 1, :enumerations => {
620 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate
637 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate
621 "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value
638 "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value
622 "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value
639 "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value
623 "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes
640 "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes
624 }
641 }
625
642
626 assert_response :redirect
643 assert_response :redirect
627 assert_redirected_to 'projects/ecookbook/settings/activities'
644 assert_redirected_to 'projects/ecookbook/settings/activities'
628
645
629 # Created project specific activities...
646 # Created project specific activities...
630 project = Project.find('ecookbook')
647 project = Project.find('ecookbook')
631
648
632 # ... Design
649 # ... Design
633 design = project.time_entry_activities.find_by_name("Design")
650 design = project.time_entry_activities.find_by_name("Design")
634 assert design, "Project activity not found"
651 assert design, "Project activity not found"
635
652
636 assert_equal 9, design.parent_id # Relate to the system activity
653 assert_equal 9, design.parent_id # Relate to the system activity
637 assert_not_equal design.parent.id, design.id # Different records
654 assert_not_equal design.parent.id, design.id # Different records
638 assert_equal design.parent.name, design.name # Same name
655 assert_equal design.parent.name, design.name # Same name
639 assert !design.active?
656 assert !design.active?
640
657
641 # ... Development
658 # ... Development
642 development = project.time_entry_activities.find_by_name("Development")
659 development = project.time_entry_activities.find_by_name("Development")
643 assert development, "Project activity not found"
660 assert development, "Project activity not found"
644
661
645 assert_equal 10, development.parent_id # Relate to the system activity
662 assert_equal 10, development.parent_id # Relate to the system activity
646 assert_not_equal development.parent.id, development.id # Different records
663 assert_not_equal development.parent.id, development.id # Different records
647 assert_equal development.parent.name, development.name # Same name
664 assert_equal development.parent.name, development.name # Same name
648 assert development.active?
665 assert development.active?
649 assert_equal "0", development.custom_value_for(billable_field).value
666 assert_equal "0", development.custom_value_for(billable_field).value
650
667
651 # ... Inactive Activity
668 # ... Inactive Activity
652 previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity")
669 previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity")
653 assert previously_inactive, "Project activity not found"
670 assert previously_inactive, "Project activity not found"
654
671
655 assert_equal 14, previously_inactive.parent_id # Relate to the system activity
672 assert_equal 14, previously_inactive.parent_id # Relate to the system activity
656 assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records
673 assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records
657 assert_equal previously_inactive.parent.name, previously_inactive.name # Same name
674 assert_equal previously_inactive.parent.name, previously_inactive.name # Same name
658 assert previously_inactive.active?
675 assert previously_inactive.active?
659 assert_equal "1", previously_inactive.custom_value_for(billable_field).value
676 assert_equal "1", previously_inactive.custom_value_for(billable_field).value
660
677
661 # ... QA
678 # ... QA
662 assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified"
679 assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified"
663 end
680 end
664
681
665 def test_save_activities_will_update_project_specific_activities
682 def test_save_activities_will_update_project_specific_activities
666 @request.session[:user_id] = 2 # manager
683 @request.session[:user_id] = 2 # manager
667
684
668 project_activity = TimeEntryActivity.new({
685 project_activity = TimeEntryActivity.new({
669 :name => 'Project Specific',
686 :name => 'Project Specific',
670 :parent => TimeEntryActivity.find(:first),
687 :parent => TimeEntryActivity.find(:first),
671 :project => Project.find(1),
688 :project => Project.find(1),
672 :active => true
689 :active => true
673 })
690 })
674 assert project_activity.save
691 assert project_activity.save
675 project_activity_two = TimeEntryActivity.new({
692 project_activity_two = TimeEntryActivity.new({
676 :name => 'Project Specific Two',
693 :name => 'Project Specific Two',
677 :parent => TimeEntryActivity.find(:last),
694 :parent => TimeEntryActivity.find(:last),
678 :project => Project.find(1),
695 :project => Project.find(1),
679 :active => true
696 :active => true
680 })
697 })
681 assert project_activity_two.save
698 assert project_activity_two.save
682
699
683
700
684 post :save_activities, :id => 1, :enumerations => {
701 post :save_activities, :id => 1, :enumerations => {
685 project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate
702 project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate
686 project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate
703 project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate
687 }
704 }
688
705
689 assert_response :redirect
706 assert_response :redirect
690 assert_redirected_to 'projects/ecookbook/settings/activities'
707 assert_redirected_to 'projects/ecookbook/settings/activities'
691
708
692 # Created project specific activities...
709 # Created project specific activities...
693 project = Project.find('ecookbook')
710 project = Project.find('ecookbook')
694 assert_equal 2, project.time_entry_activities.count
711 assert_equal 2, project.time_entry_activities.count
695
712
696 activity_one = project.time_entry_activities.find_by_name(project_activity.name)
713 activity_one = project.time_entry_activities.find_by_name(project_activity.name)
697 assert activity_one, "Project activity not found"
714 assert activity_one, "Project activity not found"
698 assert_equal project_activity.id, activity_one.id
715 assert_equal project_activity.id, activity_one.id
699 assert !activity_one.active?
716 assert !activity_one.active?
700
717
701 activity_two = project.time_entry_activities.find_by_name(project_activity_two.name)
718 activity_two = project.time_entry_activities.find_by_name(project_activity_two.name)
702 assert activity_two, "Project activity not found"
719 assert activity_two, "Project activity not found"
703 assert_equal project_activity_two.id, activity_two.id
720 assert_equal project_activity_two.id, activity_two.id
704 assert !activity_two.active?
721 assert !activity_two.active?
705 end
722 end
706
723
707 def test_save_activities_when_creating_new_activities_will_convert_existing_data
724 def test_save_activities_when_creating_new_activities_will_convert_existing_data
708 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size
725 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size
709
726
710 @request.session[:user_id] = 2 # manager
727 @request.session[:user_id] = 2 # manager
711 post :save_activities, :id => 1, :enumerations => {
728 post :save_activities, :id => 1, :enumerations => {
712 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate
729 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"} # Design, De-activate
713 }
730 }
714 assert_response :redirect
731 assert_response :redirect
715
732
716 # No more TimeEntries using the system activity
733 # No more TimeEntries using the system activity
717 assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries still assigned to system activities"
734 assert_equal 0, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries still assigned to system activities"
718 # All TimeEntries using project activity
735 # All TimeEntries using project activity
719 project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1)
736 project_specific_activity = TimeEntryActivity.find_by_parent_id_and_project_id(9, 1)
720 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_specific_activity.id, 1).size, "No Time Entries assigned to the project activity"
737 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(project_specific_activity.id, 1).size, "No Time Entries assigned to the project activity"
721 end
738 end
722
739
723 def test_save_activities_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised
740 def test_save_activities_when_creating_new_activities_will_not_convert_existing_data_if_an_exception_is_raised
724 # TODO: Need to cause an exception on create but these tests
741 # TODO: Need to cause an exception on create but these tests
725 # aren't setup for mocking. Just create a record now so the
742 # aren't setup for mocking. Just create a record now so the
726 # second one is a dupicate
743 # second one is a dupicate
727 parent = TimeEntryActivity.find(9)
744 parent = TimeEntryActivity.find(9)
728 TimeEntryActivity.create!({:name => parent.name, :project_id => 1, :position => parent.position, :active => true})
745 TimeEntryActivity.create!({:name => parent.name, :project_id => 1, :position => parent.position, :active => true})
729 TimeEntry.create!({:project_id => 1, :hours => 1.0, :user => User.find(1), :issue_id => 3, :activity_id => 10, :spent_on => '2009-01-01'})
746 TimeEntry.create!({:project_id => 1, :hours => 1.0, :user => User.find(1), :issue_id => 3, :activity_id => 10, :spent_on => '2009-01-01'})
730
747
731 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size
748 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size
732 assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size
749 assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size
733
750
734 @request.session[:user_id] = 2 # manager
751 @request.session[:user_id] = 2 # manager
735 post :save_activities, :id => 1, :enumerations => {
752 post :save_activities, :id => 1, :enumerations => {
736 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design
753 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design
737 "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"} # Development, Change custom value
754 "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"} # Development, Change custom value
738 }
755 }
739 assert_response :redirect
756 assert_response :redirect
740
757
741 # TimeEntries shouldn't have been reassigned on the failed record
758 # TimeEntries shouldn't have been reassigned on the failed record
742 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries are not assigned to system activities"
759 assert_equal 3, TimeEntry.find_all_by_activity_id_and_project_id(9, 1).size, "Time Entries are not assigned to system activities"
743 # TimeEntries shouldn't have been reassigned on the saved record either
760 # TimeEntries shouldn't have been reassigned on the saved record either
744 assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities"
761 assert_equal 1, TimeEntry.find_all_by_activity_id_and_project_id(10, 1).size, "Time Entries are not assigned to system activities"
745 end
762 end
746
763
747 # A hook that is manually registered later
764 # A hook that is manually registered later
748 class ProjectBasedTemplate < Redmine::Hook::ViewListener
765 class ProjectBasedTemplate < Redmine::Hook::ViewListener
749 def view_layouts_base_html_head(context)
766 def view_layouts_base_html_head(context)
750 # Adds a project stylesheet
767 # Adds a project stylesheet
751 stylesheet_link_tag(context[:project].identifier) if context[:project]
768 stylesheet_link_tag(context[:project].identifier) if context[:project]
752 end
769 end
753 end
770 end
754 # Don't use this hook now
771 # Don't use this hook now
755 Redmine::Hook.clear_listeners
772 Redmine::Hook.clear_listeners
756
773
757 def test_hook_response
774 def test_hook_response
758 Redmine::Hook.add_listener(ProjectBasedTemplate)
775 Redmine::Hook.add_listener(ProjectBasedTemplate)
759 get :show, :id => 1
776 get :show, :id => 1
760 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
777 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
761 :parent => {:tag => 'head'}
778 :parent => {:tag => 'head'}
762
779
763 Redmine::Hook.clear_listeners
780 Redmine::Hook.clear_listeners
764 end
781 end
765 end
782 end
@@ -1,541 +1,550
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ProjectTest < ActiveSupport::TestCase
20 class ProjectTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules,
21 fixtures :projects, :enabled_modules,
22 :issues, :issue_statuses, :journals, :journal_details,
22 :issues, :issue_statuses, :journals, :journal_details,
23 :users, :members, :member_roles, :roles, :projects_trackers, :trackers, :boards,
23 :users, :members, :member_roles, :roles, :projects_trackers, :trackers, :boards,
24 :queries
24 :queries
25
25
26 def setup
26 def setup
27 @ecookbook = Project.find(1)
27 @ecookbook = Project.find(1)
28 @ecookbook_sub1 = Project.find(3)
28 @ecookbook_sub1 = Project.find(3)
29 User.current = nil
29 end
30 end
30
31
31 should_validate_presence_of :name
32 should_validate_presence_of :name
32 should_validate_presence_of :identifier
33 should_validate_presence_of :identifier
33
34
34 should_validate_uniqueness_of :name
35 should_validate_uniqueness_of :name
35 should_validate_uniqueness_of :identifier
36 should_validate_uniqueness_of :identifier
36
37
37 context "associations" do
38 context "associations" do
38 should_have_many :members
39 should_have_many :members
39 should_have_many :users, :through => :members
40 should_have_many :users, :through => :members
40 should_have_many :member_principals
41 should_have_many :member_principals
41 should_have_many :principals, :through => :member_principals
42 should_have_many :principals, :through => :member_principals
42 should_have_many :enabled_modules
43 should_have_many :enabled_modules
43 should_have_many :issues
44 should_have_many :issues
44 should_have_many :issue_changes, :through => :issues
45 should_have_many :issue_changes, :through => :issues
45 should_have_many :versions
46 should_have_many :versions
46 should_have_many :time_entries
47 should_have_many :time_entries
47 should_have_many :queries
48 should_have_many :queries
48 should_have_many :documents
49 should_have_many :documents
49 should_have_many :news
50 should_have_many :news
50 should_have_many :issue_categories
51 should_have_many :issue_categories
51 should_have_many :boards
52 should_have_many :boards
52 should_have_many :changesets, :through => :repository
53 should_have_many :changesets, :through => :repository
53
54
54 should_have_one :repository
55 should_have_one :repository
55 should_have_one :wiki
56 should_have_one :wiki
56
57
57 should_have_and_belong_to_many :trackers
58 should_have_and_belong_to_many :trackers
58 should_have_and_belong_to_many :issue_custom_fields
59 should_have_and_belong_to_many :issue_custom_fields
59 end
60 end
60
61
61 def test_truth
62 def test_truth
62 assert_kind_of Project, @ecookbook
63 assert_kind_of Project, @ecookbook
63 assert_equal "eCookbook", @ecookbook.name
64 assert_equal "eCookbook", @ecookbook.name
64 end
65 end
65
66
66 def test_update
67 def test_update
67 assert_equal "eCookbook", @ecookbook.name
68 assert_equal "eCookbook", @ecookbook.name
68 @ecookbook.name = "eCook"
69 @ecookbook.name = "eCook"
69 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
70 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
70 @ecookbook.reload
71 @ecookbook.reload
71 assert_equal "eCook", @ecookbook.name
72 assert_equal "eCook", @ecookbook.name
72 end
73 end
73
74
74 def test_validate_identifier
75 def test_validate_identifier
75 to_test = {"abc" => true,
76 to_test = {"abc" => true,
76 "ab12" => true,
77 "ab12" => true,
77 "ab-12" => true,
78 "ab-12" => true,
78 "12" => false,
79 "12" => false,
79 "new" => false}
80 "new" => false}
80
81
81 to_test.each do |identifier, valid|
82 to_test.each do |identifier, valid|
82 p = Project.new
83 p = Project.new
83 p.identifier = identifier
84 p.identifier = identifier
84 p.valid?
85 p.valid?
85 assert_equal valid, p.errors.on('identifier').nil?
86 assert_equal valid, p.errors.on('identifier').nil?
86 end
87 end
87 end
88 end
88
89
89 def test_members_should_be_active_users
90 def test_members_should_be_active_users
90 Project.all.each do |project|
91 Project.all.each do |project|
91 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
92 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
92 end
93 end
93 end
94 end
94
95
95 def test_users_should_be_active_users
96 def test_users_should_be_active_users
96 Project.all.each do |project|
97 Project.all.each do |project|
97 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
98 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
98 end
99 end
99 end
100 end
100
101
101 def test_archive
102 def test_archive
102 user = @ecookbook.members.first.user
103 user = @ecookbook.members.first.user
103 @ecookbook.archive
104 @ecookbook.archive
104 @ecookbook.reload
105 @ecookbook.reload
105
106
106 assert !@ecookbook.active?
107 assert !@ecookbook.active?
107 assert !user.projects.include?(@ecookbook)
108 assert !user.projects.include?(@ecookbook)
108 # Subproject are also archived
109 # Subproject are also archived
109 assert !@ecookbook.children.empty?
110 assert !@ecookbook.children.empty?
110 assert @ecookbook.descendants.active.empty?
111 assert @ecookbook.descendants.active.empty?
111 end
112 end
112
113
113 def test_unarchive
114 def test_unarchive
114 user = @ecookbook.members.first.user
115 user = @ecookbook.members.first.user
115 @ecookbook.archive
116 @ecookbook.archive
116 # A subproject of an archived project can not be unarchived
117 # A subproject of an archived project can not be unarchived
117 assert !@ecookbook_sub1.unarchive
118 assert !@ecookbook_sub1.unarchive
118
119
119 # Unarchive project
120 # Unarchive project
120 assert @ecookbook.unarchive
121 assert @ecookbook.unarchive
121 @ecookbook.reload
122 @ecookbook.reload
122 assert @ecookbook.active?
123 assert @ecookbook.active?
123 assert user.projects.include?(@ecookbook)
124 assert user.projects.include?(@ecookbook)
124 # Subproject can now be unarchived
125 # Subproject can now be unarchived
125 @ecookbook_sub1.reload
126 @ecookbook_sub1.reload
126 assert @ecookbook_sub1.unarchive
127 assert @ecookbook_sub1.unarchive
127 end
128 end
128
129
129 def test_destroy
130 def test_destroy
130 # 2 active members
131 # 2 active members
131 assert_equal 2, @ecookbook.members.size
132 assert_equal 2, @ecookbook.members.size
132 # and 1 is locked
133 # and 1 is locked
133 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
134 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
134 # some boards
135 # some boards
135 assert @ecookbook.boards.any?
136 assert @ecookbook.boards.any?
136
137
137 @ecookbook.destroy
138 @ecookbook.destroy
138 # make sure that the project non longer exists
139 # make sure that the project non longer exists
139 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
140 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
140 # make sure related data was removed
141 # make sure related data was removed
141 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
142 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
142 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
143 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
143 end
144 end
144
145
145 def test_move_an_orphan_project_to_a_root_project
146 def test_move_an_orphan_project_to_a_root_project
146 sub = Project.find(2)
147 sub = Project.find(2)
147 sub.set_parent! @ecookbook
148 sub.set_parent! @ecookbook
148 assert_equal @ecookbook.id, sub.parent.id
149 assert_equal @ecookbook.id, sub.parent.id
149 @ecookbook.reload
150 @ecookbook.reload
150 assert_equal 4, @ecookbook.children.size
151 assert_equal 4, @ecookbook.children.size
151 end
152 end
152
153
153 def test_move_an_orphan_project_to_a_subproject
154 def test_move_an_orphan_project_to_a_subproject
154 sub = Project.find(2)
155 sub = Project.find(2)
155 assert sub.set_parent!(@ecookbook_sub1)
156 assert sub.set_parent!(@ecookbook_sub1)
156 end
157 end
157
158
158 def test_move_a_root_project_to_a_project
159 def test_move_a_root_project_to_a_project
159 sub = @ecookbook
160 sub = @ecookbook
160 assert sub.set_parent!(Project.find(2))
161 assert sub.set_parent!(Project.find(2))
161 end
162 end
162
163
163 def test_should_not_move_a_project_to_its_children
164 def test_should_not_move_a_project_to_its_children
164 sub = @ecookbook
165 sub = @ecookbook
165 assert !(sub.set_parent!(Project.find(3)))
166 assert !(sub.set_parent!(Project.find(3)))
166 end
167 end
167
168
168 def test_set_parent_should_add_roots_in_alphabetical_order
169 def test_set_parent_should_add_roots_in_alphabetical_order
169 ProjectCustomField.delete_all
170 ProjectCustomField.delete_all
170 Project.delete_all
171 Project.delete_all
171 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
172 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
172 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
173 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
173 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
174 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
174 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
175 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
175
176
176 assert_equal 4, Project.count
177 assert_equal 4, Project.count
177 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
178 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
178 end
179 end
179
180
180 def test_set_parent_should_add_children_in_alphabetical_order
181 def test_set_parent_should_add_children_in_alphabetical_order
181 ProjectCustomField.delete_all
182 ProjectCustomField.delete_all
182 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
183 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
183 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
184 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
184 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
185 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
185 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
186 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
186 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
187 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
187
188
188 parent.reload
189 parent.reload
189 assert_equal 4, parent.children.size
190 assert_equal 4, parent.children.size
190 assert_equal parent.children.sort_by(&:name), parent.children
191 assert_equal parent.children.sort_by(&:name), parent.children
191 end
192 end
192
193
193 def test_rebuild_should_sort_children_alphabetically
194 def test_rebuild_should_sort_children_alphabetically
194 ProjectCustomField.delete_all
195 ProjectCustomField.delete_all
195 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
196 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
196 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
197 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
197 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
198 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
198 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
199 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
199 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
200 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
200
201
201 Project.update_all("lft = NULL, rgt = NULL")
202 Project.update_all("lft = NULL, rgt = NULL")
202 Project.rebuild!
203 Project.rebuild!
203
204
204 parent.reload
205 parent.reload
205 assert_equal 4, parent.children.size
206 assert_equal 4, parent.children.size
206 assert_equal parent.children.sort_by(&:name), parent.children
207 assert_equal parent.children.sort_by(&:name), parent.children
207 end
208 end
208
209
209 def test_parent
210 def test_parent
210 p = Project.find(6).parent
211 p = Project.find(6).parent
211 assert p.is_a?(Project)
212 assert p.is_a?(Project)
212 assert_equal 5, p.id
213 assert_equal 5, p.id
213 end
214 end
214
215
215 def test_ancestors
216 def test_ancestors
216 a = Project.find(6).ancestors
217 a = Project.find(6).ancestors
217 assert a.first.is_a?(Project)
218 assert a.first.is_a?(Project)
218 assert_equal [1, 5], a.collect(&:id)
219 assert_equal [1, 5], a.collect(&:id)
219 end
220 end
220
221
221 def test_root
222 def test_root
222 r = Project.find(6).root
223 r = Project.find(6).root
223 assert r.is_a?(Project)
224 assert r.is_a?(Project)
224 assert_equal 1, r.id
225 assert_equal 1, r.id
225 end
226 end
226
227
227 def test_children
228 def test_children
228 c = Project.find(1).children
229 c = Project.find(1).children
229 assert c.first.is_a?(Project)
230 assert c.first.is_a?(Project)
230 assert_equal [5, 3, 4], c.collect(&:id)
231 assert_equal [5, 3, 4], c.collect(&:id)
231 end
232 end
232
233
233 def test_descendants
234 def test_descendants
234 d = Project.find(1).descendants
235 d = Project.find(1).descendants
235 assert d.first.is_a?(Project)
236 assert d.first.is_a?(Project)
236 assert_equal [5, 6, 3, 4], d.collect(&:id)
237 assert_equal [5, 6, 3, 4], d.collect(&:id)
237 end
238 end
238
239
240 def test_allowed_parents_should_be_empty_for_non_member_user
241 Role.non_member.add_permission!(:add_project)
242 user = User.find(9)
243 assert user.memberships.empty?
244 User.current = user
245 assert Project.new.allowed_parents.empty?
246 end
247
239 def test_users_by_role
248 def test_users_by_role
240 users_by_role = Project.find(1).users_by_role
249 users_by_role = Project.find(1).users_by_role
241 assert_kind_of Hash, users_by_role
250 assert_kind_of Hash, users_by_role
242 role = Role.find(1)
251 role = Role.find(1)
243 assert_kind_of Array, users_by_role[role]
252 assert_kind_of Array, users_by_role[role]
244 assert users_by_role[role].include?(User.find(2))
253 assert users_by_role[role].include?(User.find(2))
245 end
254 end
246
255
247 def test_rolled_up_trackers
256 def test_rolled_up_trackers
248 parent = Project.find(1)
257 parent = Project.find(1)
249 parent.trackers = Tracker.find([1,2])
258 parent.trackers = Tracker.find([1,2])
250 child = parent.children.find(3)
259 child = parent.children.find(3)
251
260
252 assert_equal [1, 2], parent.tracker_ids
261 assert_equal [1, 2], parent.tracker_ids
253 assert_equal [2, 3], child.trackers.collect(&:id)
262 assert_equal [2, 3], child.trackers.collect(&:id)
254
263
255 assert_kind_of Tracker, parent.rolled_up_trackers.first
264 assert_kind_of Tracker, parent.rolled_up_trackers.first
256 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
265 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
257
266
258 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
267 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
259 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
268 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
260 end
269 end
261
270
262 def test_rolled_up_trackers_should_ignore_archived_subprojects
271 def test_rolled_up_trackers_should_ignore_archived_subprojects
263 parent = Project.find(1)
272 parent = Project.find(1)
264 parent.trackers = Tracker.find([1,2])
273 parent.trackers = Tracker.find([1,2])
265 child = parent.children.find(3)
274 child = parent.children.find(3)
266 child.trackers = Tracker.find([1,3])
275 child.trackers = Tracker.find([1,3])
267 parent.children.each(&:archive)
276 parent.children.each(&:archive)
268
277
269 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
278 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
270 end
279 end
271
280
272 def test_next_identifier
281 def test_next_identifier
273 ProjectCustomField.delete_all
282 ProjectCustomField.delete_all
274 Project.create!(:name => 'last', :identifier => 'p2008040')
283 Project.create!(:name => 'last', :identifier => 'p2008040')
275 assert_equal 'p2008041', Project.next_identifier
284 assert_equal 'p2008041', Project.next_identifier
276 end
285 end
277
286
278 def test_next_identifier_first_project
287 def test_next_identifier_first_project
279 Project.delete_all
288 Project.delete_all
280 assert_nil Project.next_identifier
289 assert_nil Project.next_identifier
281 end
290 end
282
291
283
292
284 def test_enabled_module_names_should_not_recreate_enabled_modules
293 def test_enabled_module_names_should_not_recreate_enabled_modules
285 project = Project.find(1)
294 project = Project.find(1)
286 # Remove one module
295 # Remove one module
287 modules = project.enabled_modules.slice(0..-2)
296 modules = project.enabled_modules.slice(0..-2)
288 assert modules.any?
297 assert modules.any?
289 assert_difference 'EnabledModule.count', -1 do
298 assert_difference 'EnabledModule.count', -1 do
290 project.enabled_module_names = modules.collect(&:name)
299 project.enabled_module_names = modules.collect(&:name)
291 end
300 end
292 project.reload
301 project.reload
293 # Ids should be preserved
302 # Ids should be preserved
294 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
303 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
295 end
304 end
296
305
297 def test_copy_from_existing_project
306 def test_copy_from_existing_project
298 source_project = Project.find(1)
307 source_project = Project.find(1)
299 copied_project = Project.copy_from(1)
308 copied_project = Project.copy_from(1)
300
309
301 assert copied_project
310 assert copied_project
302 # Cleared attributes
311 # Cleared attributes
303 assert copied_project.id.blank?
312 assert copied_project.id.blank?
304 assert copied_project.name.blank?
313 assert copied_project.name.blank?
305 assert copied_project.identifier.blank?
314 assert copied_project.identifier.blank?
306
315
307 # Duplicated attributes
316 # Duplicated attributes
308 assert_equal source_project.description, copied_project.description
317 assert_equal source_project.description, copied_project.description
309 assert_equal source_project.enabled_modules, copied_project.enabled_modules
318 assert_equal source_project.enabled_modules, copied_project.enabled_modules
310 assert_equal source_project.trackers, copied_project.trackers
319 assert_equal source_project.trackers, copied_project.trackers
311
320
312 # Default attributes
321 # Default attributes
313 assert_equal 1, copied_project.status
322 assert_equal 1, copied_project.status
314 end
323 end
315
324
316 def test_activities_should_use_the_system_activities
325 def test_activities_should_use_the_system_activities
317 project = Project.find(1)
326 project = Project.find(1)
318 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
327 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
319 end
328 end
320
329
321
330
322 def test_activities_should_use_the_project_specific_activities
331 def test_activities_should_use_the_project_specific_activities
323 project = Project.find(1)
332 project = Project.find(1)
324 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
333 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
325 assert overridden_activity.save!
334 assert overridden_activity.save!
326
335
327 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
336 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
328 end
337 end
329
338
330 def test_activities_should_not_include_the_inactive_project_specific_activities
339 def test_activities_should_not_include_the_inactive_project_specific_activities
331 project = Project.find(1)
340 project = Project.find(1)
332 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
341 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
333 assert overridden_activity.save!
342 assert overridden_activity.save!
334
343
335 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
344 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
336 end
345 end
337
346
338 def test_activities_should_not_include_project_specific_activities_from_other_projects
347 def test_activities_should_not_include_project_specific_activities_from_other_projects
339 project = Project.find(1)
348 project = Project.find(1)
340 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
349 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
341 assert overridden_activity.save!
350 assert overridden_activity.save!
342
351
343 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
352 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
344 end
353 end
345
354
346 def test_activities_should_handle_nils
355 def test_activities_should_handle_nils
347 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
356 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
348 TimeEntryActivity.delete_all
357 TimeEntryActivity.delete_all
349
358
350 # No activities
359 # No activities
351 project = Project.find(1)
360 project = Project.find(1)
352 assert project.activities.empty?
361 assert project.activities.empty?
353
362
354 # No system, one overridden
363 # No system, one overridden
355 assert overridden_activity.save!
364 assert overridden_activity.save!
356 project.reload
365 project.reload
357 assert_equal [overridden_activity], project.activities
366 assert_equal [overridden_activity], project.activities
358 end
367 end
359
368
360 def test_activities_should_override_system_activities_with_project_activities
369 def test_activities_should_override_system_activities_with_project_activities
361 project = Project.find(1)
370 project = Project.find(1)
362 parent_activity = TimeEntryActivity.find(:first)
371 parent_activity = TimeEntryActivity.find(:first)
363 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
372 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
364 assert overridden_activity.save!
373 assert overridden_activity.save!
365
374
366 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
375 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
367 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
376 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
368 end
377 end
369
378
370 def test_activities_should_include_inactive_activities_if_specified
379 def test_activities_should_include_inactive_activities_if_specified
371 project = Project.find(1)
380 project = Project.find(1)
372 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
381 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
373 assert overridden_activity.save!
382 assert overridden_activity.save!
374
383
375 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
384 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
376 end
385 end
377
386
378 def test_close_completed_versions
387 def test_close_completed_versions
379 Version.update_all("status = 'open'")
388 Version.update_all("status = 'open'")
380 project = Project.find(1)
389 project = Project.find(1)
381 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
390 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
382 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
391 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
383 project.close_completed_versions
392 project.close_completed_versions
384 project.reload
393 project.reload
385 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
394 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
386 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
395 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
387 end
396 end
388
397
389 context "Project#copy" do
398 context "Project#copy" do
390 setup do
399 setup do
391 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
400 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
392 Project.destroy_all :identifier => "copy-test"
401 Project.destroy_all :identifier => "copy-test"
393 @source_project = Project.find(2)
402 @source_project = Project.find(2)
394 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
403 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
395 @project.trackers = @source_project.trackers
404 @project.trackers = @source_project.trackers
396 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
405 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
397 end
406 end
398
407
399 should "copy issues" do
408 should "copy issues" do
400 assert @project.valid?
409 assert @project.valid?
401 assert @project.issues.empty?
410 assert @project.issues.empty?
402 assert @project.copy(@source_project)
411 assert @project.copy(@source_project)
403
412
404 assert_equal @source_project.issues.size, @project.issues.size
413 assert_equal @source_project.issues.size, @project.issues.size
405 @project.issues.each do |issue|
414 @project.issues.each do |issue|
406 assert issue.valid?
415 assert issue.valid?
407 assert ! issue.assigned_to.blank?
416 assert ! issue.assigned_to.blank?
408 assert_equal @project, issue.project
417 assert_equal @project, issue.project
409 end
418 end
410 end
419 end
411
420
412 should "change the new issues to use the copied version" do
421 should "change the new issues to use the copied version" do
413 assigned_version = Version.generate!(:name => "Assigned Issues")
422 assigned_version = Version.generate!(:name => "Assigned Issues")
414 @source_project.versions << assigned_version
423 @source_project.versions << assigned_version
415 assert_equal 1, @source_project.versions.size
424 assert_equal 1, @source_project.versions.size
416 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
425 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
417 :subject => "change the new issues to use the copied version",
426 :subject => "change the new issues to use the copied version",
418 :tracker_id => 1,
427 :tracker_id => 1,
419 :project_id => @source_project.id)
428 :project_id => @source_project.id)
420
429
421 assert @project.copy(@source_project)
430 assert @project.copy(@source_project)
422 @project.reload
431 @project.reload
423 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
432 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
424
433
425 assert copied_issue
434 assert copied_issue
426 assert copied_issue.fixed_version
435 assert copied_issue.fixed_version
427 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
436 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
428 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
437 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
429 end
438 end
430
439
431 should "copy members" do
440 should "copy members" do
432 assert @project.valid?
441 assert @project.valid?
433 assert @project.members.empty?
442 assert @project.members.empty?
434 assert @project.copy(@source_project)
443 assert @project.copy(@source_project)
435
444
436 assert_equal @source_project.members.size, @project.members.size
445 assert_equal @source_project.members.size, @project.members.size
437 @project.members.each do |member|
446 @project.members.each do |member|
438 assert member
447 assert member
439 assert_equal @project, member.project
448 assert_equal @project, member.project
440 end
449 end
441 end
450 end
442
451
443 should "copy project specific queries" do
452 should "copy project specific queries" do
444 assert @project.valid?
453 assert @project.valid?
445 assert @project.queries.empty?
454 assert @project.queries.empty?
446 assert @project.copy(@source_project)
455 assert @project.copy(@source_project)
447
456
448 assert_equal @source_project.queries.size, @project.queries.size
457 assert_equal @source_project.queries.size, @project.queries.size
449 @project.queries.each do |query|
458 @project.queries.each do |query|
450 assert query
459 assert query
451 assert_equal @project, query.project
460 assert_equal @project, query.project
452 end
461 end
453 end
462 end
454
463
455 should "copy versions" do
464 should "copy versions" do
456 @source_project.versions << Version.generate!
465 @source_project.versions << Version.generate!
457 @source_project.versions << Version.generate!
466 @source_project.versions << Version.generate!
458
467
459 assert @project.versions.empty?
468 assert @project.versions.empty?
460 assert @project.copy(@source_project)
469 assert @project.copy(@source_project)
461
470
462 assert_equal @source_project.versions.size, @project.versions.size
471 assert_equal @source_project.versions.size, @project.versions.size
463 @project.versions.each do |version|
472 @project.versions.each do |version|
464 assert version
473 assert version
465 assert_equal @project, version.project
474 assert_equal @project, version.project
466 end
475 end
467 end
476 end
468
477
469 should "copy wiki" do
478 should "copy wiki" do
470 assert_difference 'Wiki.count' do
479 assert_difference 'Wiki.count' do
471 assert @project.copy(@source_project)
480 assert @project.copy(@source_project)
472 end
481 end
473
482
474 assert @project.wiki
483 assert @project.wiki
475 assert_not_equal @source_project.wiki, @project.wiki
484 assert_not_equal @source_project.wiki, @project.wiki
476 assert_equal "Start page", @project.wiki.start_page
485 assert_equal "Start page", @project.wiki.start_page
477 end
486 end
478
487
479 should "copy wiki pages and content" do
488 should "copy wiki pages and content" do
480 assert @project.copy(@source_project)
489 assert @project.copy(@source_project)
481
490
482 assert @project.wiki
491 assert @project.wiki
483 assert_equal 1, @project.wiki.pages.length
492 assert_equal 1, @project.wiki.pages.length
484
493
485 @project.wiki.pages.each do |wiki_page|
494 @project.wiki.pages.each do |wiki_page|
486 assert wiki_page.content
495 assert wiki_page.content
487 assert !@source_project.wiki.pages.include?(wiki_page)
496 assert !@source_project.wiki.pages.include?(wiki_page)
488 end
497 end
489 end
498 end
490
499
491 should "copy custom fields"
500 should "copy custom fields"
492
501
493 should "copy issue categories" do
502 should "copy issue categories" do
494 assert @project.copy(@source_project)
503 assert @project.copy(@source_project)
495
504
496 assert_equal 2, @project.issue_categories.size
505 assert_equal 2, @project.issue_categories.size
497 @project.issue_categories.each do |issue_category|
506 @project.issue_categories.each do |issue_category|
498 assert !@source_project.issue_categories.include?(issue_category)
507 assert !@source_project.issue_categories.include?(issue_category)
499 end
508 end
500 end
509 end
501
510
502 should "copy boards" do
511 should "copy boards" do
503 assert @project.copy(@source_project)
512 assert @project.copy(@source_project)
504
513
505 assert_equal 1, @project.boards.size
514 assert_equal 1, @project.boards.size
506 @project.boards.each do |board|
515 @project.boards.each do |board|
507 assert !@source_project.boards.include?(board)
516 assert !@source_project.boards.include?(board)
508 end
517 end
509 end
518 end
510
519
511 should "change the new issues to use the copied issue categories" do
520 should "change the new issues to use the copied issue categories" do
512 issue = Issue.find(4)
521 issue = Issue.find(4)
513 issue.update_attribute(:category_id, 3)
522 issue.update_attribute(:category_id, 3)
514
523
515 assert @project.copy(@source_project)
524 assert @project.copy(@source_project)
516
525
517 @project.issues.each do |issue|
526 @project.issues.each do |issue|
518 assert issue.category
527 assert issue.category
519 assert_equal "Stock management", issue.category.name # Same name
528 assert_equal "Stock management", issue.category.name # Same name
520 assert_not_equal IssueCategory.find(3), issue.category # Different record
529 assert_not_equal IssueCategory.find(3), issue.category # Different record
521 end
530 end
522 end
531 end
523
532
524 should "limit copy with :only option" do
533 should "limit copy with :only option" do
525 assert @project.members.empty?
534 assert @project.members.empty?
526 assert @project.issue_categories.empty?
535 assert @project.issue_categories.empty?
527 assert @source_project.issues.any?
536 assert @source_project.issues.any?
528
537
529 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
538 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
530
539
531 assert @project.members.any?
540 assert @project.members.any?
532 assert @project.issue_categories.any?
541 assert @project.issue_categories.any?
533 assert @project.issues.empty?
542 assert @project.issues.empty?
534 end
543 end
535
544
536 should "copy issue relations"
545 should "copy issue relations"
537 should "link issue relations if cross project issue relations are valid"
546 should "link issue relations if cross project issue relations are valid"
538
547
539 end
548 end
540
549
541 end
550 end
General Comments 0
You need to be logged in to leave comments. Login now