##// END OF EJS Templates
Added a Activities tab to Project Settings...
Eric Davis -
r2835:5833ba9f81f0
parent child
Show More
@@ -0,0 +1,37
1 <% form_tag({:controller => 'projects', :action => 'save_activities', :id => @project}, :class => "tabular") do %>
2
3 <table class="list">
4 <tr>
5 <th><%= l(:field_name) %></th>
6 <th><%= l(:enumeration_system_activity) %></th>
7 <% TimeEntryActivity.new.available_custom_fields.each do |value| %>
8 <th><%= h value.name %></th>
9 <% end %>
10 <th style="width:15%;"><%= l(:field_active) %></th>
11 </tr>
12
13 <% @project.activities(true).each do |enumeration| %>
14 <% fields_for "enumerations[#{enumeration.id}]", enumeration do |ff| %>
15 <tr class="<%= cycle('odd', 'even') %>">
16 <td>
17 <%= ff.hidden_field :parent_id, :value => enumeration.id unless enumeration.project %>
18 <%= h(enumeration) %>
19 </td>
20 <td align="center" style="width:15%;"><%= image_tag('true.png') unless enumeration.project %></td>
21 <% enumeration.custom_field_values.each do |value| %>
22 <td align="center">
23 <%= custom_field_tag "enumerations[#{enumeration.id}]", value %>
24 </td>
25 <% end %>
26 <td align="center" style="width:15%;">
27 <%= ff.check_box :active %>
28 </td>
29 </tr>
30 <% end %>
31 <% end %>
32 </table>
33
34 <%= submit_tag l(:button_save) %>
35 <% end %>
36 <%= button_to l(:button_reset), {:controller => 'projects', :action => 'reset_activities', :id => @project}, :method => :delete, :confirm => l(:text_are_you_sure), :style => "position: relative; top: -20px; left: 60px;" %>
37
@@ -1,326 +1,344
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 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_parent!(params[:project]['parent_id']) if User.current.admin? && 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 if request.get?
97 if request.get?
98 @project = Project.copy_from(params[:id])
98 @project = Project.copy_from(params[:id])
99 if @project
99 if @project
100 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
100 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
101 else
101 else
102 redirect_to :controller => 'admin', :action => 'projects'
102 redirect_to :controller => 'admin', :action => 'projects'
103 end
103 end
104 else
104 else
105 @project = Project.new(params[:project])
105 @project = Project.new(params[:project])
106 @project.enabled_module_names = params[:enabled_modules]
106 @project.enabled_module_names = params[:enabled_modules]
107 if @project.copy(params[:id])
107 if @project.copy(params[:id])
108 flash[:notice] = l(:notice_successful_create)
108 flash[:notice] = l(:notice_successful_create)
109 redirect_to :controller => 'admin', :action => 'projects'
109 redirect_to :controller => 'admin', :action => 'projects'
110 end
110 end
111 end
111 end
112 end
112 end
113
113
114
114
115 # Show @project
115 # Show @project
116 def show
116 def show
117 if params[:jump]
117 if params[:jump]
118 # try to redirect to the requested menu item
118 # try to redirect to the requested menu item
119 redirect_to_project_menu_item(@project, params[:jump]) && return
119 redirect_to_project_menu_item(@project, params[:jump]) && return
120 end
120 end
121
121
122 @users_by_role = @project.users_by_role
122 @users_by_role = @project.users_by_role
123 @subprojects = @project.children.visible
123 @subprojects = @project.children.visible
124 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
124 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
125 @trackers = @project.rolled_up_trackers
125 @trackers = @project.rolled_up_trackers
126
126
127 cond = @project.project_condition(Setting.display_subprojects_issues?)
127 cond = @project.project_condition(Setting.display_subprojects_issues?)
128
128
129 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
129 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
130 :include => [:project, :status, :tracker],
130 :include => [:project, :status, :tracker],
131 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
131 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
132 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
132 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
133 :include => [:project, :status, :tracker],
133 :include => [:project, :status, :tracker],
134 :conditions => cond)
134 :conditions => cond)
135
135
136 TimeEntry.visible_by(User.current) do
136 TimeEntry.visible_by(User.current) do
137 @total_hours = TimeEntry.sum(:hours,
137 @total_hours = TimeEntry.sum(:hours,
138 :include => :project,
138 :include => :project,
139 :conditions => cond).to_f
139 :conditions => cond).to_f
140 end
140 end
141 @key = User.current.rss_key
141 @key = User.current.rss_key
142 end
142 end
143
143
144 def settings
144 def settings
145 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
145 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
146 @issue_category ||= IssueCategory.new
146 @issue_category ||= IssueCategory.new
147 @member ||= @project.members.new
147 @member ||= @project.members.new
148 @trackers = Tracker.all
148 @trackers = Tracker.all
149 @repository ||= @project.repository
149 @repository ||= @project.repository
150 @wiki ||= @project.wiki
150 @wiki ||= @project.wiki
151 end
151 end
152
152
153 # Edit @project
153 # Edit @project
154 def edit
154 def edit
155 if request.post?
155 if request.post?
156 @project.attributes = params[:project]
156 @project.attributes = params[:project]
157 if @project.save
157 if @project.save
158 @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
158 @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
159 flash[:notice] = l(:notice_successful_update)
159 flash[:notice] = l(:notice_successful_update)
160 redirect_to :action => 'settings', :id => @project
160 redirect_to :action => 'settings', :id => @project
161 else
161 else
162 settings
162 settings
163 render :action => 'settings'
163 render :action => 'settings'
164 end
164 end
165 end
165 end
166 end
166 end
167
167
168 def modules
168 def modules
169 @project.enabled_module_names = params[:enabled_modules]
169 @project.enabled_module_names = params[:enabled_modules]
170 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
170 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
171 end
171 end
172
172
173 def archive
173 def archive
174 @project.archive if request.post? && @project.active?
174 @project.archive if request.post? && @project.active?
175 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
175 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
176 end
176 end
177
177
178 def unarchive
178 def unarchive
179 @project.unarchive if request.post? && !@project.active?
179 @project.unarchive if request.post? && !@project.active?
180 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
180 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
181 end
181 end
182
182
183 # Delete @project
183 # Delete @project
184 def destroy
184 def destroy
185 @project_to_destroy = @project
185 @project_to_destroy = @project
186 if request.post? and params[:confirm]
186 if request.post? and params[:confirm]
187 @project_to_destroy.destroy
187 @project_to_destroy.destroy
188 redirect_to :controller => 'admin', :action => 'projects'
188 redirect_to :controller => 'admin', :action => 'projects'
189 end
189 end
190 # hide project in layout
190 # hide project in layout
191 @project = nil
191 @project = nil
192 end
192 end
193
193
194 # Add a new issue category to @project
194 # Add a new issue category to @project
195 def add_issue_category
195 def add_issue_category
196 @category = @project.issue_categories.build(params[:category])
196 @category = @project.issue_categories.build(params[:category])
197 if request.post? and @category.save
197 if request.post? and @category.save
198 respond_to do |format|
198 respond_to do |format|
199 format.html do
199 format.html do
200 flash[:notice] = l(:notice_successful_create)
200 flash[:notice] = l(:notice_successful_create)
201 redirect_to :action => 'settings', :tab => 'categories', :id => @project
201 redirect_to :action => 'settings', :tab => 'categories', :id => @project
202 end
202 end
203 format.js do
203 format.js do
204 # IE doesn't support the replace_html rjs method for select box options
204 # IE doesn't support the replace_html rjs method for select box options
205 render(:update) {|page| page.replace "issue_category_id",
205 render(:update) {|page| page.replace "issue_category_id",
206 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]')
206 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]')
207 }
207 }
208 end
208 end
209 end
209 end
210 end
210 end
211 end
211 end
212
212
213 # Add a new version to @project
213 # Add a new version to @project
214 def add_version
214 def add_version
215 @version = @project.versions.build(params[:version])
215 @version = @project.versions.build(params[:version])
216 if request.post? and @version.save
216 if request.post? and @version.save
217 flash[:notice] = l(:notice_successful_create)
217 flash[:notice] = l(:notice_successful_create)
218 redirect_to :action => 'settings', :tab => 'versions', :id => @project
218 redirect_to :action => 'settings', :tab => 'versions', :id => @project
219 end
219 end
220 end
220 end
221
221
222 def add_file
222 def add_file
223 if request.post?
223 if request.post?
224 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
224 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
225 attachments = attach_files(container, params[:attachments])
225 attachments = attach_files(container, params[:attachments])
226 if !attachments.empty? && Setting.notified_events.include?('file_added')
226 if !attachments.empty? && Setting.notified_events.include?('file_added')
227 Mailer.deliver_attachments_added(attachments)
227 Mailer.deliver_attachments_added(attachments)
228 end
228 end
229 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
229 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
230 return
230 return
231 end
231 end
232 @versions = @project.versions.sort
232 @versions = @project.versions.sort
233 end
233 end
234
234
235 def save_activities
236 if request.post? && params[:enumerations]
237 params[:enumerations].each do |id, activity|
238 @project.update_or_build_time_entry_activity(id, activity)
239 end
240 @project.save
241 end
242
243 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
244 end
245
246 def reset_activities
247 @project.time_entry_activities.each do |time_entry_activity|
248 time_entry_activity.destroy
249 end
250 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
251 end
252
235 def list_files
253 def list_files
236 sort_init 'filename', 'asc'
254 sort_init 'filename', 'asc'
237 sort_update 'filename' => "#{Attachment.table_name}.filename",
255 sort_update 'filename' => "#{Attachment.table_name}.filename",
238 'created_on' => "#{Attachment.table_name}.created_on",
256 'created_on' => "#{Attachment.table_name}.created_on",
239 'size' => "#{Attachment.table_name}.filesize",
257 'size' => "#{Attachment.table_name}.filesize",
240 'downloads' => "#{Attachment.table_name}.downloads"
258 'downloads' => "#{Attachment.table_name}.downloads"
241
259
242 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
260 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
243 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
261 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
244 render :layout => !request.xhr?
262 render :layout => !request.xhr?
245 end
263 end
246
264
247 # Show changelog for @project
265 # Show changelog for @project
248 def changelog
266 def changelog
249 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
267 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
250 retrieve_selected_tracker_ids(@trackers)
268 retrieve_selected_tracker_ids(@trackers)
251 @versions = @project.versions.sort
269 @versions = @project.versions.sort
252 end
270 end
253
271
254 def roadmap
272 def roadmap
255 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
273 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
256 retrieve_selected_tracker_ids(@trackers)
274 retrieve_selected_tracker_ids(@trackers)
257 @versions = @project.versions.sort
275 @versions = @project.versions.sort
258 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
276 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
259 end
277 end
260
278
261 def activity
279 def activity
262 @days = Setting.activity_days_default.to_i
280 @days = Setting.activity_days_default.to_i
263
281
264 if params[:from]
282 if params[:from]
265 begin; @date_to = params[:from].to_date + 1; rescue; end
283 begin; @date_to = params[:from].to_date + 1; rescue; end
266 end
284 end
267
285
268 @date_to ||= Date.today + 1
286 @date_to ||= Date.today + 1
269 @date_from = @date_to - @days
287 @date_from = @date_to - @days
270 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
288 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
271 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
289 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
272
290
273 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
291 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
274 :with_subprojects => @with_subprojects,
292 :with_subprojects => @with_subprojects,
275 :author => @author)
293 :author => @author)
276 @activity.scope_select {|t| !params["show_#{t}"].nil?}
294 @activity.scope_select {|t| !params["show_#{t}"].nil?}
277 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
295 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
278
296
279 events = @activity.events(@date_from, @date_to)
297 events = @activity.events(@date_from, @date_to)
280
298
281 respond_to do |format|
299 respond_to do |format|
282 format.html {
300 format.html {
283 @events_by_day = events.group_by(&:event_date)
301 @events_by_day = events.group_by(&:event_date)
284 render :layout => false if request.xhr?
302 render :layout => false if request.xhr?
285 }
303 }
286 format.atom {
304 format.atom {
287 title = l(:label_activity)
305 title = l(:label_activity)
288 if @author
306 if @author
289 title = @author.name
307 title = @author.name
290 elsif @activity.scope.size == 1
308 elsif @activity.scope.size == 1
291 title = l("label_#{@activity.scope.first.singularize}_plural")
309 title = l("label_#{@activity.scope.first.singularize}_plural")
292 end
310 end
293 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
311 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
294 }
312 }
295 end
313 end
296
314
297 rescue ActiveRecord::RecordNotFound
315 rescue ActiveRecord::RecordNotFound
298 render_404
316 render_404
299 end
317 end
300
318
301 private
319 private
302 # Find project of id params[:id]
320 # Find project of id params[:id]
303 # if not found, redirect to project list
321 # if not found, redirect to project list
304 # Used as a before_filter
322 # Used as a before_filter
305 def find_project
323 def find_project
306 @project = Project.find(params[:id])
324 @project = Project.find(params[:id])
307 rescue ActiveRecord::RecordNotFound
325 rescue ActiveRecord::RecordNotFound
308 render_404
326 render_404
309 end
327 end
310
328
311 def find_optional_project
329 def find_optional_project
312 return true unless params[:id]
330 return true unless params[:id]
313 @project = Project.find(params[:id])
331 @project = Project.find(params[:id])
314 authorize
332 authorize
315 rescue ActiveRecord::RecordNotFound
333 rescue ActiveRecord::RecordNotFound
316 render_404
334 render_404
317 end
335 end
318
336
319 def retrieve_selected_tracker_ids(selectable_trackers)
337 def retrieve_selected_tracker_ids(selectable_trackers)
320 if ids = params[:tracker_ids]
338 if ids = params[:tracker_ids]
321 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
339 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
322 else
340 else
323 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
341 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
324 end
342 end
325 end
343 end
326 end
344 end
@@ -1,71 +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 ]
34 ]
34 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
35 tabs.select {|tab| User.current.allowed_to?(tab[:action], @project)}
35 end
36 end
36
37
37 def parent_project_select_tag(project)
38 def parent_project_select_tag(project)
38 options = '<option></option>' + project_tree_options_for_select(project.possible_parents, :selected => project.parent)
39 options = '<option></option>' + project_tree_options_for_select(project.possible_parents, :selected => project.parent)
39 content_tag('select', options, :name => 'project[parent_id]')
40 content_tag('select', options, :name => 'project[parent_id]')
40 end
41 end
41
42
42 # 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
43 # 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
44 # (eg. some intermediate nodes are private and can not be seen)
45 # (eg. some intermediate nodes are private and can not be seen)
45 def render_project_hierarchy(projects)
46 def render_project_hierarchy(projects)
46 s = ''
47 s = ''
47 if projects.any?
48 if projects.any?
48 ancestors = []
49 ancestors = []
49 projects.each do |project|
50 projects.each do |project|
50 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
51 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
51 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
52 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
52 else
53 else
53 ancestors.pop
54 ancestors.pop
54 s << "</li>"
55 s << "</li>"
55 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
56 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
56 ancestors.pop
57 ancestors.pop
57 s << "</ul></li>\n"
58 s << "</ul></li>\n"
58 end
59 end
59 end
60 end
60 classes = (ancestors.empty? ? 'root' : 'child')
61 classes = (ancestors.empty? ? 'root' : 'child')
61 s << "<li class='#{classes}'><div class='#{classes}'>" +
62 s << "<li class='#{classes}'><div class='#{classes}'>" +
62 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}")
63 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?
64 s << "</div>\n"
65 s << "</div>\n"
65 ancestors << project
66 ancestors << project
66 end
67 end
67 s << ("</li></ul>\n" * ancestors.size)
68 s << ("</li></ul>\n" * ancestors.size)
68 end
69 end
69 s
70 s
70 end
71 end
71 end
72 end
@@ -1,148 +1,174
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 Enumeration < ActiveRecord::Base
18 class Enumeration < ActiveRecord::Base
19 belongs_to :project
19 belongs_to :project
20
20
21 acts_as_list :scope => 'type = \'#{type}\''
21 acts_as_list :scope => 'type = \'#{type}\''
22 acts_as_customizable
22 acts_as_customizable
23 acts_as_tree :order => 'position ASC'
23 acts_as_tree :order => 'position ASC'
24
24
25 before_destroy :check_integrity
25 before_destroy :check_integrity
26
26
27 validates_presence_of :name
27 validates_presence_of :name
28 validates_uniqueness_of :name, :scope => [:type]
28 validates_uniqueness_of :name, :scope => [:type, :project_id]
29 validates_length_of :name, :maximum => 30
29 validates_length_of :name, :maximum => 30
30
30
31 # Backwards compatiblity named_scopes.
31 # Backwards compatiblity named_scopes.
32 # Can be removed post-0.9
32 # Can be removed post-0.9
33 named_scope :priorities, :conditions => { :type => "IssuePriority" }, :order => 'position' do
33 named_scope :priorities, :conditions => { :type => "IssuePriority" }, :order => 'position' do
34 ActiveSupport::Deprecation.warn("Enumeration#priorities is deprecated, use the IssuePriority class. (#{Redmine::Info.issue(3007)})")
34 ActiveSupport::Deprecation.warn("Enumeration#priorities is deprecated, use the IssuePriority class. (#{Redmine::Info.issue(3007)})")
35 def default
35 def default
36 find(:first, :conditions => { :is_default => true })
36 find(:first, :conditions => { :is_default => true })
37 end
37 end
38 end
38 end
39
39
40 named_scope :document_categories, :conditions => { :type => "DocumentCategory" }, :order => 'position' do
40 named_scope :document_categories, :conditions => { :type => "DocumentCategory" }, :order => 'position' do
41 ActiveSupport::Deprecation.warn("Enumeration#document_categories is deprecated, use the DocumentCategories class. (#{Redmine::Info.issue(3007)})")
41 ActiveSupport::Deprecation.warn("Enumeration#document_categories is deprecated, use the DocumentCategories class. (#{Redmine::Info.issue(3007)})")
42 def default
42 def default
43 find(:first, :conditions => { :is_default => true })
43 find(:first, :conditions => { :is_default => true })
44 end
44 end
45 end
45 end
46
46
47 named_scope :activities, :conditions => { :type => "TimeEntryActivity" }, :order => 'position' do
47 named_scope :activities, :conditions => { :type => "TimeEntryActivity" }, :order => 'position' do
48 ActiveSupport::Deprecation.warn("Enumeration#activities is deprecated, use the TimeEntryActivity class. (#{Redmine::Info.issue(3007)})")
48 ActiveSupport::Deprecation.warn("Enumeration#activities is deprecated, use the TimeEntryActivity class. (#{Redmine::Info.issue(3007)})")
49 def default
49 def default
50 find(:first, :conditions => { :is_default => true })
50 find(:first, :conditions => { :is_default => true })
51 end
51 end
52 end
52 end
53
53
54 named_scope :values, lambda {|type| { :conditions => { :type => type }, :order => 'position' } } do
54 named_scope :values, lambda {|type| { :conditions => { :type => type }, :order => 'position' } } do
55 def default
55 def default
56 find(:first, :conditions => { :is_default => true })
56 find(:first, :conditions => { :is_default => true })
57 end
57 end
58 end
58 end
59 # End backwards compatiblity named_scopes
59 # End backwards compatiblity named_scopes
60
60
61 named_scope :all, :order => 'position'
61 named_scope :all, :order => 'position', :conditions => { :project_id => nil }
62
62
63 named_scope :active, lambda {
63 named_scope :active, lambda {
64 {
64 {
65 :conditions => {:active => true, :project_id => nil},
65 :conditions => {:active => true, :project_id => nil},
66 :order => 'position'
66 :order => 'position'
67 }
67 }
68 }
68 }
69
69
70 def self.default
70 def self.default
71 # Creates a fake default scope so Enumeration.default will check
71 # Creates a fake default scope so Enumeration.default will check
72 # it's type. STI subclasses will automatically add their own
72 # it's type. STI subclasses will automatically add their own
73 # types to the finder.
73 # types to the finder.
74 if self.descends_from_active_record?
74 if self.descends_from_active_record?
75 find(:first, :conditions => { :is_default => true, :type => 'Enumeration' })
75 find(:first, :conditions => { :is_default => true, :type => 'Enumeration' })
76 else
76 else
77 # STI classes are
77 # STI classes are
78 find(:first, :conditions => { :is_default => true })
78 find(:first, :conditions => { :is_default => true })
79 end
79 end
80 end
80 end
81
81
82 # Overloaded on concrete classes
82 # Overloaded on concrete classes
83 def option_name
83 def option_name
84 nil
84 nil
85 end
85 end
86
86
87 # Backwards compatiblity. Can be removed post-0.9
87 # Backwards compatiblity. Can be removed post-0.9
88 def opt
88 def opt
89 ActiveSupport::Deprecation.warn("Enumeration#opt is deprecated, use the STI classes now. (#{Redmine::Info.issue(3007)})")
89 ActiveSupport::Deprecation.warn("Enumeration#opt is deprecated, use the STI classes now. (#{Redmine::Info.issue(3007)})")
90 return OptName
90 return OptName
91 end
91 end
92
92
93 def before_save
93 def before_save
94 if is_default? && is_default_changed?
94 if is_default? && is_default_changed?
95 Enumeration.update_all("is_default = #{connection.quoted_false}", {:type => type})
95 Enumeration.update_all("is_default = #{connection.quoted_false}", {:type => type})
96 end
96 end
97 end
97 end
98
98
99 # Overloaded on concrete classes
99 # Overloaded on concrete classes
100 def objects_count
100 def objects_count
101 0
101 0
102 end
102 end
103
103
104 def in_use?
104 def in_use?
105 self.objects_count != 0
105 self.objects_count != 0
106 end
106 end
107
107
108 # Is this enumeration overiding a system level enumeration?
108 # Is this enumeration overiding a system level enumeration?
109 def is_override?
109 def is_override?
110 !self.parent.nil?
110 !self.parent.nil?
111 end
111 end
112
112
113 alias :destroy_without_reassign :destroy
113 alias :destroy_without_reassign :destroy
114
114
115 # Destroy the enumeration
115 # Destroy the enumeration
116 # If a enumeration is specified, objects are reassigned
116 # If a enumeration is specified, objects are reassigned
117 def destroy(reassign_to = nil)
117 def destroy(reassign_to = nil)
118 if reassign_to && reassign_to.is_a?(Enumeration)
118 if reassign_to && reassign_to.is_a?(Enumeration)
119 self.transfer_relations(reassign_to)
119 self.transfer_relations(reassign_to)
120 end
120 end
121 destroy_without_reassign
121 destroy_without_reassign
122 end
122 end
123
123
124 def <=>(enumeration)
124 def <=>(enumeration)
125 position <=> enumeration.position
125 position <=> enumeration.position
126 end
126 end
127
127
128 def to_s; name end
128 def to_s; name end
129
129
130 # Returns the Subclasses of Enumeration. Each Subclass needs to be
130 # Returns the Subclasses of Enumeration. Each Subclass needs to be
131 # required in development mode.
131 # required in development mode.
132 #
132 #
133 # Note: subclasses is protected in ActiveRecord
133 # Note: subclasses is protected in ActiveRecord
134 def self.get_subclasses
134 def self.get_subclasses
135 @@subclasses[Enumeration]
135 @@subclasses[Enumeration]
136 end
136 end
137
137
138 # Does the +new+ Hash override the previous Enumeration?
139 def self.overridding_change?(new, previous)
140 if (same_active_state?(new['active'], previous.active)) && same_custom_values?(new,previous)
141 return false
142 else
143 return true
144 end
145 end
146
147 # Does the +new+ Hash have the same custom values as the previous Enumeration?
148 def self.same_custom_values?(new, previous)
149 previous.custom_field_values.each do |custom_value|
150 if custom_value.value != new["custom_field_values"][custom_value.custom_field_id.to_s]
151 return false
152 end
153 end
154
155 return true
156 end
157
158 # Are the new and previous fields equal?
159 def self.same_active_state?(new, previous)
160 new = (new == "1" ? true : false)
161 return new == previous
162 end
163
138 private
164 private
139 def check_integrity
165 def check_integrity
140 raise "Can't delete enumeration" if self.in_use?
166 raise "Can't delete enumeration" if self.in_use?
141 end
167 end
142
168
143 end
169 end
144
170
145 # Force load the subclasses in development mode
171 # Force load the subclasses in development mode
146 require_dependency 'time_entry_activity'
172 require_dependency 'time_entry_activity'
147 require_dependency 'document_category'
173 require_dependency 'document_category'
148 require_dependency 'issue_priority'
174 require_dependency 'issue_priority'
@@ -1,469 +1,527
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 has_many :time_entry_activities, :conditions => {:active => true } # Specific overidden Activities
23 # Specific overidden Activities
24 has_many :time_entry_activities do
25 def active
26 find(:all, :conditions => {:active => true})
27 end
28 end
24 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}"
25 has_many :member_principals, :class_name => 'Member',
30 has_many :member_principals, :class_name => 'Member',
26 :include => :principal,
31 :include => :principal,
27 :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})"
28 has_many :users, :through => :members
33 has_many :users, :through => :members
29 has_many :principals, :through => :member_principals, :source => :principal
34 has_many :principals, :through => :member_principals, :source => :principal
30
35
31 has_many :enabled_modules, :dependent => :delete_all
36 has_many :enabled_modules, :dependent => :delete_all
32 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
37 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
33 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]
34 has_many :issue_changes, :through => :issues, :source => :journals
39 has_many :issue_changes, :through => :issues, :source => :journals
35 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"
36 has_many :time_entries, :dependent => :delete_all
41 has_many :time_entries, :dependent => :delete_all
37 has_many :queries, :dependent => :delete_all
42 has_many :queries, :dependent => :delete_all
38 has_many :documents, :dependent => :destroy
43 has_many :documents, :dependent => :destroy
39 has_many :news, :dependent => :delete_all, :include => :author
44 has_many :news, :dependent => :delete_all, :include => :author
40 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"
41 has_many :boards, :dependent => :destroy, :order => "position ASC"
46 has_many :boards, :dependent => :destroy, :order => "position ASC"
42 has_one :repository, :dependent => :destroy
47 has_one :repository, :dependent => :destroy
43 has_many :changesets, :through => :repository
48 has_many :changesets, :through => :repository
44 has_one :wiki, :dependent => :destroy
49 has_one :wiki, :dependent => :destroy
45 # Custom field for the project issues
50 # Custom field for the project issues
46 has_and_belongs_to_many :issue_custom_fields,
51 has_and_belongs_to_many :issue_custom_fields,
47 :class_name => 'IssueCustomField',
52 :class_name => 'IssueCustomField',
48 :order => "#{CustomField.table_name}.position",
53 :order => "#{CustomField.table_name}.position",
49 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
54 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
50 :association_foreign_key => 'custom_field_id'
55 :association_foreign_key => 'custom_field_id'
51
56
52 acts_as_nested_set :order => 'name', :dependent => :destroy
57 acts_as_nested_set :order => 'name', :dependent => :destroy
53 acts_as_attachable :view_permission => :view_files,
58 acts_as_attachable :view_permission => :view_files,
54 :delete_permission => :manage_files
59 :delete_permission => :manage_files
55
60
56 acts_as_customizable
61 acts_as_customizable
57 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
62 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
58 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}"},
59 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
64 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
60 :author => nil
65 :author => nil
61
66
62 attr_protected :status, :enabled_module_names
67 attr_protected :status, :enabled_module_names
63
68
64 validates_presence_of :name, :identifier
69 validates_presence_of :name, :identifier
65 validates_uniqueness_of :name, :identifier
70 validates_uniqueness_of :name, :identifier
66 validates_associated :repository, :wiki
71 validates_associated :repository, :wiki
67 validates_length_of :name, :maximum => 30
72 validates_length_of :name, :maximum => 30
68 validates_length_of :homepage, :maximum => 255
73 validates_length_of :homepage, :maximum => 255
69 validates_length_of :identifier, :in => 1..20
74 validates_length_of :identifier, :in => 1..20
70 # donwcase letters, digits, dashes but not digits only
75 # donwcase letters, digits, dashes but not digits only
71 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? }
72 # reserved words
77 # reserved words
73 validates_exclusion_of :identifier, :in => %w( new )
78 validates_exclusion_of :identifier, :in => %w( new )
74
79
75 before_destroy :delete_all_members
80 before_destroy :delete_all_members
76
81
77 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] } }
78 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
83 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
79 named_scope :all_public, { :conditions => { :is_public => true } }
84 named_scope :all_public, { :conditions => { :is_public => true } }
80 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
85 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
81
86
82 def identifier=(identifier)
87 def identifier=(identifier)
83 super unless identifier_frozen?
88 super unless identifier_frozen?
84 end
89 end
85
90
86 def identifier_frozen?
91 def identifier_frozen?
87 errors[:identifier].nil? && !(new_record? || identifier.blank?)
92 errors[:identifier].nil? && !(new_record? || identifier.blank?)
88 end
93 end
89
94
90 def issues_with_subprojects(include_subprojects=false)
95 def issues_with_subprojects(include_subprojects=false)
91 conditions = nil
96 conditions = nil
92 if include_subprojects
97 if include_subprojects
93 ids = [id] + descendants.collect(&:id)
98 ids = [id] + descendants.collect(&:id)
94 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}"]
95 end
100 end
96 conditions ||= ["#{Project.table_name}.id = ?", id]
101 conditions ||= ["#{Project.table_name}.id = ?", id]
97 # Quick and dirty fix for Rails 2 compatibility
102 # Quick and dirty fix for Rails 2 compatibility
98 Issue.send(:with_scope, :find => { :conditions => conditions }) do
103 Issue.send(:with_scope, :find => { :conditions => conditions }) do
99 Version.send(:with_scope, :find => { :conditions => conditions }) do
104 Version.send(:with_scope, :find => { :conditions => conditions }) do
100 yield
105 yield
101 end
106 end
102 end
107 end
103 end
108 end
104
109
105 # returns latest created projects
110 # returns latest created projects
106 # 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
107 def self.latest(user=nil, count=5)
112 def self.latest(user=nil, count=5)
108 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")
109 end
114 end
110
115
111 # 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.
112 #
117 #
113 # Examples:
118 # Examples:
114 # Projects.visible_by(admin) => "projects.status = 1"
119 # Projects.visible_by(admin) => "projects.status = 1"
115 # 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"
116 def self.visible_by(user=nil)
121 def self.visible_by(user=nil)
117 user ||= User.current
122 user ||= User.current
118 if user && user.admin?
123 if user && user.admin?
119 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
124 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
120 elsif user && user.memberships.any?
125 elsif user && user.memberships.any?
121 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(',')}))"
122 else
127 else
123 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}"
124 end
129 end
125 end
130 end
126
131
127 def self.allowed_to_condition(user, permission, options={})
132 def self.allowed_to_condition(user, permission, options={})
128 statements = []
133 statements = []
129 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
134 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
130 if perm = Redmine::AccessControl.permission(permission)
135 if perm = Redmine::AccessControl.permission(permission)
131 unless perm.project_module.nil?
136 unless perm.project_module.nil?
132 # 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
133 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}')"
134 end
139 end
135 end
140 end
136 if options[:project]
141 if options[:project]
137 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
142 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
138 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]
139 base_statement = "(#{project_statement}) AND (#{base_statement})"
144 base_statement = "(#{project_statement}) AND (#{base_statement})"
140 end
145 end
141 if user.admin?
146 if user.admin?
142 # no restriction
147 # no restriction
143 else
148 else
144 statements << "1=0"
149 statements << "1=0"
145 if user.logged?
150 if user.logged?
146 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
151 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
147 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
152 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
148 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
153 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
149 elsif Role.anonymous.allowed_to?(permission)
154 elsif Role.anonymous.allowed_to?(permission)
150 # anonymous user allowed on public project
155 # anonymous user allowed on public project
151 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
156 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
152 else
157 else
153 # anonymous user is not authorized
158 # anonymous user is not authorized
154 end
159 end
155 end
160 end
156 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
161 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
157 end
162 end
158
163
159 # Returns all the Systemwide and project specific activities
164 # Returns the Systemwide and project specific activities
160 def activities
165 def activities(include_inactive=false)
161 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
166 if include_inactive
167 return all_activities
168 else
169 return active_activities
170 end
171 end
162
172
163 if overridden_activity_ids.empty?
173 # Will build a new Project specific Activity or update an existing one
164 return TimeEntryActivity.active
174 def update_or_build_time_entry_activity(id, activity_hash)
175 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
176 self.build_time_entry_activity_if_needed(activity_hash)
165 else
177 else
166 return system_activities_and_project_overrides
178 activity = project.time_entry_activities.find_by_id(id.to_i)
179 activity.update_attributes(activity_hash) if activity
180 end
181 end
182
183 # Builds new activity
184 def build_time_entry_activity_if_needed(activity)
185 # Only new override activities are built
186 if activity['parent_id']
187
188 parent_activity = TimeEntryActivity.find(activity['parent_id'])
189 activity['name'] = parent_activity.name
190 activity['position'] = parent_activity.position
191
192 if Enumeration.overridding_change?(activity, parent_activity)
193 self.time_entry_activities.build(activity)
194 end
167 end
195 end
168 end
196 end
169
197
170 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
198 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
171 #
199 #
172 # Examples:
200 # Examples:
173 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
201 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
174 # project.project_condition(false) => "projects.id = 1"
202 # project.project_condition(false) => "projects.id = 1"
175 def project_condition(with_subprojects)
203 def project_condition(with_subprojects)
176 cond = "#{Project.table_name}.id = #{id}"
204 cond = "#{Project.table_name}.id = #{id}"
177 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
205 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
178 cond
206 cond
179 end
207 end
180
208
181 def self.find(*args)
209 def self.find(*args)
182 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
210 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
183 project = find_by_identifier(*args)
211 project = find_by_identifier(*args)
184 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
212 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
185 project
213 project
186 else
214 else
187 super
215 super
188 end
216 end
189 end
217 end
190
218
191 def to_param
219 def to_param
192 # id is used for projects with a numeric identifier (compatibility)
220 # id is used for projects with a numeric identifier (compatibility)
193 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
221 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
194 end
222 end
195
223
196 def active?
224 def active?
197 self.status == STATUS_ACTIVE
225 self.status == STATUS_ACTIVE
198 end
226 end
199
227
200 # Archives the project and its descendants recursively
228 # Archives the project and its descendants recursively
201 def archive
229 def archive
202 # Archive subprojects if any
230 # Archive subprojects if any
203 children.each do |subproject|
231 children.each do |subproject|
204 subproject.archive
232 subproject.archive
205 end
233 end
206 update_attribute :status, STATUS_ARCHIVED
234 update_attribute :status, STATUS_ARCHIVED
207 end
235 end
208
236
209 # Unarchives the project
237 # Unarchives the project
210 # All its ancestors must be active
238 # All its ancestors must be active
211 def unarchive
239 def unarchive
212 return false if ancestors.detect {|a| !a.active?}
240 return false if ancestors.detect {|a| !a.active?}
213 update_attribute :status, STATUS_ACTIVE
241 update_attribute :status, STATUS_ACTIVE
214 end
242 end
215
243
216 # Returns an array of projects the project can be moved to
244 # Returns an array of projects the project can be moved to
217 def possible_parents
245 def possible_parents
218 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
246 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
219 end
247 end
220
248
221 # Sets the parent of the project
249 # Sets the parent of the project
222 # Argument can be either a Project, a String, a Fixnum or nil
250 # Argument can be either a Project, a String, a Fixnum or nil
223 def set_parent!(p)
251 def set_parent!(p)
224 unless p.nil? || p.is_a?(Project)
252 unless p.nil? || p.is_a?(Project)
225 if p.to_s.blank?
253 if p.to_s.blank?
226 p = nil
254 p = nil
227 else
255 else
228 p = Project.find_by_id(p)
256 p = Project.find_by_id(p)
229 return false unless p
257 return false unless p
230 end
258 end
231 end
259 end
232 if p == parent && !p.nil?
260 if p == parent && !p.nil?
233 # Nothing to do
261 # Nothing to do
234 true
262 true
235 elsif p.nil? || (p.active? && move_possible?(p))
263 elsif p.nil? || (p.active? && move_possible?(p))
236 # Insert the project so that target's children or root projects stay alphabetically sorted
264 # Insert the project so that target's children or root projects stay alphabetically sorted
237 sibs = (p.nil? ? self.class.roots : p.children)
265 sibs = (p.nil? ? self.class.roots : p.children)
238 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
266 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
239 if to_be_inserted_before
267 if to_be_inserted_before
240 move_to_left_of(to_be_inserted_before)
268 move_to_left_of(to_be_inserted_before)
241 elsif p.nil?
269 elsif p.nil?
242 if sibs.empty?
270 if sibs.empty?
243 # move_to_root adds the project in first (ie. left) position
271 # move_to_root adds the project in first (ie. left) position
244 move_to_root
272 move_to_root
245 else
273 else
246 move_to_right_of(sibs.last) unless self == sibs.last
274 move_to_right_of(sibs.last) unless self == sibs.last
247 end
275 end
248 else
276 else
249 # move_to_child_of adds the project in last (ie.right) position
277 # move_to_child_of adds the project in last (ie.right) position
250 move_to_child_of(p)
278 move_to_child_of(p)
251 end
279 end
252 true
280 true
253 else
281 else
254 # Can not move to the given target
282 # Can not move to the given target
255 false
283 false
256 end
284 end
257 end
285 end
258
286
259 # Returns an array of the trackers used by the project and its active sub projects
287 # Returns an array of the trackers used by the project and its active sub projects
260 def rolled_up_trackers
288 def rolled_up_trackers
261 @rolled_up_trackers ||=
289 @rolled_up_trackers ||=
262 Tracker.find(:all, :include => :projects,
290 Tracker.find(:all, :include => :projects,
263 :select => "DISTINCT #{Tracker.table_name}.*",
291 :select => "DISTINCT #{Tracker.table_name}.*",
264 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
292 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
265 :order => "#{Tracker.table_name}.position")
293 :order => "#{Tracker.table_name}.position")
266 end
294 end
267
295
268 # Returns a hash of project users grouped by role
296 # Returns a hash of project users grouped by role
269 def users_by_role
297 def users_by_role
270 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
298 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
271 m.roles.each do |r|
299 m.roles.each do |r|
272 h[r] ||= []
300 h[r] ||= []
273 h[r] << m.user
301 h[r] << m.user
274 end
302 end
275 h
303 h
276 end
304 end
277 end
305 end
278
306
279 # Deletes all project's members
307 # Deletes all project's members
280 def delete_all_members
308 def delete_all_members
281 me, mr = Member.table_name, MemberRole.table_name
309 me, mr = Member.table_name, MemberRole.table_name
282 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
310 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
283 Member.delete_all(['project_id = ?', id])
311 Member.delete_all(['project_id = ?', id])
284 end
312 end
285
313
286 # Users issues can be assigned to
314 # Users issues can be assigned to
287 def assignable_users
315 def assignable_users
288 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
316 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
289 end
317 end
290
318
291 # Returns the mail adresses of users that should be always notified on project events
319 # Returns the mail adresses of users that should be always notified on project events
292 def recipients
320 def recipients
293 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
321 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
294 end
322 end
295
323
296 # Returns an array of all custom fields enabled for project issues
324 # Returns an array of all custom fields enabled for project issues
297 # (explictly associated custom fields and custom fields enabled for all projects)
325 # (explictly associated custom fields and custom fields enabled for all projects)
298 def all_issue_custom_fields
326 def all_issue_custom_fields
299 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
327 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
300 end
328 end
301
329
302 def project
330 def project
303 self
331 self
304 end
332 end
305
333
306 def <=>(project)
334 def <=>(project)
307 name.downcase <=> project.name.downcase
335 name.downcase <=> project.name.downcase
308 end
336 end
309
337
310 def to_s
338 def to_s
311 name
339 name
312 end
340 end
313
341
314 # Returns a short description of the projects (first lines)
342 # Returns a short description of the projects (first lines)
315 def short_description(length = 255)
343 def short_description(length = 255)
316 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
344 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
317 end
345 end
318
346
319 # Return true if this project is allowed to do the specified action.
347 # Return true if this project is allowed to do the specified action.
320 # action can be:
348 # action can be:
321 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
349 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
322 # * a permission Symbol (eg. :edit_project)
350 # * a permission Symbol (eg. :edit_project)
323 def allows_to?(action)
351 def allows_to?(action)
324 if action.is_a? Hash
352 if action.is_a? Hash
325 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
353 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
326 else
354 else
327 allowed_permissions.include? action
355 allowed_permissions.include? action
328 end
356 end
329 end
357 end
330
358
331 def module_enabled?(module_name)
359 def module_enabled?(module_name)
332 module_name = module_name.to_s
360 module_name = module_name.to_s
333 enabled_modules.detect {|m| m.name == module_name}
361 enabled_modules.detect {|m| m.name == module_name}
334 end
362 end
335
363
336 def enabled_module_names=(module_names)
364 def enabled_module_names=(module_names)
337 if module_names && module_names.is_a?(Array)
365 if module_names && module_names.is_a?(Array)
338 module_names = module_names.collect(&:to_s)
366 module_names = module_names.collect(&:to_s)
339 # remove disabled modules
367 # remove disabled modules
340 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
368 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
341 # add new modules
369 # add new modules
342 module_names.each {|name| enabled_modules << EnabledModule.new(:name => name)}
370 module_names.each {|name| enabled_modules << EnabledModule.new(:name => name)}
343 else
371 else
344 enabled_modules.clear
372 enabled_modules.clear
345 end
373 end
346 end
374 end
347
375
348 # Returns an auto-generated project identifier based on the last identifier used
376 # Returns an auto-generated project identifier based on the last identifier used
349 def self.next_identifier
377 def self.next_identifier
350 p = Project.find(:first, :order => 'created_on DESC')
378 p = Project.find(:first, :order => 'created_on DESC')
351 p.nil? ? nil : p.identifier.to_s.succ
379 p.nil? ? nil : p.identifier.to_s.succ
352 end
380 end
353
381
354 # Copies and saves the Project instance based on the +project+.
382 # Copies and saves the Project instance based on the +project+.
355 # Will duplicate the source project's:
383 # Will duplicate the source project's:
356 # * Issues
384 # * Issues
357 # * Members
385 # * Members
358 # * Queries
386 # * Queries
359 def copy(project)
387 def copy(project)
360 project = project.is_a?(Project) ? project : Project.find(project)
388 project = project.is_a?(Project) ? project : Project.find(project)
361
389
362 Project.transaction do
390 Project.transaction do
363 # Wikis
391 # Wikis
364 self.wiki = Wiki.new(project.wiki.attributes.dup.except("project_id"))
392 self.wiki = Wiki.new(project.wiki.attributes.dup.except("project_id"))
365 project.wiki.pages.each do |page|
393 project.wiki.pages.each do |page|
366 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("page_id"))
394 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("page_id"))
367 new_wiki_page = WikiPage.new(page.attributes.dup.except("wiki_id"))
395 new_wiki_page = WikiPage.new(page.attributes.dup.except("wiki_id"))
368 new_wiki_page.content = new_wiki_content
396 new_wiki_page.content = new_wiki_content
369
397
370 self.wiki.pages << new_wiki_page
398 self.wiki.pages << new_wiki_page
371 end
399 end
372
400
373 # Versions
401 # Versions
374 project.versions.each do |version|
402 project.versions.each do |version|
375 new_version = Version.new
403 new_version = Version.new
376 new_version.attributes = version.attributes.dup.except("project_id")
404 new_version.attributes = version.attributes.dup.except("project_id")
377 self.versions << new_version
405 self.versions << new_version
378 end
406 end
379
407
380 project.issue_categories.each do |issue_category|
408 project.issue_categories.each do |issue_category|
381 new_issue_category = IssueCategory.new
409 new_issue_category = IssueCategory.new
382 new_issue_category.attributes = issue_category.attributes.dup.except("project_id")
410 new_issue_category.attributes = issue_category.attributes.dup.except("project_id")
383 self.issue_categories << new_issue_category
411 self.issue_categories << new_issue_category
384 end
412 end
385
413
386 # Issues
414 # Issues
387 project.issues.each do |issue|
415 project.issues.each do |issue|
388 new_issue = Issue.new
416 new_issue = Issue.new
389 new_issue.copy_from(issue)
417 new_issue.copy_from(issue)
390 # Reassign fixed_versions by name, since names are unique per
418 # Reassign fixed_versions by name, since names are unique per
391 # project and the versions for self are not yet saved
419 # project and the versions for self are not yet saved
392 if issue.fixed_version
420 if issue.fixed_version
393 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
421 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
394 end
422 end
395 # Reassign the category by name, since names are unique per
423 # Reassign the category by name, since names are unique per
396 # project and the categories for self are not yet saved
424 # project and the categories for self are not yet saved
397 if issue.category
425 if issue.category
398 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
426 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
399 end
427 end
400
428
401 self.issues << new_issue
429 self.issues << new_issue
402 end
430 end
403
431
404 # Members
432 # Members
405 project.members.each do |member|
433 project.members.each do |member|
406 new_member = Member.new
434 new_member = Member.new
407 new_member.attributes = member.attributes.dup.except("project_id")
435 new_member.attributes = member.attributes.dup.except("project_id")
408 new_member.role_ids = member.role_ids.dup
436 new_member.role_ids = member.role_ids.dup
409 new_member.project = self
437 new_member.project = self
410 self.members << new_member
438 self.members << new_member
411 end
439 end
412
440
413 # Queries
441 # Queries
414 project.queries.each do |query|
442 project.queries.each do |query|
415 new_query = Query.new
443 new_query = Query.new
416 new_query.attributes = query.attributes.dup.except("project_id", "sort_criteria")
444 new_query.attributes = query.attributes.dup.except("project_id", "sort_criteria")
417 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
445 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
418 new_query.project = self
446 new_query.project = self
419 self.queries << new_query
447 self.queries << new_query
420 end
448 end
421
449
422 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
450 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
423 self.save
451 self.save
424 end
452 end
425 end
453 end
426
454
427
455
428 # Copies +project+ and returns the new instance. This will not save
456 # Copies +project+ and returns the new instance. This will not save
429 # the copy
457 # the copy
430 def self.copy_from(project)
458 def self.copy_from(project)
431 begin
459 begin
432 project = project.is_a?(Project) ? project : Project.find(project)
460 project = project.is_a?(Project) ? project : Project.find(project)
433 if project
461 if project
434 # clear unique attributes
462 # clear unique attributes
435 attributes = project.attributes.dup.except('name', 'identifier', 'id', 'status')
463 attributes = project.attributes.dup.except('name', 'identifier', 'id', 'status')
436 copy = Project.new(attributes)
464 copy = Project.new(attributes)
437 copy.enabled_modules = project.enabled_modules
465 copy.enabled_modules = project.enabled_modules
438 copy.trackers = project.trackers
466 copy.trackers = project.trackers
439 copy.custom_values = project.custom_values.collect {|v| v.clone}
467 copy.custom_values = project.custom_values.collect {|v| v.clone}
440 copy.issue_custom_fields = project.issue_custom_fields
468 copy.issue_custom_fields = project.issue_custom_fields
441 return copy
469 return copy
442 else
470 else
443 return nil
471 return nil
444 end
472 end
445 rescue ActiveRecord::RecordNotFound
473 rescue ActiveRecord::RecordNotFound
446 return nil
474 return nil
447 end
475 end
448 end
476 end
449
477
450 private
478 private
451 def allowed_permissions
479 def allowed_permissions
452 @allowed_permissions ||= begin
480 @allowed_permissions ||= begin
453 module_names = enabled_modules.collect {|m| m.name}
481 module_names = enabled_modules.collect {|m| m.name}
454 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
482 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
455 end
483 end
456 end
484 end
457
485
458 def allowed_actions
486 def allowed_actions
459 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
487 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
460 end
488 end
461
489
462 # Returns the systemwide activities merged with the project specific overrides
490 # Returns all the active Systemwide and project specific activities
463 def system_activities_and_project_overrides
491 def active_activities
464 return TimeEntryActivity.active.
492 overridden_activity_ids = self.time_entry_activities.active.collect(&:parent_id)
493
494 if overridden_activity_ids.empty?
495 return TimeEntryActivity.active
496 else
497 return system_activities_and_project_overrides
498 end
499 end
500
501 # Returns all the Systemwide and project specific activities
502 # (inactive and active)
503 def all_activities
504 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
505
506 if overridden_activity_ids.empty?
507 return TimeEntryActivity.all
508 else
509 return system_activities_and_project_overrides(true)
510 end
511 end
512
513 # Returns the systemwide active activities merged with the project specific overrides
514 def system_activities_and_project_overrides(include_inactive=false)
515 if include_inactive
516 return TimeEntryActivity.all.
465 find(:all,
517 find(:all,
466 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
518 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
467 self.time_entry_activities
519 self.time_entry_activities
520 else
521 return TimeEntryActivity.active.
522 find(:all,
523 :conditions => ["id NOT IN (?)", self.time_entry_activities.active.collect(&:parent_id)]) +
524 self.time_entry_activities.active
525 end
468 end
526 end
469 end
527 end
@@ -1,832 +1,833
1 en:
1 en:
2 date:
2 date:
3 formats:
3 formats:
4 # Use the strftime parameters for formats.
4 # Use the strftime parameters for formats.
5 # When no format has been given, it uses default.
5 # When no format has been given, it uses default.
6 # You can provide other formats here if you like!
6 # You can provide other formats here if you like!
7 default: "%m/%d/%Y"
7 default: "%m/%d/%Y"
8 short: "%b %d"
8 short: "%b %d"
9 long: "%B %d, %Y"
9 long: "%B %d, %Y"
10
10
11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13
13
14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17 # Used in date_select and datime_select.
17 # Used in date_select and datime_select.
18 order: [ :year, :month, :day ]
18 order: [ :year, :month, :day ]
19
19
20 time:
20 time:
21 formats:
21 formats:
22 default: "%m/%d/%Y %I:%M %p"
22 default: "%m/%d/%Y %I:%M %p"
23 time: "%I:%M %p"
23 time: "%I:%M %p"
24 short: "%d %b %H:%M"
24 short: "%d %b %H:%M"
25 long: "%B %d, %Y %H:%M"
25 long: "%B %d, %Y %H:%M"
26 am: "am"
26 am: "am"
27 pm: "pm"
27 pm: "pm"
28
28
29 datetime:
29 datetime:
30 distance_in_words:
30 distance_in_words:
31 half_a_minute: "half a minute"
31 half_a_minute: "half a minute"
32 less_than_x_seconds:
32 less_than_x_seconds:
33 one: "less than 1 second"
33 one: "less than 1 second"
34 other: "less than {{count}} seconds"
34 other: "less than {{count}} seconds"
35 x_seconds:
35 x_seconds:
36 one: "1 second"
36 one: "1 second"
37 other: "{{count}} seconds"
37 other: "{{count}} seconds"
38 less_than_x_minutes:
38 less_than_x_minutes:
39 one: "less than a minute"
39 one: "less than a minute"
40 other: "less than {{count}} minutes"
40 other: "less than {{count}} minutes"
41 x_minutes:
41 x_minutes:
42 one: "1 minute"
42 one: "1 minute"
43 other: "{{count}} minutes"
43 other: "{{count}} minutes"
44 about_x_hours:
44 about_x_hours:
45 one: "about 1 hour"
45 one: "about 1 hour"
46 other: "about {{count}} hours"
46 other: "about {{count}} hours"
47 x_days:
47 x_days:
48 one: "1 day"
48 one: "1 day"
49 other: "{{count}} days"
49 other: "{{count}} days"
50 about_x_months:
50 about_x_months:
51 one: "about 1 month"
51 one: "about 1 month"
52 other: "about {{count}} months"
52 other: "about {{count}} months"
53 x_months:
53 x_months:
54 one: "1 month"
54 one: "1 month"
55 other: "{{count}} months"
55 other: "{{count}} months"
56 about_x_years:
56 about_x_years:
57 one: "about 1 year"
57 one: "about 1 year"
58 other: "about {{count}} years"
58 other: "about {{count}} years"
59 over_x_years:
59 over_x_years:
60 one: "over 1 year"
60 one: "over 1 year"
61 other: "over {{count}} years"
61 other: "over {{count}} years"
62
62
63 number:
63 number:
64 human:
64 human:
65 format:
65 format:
66 delimiter: ""
66 delimiter: ""
67 precision: 1
67 precision: 1
68 storage_units:
68 storage_units:
69 format: "%n %u"
69 format: "%n %u"
70 units:
70 units:
71 byte:
71 byte:
72 one: "Byte"
72 one: "Byte"
73 other: "Bytes"
73 other: "Bytes"
74 kb: "KB"
74 kb: "KB"
75 mb: "MB"
75 mb: "MB"
76 gb: "GB"
76 gb: "GB"
77 tb: "TB"
77 tb: "TB"
78
78
79
79
80 # Used in array.to_sentence.
80 # Used in array.to_sentence.
81 support:
81 support:
82 array:
82 array:
83 sentence_connector: "and"
83 sentence_connector: "and"
84 skip_last_comma: false
84 skip_last_comma: false
85
85
86 activerecord:
86 activerecord:
87 errors:
87 errors:
88 messages:
88 messages:
89 inclusion: "is not included in the list"
89 inclusion: "is not included in the list"
90 exclusion: "is reserved"
90 exclusion: "is reserved"
91 invalid: "is invalid"
91 invalid: "is invalid"
92 confirmation: "doesn't match confirmation"
92 confirmation: "doesn't match confirmation"
93 accepted: "must be accepted"
93 accepted: "must be accepted"
94 empty: "can't be empty"
94 empty: "can't be empty"
95 blank: "can't be blank"
95 blank: "can't be blank"
96 too_long: "is too long (maximum is {{count}} characters)"
96 too_long: "is too long (maximum is {{count}} characters)"
97 too_short: "is too short (minimum is {{count}} characters)"
97 too_short: "is too short (minimum is {{count}} characters)"
98 wrong_length: "is the wrong length (should be {{count}} characters)"
98 wrong_length: "is the wrong length (should be {{count}} characters)"
99 taken: "has already been taken"
99 taken: "has already been taken"
100 not_a_number: "is not a number"
100 not_a_number: "is not a number"
101 not_a_date: "is not a valid date"
101 not_a_date: "is not a valid date"
102 greater_than: "must be greater than {{count}}"
102 greater_than: "must be greater than {{count}}"
103 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
103 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
104 equal_to: "must be equal to {{count}}"
104 equal_to: "must be equal to {{count}}"
105 less_than: "must be less than {{count}}"
105 less_than: "must be less than {{count}}"
106 less_than_or_equal_to: "must be less than or equal to {{count}}"
106 less_than_or_equal_to: "must be less than or equal to {{count}}"
107 odd: "must be odd"
107 odd: "must be odd"
108 even: "must be even"
108 even: "must be even"
109 greater_than_start_date: "must be greater than start date"
109 greater_than_start_date: "must be greater than start date"
110 not_same_project: "doesn't belong to the same project"
110 not_same_project: "doesn't belong to the same project"
111 circular_dependency: "This relation would create a circular dependency"
111 circular_dependency: "This relation would create a circular dependency"
112
112
113 actionview_instancetag_blank_option: Please select
113 actionview_instancetag_blank_option: Please select
114
114
115 general_text_No: 'No'
115 general_text_No: 'No'
116 general_text_Yes: 'Yes'
116 general_text_Yes: 'Yes'
117 general_text_no: 'no'
117 general_text_no: 'no'
118 general_text_yes: 'yes'
118 general_text_yes: 'yes'
119 general_lang_name: 'English'
119 general_lang_name: 'English'
120 general_csv_separator: ','
120 general_csv_separator: ','
121 general_csv_decimal_separator: '.'
121 general_csv_decimal_separator: '.'
122 general_csv_encoding: ISO-8859-1
122 general_csv_encoding: ISO-8859-1
123 general_pdf_encoding: ISO-8859-1
123 general_pdf_encoding: ISO-8859-1
124 general_first_day_of_week: '7'
124 general_first_day_of_week: '7'
125
125
126 notice_account_updated: Account was successfully updated.
126 notice_account_updated: Account was successfully updated.
127 notice_account_invalid_creditentials: Invalid user or password
127 notice_account_invalid_creditentials: Invalid user or password
128 notice_account_password_updated: Password was successfully updated.
128 notice_account_password_updated: Password was successfully updated.
129 notice_account_wrong_password: Wrong password
129 notice_account_wrong_password: Wrong password
130 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
130 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
131 notice_account_unknown_email: Unknown user.
131 notice_account_unknown_email: Unknown user.
132 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
132 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
133 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
133 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
134 notice_account_activated: Your account has been activated. You can now log in.
134 notice_account_activated: Your account has been activated. You can now log in.
135 notice_successful_create: Successful creation.
135 notice_successful_create: Successful creation.
136 notice_successful_update: Successful update.
136 notice_successful_update: Successful update.
137 notice_successful_delete: Successful deletion.
137 notice_successful_delete: Successful deletion.
138 notice_successful_connection: Successful connection.
138 notice_successful_connection: Successful connection.
139 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
139 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
140 notice_locking_conflict: Data has been updated by another user.
140 notice_locking_conflict: Data has been updated by another user.
141 notice_not_authorized: You are not authorized to access this page.
141 notice_not_authorized: You are not authorized to access this page.
142 notice_email_sent: "An email was sent to {{value}}"
142 notice_email_sent: "An email was sent to {{value}}"
143 notice_email_error: "An error occurred while sending mail ({{value}})"
143 notice_email_error: "An error occurred while sending mail ({{value}})"
144 notice_feeds_access_key_reseted: Your RSS access key was reset.
144 notice_feeds_access_key_reseted: Your RSS access key was reset.
145 notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
145 notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
146 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
146 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
147 notice_account_pending: "Your account was created and is now pending administrator approval."
147 notice_account_pending: "Your account was created and is now pending administrator approval."
148 notice_default_data_loaded: Default configuration successfully loaded.
148 notice_default_data_loaded: Default configuration successfully loaded.
149 notice_unable_delete_version: Unable to delete version.
149 notice_unable_delete_version: Unable to delete version.
150
150
151 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
151 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
152 error_scm_not_found: "The entry or revision was not found in the repository."
152 error_scm_not_found: "The entry or revision was not found in the repository."
153 error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}"
153 error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}"
154 error_scm_annotate: "The entry does not exist or can not be annotated."
154 error_scm_annotate: "The entry does not exist or can not be annotated."
155 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
155 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
156 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
156 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
157 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
157 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
158
158
159 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
159 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
160
160
161 mail_subject_lost_password: "Your {{value}} password"
161 mail_subject_lost_password: "Your {{value}} password"
162 mail_body_lost_password: 'To change your password, click on the following link:'
162 mail_body_lost_password: 'To change your password, click on the following link:'
163 mail_subject_register: "Your {{value}} account activation"
163 mail_subject_register: "Your {{value}} account activation"
164 mail_body_register: 'To activate your account, click on the following link:'
164 mail_body_register: 'To activate your account, click on the following link:'
165 mail_body_account_information_external: "You can use your {{value}} account to log in."
165 mail_body_account_information_external: "You can use your {{value}} account to log in."
166 mail_body_account_information: Your account information
166 mail_body_account_information: Your account information
167 mail_subject_account_activation_request: "{{value}} account activation request"
167 mail_subject_account_activation_request: "{{value}} account activation request"
168 mail_body_account_activation_request: "A new user ({{value}}) has registered. The account is pending your approval:"
168 mail_body_account_activation_request: "A new user ({{value}}) has registered. The account is pending your approval:"
169 mail_subject_reminder: "{{count}} issue(s) due in the next days"
169 mail_subject_reminder: "{{count}} issue(s) due in the next days"
170 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
170 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
171 mail_subject_wiki_content_added: "'{{page}}' wiki page has been added"
171 mail_subject_wiki_content_added: "'{{page}}' wiki page has been added"
172 mail_body_wiki_content_added: "The '{{page}}' wiki page has been added by {{author}}."
172 mail_body_wiki_content_added: "The '{{page}}' wiki page has been added by {{author}}."
173 mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated"
173 mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated"
174 mail_body_wiki_content_updated: "The '{{page}}' wiki page has been updated by {{author}}."
174 mail_body_wiki_content_updated: "The '{{page}}' wiki page has been updated by {{author}}."
175
175
176 gui_validation_error: 1 error
176 gui_validation_error: 1 error
177 gui_validation_error_plural: "{{count}} errors"
177 gui_validation_error_plural: "{{count}} errors"
178
178
179 field_name: Name
179 field_name: Name
180 field_description: Description
180 field_description: Description
181 field_summary: Summary
181 field_summary: Summary
182 field_is_required: Required
182 field_is_required: Required
183 field_firstname: Firstname
183 field_firstname: Firstname
184 field_lastname: Lastname
184 field_lastname: Lastname
185 field_mail: Email
185 field_mail: Email
186 field_filename: File
186 field_filename: File
187 field_filesize: Size
187 field_filesize: Size
188 field_downloads: Downloads
188 field_downloads: Downloads
189 field_author: Author
189 field_author: Author
190 field_created_on: Created
190 field_created_on: Created
191 field_updated_on: Updated
191 field_updated_on: Updated
192 field_field_format: Format
192 field_field_format: Format
193 field_is_for_all: For all projects
193 field_is_for_all: For all projects
194 field_possible_values: Possible values
194 field_possible_values: Possible values
195 field_regexp: Regular expression
195 field_regexp: Regular expression
196 field_min_length: Minimum length
196 field_min_length: Minimum length
197 field_max_length: Maximum length
197 field_max_length: Maximum length
198 field_value: Value
198 field_value: Value
199 field_category: Category
199 field_category: Category
200 field_title: Title
200 field_title: Title
201 field_project: Project
201 field_project: Project
202 field_issue: Issue
202 field_issue: Issue
203 field_status: Status
203 field_status: Status
204 field_notes: Notes
204 field_notes: Notes
205 field_is_closed: Issue closed
205 field_is_closed: Issue closed
206 field_is_default: Default value
206 field_is_default: Default value
207 field_tracker: Tracker
207 field_tracker: Tracker
208 field_subject: Subject
208 field_subject: Subject
209 field_due_date: Due date
209 field_due_date: Due date
210 field_assigned_to: Assigned to
210 field_assigned_to: Assigned to
211 field_priority: Priority
211 field_priority: Priority
212 field_fixed_version: Target version
212 field_fixed_version: Target version
213 field_user: User
213 field_user: User
214 field_role: Role
214 field_role: Role
215 field_homepage: Homepage
215 field_homepage: Homepage
216 field_is_public: Public
216 field_is_public: Public
217 field_parent: Subproject of
217 field_parent: Subproject of
218 field_is_in_chlog: Issues displayed in changelog
218 field_is_in_chlog: Issues displayed in changelog
219 field_is_in_roadmap: Issues displayed in roadmap
219 field_is_in_roadmap: Issues displayed in roadmap
220 field_login: Login
220 field_login: Login
221 field_mail_notification: Email notifications
221 field_mail_notification: Email notifications
222 field_admin: Administrator
222 field_admin: Administrator
223 field_last_login_on: Last connection
223 field_last_login_on: Last connection
224 field_language: Language
224 field_language: Language
225 field_effective_date: Date
225 field_effective_date: Date
226 field_password: Password
226 field_password: Password
227 field_new_password: New password
227 field_new_password: New password
228 field_password_confirmation: Confirmation
228 field_password_confirmation: Confirmation
229 field_version: Version
229 field_version: Version
230 field_type: Type
230 field_type: Type
231 field_host: Host
231 field_host: Host
232 field_port: Port
232 field_port: Port
233 field_account: Account
233 field_account: Account
234 field_base_dn: Base DN
234 field_base_dn: Base DN
235 field_attr_login: Login attribute
235 field_attr_login: Login attribute
236 field_attr_firstname: Firstname attribute
236 field_attr_firstname: Firstname attribute
237 field_attr_lastname: Lastname attribute
237 field_attr_lastname: Lastname attribute
238 field_attr_mail: Email attribute
238 field_attr_mail: Email attribute
239 field_onthefly: On-the-fly user creation
239 field_onthefly: On-the-fly user creation
240 field_start_date: Start
240 field_start_date: Start
241 field_done_ratio: % Done
241 field_done_ratio: % Done
242 field_auth_source: Authentication mode
242 field_auth_source: Authentication mode
243 field_hide_mail: Hide my email address
243 field_hide_mail: Hide my email address
244 field_comments: Comment
244 field_comments: Comment
245 field_url: URL
245 field_url: URL
246 field_start_page: Start page
246 field_start_page: Start page
247 field_subproject: Subproject
247 field_subproject: Subproject
248 field_hours: Hours
248 field_hours: Hours
249 field_activity: Activity
249 field_activity: Activity
250 field_spent_on: Date
250 field_spent_on: Date
251 field_identifier: Identifier
251 field_identifier: Identifier
252 field_is_filter: Used as a filter
252 field_is_filter: Used as a filter
253 field_issue_to: Related issue
253 field_issue_to: Related issue
254 field_delay: Delay
254 field_delay: Delay
255 field_assignable: Issues can be assigned to this role
255 field_assignable: Issues can be assigned to this role
256 field_redirect_existing_links: Redirect existing links
256 field_redirect_existing_links: Redirect existing links
257 field_estimated_hours: Estimated time
257 field_estimated_hours: Estimated time
258 field_column_names: Columns
258 field_column_names: Columns
259 field_time_zone: Time zone
259 field_time_zone: Time zone
260 field_searchable: Searchable
260 field_searchable: Searchable
261 field_default_value: Default value
261 field_default_value: Default value
262 field_comments_sorting: Display comments
262 field_comments_sorting: Display comments
263 field_parent_title: Parent page
263 field_parent_title: Parent page
264 field_editable: Editable
264 field_editable: Editable
265 field_watcher: Watcher
265 field_watcher: Watcher
266 field_identity_url: OpenID URL
266 field_identity_url: OpenID URL
267 field_content: Content
267 field_content: Content
268 field_group_by: Group results by
268 field_group_by: Group results by
269
269
270 setting_app_title: Application title
270 setting_app_title: Application title
271 setting_app_subtitle: Application subtitle
271 setting_app_subtitle: Application subtitle
272 setting_welcome_text: Welcome text
272 setting_welcome_text: Welcome text
273 setting_default_language: Default language
273 setting_default_language: Default language
274 setting_login_required: Authentication required
274 setting_login_required: Authentication required
275 setting_self_registration: Self-registration
275 setting_self_registration: Self-registration
276 setting_attachment_max_size: Attachment max. size
276 setting_attachment_max_size: Attachment max. size
277 setting_issues_export_limit: Issues export limit
277 setting_issues_export_limit: Issues export limit
278 setting_mail_from: Emission email address
278 setting_mail_from: Emission email address
279 setting_bcc_recipients: Blind carbon copy recipients (bcc)
279 setting_bcc_recipients: Blind carbon copy recipients (bcc)
280 setting_plain_text_mail: Plain text mail (no HTML)
280 setting_plain_text_mail: Plain text mail (no HTML)
281 setting_host_name: Host name and path
281 setting_host_name: Host name and path
282 setting_text_formatting: Text formatting
282 setting_text_formatting: Text formatting
283 setting_wiki_compression: Wiki history compression
283 setting_wiki_compression: Wiki history compression
284 setting_feeds_limit: Feed content limit
284 setting_feeds_limit: Feed content limit
285 setting_default_projects_public: New projects are public by default
285 setting_default_projects_public: New projects are public by default
286 setting_autofetch_changesets: Autofetch commits
286 setting_autofetch_changesets: Autofetch commits
287 setting_sys_api_enabled: Enable WS for repository management
287 setting_sys_api_enabled: Enable WS for repository management
288 setting_commit_ref_keywords: Referencing keywords
288 setting_commit_ref_keywords: Referencing keywords
289 setting_commit_fix_keywords: Fixing keywords
289 setting_commit_fix_keywords: Fixing keywords
290 setting_autologin: Autologin
290 setting_autologin: Autologin
291 setting_date_format: Date format
291 setting_date_format: Date format
292 setting_time_format: Time format
292 setting_time_format: Time format
293 setting_cross_project_issue_relations: Allow cross-project issue relations
293 setting_cross_project_issue_relations: Allow cross-project issue relations
294 setting_issue_list_default_columns: Default columns displayed on the issue list
294 setting_issue_list_default_columns: Default columns displayed on the issue list
295 setting_repositories_encodings: Repositories encodings
295 setting_repositories_encodings: Repositories encodings
296 setting_commit_logs_encoding: Commit messages encoding
296 setting_commit_logs_encoding: Commit messages encoding
297 setting_emails_footer: Emails footer
297 setting_emails_footer: Emails footer
298 setting_protocol: Protocol
298 setting_protocol: Protocol
299 setting_per_page_options: Objects per page options
299 setting_per_page_options: Objects per page options
300 setting_user_format: Users display format
300 setting_user_format: Users display format
301 setting_activity_days_default: Days displayed on project activity
301 setting_activity_days_default: Days displayed on project activity
302 setting_display_subprojects_issues: Display subprojects issues on main projects by default
302 setting_display_subprojects_issues: Display subprojects issues on main projects by default
303 setting_enabled_scm: Enabled SCM
303 setting_enabled_scm: Enabled SCM
304 setting_mail_handler_api_enabled: Enable WS for incoming emails
304 setting_mail_handler_api_enabled: Enable WS for incoming emails
305 setting_mail_handler_api_key: API key
305 setting_mail_handler_api_key: API key
306 setting_sequential_project_identifiers: Generate sequential project identifiers
306 setting_sequential_project_identifiers: Generate sequential project identifiers
307 setting_gravatar_enabled: Use Gravatar user icons
307 setting_gravatar_enabled: Use Gravatar user icons
308 setting_diff_max_lines_displayed: Max number of diff lines displayed
308 setting_diff_max_lines_displayed: Max number of diff lines displayed
309 setting_file_max_size_displayed: Max size of text files displayed inline
309 setting_file_max_size_displayed: Max size of text files displayed inline
310 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
310 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
311 setting_openid: Allow OpenID login and registration
311 setting_openid: Allow OpenID login and registration
312 setting_password_min_length: Minimum password length
312 setting_password_min_length: Minimum password length
313 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
313 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
314
314
315 permission_add_project: Create project
315 permission_add_project: Create project
316 permission_edit_project: Edit project
316 permission_edit_project: Edit project
317 permission_select_project_modules: Select project modules
317 permission_select_project_modules: Select project modules
318 permission_manage_members: Manage members
318 permission_manage_members: Manage members
319 permission_manage_versions: Manage versions
319 permission_manage_versions: Manage versions
320 permission_manage_categories: Manage issue categories
320 permission_manage_categories: Manage issue categories
321 permission_add_issues: Add issues
321 permission_add_issues: Add issues
322 permission_edit_issues: Edit issues
322 permission_edit_issues: Edit issues
323 permission_manage_issue_relations: Manage issue relations
323 permission_manage_issue_relations: Manage issue relations
324 permission_add_issue_notes: Add notes
324 permission_add_issue_notes: Add notes
325 permission_edit_issue_notes: Edit notes
325 permission_edit_issue_notes: Edit notes
326 permission_edit_own_issue_notes: Edit own notes
326 permission_edit_own_issue_notes: Edit own notes
327 permission_move_issues: Move issues
327 permission_move_issues: Move issues
328 permission_delete_issues: Delete issues
328 permission_delete_issues: Delete issues
329 permission_manage_public_queries: Manage public queries
329 permission_manage_public_queries: Manage public queries
330 permission_save_queries: Save queries
330 permission_save_queries: Save queries
331 permission_view_gantt: View gantt chart
331 permission_view_gantt: View gantt chart
332 permission_view_calendar: View calender
332 permission_view_calendar: View calender
333 permission_view_issue_watchers: View watchers list
333 permission_view_issue_watchers: View watchers list
334 permission_add_issue_watchers: Add watchers
334 permission_add_issue_watchers: Add watchers
335 permission_log_time: Log spent time
335 permission_log_time: Log spent time
336 permission_view_time_entries: View spent time
336 permission_view_time_entries: View spent time
337 permission_edit_time_entries: Edit time logs
337 permission_edit_time_entries: Edit time logs
338 permission_edit_own_time_entries: Edit own time logs
338 permission_edit_own_time_entries: Edit own time logs
339 permission_manage_news: Manage news
339 permission_manage_news: Manage news
340 permission_comment_news: Comment news
340 permission_comment_news: Comment news
341 permission_manage_documents: Manage documents
341 permission_manage_documents: Manage documents
342 permission_view_documents: View documents
342 permission_view_documents: View documents
343 permission_manage_files: Manage files
343 permission_manage_files: Manage files
344 permission_view_files: View files
344 permission_view_files: View files
345 permission_manage_wiki: Manage wiki
345 permission_manage_wiki: Manage wiki
346 permission_rename_wiki_pages: Rename wiki pages
346 permission_rename_wiki_pages: Rename wiki pages
347 permission_delete_wiki_pages: Delete wiki pages
347 permission_delete_wiki_pages: Delete wiki pages
348 permission_view_wiki_pages: View wiki
348 permission_view_wiki_pages: View wiki
349 permission_view_wiki_edits: View wiki history
349 permission_view_wiki_edits: View wiki history
350 permission_edit_wiki_pages: Edit wiki pages
350 permission_edit_wiki_pages: Edit wiki pages
351 permission_delete_wiki_pages_attachments: Delete attachments
351 permission_delete_wiki_pages_attachments: Delete attachments
352 permission_protect_wiki_pages: Protect wiki pages
352 permission_protect_wiki_pages: Protect wiki pages
353 permission_manage_repository: Manage repository
353 permission_manage_repository: Manage repository
354 permission_browse_repository: Browse repository
354 permission_browse_repository: Browse repository
355 permission_view_changesets: View changesets
355 permission_view_changesets: View changesets
356 permission_commit_access: Commit access
356 permission_commit_access: Commit access
357 permission_manage_boards: Manage boards
357 permission_manage_boards: Manage boards
358 permission_view_messages: View messages
358 permission_view_messages: View messages
359 permission_add_messages: Post messages
359 permission_add_messages: Post messages
360 permission_edit_messages: Edit messages
360 permission_edit_messages: Edit messages
361 permission_edit_own_messages: Edit own messages
361 permission_edit_own_messages: Edit own messages
362 permission_delete_messages: Delete messages
362 permission_delete_messages: Delete messages
363 permission_delete_own_messages: Delete own messages
363 permission_delete_own_messages: Delete own messages
364
364
365 project_module_issue_tracking: Issue tracking
365 project_module_issue_tracking: Issue tracking
366 project_module_time_tracking: Time tracking
366 project_module_time_tracking: Time tracking
367 project_module_news: News
367 project_module_news: News
368 project_module_documents: Documents
368 project_module_documents: Documents
369 project_module_files: Files
369 project_module_files: Files
370 project_module_wiki: Wiki
370 project_module_wiki: Wiki
371 project_module_repository: Repository
371 project_module_repository: Repository
372 project_module_boards: Boards
372 project_module_boards: Boards
373
373
374 label_user: User
374 label_user: User
375 label_user_plural: Users
375 label_user_plural: Users
376 label_user_new: New user
376 label_user_new: New user
377 label_project: Project
377 label_project: Project
378 label_project_new: New project
378 label_project_new: New project
379 label_project_plural: Projects
379 label_project_plural: Projects
380 label_x_projects:
380 label_x_projects:
381 zero: no projects
381 zero: no projects
382 one: 1 project
382 one: 1 project
383 other: "{{count}} projects"
383 other: "{{count}} projects"
384 label_project_all: All Projects
384 label_project_all: All Projects
385 label_project_latest: Latest projects
385 label_project_latest: Latest projects
386 label_issue: Issue
386 label_issue: Issue
387 label_issue_new: New issue
387 label_issue_new: New issue
388 label_issue_plural: Issues
388 label_issue_plural: Issues
389 label_issue_view_all: View all issues
389 label_issue_view_all: View all issues
390 label_issues_by: "Issues by {{value}}"
390 label_issues_by: "Issues by {{value}}"
391 label_issue_added: Issue added
391 label_issue_added: Issue added
392 label_issue_updated: Issue updated
392 label_issue_updated: Issue updated
393 label_document: Document
393 label_document: Document
394 label_document_new: New document
394 label_document_new: New document
395 label_document_plural: Documents
395 label_document_plural: Documents
396 label_document_added: Document added
396 label_document_added: Document added
397 label_role: Role
397 label_role: Role
398 label_role_plural: Roles
398 label_role_plural: Roles
399 label_role_new: New role
399 label_role_new: New role
400 label_role_and_permissions: Roles and permissions
400 label_role_and_permissions: Roles and permissions
401 label_member: Member
401 label_member: Member
402 label_member_new: New member
402 label_member_new: New member
403 label_member_plural: Members
403 label_member_plural: Members
404 label_tracker: Tracker
404 label_tracker: Tracker
405 label_tracker_plural: Trackers
405 label_tracker_plural: Trackers
406 label_tracker_new: New tracker
406 label_tracker_new: New tracker
407 label_workflow: Workflow
407 label_workflow: Workflow
408 label_issue_status: Issue status
408 label_issue_status: Issue status
409 label_issue_status_plural: Issue statuses
409 label_issue_status_plural: Issue statuses
410 label_issue_status_new: New status
410 label_issue_status_new: New status
411 label_issue_category: Issue category
411 label_issue_category: Issue category
412 label_issue_category_plural: Issue categories
412 label_issue_category_plural: Issue categories
413 label_issue_category_new: New category
413 label_issue_category_new: New category
414 label_custom_field: Custom field
414 label_custom_field: Custom field
415 label_custom_field_plural: Custom fields
415 label_custom_field_plural: Custom fields
416 label_custom_field_new: New custom field
416 label_custom_field_new: New custom field
417 label_enumerations: Enumerations
417 label_enumerations: Enumerations
418 label_enumeration_new: New value
418 label_enumeration_new: New value
419 label_information: Information
419 label_information: Information
420 label_information_plural: Information
420 label_information_plural: Information
421 label_please_login: Please log in
421 label_please_login: Please log in
422 label_register: Register
422 label_register: Register
423 label_login_with_open_id_option: or login with OpenID
423 label_login_with_open_id_option: or login with OpenID
424 label_password_lost: Lost password
424 label_password_lost: Lost password
425 label_home: Home
425 label_home: Home
426 label_my_page: My page
426 label_my_page: My page
427 label_my_account: My account
427 label_my_account: My account
428 label_my_projects: My projects
428 label_my_projects: My projects
429 label_administration: Administration
429 label_administration: Administration
430 label_login: Sign in
430 label_login: Sign in
431 label_logout: Sign out
431 label_logout: Sign out
432 label_help: Help
432 label_help: Help
433 label_reported_issues: Reported issues
433 label_reported_issues: Reported issues
434 label_assigned_to_me_issues: Issues assigned to me
434 label_assigned_to_me_issues: Issues assigned to me
435 label_last_login: Last connection
435 label_last_login: Last connection
436 label_registered_on: Registered on
436 label_registered_on: Registered on
437 label_activity: Activity
437 label_activity: Activity
438 label_overall_activity: Overall activity
438 label_overall_activity: Overall activity
439 label_user_activity: "{{value}}'s activity"
439 label_user_activity: "{{value}}'s activity"
440 label_new: New
440 label_new: New
441 label_logged_as: Logged in as
441 label_logged_as: Logged in as
442 label_environment: Environment
442 label_environment: Environment
443 label_authentication: Authentication
443 label_authentication: Authentication
444 label_auth_source: Authentication mode
444 label_auth_source: Authentication mode
445 label_auth_source_new: New authentication mode
445 label_auth_source_new: New authentication mode
446 label_auth_source_plural: Authentication modes
446 label_auth_source_plural: Authentication modes
447 label_subproject_plural: Subprojects
447 label_subproject_plural: Subprojects
448 label_and_its_subprojects: "{{value}} and its subprojects"
448 label_and_its_subprojects: "{{value}} and its subprojects"
449 label_min_max_length: Min - Max length
449 label_min_max_length: Min - Max length
450 label_list: List
450 label_list: List
451 label_date: Date
451 label_date: Date
452 label_integer: Integer
452 label_integer: Integer
453 label_float: Float
453 label_float: Float
454 label_boolean: Boolean
454 label_boolean: Boolean
455 label_string: Text
455 label_string: Text
456 label_text: Long text
456 label_text: Long text
457 label_attribute: Attribute
457 label_attribute: Attribute
458 label_attribute_plural: Attributes
458 label_attribute_plural: Attributes
459 label_download: "{{count}} Download"
459 label_download: "{{count}} Download"
460 label_download_plural: "{{count}} Downloads"
460 label_download_plural: "{{count}} Downloads"
461 label_no_data: No data to display
461 label_no_data: No data to display
462 label_change_status: Change status
462 label_change_status: Change status
463 label_history: History
463 label_history: History
464 label_attachment: File
464 label_attachment: File
465 label_attachment_new: New file
465 label_attachment_new: New file
466 label_attachment_delete: Delete file
466 label_attachment_delete: Delete file
467 label_attachment_plural: Files
467 label_attachment_plural: Files
468 label_file_added: File added
468 label_file_added: File added
469 label_report: Report
469 label_report: Report
470 label_report_plural: Reports
470 label_report_plural: Reports
471 label_news: News
471 label_news: News
472 label_news_new: Add news
472 label_news_new: Add news
473 label_news_plural: News
473 label_news_plural: News
474 label_news_latest: Latest news
474 label_news_latest: Latest news
475 label_news_view_all: View all news
475 label_news_view_all: View all news
476 label_news_added: News added
476 label_news_added: News added
477 label_change_log: Change log
477 label_change_log: Change log
478 label_settings: Settings
478 label_settings: Settings
479 label_overview: Overview
479 label_overview: Overview
480 label_version: Version
480 label_version: Version
481 label_version_new: New version
481 label_version_new: New version
482 label_version_plural: Versions
482 label_version_plural: Versions
483 label_confirmation: Confirmation
483 label_confirmation: Confirmation
484 label_export_to: 'Also available in:'
484 label_export_to: 'Also available in:'
485 label_read: Read...
485 label_read: Read...
486 label_public_projects: Public projects
486 label_public_projects: Public projects
487 label_open_issues: open
487 label_open_issues: open
488 label_open_issues_plural: open
488 label_open_issues_plural: open
489 label_closed_issues: closed
489 label_closed_issues: closed
490 label_closed_issues_plural: closed
490 label_closed_issues_plural: closed
491 label_x_open_issues_abbr_on_total:
491 label_x_open_issues_abbr_on_total:
492 zero: 0 open / {{total}}
492 zero: 0 open / {{total}}
493 one: 1 open / {{total}}
493 one: 1 open / {{total}}
494 other: "{{count}} open / {{total}}"
494 other: "{{count}} open / {{total}}"
495 label_x_open_issues_abbr:
495 label_x_open_issues_abbr:
496 zero: 0 open
496 zero: 0 open
497 one: 1 open
497 one: 1 open
498 other: "{{count}} open"
498 other: "{{count}} open"
499 label_x_closed_issues_abbr:
499 label_x_closed_issues_abbr:
500 zero: 0 closed
500 zero: 0 closed
501 one: 1 closed
501 one: 1 closed
502 other: "{{count}} closed"
502 other: "{{count}} closed"
503 label_total: Total
503 label_total: Total
504 label_permissions: Permissions
504 label_permissions: Permissions
505 label_current_status: Current status
505 label_current_status: Current status
506 label_new_statuses_allowed: New statuses allowed
506 label_new_statuses_allowed: New statuses allowed
507 label_all: all
507 label_all: all
508 label_none: none
508 label_none: none
509 label_nobody: nobody
509 label_nobody: nobody
510 label_next: Next
510 label_next: Next
511 label_previous: Previous
511 label_previous: Previous
512 label_used_by: Used by
512 label_used_by: Used by
513 label_details: Details
513 label_details: Details
514 label_add_note: Add a note
514 label_add_note: Add a note
515 label_per_page: Per page
515 label_per_page: Per page
516 label_calendar: Calendar
516 label_calendar: Calendar
517 label_months_from: months from
517 label_months_from: months from
518 label_gantt: Gantt
518 label_gantt: Gantt
519 label_internal: Internal
519 label_internal: Internal
520 label_last_changes: "last {{count}} changes"
520 label_last_changes: "last {{count}} changes"
521 label_change_view_all: View all changes
521 label_change_view_all: View all changes
522 label_personalize_page: Personalize this page
522 label_personalize_page: Personalize this page
523 label_comment: Comment
523 label_comment: Comment
524 label_comment_plural: Comments
524 label_comment_plural: Comments
525 label_x_comments:
525 label_x_comments:
526 zero: no comments
526 zero: no comments
527 one: 1 comment
527 one: 1 comment
528 other: "{{count}} comments"
528 other: "{{count}} comments"
529 label_comment_add: Add a comment
529 label_comment_add: Add a comment
530 label_comment_added: Comment added
530 label_comment_added: Comment added
531 label_comment_delete: Delete comments
531 label_comment_delete: Delete comments
532 label_query: Custom query
532 label_query: Custom query
533 label_query_plural: Custom queries
533 label_query_plural: Custom queries
534 label_query_new: New query
534 label_query_new: New query
535 label_filter_add: Add filter
535 label_filter_add: Add filter
536 label_filter_plural: Filters
536 label_filter_plural: Filters
537 label_equals: is
537 label_equals: is
538 label_not_equals: is not
538 label_not_equals: is not
539 label_in_less_than: in less than
539 label_in_less_than: in less than
540 label_in_more_than: in more than
540 label_in_more_than: in more than
541 label_greater_or_equal: '>='
541 label_greater_or_equal: '>='
542 label_less_or_equal: '<='
542 label_less_or_equal: '<='
543 label_in: in
543 label_in: in
544 label_today: today
544 label_today: today
545 label_all_time: all time
545 label_all_time: all time
546 label_yesterday: yesterday
546 label_yesterday: yesterday
547 label_this_week: this week
547 label_this_week: this week
548 label_last_week: last week
548 label_last_week: last week
549 label_last_n_days: "last {{count}} days"
549 label_last_n_days: "last {{count}} days"
550 label_this_month: this month
550 label_this_month: this month
551 label_last_month: last month
551 label_last_month: last month
552 label_this_year: this year
552 label_this_year: this year
553 label_date_range: Date range
553 label_date_range: Date range
554 label_less_than_ago: less than days ago
554 label_less_than_ago: less than days ago
555 label_more_than_ago: more than days ago
555 label_more_than_ago: more than days ago
556 label_ago: days ago
556 label_ago: days ago
557 label_contains: contains
557 label_contains: contains
558 label_not_contains: doesn't contain
558 label_not_contains: doesn't contain
559 label_day_plural: days
559 label_day_plural: days
560 label_repository: Repository
560 label_repository: Repository
561 label_repository_plural: Repositories
561 label_repository_plural: Repositories
562 label_browse: Browse
562 label_browse: Browse
563 label_modification: "{{count}} change"
563 label_modification: "{{count}} change"
564 label_modification_plural: "{{count}} changes"
564 label_modification_plural: "{{count}} changes"
565 label_branch: Branch
565 label_branch: Branch
566 label_tag: Tag
566 label_tag: Tag
567 label_revision: Revision
567 label_revision: Revision
568 label_revision_plural: Revisions
568 label_revision_plural: Revisions
569 label_associated_revisions: Associated revisions
569 label_associated_revisions: Associated revisions
570 label_added: added
570 label_added: added
571 label_modified: modified
571 label_modified: modified
572 label_copied: copied
572 label_copied: copied
573 label_renamed: renamed
573 label_renamed: renamed
574 label_deleted: deleted
574 label_deleted: deleted
575 label_latest_revision: Latest revision
575 label_latest_revision: Latest revision
576 label_latest_revision_plural: Latest revisions
576 label_latest_revision_plural: Latest revisions
577 label_view_revisions: View revisions
577 label_view_revisions: View revisions
578 label_view_all_revisions: View all revisions
578 label_view_all_revisions: View all revisions
579 label_max_size: Maximum size
579 label_max_size: Maximum size
580 label_sort_highest: Move to top
580 label_sort_highest: Move to top
581 label_sort_higher: Move up
581 label_sort_higher: Move up
582 label_sort_lower: Move down
582 label_sort_lower: Move down
583 label_sort_lowest: Move to bottom
583 label_sort_lowest: Move to bottom
584 label_roadmap: Roadmap
584 label_roadmap: Roadmap
585 label_roadmap_due_in: "Due in {{value}}"
585 label_roadmap_due_in: "Due in {{value}}"
586 label_roadmap_overdue: "{{value}} late"
586 label_roadmap_overdue: "{{value}} late"
587 label_roadmap_no_issues: No issues for this version
587 label_roadmap_no_issues: No issues for this version
588 label_search: Search
588 label_search: Search
589 label_result_plural: Results
589 label_result_plural: Results
590 label_all_words: All words
590 label_all_words: All words
591 label_wiki: Wiki
591 label_wiki: Wiki
592 label_wiki_edit: Wiki edit
592 label_wiki_edit: Wiki edit
593 label_wiki_edit_plural: Wiki edits
593 label_wiki_edit_plural: Wiki edits
594 label_wiki_page: Wiki page
594 label_wiki_page: Wiki page
595 label_wiki_page_plural: Wiki pages
595 label_wiki_page_plural: Wiki pages
596 label_index_by_title: Index by title
596 label_index_by_title: Index by title
597 label_index_by_date: Index by date
597 label_index_by_date: Index by date
598 label_current_version: Current version
598 label_current_version: Current version
599 label_preview: Preview
599 label_preview: Preview
600 label_feed_plural: Feeds
600 label_feed_plural: Feeds
601 label_changes_details: Details of all changes
601 label_changes_details: Details of all changes
602 label_issue_tracking: Issue tracking
602 label_issue_tracking: Issue tracking
603 label_spent_time: Spent time
603 label_spent_time: Spent time
604 label_f_hour: "{{value}} hour"
604 label_f_hour: "{{value}} hour"
605 label_f_hour_plural: "{{value}} hours"
605 label_f_hour_plural: "{{value}} hours"
606 label_time_tracking: Time tracking
606 label_time_tracking: Time tracking
607 label_change_plural: Changes
607 label_change_plural: Changes
608 label_statistics: Statistics
608 label_statistics: Statistics
609 label_commits_per_month: Commits per month
609 label_commits_per_month: Commits per month
610 label_commits_per_author: Commits per author
610 label_commits_per_author: Commits per author
611 label_view_diff: View differences
611 label_view_diff: View differences
612 label_diff_inline: inline
612 label_diff_inline: inline
613 label_diff_side_by_side: side by side
613 label_diff_side_by_side: side by side
614 label_options: Options
614 label_options: Options
615 label_copy_workflow_from: Copy workflow from
615 label_copy_workflow_from: Copy workflow from
616 label_permissions_report: Permissions report
616 label_permissions_report: Permissions report
617 label_watched_issues: Watched issues
617 label_watched_issues: Watched issues
618 label_related_issues: Related issues
618 label_related_issues: Related issues
619 label_applied_status: Applied status
619 label_applied_status: Applied status
620 label_loading: Loading...
620 label_loading: Loading...
621 label_relation_new: New relation
621 label_relation_new: New relation
622 label_relation_delete: Delete relation
622 label_relation_delete: Delete relation
623 label_relates_to: related to
623 label_relates_to: related to
624 label_duplicates: duplicates
624 label_duplicates: duplicates
625 label_duplicated_by: duplicated by
625 label_duplicated_by: duplicated by
626 label_blocks: blocks
626 label_blocks: blocks
627 label_blocked_by: blocked by
627 label_blocked_by: blocked by
628 label_precedes: precedes
628 label_precedes: precedes
629 label_follows: follows
629 label_follows: follows
630 label_end_to_start: end to start
630 label_end_to_start: end to start
631 label_end_to_end: end to end
631 label_end_to_end: end to end
632 label_start_to_start: start to start
632 label_start_to_start: start to start
633 label_start_to_end: start to end
633 label_start_to_end: start to end
634 label_stay_logged_in: Stay logged in
634 label_stay_logged_in: Stay logged in
635 label_disabled: disabled
635 label_disabled: disabled
636 label_show_completed_versions: Show completed versions
636 label_show_completed_versions: Show completed versions
637 label_me: me
637 label_me: me
638 label_board: Forum
638 label_board: Forum
639 label_board_new: New forum
639 label_board_new: New forum
640 label_board_plural: Forums
640 label_board_plural: Forums
641 label_topic_plural: Topics
641 label_topic_plural: Topics
642 label_message_plural: Messages
642 label_message_plural: Messages
643 label_message_last: Last message
643 label_message_last: Last message
644 label_message_new: New message
644 label_message_new: New message
645 label_message_posted: Message added
645 label_message_posted: Message added
646 label_reply_plural: Replies
646 label_reply_plural: Replies
647 label_send_information: Send account information to the user
647 label_send_information: Send account information to the user
648 label_year: Year
648 label_year: Year
649 label_month: Month
649 label_month: Month
650 label_week: Week
650 label_week: Week
651 label_date_from: From
651 label_date_from: From
652 label_date_to: To
652 label_date_to: To
653 label_language_based: Based on user's language
653 label_language_based: Based on user's language
654 label_sort_by: "Sort by {{value}}"
654 label_sort_by: "Sort by {{value}}"
655 label_send_test_email: Send a test email
655 label_send_test_email: Send a test email
656 label_feeds_access_key_created_on: "RSS access key created {{value}} ago"
656 label_feeds_access_key_created_on: "RSS access key created {{value}} ago"
657 label_module_plural: Modules
657 label_module_plural: Modules
658 label_added_time_by: "Added by {{author}} {{age}} ago"
658 label_added_time_by: "Added by {{author}} {{age}} ago"
659 label_updated_time_by: "Updated by {{author}} {{age}} ago"
659 label_updated_time_by: "Updated by {{author}} {{age}} ago"
660 label_updated_time: "Updated {{value}} ago"
660 label_updated_time: "Updated {{value}} ago"
661 label_jump_to_a_project: Jump to a project...
661 label_jump_to_a_project: Jump to a project...
662 label_file_plural: Files
662 label_file_plural: Files
663 label_changeset_plural: Changesets
663 label_changeset_plural: Changesets
664 label_default_columns: Default columns
664 label_default_columns: Default columns
665 label_no_change_option: (No change)
665 label_no_change_option: (No change)
666 label_bulk_edit_selected_issues: Bulk edit selected issues
666 label_bulk_edit_selected_issues: Bulk edit selected issues
667 label_theme: Theme
667 label_theme: Theme
668 label_default: Default
668 label_default: Default
669 label_search_titles_only: Search titles only
669 label_search_titles_only: Search titles only
670 label_user_mail_option_all: "For any event on all my projects"
670 label_user_mail_option_all: "For any event on all my projects"
671 label_user_mail_option_selected: "For any event on the selected projects only..."
671 label_user_mail_option_selected: "For any event on the selected projects only..."
672 label_user_mail_option_none: "Only for things I watch or I'm involved in"
672 label_user_mail_option_none: "Only for things I watch or I'm involved in"
673 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
673 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
674 label_registration_activation_by_email: account activation by email
674 label_registration_activation_by_email: account activation by email
675 label_registration_manual_activation: manual account activation
675 label_registration_manual_activation: manual account activation
676 label_registration_automatic_activation: automatic account activation
676 label_registration_automatic_activation: automatic account activation
677 label_display_per_page: "Per page: {{value}}"
677 label_display_per_page: "Per page: {{value}}"
678 label_age: Age
678 label_age: Age
679 label_change_properties: Change properties
679 label_change_properties: Change properties
680 label_general: General
680 label_general: General
681 label_more: More
681 label_more: More
682 label_scm: SCM
682 label_scm: SCM
683 label_plugins: Plugins
683 label_plugins: Plugins
684 label_ldap_authentication: LDAP authentication
684 label_ldap_authentication: LDAP authentication
685 label_downloads_abbr: D/L
685 label_downloads_abbr: D/L
686 label_optional_description: Optional description
686 label_optional_description: Optional description
687 label_add_another_file: Add another file
687 label_add_another_file: Add another file
688 label_preferences: Preferences
688 label_preferences: Preferences
689 label_chronological_order: In chronological order
689 label_chronological_order: In chronological order
690 label_reverse_chronological_order: In reverse chronological order
690 label_reverse_chronological_order: In reverse chronological order
691 label_planning: Planning
691 label_planning: Planning
692 label_incoming_emails: Incoming emails
692 label_incoming_emails: Incoming emails
693 label_generate_key: Generate a key
693 label_generate_key: Generate a key
694 label_issue_watchers: Watchers
694 label_issue_watchers: Watchers
695 label_example: Example
695 label_example: Example
696 label_display: Display
696 label_display: Display
697 label_sort: Sort
697 label_sort: Sort
698 label_ascending: Ascending
698 label_ascending: Ascending
699 label_descending: Descending
699 label_descending: Descending
700 label_date_from_to: From {{start}} to {{end}}
700 label_date_from_to: From {{start}} to {{end}}
701 label_wiki_content_added: Wiki page added
701 label_wiki_content_added: Wiki page added
702 label_wiki_content_updated: Wiki page updated
702 label_wiki_content_updated: Wiki page updated
703 label_group: Group
703 label_group: Group
704 label_group_plural: Groups
704 label_group_plural: Groups
705 label_group_new: New group
705 label_group_new: New group
706 label_time_entry_plural: Spent time
706 label_time_entry_plural: Spent time
707
707
708 button_login: Login
708 button_login: Login
709 button_submit: Submit
709 button_submit: Submit
710 button_save: Save
710 button_save: Save
711 button_check_all: Check all
711 button_check_all: Check all
712 button_uncheck_all: Uncheck all
712 button_uncheck_all: Uncheck all
713 button_delete: Delete
713 button_delete: Delete
714 button_create: Create
714 button_create: Create
715 button_create_and_continue: Create and continue
715 button_create_and_continue: Create and continue
716 button_test: Test
716 button_test: Test
717 button_edit: Edit
717 button_edit: Edit
718 button_add: Add
718 button_add: Add
719 button_change: Change
719 button_change: Change
720 button_apply: Apply
720 button_apply: Apply
721 button_clear: Clear
721 button_clear: Clear
722 button_lock: Lock
722 button_lock: Lock
723 button_unlock: Unlock
723 button_unlock: Unlock
724 button_download: Download
724 button_download: Download
725 button_list: List
725 button_list: List
726 button_view: View
726 button_view: View
727 button_move: Move
727 button_move: Move
728 button_back: Back
728 button_back: Back
729 button_cancel: Cancel
729 button_cancel: Cancel
730 button_activate: Activate
730 button_activate: Activate
731 button_sort: Sort
731 button_sort: Sort
732 button_log_time: Log time
732 button_log_time: Log time
733 button_rollback: Rollback to this version
733 button_rollback: Rollback to this version
734 button_watch: Watch
734 button_watch: Watch
735 button_unwatch: Unwatch
735 button_unwatch: Unwatch
736 button_reply: Reply
736 button_reply: Reply
737 button_archive: Archive
737 button_archive: Archive
738 button_unarchive: Unarchive
738 button_unarchive: Unarchive
739 button_reset: Reset
739 button_reset: Reset
740 button_rename: Rename
740 button_rename: Rename
741 button_change_password: Change password
741 button_change_password: Change password
742 button_copy: Copy
742 button_copy: Copy
743 button_annotate: Annotate
743 button_annotate: Annotate
744 button_update: Update
744 button_update: Update
745 button_configure: Configure
745 button_configure: Configure
746 button_quote: Quote
746 button_quote: Quote
747
747
748 status_active: active
748 status_active: active
749 status_registered: registered
749 status_registered: registered
750 status_locked: locked
750 status_locked: locked
751
751
752 field_active: Active
752 field_active: Active
753
753
754 text_select_mail_notifications: Select actions for which email notifications should be sent.
754 text_select_mail_notifications: Select actions for which email notifications should be sent.
755 text_regexp_info: eg. ^[A-Z0-9]+$
755 text_regexp_info: eg. ^[A-Z0-9]+$
756 text_min_max_length_info: 0 means no restriction
756 text_min_max_length_info: 0 means no restriction
757 text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
757 text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
758 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted."
758 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted."
759 text_workflow_edit: Select a role and a tracker to edit the workflow
759 text_workflow_edit: Select a role and a tracker to edit the workflow
760 text_are_you_sure: Are you sure ?
760 text_are_you_sure: Are you sure ?
761 text_journal_changed: "{{label}} changed from {{old}} to {{new}}"
761 text_journal_changed: "{{label}} changed from {{old}} to {{new}}"
762 text_journal_set_to: "{{label}} set to {{value}}"
762 text_journal_set_to: "{{label}} set to {{value}}"
763 text_journal_deleted: "{{label}} deleted ({{old}})"
763 text_journal_deleted: "{{label}} deleted ({{old}})"
764 text_journal_added: "{{label}} {{value}} added"
764 text_journal_added: "{{label}} {{value}} added"
765 text_tip_task_begin_day: task beginning this day
765 text_tip_task_begin_day: task beginning this day
766 text_tip_task_end_day: task ending this day
766 text_tip_task_end_day: task ending this day
767 text_tip_task_begin_end_day: task beginning and ending this day
767 text_tip_task_begin_end_day: task beginning and ending this day
768 text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.<br />Once saved, the identifier can not be changed.'
768 text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.<br />Once saved, the identifier can not be changed.'
769 text_caracters_maximum: "{{count}} characters maximum."
769 text_caracters_maximum: "{{count}} characters maximum."
770 text_caracters_minimum: "Must be at least {{count}} characters long."
770 text_caracters_minimum: "Must be at least {{count}} characters long."
771 text_length_between: "Length between {{min}} and {{max}} characters."
771 text_length_between: "Length between {{min}} and {{max}} characters."
772 text_tracker_no_workflow: No workflow defined for this tracker
772 text_tracker_no_workflow: No workflow defined for this tracker
773 text_unallowed_characters: Unallowed characters
773 text_unallowed_characters: Unallowed characters
774 text_comma_separated: Multiple values allowed (comma separated).
774 text_comma_separated: Multiple values allowed (comma separated).
775 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
775 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
776 text_issue_added: "Issue {{id}} has been reported by {{author}}."
776 text_issue_added: "Issue {{id}} has been reported by {{author}}."
777 text_issue_updated: "Issue {{id}} has been updated by {{author}}."
777 text_issue_updated: "Issue {{id}} has been updated by {{author}}."
778 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
778 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
779 text_issue_category_destroy_question: "Some issues ({{count}}) are assigned to this category. What do you want to do ?"
779 text_issue_category_destroy_question: "Some issues ({{count}}) are assigned to this category. What do you want to do ?"
780 text_issue_category_destroy_assignments: Remove category assignments
780 text_issue_category_destroy_assignments: Remove category assignments
781 text_issue_category_reassign_to: Reassign issues to this category
781 text_issue_category_reassign_to: Reassign issues to this category
782 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
782 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
783 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
783 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
784 text_load_default_configuration: Load the default configuration
784 text_load_default_configuration: Load the default configuration
785 text_status_changed_by_changeset: "Applied in changeset {{value}}."
785 text_status_changed_by_changeset: "Applied in changeset {{value}}."
786 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
786 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
787 text_select_project_modules: 'Select modules to enable for this project:'
787 text_select_project_modules: 'Select modules to enable for this project:'
788 text_default_administrator_account_changed: Default administrator account changed
788 text_default_administrator_account_changed: Default administrator account changed
789 text_file_repository_writable: Attachments directory writable
789 text_file_repository_writable: Attachments directory writable
790 text_plugin_assets_writable: Plugin assets directory writable
790 text_plugin_assets_writable: Plugin assets directory writable
791 text_rmagick_available: RMagick available (optional)
791 text_rmagick_available: RMagick available (optional)
792 text_destroy_time_entries_question: "{{hours}} hours were reported on the issues you are about to delete. What do you want to do ?"
792 text_destroy_time_entries_question: "{{hours}} hours were reported on the issues you are about to delete. What do you want to do ?"
793 text_destroy_time_entries: Delete reported hours
793 text_destroy_time_entries: Delete reported hours
794 text_assign_time_entries_to_project: Assign reported hours to the project
794 text_assign_time_entries_to_project: Assign reported hours to the project
795 text_reassign_time_entries: 'Reassign reported hours to this issue:'
795 text_reassign_time_entries: 'Reassign reported hours to this issue:'
796 text_user_wrote: "{{value}} wrote:"
796 text_user_wrote: "{{value}} wrote:"
797 text_enumeration_destroy_question: "{{count}} objects are assigned to this value."
797 text_enumeration_destroy_question: "{{count}} objects are assigned to this value."
798 text_enumeration_category_reassign_to: 'Reassign them to this value:'
798 text_enumeration_category_reassign_to: 'Reassign them to this value:'
799 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
799 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
800 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
800 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
801 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
801 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
802 text_custom_field_possible_values_info: 'One line for each value'
802 text_custom_field_possible_values_info: 'One line for each value'
803 text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?"
803 text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?"
804 text_wiki_page_nullify_children: "Keep child pages as root pages"
804 text_wiki_page_nullify_children: "Keep child pages as root pages"
805 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
805 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
806 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
806 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
807
807
808 default_role_manager: Manager
808 default_role_manager: Manager
809 default_role_developper: Developer
809 default_role_developper: Developer
810 default_role_reporter: Reporter
810 default_role_reporter: Reporter
811 default_tracker_bug: Bug
811 default_tracker_bug: Bug
812 default_tracker_feature: Feature
812 default_tracker_feature: Feature
813 default_tracker_support: Support
813 default_tracker_support: Support
814 default_issue_status_new: New
814 default_issue_status_new: New
815 default_issue_status_in_progress: In Progress
815 default_issue_status_in_progress: In Progress
816 default_issue_status_resolved: Resolved
816 default_issue_status_resolved: Resolved
817 default_issue_status_feedback: Feedback
817 default_issue_status_feedback: Feedback
818 default_issue_status_closed: Closed
818 default_issue_status_closed: Closed
819 default_issue_status_rejected: Rejected
819 default_issue_status_rejected: Rejected
820 default_doc_category_user: User documentation
820 default_doc_category_user: User documentation
821 default_doc_category_tech: Technical documentation
821 default_doc_category_tech: Technical documentation
822 default_priority_low: Low
822 default_priority_low: Low
823 default_priority_normal: Normal
823 default_priority_normal: Normal
824 default_priority_high: High
824 default_priority_high: High
825 default_priority_urgent: Urgent
825 default_priority_urgent: Urgent
826 default_priority_immediate: Immediate
826 default_priority_immediate: Immediate
827 default_activity_design: Design
827 default_activity_design: Design
828 default_activity_development: Development
828 default_activity_development: Development
829
829
830 enumeration_issue_priorities: Issue priorities
830 enumeration_issue_priorities: Issue priorities
831 enumeration_doc_categories: Document categories
831 enumeration_doc_categories: Document categories
832 enumeration_activities: Activities (time tracking)
832 enumeration_activities: Activities (time tracking)
833 enumeration_system_activity: System Activity
@@ -1,257 +1,262
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome'
9 map.home '', :controller => 'welcome'
10
10
11 map.signin 'login', :controller => 'account', :action => 'login'
11 map.signin 'login', :controller => 'account', :action => 'login'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
12 map.signout 'logout', :controller => 'account', :action => 'logout'
13
13
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
15 map.connect 'help/:ctrl/:page', :controller => 'help'
16
16
17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
20
20
21 map.with_options :controller => 'timelog' do |timelog|
21 map.with_options :controller => 'timelog' do |timelog|
22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
23
23
24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
25 time_details.connect 'time_entries'
25 time_details.connect 'time_entries'
26 time_details.connect 'time_entries.:format'
26 time_details.connect 'time_entries.:format'
27 time_details.connect 'issues/:issue_id/time_entries'
27 time_details.connect 'issues/:issue_id/time_entries'
28 time_details.connect 'issues/:issue_id/time_entries.:format'
28 time_details.connect 'issues/:issue_id/time_entries.:format'
29 time_details.connect 'projects/:project_id/time_entries.:format'
29 time_details.connect 'projects/:project_id/time_entries.:format'
30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
32 end
32 end
33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
35 time_report.connect 'time_entries/report'
35 time_report.connect 'time_entries/report'
36 time_report.connect 'time_entries/report.:format'
36 time_report.connect 'time_entries/report.:format'
37 time_report.connect 'projects/:project_id/time_entries/report.:format'
37 time_report.connect 'projects/:project_id/time_entries/report.:format'
38 end
38 end
39
39
40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
41 time_edit.connect 'issues/:issue_id/time_entries/new'
41 time_edit.connect 'issues/:issue_id/time_entries/new'
42 end
42 end
43
43
44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
45 end
45 end
46
46
47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
50 map.with_options :controller => 'wiki' do |wiki_routes|
50 map.with_options :controller => 'wiki' do |wiki_routes|
51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
59 end
59 end
60
60
61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
62 :action => /edit|rename|destroy|preview|protect/,
62 :action => /edit|rename|destroy|preview|protect/,
63 :conditions => {:method => :post}
63 :conditions => {:method => :post}
64 end
64 end
65
65
66 map.with_options :controller => 'messages' do |messages_routes|
66 map.with_options :controller => 'messages' do |messages_routes|
67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
71 end
71 end
72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
76 end
76 end
77 end
77 end
78
78
79 map.with_options :controller => 'boards' do |board_routes|
79 map.with_options :controller => 'boards' do |board_routes|
80 board_routes.with_options :conditions => {:method => :get} do |board_views|
80 board_routes.with_options :conditions => {:method => :get} do |board_views|
81 board_views.connect 'projects/:project_id/boards', :action => 'index'
81 board_views.connect 'projects/:project_id/boards', :action => 'index'
82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
86 end
86 end
87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
90 end
90 end
91 end
91 end
92
92
93 map.with_options :controller => 'documents' do |document_routes|
93 map.with_options :controller => 'documents' do |document_routes|
94 document_routes.with_options :conditions => {:method => :get} do |document_views|
94 document_routes.with_options :conditions => {:method => :get} do |document_views|
95 document_views.connect 'projects/:project_id/documents', :action => 'index'
95 document_views.connect 'projects/:project_id/documents', :action => 'index'
96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
97 document_views.connect 'documents/:id', :action => 'show'
97 document_views.connect 'documents/:id', :action => 'show'
98 document_views.connect 'documents/:id/edit', :action => 'edit'
98 document_views.connect 'documents/:id/edit', :action => 'edit'
99 end
99 end
100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
103 end
103 end
104 end
104 end
105
105
106 map.with_options :controller => 'issues' do |issues_routes|
106 map.with_options :controller => 'issues' do |issues_routes|
107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
108 issues_views.connect 'issues', :action => 'index'
108 issues_views.connect 'issues', :action => 'index'
109 issues_views.connect 'issues.:format', :action => 'index'
109 issues_views.connect 'issues.:format', :action => 'index'
110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
120 end
120 end
121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
122 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
122 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
123 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
123 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
124 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
124 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
125 end
125 end
126 issues_routes.connect 'issues/:action'
126 issues_routes.connect 'issues/:action'
127 end
127 end
128
128
129 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
129 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
130 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
130 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
131 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
131 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
132 end
132 end
133
133
134 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
134 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
135 reports.connect 'projects/:id/issues/report'
135 reports.connect 'projects/:id/issues/report'
136 reports.connect 'projects/:id/issues/report/:detail'
136 reports.connect 'projects/:id/issues/report/:detail'
137 end
137 end
138
138
139 map.with_options :controller => 'news' do |news_routes|
139 map.with_options :controller => 'news' do |news_routes|
140 news_routes.with_options :conditions => {:method => :get} do |news_views|
140 news_routes.with_options :conditions => {:method => :get} do |news_views|
141 news_views.connect 'news', :action => 'index'
141 news_views.connect 'news', :action => 'index'
142 news_views.connect 'projects/:project_id/news', :action => 'index'
142 news_views.connect 'projects/:project_id/news', :action => 'index'
143 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
143 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
144 news_views.connect 'news.:format', :action => 'index'
144 news_views.connect 'news.:format', :action => 'index'
145 news_views.connect 'projects/:project_id/news/new', :action => 'new'
145 news_views.connect 'projects/:project_id/news/new', :action => 'new'
146 news_views.connect 'news/:id', :action => 'show'
146 news_views.connect 'news/:id', :action => 'show'
147 news_views.connect 'news/:id/edit', :action => 'edit'
147 news_views.connect 'news/:id/edit', :action => 'edit'
148 end
148 end
149 news_routes.with_options do |news_actions|
149 news_routes.with_options do |news_actions|
150 news_actions.connect 'projects/:project_id/news', :action => 'new'
150 news_actions.connect 'projects/:project_id/news', :action => 'new'
151 news_actions.connect 'news/:id/edit', :action => 'edit'
151 news_actions.connect 'news/:id/edit', :action => 'edit'
152 news_actions.connect 'news/:id/destroy', :action => 'destroy'
152 news_actions.connect 'news/:id/destroy', :action => 'destroy'
153 end
153 end
154 end
154 end
155
155
156 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
156 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
157
157
158 map.with_options :controller => 'users' do |users|
158 map.with_options :controller => 'users' do |users|
159 users.with_options :conditions => {:method => :get} do |user_views|
159 users.with_options :conditions => {:method => :get} do |user_views|
160 user_views.connect 'users', :action => 'list'
160 user_views.connect 'users', :action => 'list'
161 user_views.connect 'users', :action => 'index'
161 user_views.connect 'users', :action => 'index'
162 user_views.connect 'users/new', :action => 'add'
162 user_views.connect 'users/new', :action => 'add'
163 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
163 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
164 end
164 end
165 users.with_options :conditions => {:method => :post} do |user_actions|
165 users.with_options :conditions => {:method => :post} do |user_actions|
166 user_actions.connect 'users', :action => 'add'
166 user_actions.connect 'users', :action => 'add'
167 user_actions.connect 'users/new', :action => 'add'
167 user_actions.connect 'users/new', :action => 'add'
168 user_actions.connect 'users/:id/edit', :action => 'edit'
168 user_actions.connect 'users/:id/edit', :action => 'edit'
169 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
169 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
170 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
170 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
171 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
171 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
172 end
172 end
173 end
173 end
174
174
175 map.with_options :controller => 'projects' do |projects|
175 map.with_options :controller => 'projects' do |projects|
176 projects.with_options :conditions => {:method => :get} do |project_views|
176 projects.with_options :conditions => {:method => :get} do |project_views|
177 project_views.connect 'projects', :action => 'index'
177 project_views.connect 'projects', :action => 'index'
178 project_views.connect 'projects.:format', :action => 'index'
178 project_views.connect 'projects.:format', :action => 'index'
179 project_views.connect 'projects/new', :action => 'add'
179 project_views.connect 'projects/new', :action => 'add'
180 project_views.connect 'projects/:id', :action => 'show'
180 project_views.connect 'projects/:id', :action => 'show'
181 project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings/
181 project_views.connect 'projects/:id/:action', :action => /roadmap|changelog|destroy|settings/
182 project_views.connect 'projects/:id/files', :action => 'list_files'
182 project_views.connect 'projects/:id/files', :action => 'list_files'
183 project_views.connect 'projects/:id/files/new', :action => 'add_file'
183 project_views.connect 'projects/:id/files/new', :action => 'add_file'
184 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
184 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
185 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
185 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
186 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
186 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
187 end
187 end
188
188
189 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
189 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
190 activity.connect 'projects/:id/activity'
190 activity.connect 'projects/:id/activity'
191 activity.connect 'projects/:id/activity.:format'
191 activity.connect 'projects/:id/activity.:format'
192 activity.connect 'activity', :id => nil
192 activity.connect 'activity', :id => nil
193 activity.connect 'activity.:format', :id => nil
193 activity.connect 'activity.:format', :id => nil
194 end
194 end
195
195
196 projects.with_options :conditions => {:method => :post} do |project_actions|
196 projects.with_options :conditions => {:method => :post} do |project_actions|
197 project_actions.connect 'projects/new', :action => 'add'
197 project_actions.connect 'projects/new', :action => 'add'
198 project_actions.connect 'projects', :action => 'add'
198 project_actions.connect 'projects', :action => 'add'
199 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
199 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
200 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
200 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
201 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
201 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
202 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
202 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
203 project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
204 end
205
206 projects.with_options :conditions => {:method => :delete} do |project_actions|
207 project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
203 end
208 end
204 end
209 end
205
210
206 map.with_options :controller => 'repositories' do |repositories|
211 map.with_options :controller => 'repositories' do |repositories|
207 repositories.with_options :conditions => {:method => :get} do |repository_views|
212 repositories.with_options :conditions => {:method => :get} do |repository_views|
208 repository_views.connect 'projects/:id/repository', :action => 'show'
213 repository_views.connect 'projects/:id/repository', :action => 'show'
209 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
214 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
210 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
215 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
211 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
216 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
212 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
217 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
213 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
218 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
214 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
219 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
215 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
220 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
216 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
221 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
217 repository_views.connect 'projects/:id/repository/:action/*path'
222 repository_views.connect 'projects/:id/repository/:action/*path'
218 end
223 end
219
224
220 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
225 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
221 end
226 end
222
227
223 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
228 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
224 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
229 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
225 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
230 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
226
231
227 map.resources :groups
232 map.resources :groups
228
233
229 #left old routes at the bottom for backwards compat
234 #left old routes at the bottom for backwards compat
230 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
235 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
231 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
236 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
232 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
237 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
233 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
238 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
234 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
239 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
235 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
240 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
236 map.connect 'projects/:project_id/news/:action', :controller => 'news'
241 map.connect 'projects/:project_id/news/:action', :controller => 'news'
237 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
242 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
238 map.with_options :controller => 'repositories' do |omap|
243 map.with_options :controller => 'repositories' do |omap|
239 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
244 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
240 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
245 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
241 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
246 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
242 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
247 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
243 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
248 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
244 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
249 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
245 end
250 end
246
251
247 map.with_options :controller => 'sys' do |sys|
252 map.with_options :controller => 'sys' do |sys|
248 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
253 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
249 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
254 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
250 end
255 end
251
256
252 # Install the default route as the lowest priority.
257 # Install the default route as the lowest priority.
253 map.connect ':controller/:action/:id'
258 map.connect ':controller/:action/:id'
254 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
259 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
255 # Used for OpenID
260 # Used for OpenID
256 map.root :controller => 'account', :action => 'login'
261 map.root :controller => 'account', :action => 'login'
257 end
262 end
@@ -1,165 +1,166
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/activity'
3 require 'redmine/activity'
4 require 'redmine/mime_type'
4 require 'redmine/mime_type'
5 require 'redmine/core_ext'
5 require 'redmine/core_ext'
6 require 'redmine/themes'
6 require 'redmine/themes'
7 require 'redmine/hook'
7 require 'redmine/hook'
8 require 'redmine/plugin'
8 require 'redmine/plugin'
9 require 'redmine/wiki_formatting'
9 require 'redmine/wiki_formatting'
10
10
11 begin
11 begin
12 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
12 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
13 rescue LoadError
13 rescue LoadError
14 # RMagick is not available
14 # RMagick is not available
15 end
15 end
16
16
17 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
17 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
18
18
19 # Permissions
19 # Permissions
20 Redmine::AccessControl.map do |map|
20 Redmine::AccessControl.map do |map|
21 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
21 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
22 map.permission :search_project, {:search => :index}, :public => true
22 map.permission :search_project, {:search => :index}, :public => true
23 map.permission :add_project, {:projects => :add}, :require => :loggedin
23 map.permission :add_project, {:projects => :add}, :require => :loggedin
24 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
24 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
25 map.permission :select_project_modules, {:projects => :modules}, :require => :member
25 map.permission :select_project_modules, {:projects => :modules}, :require => :member
26 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
26 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
27 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
27 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :destroy]}, :require => :member
28
28
29 map.project_module :issue_tracking do |map|
29 map.project_module :issue_tracking do |map|
30 # Issue categories
30 # Issue categories
31 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
31 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
32 # Issues
32 # Issues
33 map.permission :view_issues, {:projects => [:changelog, :roadmap],
33 map.permission :view_issues, {:projects => [:changelog, :roadmap],
34 :issues => [:index, :changes, :show, :context_menu],
34 :issues => [:index, :changes, :show, :context_menu],
35 :versions => [:show, :status_by],
35 :versions => [:show, :status_by],
36 :queries => :index,
36 :queries => :index,
37 :reports => :issue_report}, :public => true
37 :reports => :issue_report}, :public => true
38 map.permission :add_issues, {:issues => :new}
38 map.permission :add_issues, {:issues => :new}
39 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
39 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
40 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
40 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
41 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
41 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
42 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
42 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
43 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
43 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
44 map.permission :move_issues, {:issues => :move}, :require => :loggedin
44 map.permission :move_issues, {:issues => :move}, :require => :loggedin
45 map.permission :delete_issues, {:issues => :destroy}, :require => :member
45 map.permission :delete_issues, {:issues => :destroy}, :require => :member
46 # Queries
46 # Queries
47 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
47 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
48 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
48 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
49 # Gantt & calendar
49 # Gantt & calendar
50 map.permission :view_gantt, :issues => :gantt
50 map.permission :view_gantt, :issues => :gantt
51 map.permission :view_calendar, :issues => :calendar
51 map.permission :view_calendar, :issues => :calendar
52 # Watchers
52 # Watchers
53 map.permission :view_issue_watchers, {}
53 map.permission :view_issue_watchers, {}
54 map.permission :add_issue_watchers, {:watchers => :new}
54 map.permission :add_issue_watchers, {:watchers => :new}
55 end
55 end
56
56
57 map.project_module :time_tracking do |map|
57 map.project_module :time_tracking do |map|
58 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
58 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
59 map.permission :view_time_entries, :timelog => [:details, :report]
59 map.permission :view_time_entries, :timelog => [:details, :report]
60 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
60 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
61 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
61 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
62 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
62 end
63 end
63
64
64 map.project_module :news do |map|
65 map.project_module :news do |map|
65 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
66 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
66 map.permission :view_news, {:news => [:index, :show]}, :public => true
67 map.permission :view_news, {:news => [:index, :show]}, :public => true
67 map.permission :comment_news, {:news => :add_comment}
68 map.permission :comment_news, {:news => :add_comment}
68 end
69 end
69
70
70 map.project_module :documents do |map|
71 map.project_module :documents do |map|
71 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
72 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
72 map.permission :view_documents, :documents => [:index, :show, :download]
73 map.permission :view_documents, :documents => [:index, :show, :download]
73 end
74 end
74
75
75 map.project_module :files do |map|
76 map.project_module :files do |map|
76 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
77 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
77 map.permission :view_files, :projects => :list_files, :versions => :download
78 map.permission :view_files, :projects => :list_files, :versions => :download
78 end
79 end
79
80
80 map.project_module :wiki do |map|
81 map.project_module :wiki do |map|
81 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
82 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
82 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
83 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
83 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
84 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
84 map.permission :view_wiki_pages, :wiki => [:index, :special]
85 map.permission :view_wiki_pages, :wiki => [:index, :special]
85 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
86 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
86 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
87 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
87 map.permission :delete_wiki_pages_attachments, {}
88 map.permission :delete_wiki_pages_attachments, {}
88 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
89 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
89 end
90 end
90
91
91 map.project_module :repository do |map|
92 map.project_module :repository do |map|
92 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
93 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
93 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
94 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
94 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
95 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
95 map.permission :commit_access, {}
96 map.permission :commit_access, {}
96 end
97 end
97
98
98 map.project_module :boards do |map|
99 map.project_module :boards do |map|
99 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
100 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
100 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
101 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
101 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
102 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
102 map.permission :edit_messages, {:messages => :edit}, :require => :member
103 map.permission :edit_messages, {:messages => :edit}, :require => :member
103 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
104 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
104 map.permission :delete_messages, {:messages => :destroy}, :require => :member
105 map.permission :delete_messages, {:messages => :destroy}, :require => :member
105 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
106 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
106 end
107 end
107 end
108 end
108
109
109 Redmine::MenuManager.map :top_menu do |menu|
110 Redmine::MenuManager.map :top_menu do |menu|
110 menu.push :home, :home_path
111 menu.push :home, :home_path
111 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
112 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
112 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
113 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
113 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
114 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
114 menu.push :help, Redmine::Info.help_url, :last => true
115 menu.push :help, Redmine::Info.help_url, :last => true
115 end
116 end
116
117
117 Redmine::MenuManager.map :account_menu do |menu|
118 Redmine::MenuManager.map :account_menu do |menu|
118 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
119 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
119 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
120 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
120 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
121 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
121 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
122 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
122 end
123 end
123
124
124 Redmine::MenuManager.map :application_menu do |menu|
125 Redmine::MenuManager.map :application_menu do |menu|
125 # Empty
126 # Empty
126 end
127 end
127
128
128 Redmine::MenuManager.map :admin_menu do |menu|
129 Redmine::MenuManager.map :admin_menu do |menu|
129 # Empty
130 # Empty
130 end
131 end
131
132
132 Redmine::MenuManager.map :project_menu do |menu|
133 Redmine::MenuManager.map :project_menu do |menu|
133 menu.push :overview, { :controller => 'projects', :action => 'show' }
134 menu.push :overview, { :controller => 'projects', :action => 'show' }
134 menu.push :activity, { :controller => 'projects', :action => 'activity' }
135 menu.push :activity, { :controller => 'projects', :action => 'activity' }
135 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
136 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
136 :if => Proc.new { |p| p.versions.any? }
137 :if => Proc.new { |p| p.versions.any? }
137 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
138 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
138 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
139 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
139 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
140 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
140 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
141 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
141 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
142 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
142 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
143 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
143 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
144 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
144 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
145 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
145 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
146 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
146 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
147 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
147 menu.push :repository, { :controller => 'repositories', :action => 'show' },
148 menu.push :repository, { :controller => 'repositories', :action => 'show' },
148 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
149 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
149 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
150 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
150 end
151 end
151
152
152 Redmine::Activity.map do |activity|
153 Redmine::Activity.map do |activity|
153 activity.register :issues, :class_name => %w(Issue Journal)
154 activity.register :issues, :class_name => %w(Issue Journal)
154 activity.register :changesets
155 activity.register :changesets
155 activity.register :news
156 activity.register :news
156 activity.register :documents, :class_name => %w(Document Attachment)
157 activity.register :documents, :class_name => %w(Document Attachment)
157 activity.register :files, :class_name => 'Attachment'
158 activity.register :files, :class_name => 'Attachment'
158 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
159 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
159 activity.register :messages, :default => false
160 activity.register :messages, :default => false
160 activity.register :time_entries, :default => false
161 activity.register :time_entries, :default => false
161 end
162 end
162
163
163 Redmine::WikiFormatting.map do |format|
164 Redmine::WikiFormatting.map do |format|
164 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
165 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
165 end
166 end
@@ -1,91 +1,97
1 ---
1 ---
2 custom_values_006:
2 custom_values_006:
3 customized_type: Issue
3 customized_type: Issue
4 custom_field_id: 2
4 custom_field_id: 2
5 customized_id: 3
5 customized_id: 3
6 id: 6
6 id: 6
7 value: "125"
7 value: "125"
8 custom_values_007:
8 custom_values_007:
9 customized_type: Project
9 customized_type: Project
10 custom_field_id: 3
10 custom_field_id: 3
11 customized_id: 1
11 customized_id: 1
12 id: 7
12 id: 7
13 value: Stable
13 value: Stable
14 custom_values_001:
14 custom_values_001:
15 customized_type: Principal
15 customized_type: Principal
16 custom_field_id: 4
16 custom_field_id: 4
17 customized_id: 3
17 customized_id: 3
18 id: 1
18 id: 1
19 value: ""
19 value: ""
20 custom_values_002:
20 custom_values_002:
21 customized_type: Principal
21 customized_type: Principal
22 custom_field_id: 4
22 custom_field_id: 4
23 customized_id: 4
23 customized_id: 4
24 id: 2
24 id: 2
25 value: 01 23 45 67 89
25 value: 01 23 45 67 89
26 custom_values_003:
26 custom_values_003:
27 customized_type: Principal
27 customized_type: Principal
28 custom_field_id: 4
28 custom_field_id: 4
29 customized_id: 2
29 customized_id: 2
30 id: 3
30 id: 3
31 value: ""
31 value: ""
32 custom_values_004:
32 custom_values_004:
33 customized_type: Issue
33 customized_type: Issue
34 custom_field_id: 2
34 custom_field_id: 2
35 customized_id: 1
35 customized_id: 1
36 id: 4
36 id: 4
37 value: "125"
37 value: "125"
38 custom_values_005:
38 custom_values_005:
39 customized_type: Issue
39 customized_type: Issue
40 custom_field_id: 2
40 custom_field_id: 2
41 customized_id: 2
41 customized_id: 2
42 id: 5
42 id: 5
43 value: ""
43 value: ""
44 custom_values_008:
44 custom_values_008:
45 customized_type: Issue
45 customized_type: Issue
46 custom_field_id: 1
46 custom_field_id: 1
47 customized_id: 3
47 customized_id: 3
48 id: 8
48 id: 8
49 value: "MySQL"
49 value: "MySQL"
50 custom_values_009:
50 custom_values_009:
51 customized_type: Issue
51 customized_type: Issue
52 custom_field_id: 2
52 custom_field_id: 2
53 customized_id: 3
53 customized_id: 3
54 id: 9
54 id: 9
55 value: "this is a stringforcustomfield search"
55 value: "this is a stringforcustomfield search"
56 custom_values_010:
56 custom_values_010:
57 customized_type: Issue
57 customized_type: Issue
58 custom_field_id: 6
58 custom_field_id: 6
59 customized_id: 1
59 customized_id: 1
60 id: 10
60 id: 10
61 value: "2.1"
61 value: "2.1"
62 custom_values_011:
62 custom_values_011:
63 customized_type: Issue
63 customized_type: Issue
64 custom_field_id: 6
64 custom_field_id: 6
65 customized_id: 2
65 customized_id: 2
66 id: 11
66 id: 11
67 value: "2.05"
67 value: "2.05"
68 custom_values_012:
68 custom_values_012:
69 customized_type: Issue
69 customized_type: Issue
70 custom_field_id: 6
70 custom_field_id: 6
71 customized_id: 3
71 customized_id: 3
72 id: 12
72 id: 12
73 value: "11.65"
73 value: "11.65"
74 custom_values_013:
74 custom_values_013:
75 customized_type: Issue
75 customized_type: Issue
76 custom_field_id: 6
76 custom_field_id: 6
77 customized_id: 7
77 customized_id: 7
78 id: 13
78 id: 13
79 value: ""
79 value: ""
80 custom_values_014:
80 custom_values_014:
81 customized_type: Issue
81 customized_type: Issue
82 custom_field_id: 6
82 custom_field_id: 6
83 customized_id: 5
83 customized_id: 5
84 id: 14
84 id: 14
85 value: "-7.6"
85 value: "-7.6"
86 custom_values_015:
86 custom_values_015:
87 customized_type: TimeEntryActivity
87 customized_type: TimeEntryActivity
88 custom_field_id: 7
88 custom_field_id: 7
89 customized_id: 10
89 customized_id: 10
90 id: 15
90 id: 15
91 value: true
91 value: true
92 custom_values_016:
93 customized_type: Enumeration
94 custom_field_id: 7
95 customized_id: 11
96 id: 16
97 value: true
@@ -1,177 +1,178
1 ---
1 ---
2 roles_001:
2 roles_001:
3 name: Manager
3 name: Manager
4 id: 1
4 id: 1
5 builtin: 0
5 builtin: 0
6 permissions: |
6 permissions: |
7 ---
7 ---
8 - :add_project
8 - :add_project
9 - :edit_project
9 - :edit_project
10 - :manage_members
10 - :manage_members
11 - :manage_versions
11 - :manage_versions
12 - :manage_categories
12 - :manage_categories
13 - :add_issues
13 - :add_issues
14 - :edit_issues
14 - :edit_issues
15 - :manage_issue_relations
15 - :manage_issue_relations
16 - :add_issue_notes
16 - :add_issue_notes
17 - :move_issues
17 - :move_issues
18 - :delete_issues
18 - :delete_issues
19 - :view_issue_watchers
19 - :view_issue_watchers
20 - :add_issue_watchers
20 - :add_issue_watchers
21 - :manage_public_queries
21 - :manage_public_queries
22 - :save_queries
22 - :save_queries
23 - :view_gantt
23 - :view_gantt
24 - :view_calendar
24 - :view_calendar
25 - :log_time
25 - :log_time
26 - :view_time_entries
26 - :view_time_entries
27 - :edit_time_entries
27 - :edit_time_entries
28 - :delete_time_entries
28 - :delete_time_entries
29 - :manage_news
29 - :manage_news
30 - :comment_news
30 - :comment_news
31 - :view_documents
31 - :view_documents
32 - :manage_documents
32 - :manage_documents
33 - :view_wiki_pages
33 - :view_wiki_pages
34 - :view_wiki_edits
34 - :view_wiki_edits
35 - :edit_wiki_pages
35 - :edit_wiki_pages
36 - :delete_wiki_pages_attachments
36 - :delete_wiki_pages_attachments
37 - :protect_wiki_pages
37 - :protect_wiki_pages
38 - :delete_wiki_pages
38 - :delete_wiki_pages
39 - :rename_wiki_pages
39 - :rename_wiki_pages
40 - :add_messages
40 - :add_messages
41 - :edit_messages
41 - :edit_messages
42 - :delete_messages
42 - :delete_messages
43 - :manage_boards
43 - :manage_boards
44 - :view_files
44 - :view_files
45 - :manage_files
45 - :manage_files
46 - :browse_repository
46 - :browse_repository
47 - :manage_repository
47 - :manage_repository
48 - :view_changesets
48 - :view_changesets
49 - :manage_project_activities
49
50
50 position: 1
51 position: 1
51 roles_002:
52 roles_002:
52 name: Developer
53 name: Developer
53 id: 2
54 id: 2
54 builtin: 0
55 builtin: 0
55 permissions: |
56 permissions: |
56 ---
57 ---
57 - :edit_project
58 - :edit_project
58 - :manage_members
59 - :manage_members
59 - :manage_versions
60 - :manage_versions
60 - :manage_categories
61 - :manage_categories
61 - :add_issues
62 - :add_issues
62 - :edit_issues
63 - :edit_issues
63 - :manage_issue_relations
64 - :manage_issue_relations
64 - :add_issue_notes
65 - :add_issue_notes
65 - :move_issues
66 - :move_issues
66 - :delete_issues
67 - :delete_issues
67 - :view_issue_watchers
68 - :view_issue_watchers
68 - :save_queries
69 - :save_queries
69 - :view_gantt
70 - :view_gantt
70 - :view_calendar
71 - :view_calendar
71 - :log_time
72 - :log_time
72 - :view_time_entries
73 - :view_time_entries
73 - :edit_own_time_entries
74 - :edit_own_time_entries
74 - :manage_news
75 - :manage_news
75 - :comment_news
76 - :comment_news
76 - :view_documents
77 - :view_documents
77 - :manage_documents
78 - :manage_documents
78 - :view_wiki_pages
79 - :view_wiki_pages
79 - :view_wiki_edits
80 - :view_wiki_edits
80 - :edit_wiki_pages
81 - :edit_wiki_pages
81 - :protect_wiki_pages
82 - :protect_wiki_pages
82 - :delete_wiki_pages
83 - :delete_wiki_pages
83 - :add_messages
84 - :add_messages
84 - :edit_own_messages
85 - :edit_own_messages
85 - :delete_own_messages
86 - :delete_own_messages
86 - :manage_boards
87 - :manage_boards
87 - :view_files
88 - :view_files
88 - :manage_files
89 - :manage_files
89 - :browse_repository
90 - :browse_repository
90 - :view_changesets
91 - :view_changesets
91
92
92 position: 2
93 position: 2
93 roles_003:
94 roles_003:
94 name: Reporter
95 name: Reporter
95 id: 3
96 id: 3
96 builtin: 0
97 builtin: 0
97 permissions: |
98 permissions: |
98 ---
99 ---
99 - :edit_project
100 - :edit_project
100 - :manage_members
101 - :manage_members
101 - :manage_versions
102 - :manage_versions
102 - :manage_categories
103 - :manage_categories
103 - :add_issues
104 - :add_issues
104 - :edit_issues
105 - :edit_issues
105 - :manage_issue_relations
106 - :manage_issue_relations
106 - :add_issue_notes
107 - :add_issue_notes
107 - :move_issues
108 - :move_issues
108 - :view_issue_watchers
109 - :view_issue_watchers
109 - :save_queries
110 - :save_queries
110 - :view_gantt
111 - :view_gantt
111 - :view_calendar
112 - :view_calendar
112 - :log_time
113 - :log_time
113 - :view_time_entries
114 - :view_time_entries
114 - :manage_news
115 - :manage_news
115 - :comment_news
116 - :comment_news
116 - :view_documents
117 - :view_documents
117 - :manage_documents
118 - :manage_documents
118 - :view_wiki_pages
119 - :view_wiki_pages
119 - :view_wiki_edits
120 - :view_wiki_edits
120 - :edit_wiki_pages
121 - :edit_wiki_pages
121 - :delete_wiki_pages
122 - :delete_wiki_pages
122 - :add_messages
123 - :add_messages
123 - :manage_boards
124 - :manage_boards
124 - :view_files
125 - :view_files
125 - :manage_files
126 - :manage_files
126 - :browse_repository
127 - :browse_repository
127 - :view_changesets
128 - :view_changesets
128
129
129 position: 3
130 position: 3
130 roles_004:
131 roles_004:
131 name: Non member
132 name: Non member
132 id: 4
133 id: 4
133 builtin: 1
134 builtin: 1
134 permissions: |
135 permissions: |
135 ---
136 ---
136 - :add_issues
137 - :add_issues
137 - :edit_issues
138 - :edit_issues
138 - :manage_issue_relations
139 - :manage_issue_relations
139 - :add_issue_notes
140 - :add_issue_notes
140 - :move_issues
141 - :move_issues
141 - :save_queries
142 - :save_queries
142 - :view_gantt
143 - :view_gantt
143 - :view_calendar
144 - :view_calendar
144 - :log_time
145 - :log_time
145 - :view_time_entries
146 - :view_time_entries
146 - :comment_news
147 - :comment_news
147 - :view_documents
148 - :view_documents
148 - :manage_documents
149 - :manage_documents
149 - :view_wiki_pages
150 - :view_wiki_pages
150 - :view_wiki_edits
151 - :view_wiki_edits
151 - :edit_wiki_pages
152 - :edit_wiki_pages
152 - :add_messages
153 - :add_messages
153 - :view_files
154 - :view_files
154 - :manage_files
155 - :manage_files
155 - :browse_repository
156 - :browse_repository
156 - :view_changesets
157 - :view_changesets
157
158
158 position: 4
159 position: 4
159 roles_005:
160 roles_005:
160 name: Anonymous
161 name: Anonymous
161 id: 5
162 id: 5
162 builtin: 2
163 builtin: 2
163 permissions: |
164 permissions: |
164 ---
165 ---
165 - :add_issue_notes
166 - :add_issue_notes
166 - :view_gantt
167 - :view_gantt
167 - :view_calendar
168 - :view_calendar
168 - :view_time_entries
169 - :view_time_entries
169 - :view_documents
170 - :view_documents
170 - :view_wiki_pages
171 - :view_wiki_pages
171 - :view_wiki_edits
172 - :view_wiki_edits
172 - :view_files
173 - :view_files
173 - :browse_repository
174 - :browse_repository
174 - :view_changesets
175 - :view_changesets
175
176
176 position: 5
177 position: 5
177
178
@@ -1,577 +1,704
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
27 :attachments, :custom_fields, :custom_values
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 end
120 end
121
121
122 def test_post_add_by_non_admin
122 def test_post_add_by_non_admin
123 @request.session[:user_id] = 2
123 @request.session[:user_id] = 2
124 post :add, :project => { :name => "blog",
124 post :add, :project => { :name => "blog",
125 :description => "weblog",
125 :description => "weblog",
126 :identifier => "blog",
126 :identifier => "blog",
127 :is_public => 1,
127 :is_public => 1,
128 :custom_field_values => { '3' => 'Beta' }
128 :custom_field_values => { '3' => 'Beta' }
129 }
129 }
130 assert_redirected_to '/projects/blog/settings'
130 assert_redirected_to '/projects/blog/settings'
131
131
132 project = Project.find_by_name('blog')
132 project = Project.find_by_name('blog')
133 assert_kind_of Project, project
133 assert_kind_of Project, project
134 assert_equal 'weblog', project.description
134 assert_equal 'weblog', project.description
135 assert_equal true, project.is_public?
135 assert_equal true, project.is_public?
136
136
137 # User should be added as a project member
137 # User should be added as a project member
138 assert User.find(2).member_of?(project)
138 assert User.find(2).member_of?(project)
139 assert_equal 1, project.members.size
139 assert_equal 1, project.members.size
140 end
140 end
141
141
142 def test_show_routing
142 def test_show_routing
143 assert_routing(
143 assert_routing(
144 {:method => :get, :path => '/projects/test'},
144 {:method => :get, :path => '/projects/test'},
145 :controller => 'projects', :action => 'show', :id => 'test'
145 :controller => 'projects', :action => 'show', :id => 'test'
146 )
146 )
147 end
147 end
148
148
149 def test_show_by_id
149 def test_show_by_id
150 get :show, :id => 1
150 get :show, :id => 1
151 assert_response :success
151 assert_response :success
152 assert_template 'show'
152 assert_template 'show'
153 assert_not_nil assigns(:project)
153 assert_not_nil assigns(:project)
154 end
154 end
155
155
156 def test_show_by_identifier
156 def test_show_by_identifier
157 get :show, :id => 'ecookbook'
157 get :show, :id => 'ecookbook'
158 assert_response :success
158 assert_response :success
159 assert_template 'show'
159 assert_template 'show'
160 assert_not_nil assigns(:project)
160 assert_not_nil assigns(:project)
161 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
161 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
162 end
162 end
163
163
164 def test_show_should_not_fail_when_custom_values_are_nil
164 def test_show_should_not_fail_when_custom_values_are_nil
165 project = Project.find_by_identifier('ecookbook')
165 project = Project.find_by_identifier('ecookbook')
166 project.custom_values.first.update_attribute(:value, nil)
166 project.custom_values.first.update_attribute(:value, nil)
167 get :show, :id => 'ecookbook'
167 get :show, :id => 'ecookbook'
168 assert_response :success
168 assert_response :success
169 assert_template 'show'
169 assert_template 'show'
170 assert_not_nil assigns(:project)
170 assert_not_nil assigns(:project)
171 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
171 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
172 end
172 end
173
173
174 def test_private_subprojects_hidden
174 def test_private_subprojects_hidden
175 get :show, :id => 'ecookbook'
175 get :show, :id => 'ecookbook'
176 assert_response :success
176 assert_response :success
177 assert_template 'show'
177 assert_template 'show'
178 assert_no_tag :tag => 'a', :content => /Private child/
178 assert_no_tag :tag => 'a', :content => /Private child/
179 end
179 end
180
180
181 def test_private_subprojects_visible
181 def test_private_subprojects_visible
182 @request.session[:user_id] = 2 # manager who is a member of the private subproject
182 @request.session[:user_id] = 2 # manager who is a member of the private subproject
183 get :show, :id => 'ecookbook'
183 get :show, :id => 'ecookbook'
184 assert_response :success
184 assert_response :success
185 assert_template 'show'
185 assert_template 'show'
186 assert_tag :tag => 'a', :content => /Private child/
186 assert_tag :tag => 'a', :content => /Private child/
187 end
187 end
188
188
189 def test_settings_routing
189 def test_settings_routing
190 assert_routing(
190 assert_routing(
191 {:method => :get, :path => '/projects/4223/settings'},
191 {:method => :get, :path => '/projects/4223/settings'},
192 :controller => 'projects', :action => 'settings', :id => '4223'
192 :controller => 'projects', :action => 'settings', :id => '4223'
193 )
193 )
194 assert_routing(
194 assert_routing(
195 {:method => :get, :path => '/projects/4223/settings/members'},
195 {:method => :get, :path => '/projects/4223/settings/members'},
196 :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
196 :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
197 )
197 )
198 end
198 end
199
199
200 def test_settings
200 def test_settings
201 @request.session[:user_id] = 2 # manager
201 @request.session[:user_id] = 2 # manager
202 get :settings, :id => 1
202 get :settings, :id => 1
203 assert_response :success
203 assert_response :success
204 assert_template 'settings'
204 assert_template 'settings'
205 end
205 end
206
206
207 def test_edit
207 def test_edit
208 @request.session[:user_id] = 2 # manager
208 @request.session[:user_id] = 2 # manager
209 post :edit, :id => 1, :project => {:name => 'Test changed name',
209 post :edit, :id => 1, :project => {:name => 'Test changed name',
210 :issue_custom_field_ids => ['']}
210 :issue_custom_field_ids => ['']}
211 assert_redirected_to 'projects/ecookbook/settings'
211 assert_redirected_to 'projects/ecookbook/settings'
212 project = Project.find(1)
212 project = Project.find(1)
213 assert_equal 'Test changed name', project.name
213 assert_equal 'Test changed name', project.name
214 end
214 end
215
215
216 def test_add_version_routing
216 def test_add_version_routing
217 assert_routing(
217 assert_routing(
218 {:method => :get, :path => 'projects/64/versions/new'},
218 {:method => :get, :path => 'projects/64/versions/new'},
219 :controller => 'projects', :action => 'add_version', :id => '64'
219 :controller => 'projects', :action => 'add_version', :id => '64'
220 )
220 )
221 assert_routing(
221 assert_routing(
222 #TODO: use PUT
222 #TODO: use PUT
223 {:method => :post, :path => 'projects/64/versions/new'},
223 {:method => :post, :path => 'projects/64/versions/new'},
224 :controller => 'projects', :action => 'add_version', :id => '64'
224 :controller => 'projects', :action => 'add_version', :id => '64'
225 )
225 )
226 end
226 end
227
227
228 def test_add_issue_category_routing
228 def test_add_issue_category_routing
229 assert_routing(
229 assert_routing(
230 {:method => :get, :path => 'projects/test/categories/new'},
230 {:method => :get, :path => 'projects/test/categories/new'},
231 :controller => 'projects', :action => 'add_issue_category', :id => 'test'
231 :controller => 'projects', :action => 'add_issue_category', :id => 'test'
232 )
232 )
233 assert_routing(
233 assert_routing(
234 #TODO: use PUT and update form
234 #TODO: use PUT and update form
235 {:method => :post, :path => 'projects/64/categories/new'},
235 {:method => :post, :path => 'projects/64/categories/new'},
236 :controller => 'projects', :action => 'add_issue_category', :id => '64'
236 :controller => 'projects', :action => 'add_issue_category', :id => '64'
237 )
237 )
238 end
238 end
239
239
240 def test_destroy_routing
240 def test_destroy_routing
241 assert_routing(
241 assert_routing(
242 {:method => :get, :path => '/projects/567/destroy'},
242 {:method => :get, :path => '/projects/567/destroy'},
243 :controller => 'projects', :action => 'destroy', :id => '567'
243 :controller => 'projects', :action => 'destroy', :id => '567'
244 )
244 )
245 assert_routing(
245 assert_routing(
246 #TODO: use DELETE and update form
246 #TODO: use DELETE and update form
247 {:method => :post, :path => 'projects/64/destroy'},
247 {:method => :post, :path => 'projects/64/destroy'},
248 :controller => 'projects', :action => 'destroy', :id => '64'
248 :controller => 'projects', :action => 'destroy', :id => '64'
249 )
249 )
250 end
250 end
251
251
252 def test_get_destroy
252 def test_get_destroy
253 @request.session[:user_id] = 1 # admin
253 @request.session[:user_id] = 1 # admin
254 get :destroy, :id => 1
254 get :destroy, :id => 1
255 assert_response :success
255 assert_response :success
256 assert_template 'destroy'
256 assert_template 'destroy'
257 assert_not_nil Project.find_by_id(1)
257 assert_not_nil Project.find_by_id(1)
258 end
258 end
259
259
260 def test_post_destroy
260 def test_post_destroy
261 @request.session[:user_id] = 1 # admin
261 @request.session[:user_id] = 1 # admin
262 post :destroy, :id => 1, :confirm => 1
262 post :destroy, :id => 1, :confirm => 1
263 assert_redirected_to 'admin/projects'
263 assert_redirected_to 'admin/projects'
264 assert_nil Project.find_by_id(1)
264 assert_nil Project.find_by_id(1)
265 end
265 end
266
266
267 def test_add_file
267 def test_add_file
268 set_tmp_attachments_directory
268 set_tmp_attachments_directory
269 @request.session[:user_id] = 2
269 @request.session[:user_id] = 2
270 Setting.notified_events = ['file_added']
270 Setting.notified_events = ['file_added']
271 ActionMailer::Base.deliveries.clear
271 ActionMailer::Base.deliveries.clear
272
272
273 assert_difference 'Attachment.count' do
273 assert_difference 'Attachment.count' do
274 post :add_file, :id => 1, :version_id => '',
274 post :add_file, :id => 1, :version_id => '',
275 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
275 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
276 end
276 end
277 assert_redirected_to 'projects/ecookbook/files'
277 assert_redirected_to 'projects/ecookbook/files'
278 a = Attachment.find(:first, :order => 'created_on DESC')
278 a = Attachment.find(:first, :order => 'created_on DESC')
279 assert_equal 'testfile.txt', a.filename
279 assert_equal 'testfile.txt', a.filename
280 assert_equal Project.find(1), a.container
280 assert_equal Project.find(1), a.container
281
281
282 mail = ActionMailer::Base.deliveries.last
282 mail = ActionMailer::Base.deliveries.last
283 assert_kind_of TMail::Mail, mail
283 assert_kind_of TMail::Mail, mail
284 assert_equal "[eCookbook] New file", mail.subject
284 assert_equal "[eCookbook] New file", mail.subject
285 assert mail.body.include?('testfile.txt')
285 assert mail.body.include?('testfile.txt')
286 end
286 end
287
287
288 def test_add_file_routing
288 def test_add_file_routing
289 assert_routing(
289 assert_routing(
290 {:method => :get, :path => '/projects/33/files/new'},
290 {:method => :get, :path => '/projects/33/files/new'},
291 :controller => 'projects', :action => 'add_file', :id => '33'
291 :controller => 'projects', :action => 'add_file', :id => '33'
292 )
292 )
293 assert_routing(
293 assert_routing(
294 {:method => :post, :path => '/projects/33/files/new'},
294 {:method => :post, :path => '/projects/33/files/new'},
295 :controller => 'projects', :action => 'add_file', :id => '33'
295 :controller => 'projects', :action => 'add_file', :id => '33'
296 )
296 )
297 end
297 end
298
298
299 def test_add_version_file
299 def test_add_version_file
300 set_tmp_attachments_directory
300 set_tmp_attachments_directory
301 @request.session[:user_id] = 2
301 @request.session[:user_id] = 2
302 Setting.notified_events = ['file_added']
302 Setting.notified_events = ['file_added']
303
303
304 assert_difference 'Attachment.count' do
304 assert_difference 'Attachment.count' do
305 post :add_file, :id => 1, :version_id => '2',
305 post :add_file, :id => 1, :version_id => '2',
306 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
306 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
307 end
307 end
308 assert_redirected_to 'projects/ecookbook/files'
308 assert_redirected_to 'projects/ecookbook/files'
309 a = Attachment.find(:first, :order => 'created_on DESC')
309 a = Attachment.find(:first, :order => 'created_on DESC')
310 assert_equal 'testfile.txt', a.filename
310 assert_equal 'testfile.txt', a.filename
311 assert_equal Version.find(2), a.container
311 assert_equal Version.find(2), a.container
312 end
312 end
313
313
314 def test_list_files
314 def test_list_files
315 get :list_files, :id => 1
315 get :list_files, :id => 1
316 assert_response :success
316 assert_response :success
317 assert_template 'list_files'
317 assert_template 'list_files'
318 assert_not_nil assigns(:containers)
318 assert_not_nil assigns(:containers)
319
319
320 # file attached to the project
320 # file attached to the project
321 assert_tag :a, :content => 'project_file.zip',
321 assert_tag :a, :content => 'project_file.zip',
322 :attributes => { :href => '/attachments/download/8/project_file.zip' }
322 :attributes => { :href => '/attachments/download/8/project_file.zip' }
323
323
324 # file attached to a project's version
324 # file attached to a project's version
325 assert_tag :a, :content => 'version_file.zip',
325 assert_tag :a, :content => 'version_file.zip',
326 :attributes => { :href => '/attachments/download/9/version_file.zip' }
326 :attributes => { :href => '/attachments/download/9/version_file.zip' }
327 end
327 end
328
328
329 def test_list_files_routing
329 def test_list_files_routing
330 assert_routing(
330 assert_routing(
331 {:method => :get, :path => '/projects/33/files'},
331 {:method => :get, :path => '/projects/33/files'},
332 :controller => 'projects', :action => 'list_files', :id => '33'
332 :controller => 'projects', :action => 'list_files', :id => '33'
333 )
333 )
334 end
334 end
335
335
336 def test_changelog_routing
336 def test_changelog_routing
337 assert_routing(
337 assert_routing(
338 {:method => :get, :path => '/projects/44/changelog'},
338 {:method => :get, :path => '/projects/44/changelog'},
339 :controller => 'projects', :action => 'changelog', :id => '44'
339 :controller => 'projects', :action => 'changelog', :id => '44'
340 )
340 )
341 end
341 end
342
342
343 def test_changelog
343 def test_changelog
344 get :changelog, :id => 1
344 get :changelog, :id => 1
345 assert_response :success
345 assert_response :success
346 assert_template 'changelog'
346 assert_template 'changelog'
347 assert_not_nil assigns(:versions)
347 assert_not_nil assigns(:versions)
348 end
348 end
349
349
350 def test_roadmap_routing
350 def test_roadmap_routing
351 assert_routing(
351 assert_routing(
352 {:method => :get, :path => 'projects/33/roadmap'},
352 {:method => :get, :path => 'projects/33/roadmap'},
353 :controller => 'projects', :action => 'roadmap', :id => '33'
353 :controller => 'projects', :action => 'roadmap', :id => '33'
354 )
354 )
355 end
355 end
356
356
357 def test_roadmap
357 def test_roadmap
358 get :roadmap, :id => 1
358 get :roadmap, :id => 1
359 assert_response :success
359 assert_response :success
360 assert_template 'roadmap'
360 assert_template 'roadmap'
361 assert_not_nil assigns(:versions)
361 assert_not_nil assigns(:versions)
362 # Version with no date set appears
362 # Version with no date set appears
363 assert assigns(:versions).include?(Version.find(3))
363 assert assigns(:versions).include?(Version.find(3))
364 # Completed version doesn't appear
364 # Completed version doesn't appear
365 assert !assigns(:versions).include?(Version.find(1))
365 assert !assigns(:versions).include?(Version.find(1))
366 end
366 end
367
367
368 def test_roadmap_with_completed_versions
368 def test_roadmap_with_completed_versions
369 get :roadmap, :id => 1, :completed => 1
369 get :roadmap, :id => 1, :completed => 1
370 assert_response :success
370 assert_response :success
371 assert_template 'roadmap'
371 assert_template 'roadmap'
372 assert_not_nil assigns(:versions)
372 assert_not_nil assigns(:versions)
373 # Version with no date set appears
373 # Version with no date set appears
374 assert assigns(:versions).include?(Version.find(3))
374 assert assigns(:versions).include?(Version.find(3))
375 # Completed version appears
375 # Completed version appears
376 assert assigns(:versions).include?(Version.find(1))
376 assert assigns(:versions).include?(Version.find(1))
377 end
377 end
378
378
379 def test_project_activity_routing
379 def test_project_activity_routing
380 assert_routing(
380 assert_routing(
381 {:method => :get, :path => '/projects/1/activity'},
381 {:method => :get, :path => '/projects/1/activity'},
382 :controller => 'projects', :action => 'activity', :id => '1'
382 :controller => 'projects', :action => 'activity', :id => '1'
383 )
383 )
384 end
384 end
385
385
386 def test_project_activity_atom_routing
386 def test_project_activity_atom_routing
387 assert_routing(
387 assert_routing(
388 {:method => :get, :path => '/projects/1/activity.atom'},
388 {:method => :get, :path => '/projects/1/activity.atom'},
389 :controller => 'projects', :action => 'activity', :id => '1', :format => 'atom'
389 :controller => 'projects', :action => 'activity', :id => '1', :format => 'atom'
390 )
390 )
391 end
391 end
392
392
393 def test_project_activity
393 def test_project_activity
394 get :activity, :id => 1, :with_subprojects => 0
394 get :activity, :id => 1, :with_subprojects => 0
395 assert_response :success
395 assert_response :success
396 assert_template 'activity'
396 assert_template 'activity'
397 assert_not_nil assigns(:events_by_day)
397 assert_not_nil assigns(:events_by_day)
398
398
399 assert_tag :tag => "h3",
399 assert_tag :tag => "h3",
400 :content => /#{2.days.ago.to_date.day}/,
400 :content => /#{2.days.ago.to_date.day}/,
401 :sibling => { :tag => "dl",
401 :sibling => { :tag => "dl",
402 :child => { :tag => "dt",
402 :child => { :tag => "dt",
403 :attributes => { :class => /issue-edit/ },
403 :attributes => { :class => /issue-edit/ },
404 :child => { :tag => "a",
404 :child => { :tag => "a",
405 :content => /(#{IssueStatus.find(2).name})/,
405 :content => /(#{IssueStatus.find(2).name})/,
406 }
406 }
407 }
407 }
408 }
408 }
409 end
409 end
410
410
411 def test_previous_project_activity
411 def test_previous_project_activity
412 get :activity, :id => 1, :from => 3.days.ago.to_date
412 get :activity, :id => 1, :from => 3.days.ago.to_date
413 assert_response :success
413 assert_response :success
414 assert_template 'activity'
414 assert_template 'activity'
415 assert_not_nil assigns(:events_by_day)
415 assert_not_nil assigns(:events_by_day)
416
416
417 assert_tag :tag => "h3",
417 assert_tag :tag => "h3",
418 :content => /#{3.day.ago.to_date.day}/,
418 :content => /#{3.day.ago.to_date.day}/,
419 :sibling => { :tag => "dl",
419 :sibling => { :tag => "dl",
420 :child => { :tag => "dt",
420 :child => { :tag => "dt",
421 :attributes => { :class => /issue/ },
421 :attributes => { :class => /issue/ },
422 :child => { :tag => "a",
422 :child => { :tag => "a",
423 :content => /#{Issue.find(1).subject}/,
423 :content => /#{Issue.find(1).subject}/,
424 }
424 }
425 }
425 }
426 }
426 }
427 end
427 end
428
428
429 def test_global_activity_routing
429 def test_global_activity_routing
430 assert_routing({:method => :get, :path => '/activity'}, :controller => 'projects', :action => 'activity', :id => nil)
430 assert_routing({:method => :get, :path => '/activity'}, :controller => 'projects', :action => 'activity', :id => nil)
431 end
431 end
432
432
433 def test_global_activity
433 def test_global_activity
434 get :activity
434 get :activity
435 assert_response :success
435 assert_response :success
436 assert_template 'activity'
436 assert_template 'activity'
437 assert_not_nil assigns(:events_by_day)
437 assert_not_nil assigns(:events_by_day)
438
438
439 assert_tag :tag => "h3",
439 assert_tag :tag => "h3",
440 :content => /#{5.day.ago.to_date.day}/,
440 :content => /#{5.day.ago.to_date.day}/,
441 :sibling => { :tag => "dl",
441 :sibling => { :tag => "dl",
442 :child => { :tag => "dt",
442 :child => { :tag => "dt",
443 :attributes => { :class => /issue/ },
443 :attributes => { :class => /issue/ },
444 :child => { :tag => "a",
444 :child => { :tag => "a",
445 :content => /#{Issue.find(5).subject}/,
445 :content => /#{Issue.find(5).subject}/,
446 }
446 }
447 }
447 }
448 }
448 }
449 end
449 end
450
450
451 def test_user_activity
451 def test_user_activity
452 get :activity, :user_id => 2
452 get :activity, :user_id => 2
453 assert_response :success
453 assert_response :success
454 assert_template 'activity'
454 assert_template 'activity'
455 assert_not_nil assigns(:events_by_day)
455 assert_not_nil assigns(:events_by_day)
456
456
457 assert_tag :tag => "h3",
457 assert_tag :tag => "h3",
458 :content => /#{3.day.ago.to_date.day}/,
458 :content => /#{3.day.ago.to_date.day}/,
459 :sibling => { :tag => "dl",
459 :sibling => { :tag => "dl",
460 :child => { :tag => "dt",
460 :child => { :tag => "dt",
461 :attributes => { :class => /issue/ },
461 :attributes => { :class => /issue/ },
462 :child => { :tag => "a",
462 :child => { :tag => "a",
463 :content => /#{Issue.find(1).subject}/,
463 :content => /#{Issue.find(1).subject}/,
464 }
464 }
465 }
465 }
466 }
466 }
467 end
467 end
468
468
469 def test_global_activity_atom_routing
469 def test_global_activity_atom_routing
470 assert_routing({:method => :get, :path => '/activity.atom'}, :controller => 'projects', :action => 'activity', :id => nil, :format => 'atom')
470 assert_routing({:method => :get, :path => '/activity.atom'}, :controller => 'projects', :action => 'activity', :id => nil, :format => 'atom')
471 end
471 end
472
472
473 def test_activity_atom_feed
473 def test_activity_atom_feed
474 get :activity, :format => 'atom'
474 get :activity, :format => 'atom'
475 assert_response :success
475 assert_response :success
476 assert_template 'common/feed.atom.rxml'
476 assert_template 'common/feed.atom.rxml'
477 end
477 end
478
478
479 def test_archive_routing
479 def test_archive_routing
480 assert_routing(
480 assert_routing(
481 #TODO: use PUT to project path and modify form
481 #TODO: use PUT to project path and modify form
482 {:method => :post, :path => 'projects/64/archive'},
482 {:method => :post, :path => 'projects/64/archive'},
483 :controller => 'projects', :action => 'archive', :id => '64'
483 :controller => 'projects', :action => 'archive', :id => '64'
484 )
484 )
485 end
485 end
486
486
487 def test_archive
487 def test_archive
488 @request.session[:user_id] = 1 # admin
488 @request.session[:user_id] = 1 # admin
489 post :archive, :id => 1
489 post :archive, :id => 1
490 assert_redirected_to 'admin/projects'
490 assert_redirected_to 'admin/projects'
491 assert !Project.find(1).active?
491 assert !Project.find(1).active?
492 end
492 end
493
493
494 def test_unarchive_routing
494 def test_unarchive_routing
495 assert_routing(
495 assert_routing(
496 #TODO: use PUT to project path and modify form
496 #TODO: use PUT to project path and modify form
497 {:method => :post, :path => '/projects/567/unarchive'},
497 {:method => :post, :path => '/projects/567/unarchive'},
498 :controller => 'projects', :action => 'unarchive', :id => '567'
498 :controller => 'projects', :action => 'unarchive', :id => '567'
499 )
499 )
500 end
500 end
501
501
502 def test_unarchive
502 def test_unarchive
503 @request.session[:user_id] = 1 # admin
503 @request.session[:user_id] = 1 # admin
504 Project.find(1).archive
504 Project.find(1).archive
505 post :unarchive, :id => 1
505 post :unarchive, :id => 1
506 assert_redirected_to 'admin/projects'
506 assert_redirected_to 'admin/projects'
507 assert Project.find(1).active?
507 assert Project.find(1).active?
508 end
508 end
509
509
510 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
510 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
511 CustomField.delete_all
511 CustomField.delete_all
512 parent = nil
512 parent = nil
513 6.times do |i|
513 6.times do |i|
514 p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}")
514 p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}")
515 p.set_parent!(parent)
515 p.set_parent!(parent)
516 get :show, :id => p
516 get :show, :id => p
517 assert_tag :h1, :parent => { :attributes => {:id => 'header'}},
517 assert_tag :h1, :parent => { :attributes => {:id => 'header'}},
518 :children => { :count => [i, 3].min,
518 :children => { :count => [i, 3].min,
519 :only => { :tag => 'a' } }
519 :only => { :tag => 'a' } }
520
520
521 parent = p
521 parent = p
522 end
522 end
523 end
523 end
524
524
525 def test_copy_with_project
525 def test_copy_with_project
526 @request.session[:user_id] = 1 # admin
526 @request.session[:user_id] = 1 # admin
527 get :copy, :id => 1
527 get :copy, :id => 1
528 assert_response :success
528 assert_response :success
529 assert_template 'copy'
529 assert_template 'copy'
530 assert assigns(:project)
530 assert assigns(:project)
531 assert_equal Project.find(1).description, assigns(:project).description
531 assert_equal Project.find(1).description, assigns(:project).description
532 assert_nil assigns(:project).id
532 assert_nil assigns(:project).id
533 end
533 end
534
534
535 def test_copy_without_project
535 def test_copy_without_project
536 @request.session[:user_id] = 1 # admin
536 @request.session[:user_id] = 1 # admin
537 get :copy
537 get :copy
538 assert_response :redirect
538 assert_response :redirect
539 assert_redirected_to :controller => 'admin', :action => 'projects'
539 assert_redirected_to :controller => 'admin', :action => 'projects'
540 end
540 end
541
541
542 def test_jump_should_redirect_to_active_tab
542 def test_jump_should_redirect_to_active_tab
543 get :show, :id => 1, :jump => 'issues'
543 get :show, :id => 1, :jump => 'issues'
544 assert_redirected_to 'projects/ecookbook/issues'
544 assert_redirected_to 'projects/ecookbook/issues'
545 end
545 end
546
546
547 def test_jump_should_not_redirect_to_inactive_tab
547 def test_jump_should_not_redirect_to_inactive_tab
548 get :show, :id => 3, :jump => 'documents'
548 get :show, :id => 3, :jump => 'documents'
549 assert_response :success
549 assert_response :success
550 assert_template 'show'
550 assert_template 'show'
551 end
551 end
552
552
553 def test_jump_should_not_redirect_to_unknown_tab
553 def test_jump_should_not_redirect_to_unknown_tab
554 get :show, :id => 3, :jump => 'foobar'
554 get :show, :id => 3, :jump => 'foobar'
555 assert_response :success
555 assert_response :success
556 assert_template 'show'
556 assert_template 'show'
557 end
557 end
558
558
559 def test_reset_activities_routing
560 assert_routing({:method => :delete, :path => 'projects/64/reset_activities'},
561 :controller => 'projects', :action => 'reset_activities', :id => '64')
562 end
563
564 def test_reset_activities
565 @request.session[:user_id] = 2 # manager
566 project_activity = TimeEntryActivity.new({
567 :name => 'Project Specific',
568 :parent => TimeEntryActivity.find(:first),
569 :project => Project.find(1),
570 :active => true
571 })
572 assert project_activity.save
573 project_activity_two = TimeEntryActivity.new({
574 :name => 'Project Specific Two',
575 :parent => TimeEntryActivity.find(:last),
576 :project => Project.find(1),
577 :active => true
578 })
579 assert project_activity_two.save
580
581 delete :reset_activities, :id => 1
582 assert_response :redirect
583 assert_redirected_to 'projects/ecookbook/settings/activities'
584
585 assert_nil TimeEntryActivity.find_by_id(project_activity.id)
586 assert_nil TimeEntryActivity.find_by_id(project_activity_two.id)
587 end
588
589 def test_save_activities_routing
590 assert_routing({:method => :post, :path => 'projects/64/activities/save'},
591 :controller => 'projects', :action => 'save_activities', :id => '64')
592 end
593
594 def test_save_activities_to_override_system_activities
595 @request.session[:user_id] = 2 # manager
596 billable_field = TimeEntryActivityCustomField.find_by_name("Billable")
597
598 post :save_activities, :id => 1, :enumerations => {
599 "9"=> {"parent_id"=>"9", "custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # Design, De-activate
600 "10"=> {"parent_id"=>"10", "custom_field_values"=>{"7"=>"0"}, "active"=>"1"}, # Development, Change custom value
601 "14"=>{"parent_id"=>"14", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"}, # Inactive Activity, Activate with custom value
602 "11"=>{"parent_id"=>"11", "custom_field_values"=>{"7"=>"1"}, "active"=>"1"} # QA, no changes
603 }
604
605 assert_response :redirect
606 assert_redirected_to 'projects/ecookbook/settings/activities'
607
608 # Created project specific activities...
609 project = Project.find('ecookbook')
610
611 # ... Design
612 design = project.time_entry_activities.find_by_name("Design")
613 assert design, "Project activity not found"
614
615 assert_equal 9, design.parent_id # Relate to the system activity
616 assert_not_equal design.parent.id, design.id # Different records
617 assert_equal design.parent.name, design.name # Same name
618 assert !design.active?
619
620 # ... Development
621 development = project.time_entry_activities.find_by_name("Development")
622 assert development, "Project activity not found"
623
624 assert_equal 10, development.parent_id # Relate to the system activity
625 assert_not_equal development.parent.id, development.id # Different records
626 assert_equal development.parent.name, development.name # Same name
627 assert development.active?
628 assert_equal "0", development.custom_value_for(billable_field).value
629
630 # ... Inactive Activity
631 previously_inactive = project.time_entry_activities.find_by_name("Inactive Activity")
632 assert previously_inactive, "Project activity not found"
633
634 assert_equal 14, previously_inactive.parent_id # Relate to the system activity
635 assert_not_equal previously_inactive.parent.id, previously_inactive.id # Different records
636 assert_equal previously_inactive.parent.name, previously_inactive.name # Same name
637 assert previously_inactive.active?
638 assert_equal "1", previously_inactive.custom_value_for(billable_field).value
639
640 # ... QA
641 assert_equal nil, project.time_entry_activities.find_by_name("QA"), "Custom QA activity created when it wasn't modified"
642 end
643
644 def test_save_activities_will_update_project_specific_activities
645 @request.session[:user_id] = 2 # manager
646
647 project_activity = TimeEntryActivity.new({
648 :name => 'Project Specific',
649 :parent => TimeEntryActivity.find(:first),
650 :project => Project.find(1),
651 :active => true
652 })
653 assert project_activity.save
654 project_activity_two = TimeEntryActivity.new({
655 :name => 'Project Specific Two',
656 :parent => TimeEntryActivity.find(:last),
657 :project => Project.find(1),
658 :active => true
659 })
660 assert project_activity_two.save
661
662
663 post :save_activities, :id => 1, :enumerations => {
664 project_activity.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"}, # De-activate
665 project_activity_two.id => {"custom_field_values"=>{"7" => "1"}, "active"=>"0"} # De-activate
666 }
667
668 assert_response :redirect
669 assert_redirected_to 'projects/ecookbook/settings/activities'
670
671 # Created project specific activities...
672 project = Project.find('ecookbook')
673 assert_equal 2, project.time_entry_activities.count
674
675 activity_one = project.time_entry_activities.find_by_name(project_activity.name)
676 assert activity_one, "Project activity not found"
677 assert_equal project_activity.id, activity_one.id
678 assert !activity_one.active?
679
680 activity_two = project.time_entry_activities.find_by_name(project_activity_two.name)
681 assert activity_two, "Project activity not found"
682 assert_equal project_activity_two.id, activity_two.id
683 assert !activity_two.active?
684 end
685
559 # A hook that is manually registered later
686 # A hook that is manually registered later
560 class ProjectBasedTemplate < Redmine::Hook::ViewListener
687 class ProjectBasedTemplate < Redmine::Hook::ViewListener
561 def view_layouts_base_html_head(context)
688 def view_layouts_base_html_head(context)
562 # Adds a project stylesheet
689 # Adds a project stylesheet
563 stylesheet_link_tag(context[:project].identifier) if context[:project]
690 stylesheet_link_tag(context[:project].identifier) if context[:project]
564 end
691 end
565 end
692 end
566 # Don't use this hook now
693 # Don't use this hook now
567 Redmine::Hook.clear_listeners
694 Redmine::Hook.clear_listeners
568
695
569 def test_hook_response
696 def test_hook_response
570 Redmine::Hook.add_listener(ProjectBasedTemplate)
697 Redmine::Hook.add_listener(ProjectBasedTemplate)
571 get :show, :id => 1
698 get :show, :id => 1
572 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
699 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
573 :parent => {:tag => 'head'}
700 :parent => {:tag => 'head'}
574
701
575 Redmine::Hook.clear_listeners
702 Redmine::Hook.clear_listeners
576 end
703 end
577 end
704 end
@@ -1,492 +1,507
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 end
29 end
30
30
31 should_validate_presence_of :name
31 should_validate_presence_of :name
32 should_validate_presence_of :identifier
32 should_validate_presence_of :identifier
33
33
34 should_validate_uniqueness_of :name
34 should_validate_uniqueness_of :name
35 should_validate_uniqueness_of :identifier
35 should_validate_uniqueness_of :identifier
36
36
37 context "associations" do
37 context "associations" do
38 should_have_many :members
38 should_have_many :members
39 should_have_many :users, :through => :members
39 should_have_many :users, :through => :members
40 should_have_many :member_principals
40 should_have_many :member_principals
41 should_have_many :principals, :through => :member_principals
41 should_have_many :principals, :through => :member_principals
42 should_have_many :enabled_modules
42 should_have_many :enabled_modules
43 should_have_many :issues
43 should_have_many :issues
44 should_have_many :issue_changes, :through => :issues
44 should_have_many :issue_changes, :through => :issues
45 should_have_many :versions
45 should_have_many :versions
46 should_have_many :time_entries
46 should_have_many :time_entries
47 should_have_many :queries
47 should_have_many :queries
48 should_have_many :documents
48 should_have_many :documents
49 should_have_many :news
49 should_have_many :news
50 should_have_many :issue_categories
50 should_have_many :issue_categories
51 should_have_many :boards
51 should_have_many :boards
52 should_have_many :changesets, :through => :repository
52 should_have_many :changesets, :through => :repository
53
53
54 should_have_one :repository
54 should_have_one :repository
55 should_have_one :wiki
55 should_have_one :wiki
56
56
57 should_have_and_belong_to_many :trackers
57 should_have_and_belong_to_many :trackers
58 should_have_and_belong_to_many :issue_custom_fields
58 should_have_and_belong_to_many :issue_custom_fields
59 end
59 end
60
60
61 def test_truth
61 def test_truth
62 assert_kind_of Project, @ecookbook
62 assert_kind_of Project, @ecookbook
63 assert_equal "eCookbook", @ecookbook.name
63 assert_equal "eCookbook", @ecookbook.name
64 end
64 end
65
65
66 def test_update
66 def test_update
67 assert_equal "eCookbook", @ecookbook.name
67 assert_equal "eCookbook", @ecookbook.name
68 @ecookbook.name = "eCook"
68 @ecookbook.name = "eCook"
69 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
69 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
70 @ecookbook.reload
70 @ecookbook.reload
71 assert_equal "eCook", @ecookbook.name
71 assert_equal "eCook", @ecookbook.name
72 end
72 end
73
73
74 def test_validate_identifier
74 def test_validate_identifier
75 to_test = {"abc" => true,
75 to_test = {"abc" => true,
76 "ab12" => true,
76 "ab12" => true,
77 "ab-12" => true,
77 "ab-12" => true,
78 "12" => false,
78 "12" => false,
79 "new" => false}
79 "new" => false}
80
80
81 to_test.each do |identifier, valid|
81 to_test.each do |identifier, valid|
82 p = Project.new
82 p = Project.new
83 p.identifier = identifier
83 p.identifier = identifier
84 p.valid?
84 p.valid?
85 assert_equal valid, p.errors.on('identifier').nil?
85 assert_equal valid, p.errors.on('identifier').nil?
86 end
86 end
87 end
87 end
88
88
89 def test_members_should_be_active_users
89 def test_members_should_be_active_users
90 Project.all.each do |project|
90 Project.all.each do |project|
91 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
91 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
92 end
92 end
93 end
93 end
94
94
95 def test_users_should_be_active_users
95 def test_users_should_be_active_users
96 Project.all.each do |project|
96 Project.all.each do |project|
97 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
97 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
98 end
98 end
99 end
99 end
100
100
101 def test_archive
101 def test_archive
102 user = @ecookbook.members.first.user
102 user = @ecookbook.members.first.user
103 @ecookbook.archive
103 @ecookbook.archive
104 @ecookbook.reload
104 @ecookbook.reload
105
105
106 assert !@ecookbook.active?
106 assert !@ecookbook.active?
107 assert !user.projects.include?(@ecookbook)
107 assert !user.projects.include?(@ecookbook)
108 # Subproject are also archived
108 # Subproject are also archived
109 assert !@ecookbook.children.empty?
109 assert !@ecookbook.children.empty?
110 assert @ecookbook.descendants.active.empty?
110 assert @ecookbook.descendants.active.empty?
111 end
111 end
112
112
113 def test_unarchive
113 def test_unarchive
114 user = @ecookbook.members.first.user
114 user = @ecookbook.members.first.user
115 @ecookbook.archive
115 @ecookbook.archive
116 # A subproject of an archived project can not be unarchived
116 # A subproject of an archived project can not be unarchived
117 assert !@ecookbook_sub1.unarchive
117 assert !@ecookbook_sub1.unarchive
118
118
119 # Unarchive project
119 # Unarchive project
120 assert @ecookbook.unarchive
120 assert @ecookbook.unarchive
121 @ecookbook.reload
121 @ecookbook.reload
122 assert @ecookbook.active?
122 assert @ecookbook.active?
123 assert user.projects.include?(@ecookbook)
123 assert user.projects.include?(@ecookbook)
124 # Subproject can now be unarchived
124 # Subproject can now be unarchived
125 @ecookbook_sub1.reload
125 @ecookbook_sub1.reload
126 assert @ecookbook_sub1.unarchive
126 assert @ecookbook_sub1.unarchive
127 end
127 end
128
128
129 def test_destroy
129 def test_destroy
130 # 2 active members
130 # 2 active members
131 assert_equal 2, @ecookbook.members.size
131 assert_equal 2, @ecookbook.members.size
132 # and 1 is locked
132 # and 1 is locked
133 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
133 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
134 # some boards
134 # some boards
135 assert @ecookbook.boards.any?
135 assert @ecookbook.boards.any?
136
136
137 @ecookbook.destroy
137 @ecookbook.destroy
138 # make sure that the project non longer exists
138 # make sure that the project non longer exists
139 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
139 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
140 # make sure related data was removed
140 # make sure related data was removed
141 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
141 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
142 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
142 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
143 end
143 end
144
144
145 def test_move_an_orphan_project_to_a_root_project
145 def test_move_an_orphan_project_to_a_root_project
146 sub = Project.find(2)
146 sub = Project.find(2)
147 sub.set_parent! @ecookbook
147 sub.set_parent! @ecookbook
148 assert_equal @ecookbook.id, sub.parent.id
148 assert_equal @ecookbook.id, sub.parent.id
149 @ecookbook.reload
149 @ecookbook.reload
150 assert_equal 4, @ecookbook.children.size
150 assert_equal 4, @ecookbook.children.size
151 end
151 end
152
152
153 def test_move_an_orphan_project_to_a_subproject
153 def test_move_an_orphan_project_to_a_subproject
154 sub = Project.find(2)
154 sub = Project.find(2)
155 assert sub.set_parent!(@ecookbook_sub1)
155 assert sub.set_parent!(@ecookbook_sub1)
156 end
156 end
157
157
158 def test_move_a_root_project_to_a_project
158 def test_move_a_root_project_to_a_project
159 sub = @ecookbook
159 sub = @ecookbook
160 assert sub.set_parent!(Project.find(2))
160 assert sub.set_parent!(Project.find(2))
161 end
161 end
162
162
163 def test_should_not_move_a_project_to_its_children
163 def test_should_not_move_a_project_to_its_children
164 sub = @ecookbook
164 sub = @ecookbook
165 assert !(sub.set_parent!(Project.find(3)))
165 assert !(sub.set_parent!(Project.find(3)))
166 end
166 end
167
167
168 def test_set_parent_should_add_roots_in_alphabetical_order
168 def test_set_parent_should_add_roots_in_alphabetical_order
169 ProjectCustomField.delete_all
169 ProjectCustomField.delete_all
170 Project.delete_all
170 Project.delete_all
171 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
171 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
172 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
172 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
173 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
173 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
174 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
174 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
175
175
176 assert_equal 4, Project.count
176 assert_equal 4, Project.count
177 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
177 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
178 end
178 end
179
179
180 def test_set_parent_should_add_children_in_alphabetical_order
180 def test_set_parent_should_add_children_in_alphabetical_order
181 ProjectCustomField.delete_all
181 ProjectCustomField.delete_all
182 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
182 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
183 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
183 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
184 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
184 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
185 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
185 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
186 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
186 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
187
187
188 parent.reload
188 parent.reload
189 assert_equal 4, parent.children.size
189 assert_equal 4, parent.children.size
190 assert_equal parent.children.sort_by(&:name), parent.children
190 assert_equal parent.children.sort_by(&:name), parent.children
191 end
191 end
192
192
193 def test_rebuild_should_sort_children_alphabetically
193 def test_rebuild_should_sort_children_alphabetically
194 ProjectCustomField.delete_all
194 ProjectCustomField.delete_all
195 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
195 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
196 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
196 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)
197 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)
198 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)
199 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
200
200
201 Project.update_all("lft = NULL, rgt = NULL")
201 Project.update_all("lft = NULL, rgt = NULL")
202 Project.rebuild!
202 Project.rebuild!
203
203
204 parent.reload
204 parent.reload
205 assert_equal 4, parent.children.size
205 assert_equal 4, parent.children.size
206 assert_equal parent.children.sort_by(&:name), parent.children
206 assert_equal parent.children.sort_by(&:name), parent.children
207 end
207 end
208
208
209 def test_parent
209 def test_parent
210 p = Project.find(6).parent
210 p = Project.find(6).parent
211 assert p.is_a?(Project)
211 assert p.is_a?(Project)
212 assert_equal 5, p.id
212 assert_equal 5, p.id
213 end
213 end
214
214
215 def test_ancestors
215 def test_ancestors
216 a = Project.find(6).ancestors
216 a = Project.find(6).ancestors
217 assert a.first.is_a?(Project)
217 assert a.first.is_a?(Project)
218 assert_equal [1, 5], a.collect(&:id)
218 assert_equal [1, 5], a.collect(&:id)
219 end
219 end
220
220
221 def test_root
221 def test_root
222 r = Project.find(6).root
222 r = Project.find(6).root
223 assert r.is_a?(Project)
223 assert r.is_a?(Project)
224 assert_equal 1, r.id
224 assert_equal 1, r.id
225 end
225 end
226
226
227 def test_children
227 def test_children
228 c = Project.find(1).children
228 c = Project.find(1).children
229 assert c.first.is_a?(Project)
229 assert c.first.is_a?(Project)
230 assert_equal [5, 3, 4], c.collect(&:id)
230 assert_equal [5, 3, 4], c.collect(&:id)
231 end
231 end
232
232
233 def test_descendants
233 def test_descendants
234 d = Project.find(1).descendants
234 d = Project.find(1).descendants
235 assert d.first.is_a?(Project)
235 assert d.first.is_a?(Project)
236 assert_equal [5, 6, 3, 4], d.collect(&:id)
236 assert_equal [5, 6, 3, 4], d.collect(&:id)
237 end
237 end
238
238
239 def test_users_by_role
239 def test_users_by_role
240 users_by_role = Project.find(1).users_by_role
240 users_by_role = Project.find(1).users_by_role
241 assert_kind_of Hash, users_by_role
241 assert_kind_of Hash, users_by_role
242 role = Role.find(1)
242 role = Role.find(1)
243 assert_kind_of Array, users_by_role[role]
243 assert_kind_of Array, users_by_role[role]
244 assert users_by_role[role].include?(User.find(2))
244 assert users_by_role[role].include?(User.find(2))
245 end
245 end
246
246
247 def test_rolled_up_trackers
247 def test_rolled_up_trackers
248 parent = Project.find(1)
248 parent = Project.find(1)
249 parent.trackers = Tracker.find([1,2])
249 parent.trackers = Tracker.find([1,2])
250 child = parent.children.find(3)
250 child = parent.children.find(3)
251
251
252 assert_equal [1, 2], parent.tracker_ids
252 assert_equal [1, 2], parent.tracker_ids
253 assert_equal [2, 3], child.trackers.collect(&:id)
253 assert_equal [2, 3], child.trackers.collect(&:id)
254
254
255 assert_kind_of Tracker, parent.rolled_up_trackers.first
255 assert_kind_of Tracker, parent.rolled_up_trackers.first
256 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
256 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
257
257
258 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
258 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
259 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
259 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
260 end
260 end
261
261
262 def test_rolled_up_trackers_should_ignore_archived_subprojects
262 def test_rolled_up_trackers_should_ignore_archived_subprojects
263 parent = Project.find(1)
263 parent = Project.find(1)
264 parent.trackers = Tracker.find([1,2])
264 parent.trackers = Tracker.find([1,2])
265 child = parent.children.find(3)
265 child = parent.children.find(3)
266 child.trackers = Tracker.find([1,3])
266 child.trackers = Tracker.find([1,3])
267 parent.children.each(&:archive)
267 parent.children.each(&:archive)
268
268
269 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
269 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
270 end
270 end
271
271
272 def test_next_identifier
272 def test_next_identifier
273 ProjectCustomField.delete_all
273 ProjectCustomField.delete_all
274 Project.create!(:name => 'last', :identifier => 'p2008040')
274 Project.create!(:name => 'last', :identifier => 'p2008040')
275 assert_equal 'p2008041', Project.next_identifier
275 assert_equal 'p2008041', Project.next_identifier
276 end
276 end
277
277
278 def test_next_identifier_first_project
278 def test_next_identifier_first_project
279 Project.delete_all
279 Project.delete_all
280 assert_nil Project.next_identifier
280 assert_nil Project.next_identifier
281 end
281 end
282
282
283
283
284 def test_enabled_module_names_should_not_recreate_enabled_modules
284 def test_enabled_module_names_should_not_recreate_enabled_modules
285 project = Project.find(1)
285 project = Project.find(1)
286 # Remove one module
286 # Remove one module
287 modules = project.enabled_modules.slice(0..-2)
287 modules = project.enabled_modules.slice(0..-2)
288 assert modules.any?
288 assert modules.any?
289 assert_difference 'EnabledModule.count', -1 do
289 assert_difference 'EnabledModule.count', -1 do
290 project.enabled_module_names = modules.collect(&:name)
290 project.enabled_module_names = modules.collect(&:name)
291 end
291 end
292 project.reload
292 project.reload
293 # Ids should be preserved
293 # Ids should be preserved
294 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
294 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
295 end
295 end
296
296
297 def test_copy_from_existing_project
297 def test_copy_from_existing_project
298 source_project = Project.find(1)
298 source_project = Project.find(1)
299 copied_project = Project.copy_from(1)
299 copied_project = Project.copy_from(1)
300
300
301 assert copied_project
301 assert copied_project
302 # Cleared attributes
302 # Cleared attributes
303 assert copied_project.id.blank?
303 assert copied_project.id.blank?
304 assert copied_project.name.blank?
304 assert copied_project.name.blank?
305 assert copied_project.identifier.blank?
305 assert copied_project.identifier.blank?
306
306
307 # Duplicated attributes
307 # Duplicated attributes
308 assert_equal source_project.description, copied_project.description
308 assert_equal source_project.description, copied_project.description
309 assert_equal source_project.enabled_modules, copied_project.enabled_modules
309 assert_equal source_project.enabled_modules, copied_project.enabled_modules
310 assert_equal source_project.trackers, copied_project.trackers
310 assert_equal source_project.trackers, copied_project.trackers
311
311
312 # Default attributes
312 # Default attributes
313 assert_equal 1, copied_project.status
313 assert_equal 1, copied_project.status
314 end
314 end
315
315
316 def test_activities_should_use_the_system_activities
316 def test_activities_should_use_the_system_activities
317 project = Project.find(1)
317 project = Project.find(1)
318 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
318 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
319 end
319 end
320
320
321
321
322 def test_activities_should_use_the_project_specific_activities
322 def test_activities_should_use_the_project_specific_activities
323 project = Project.find(1)
323 project = Project.find(1)
324 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
324 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
325 assert overridden_activity.save!
325 assert overridden_activity.save!
326
326
327 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
327 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
328 end
328 end
329
329
330 def test_activities_should_not_include_the_inactive_project_specific_activities
330 def test_activities_should_not_include_the_inactive_project_specific_activities
331 project = Project.find(1)
331 project = Project.find(1)
332 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
332 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
333 assert overridden_activity.save!
333 assert overridden_activity.save!
334
334
335 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
335 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
336 end
336 end
337
337
338 def test_activities_should_not_include_project_specific_activities_from_other_projects
338 def test_activities_should_not_include_project_specific_activities_from_other_projects
339 project = Project.find(1)
339 project = Project.find(1)
340 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
340 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
341 assert overridden_activity.save!
341 assert overridden_activity.save!
342
342
343 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
343 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
344 end
344 end
345
345
346 def test_activities_should_handle_nils
346 def test_activities_should_handle_nils
347 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
347 TimeEntryActivity.delete_all
348 TimeEntryActivity.delete_all
348
349
350 # No activities
349 project = Project.find(1)
351 project = Project.find(1)
350 assert project.activities.empty?
352 assert project.activities.empty?
353
354 # No system, one overridden
355 assert overridden_activity.save!
356 project.reload
357 assert_equal [overridden_activity], project.activities
351 end
358 end
352
359
353 def test_activities_should_override_system_activities_with_project_activities
360 def test_activities_should_override_system_activities_with_project_activities
354 project = Project.find(1)
361 project = Project.find(1)
355 parent_activity = TimeEntryActivity.find(:first)
362 parent_activity = TimeEntryActivity.find(:first)
356 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
363 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
357 assert overridden_activity.save!
364 assert overridden_activity.save!
358
365
359 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
366 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
360 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
367 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
361 end
368 end
362
369
370 def test_activities_should_include_inactive_activities_if_specified
371 project = Project.find(1)
372 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
373 assert overridden_activity.save!
374
375 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
376 end
377
363 context "Project#copy" do
378 context "Project#copy" do
364 setup do
379 setup do
365 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
380 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
366 Project.destroy_all :identifier => "copy-test"
381 Project.destroy_all :identifier => "copy-test"
367 @source_project = Project.find(2)
382 @source_project = Project.find(2)
368 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
383 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
369 @project.trackers = @source_project.trackers
384 @project.trackers = @source_project.trackers
370 @project.enabled_modules = @source_project.enabled_modules
385 @project.enabled_modules = @source_project.enabled_modules
371 end
386 end
372
387
373 should "copy issues" do
388 should "copy issues" do
374 assert @project.valid?
389 assert @project.valid?
375 assert @project.issues.empty?
390 assert @project.issues.empty?
376 assert @project.copy(@source_project)
391 assert @project.copy(@source_project)
377
392
378 assert_equal @source_project.issues.size, @project.issues.size
393 assert_equal @source_project.issues.size, @project.issues.size
379 @project.issues.each do |issue|
394 @project.issues.each do |issue|
380 assert issue.valid?
395 assert issue.valid?
381 assert ! issue.assigned_to.blank?
396 assert ! issue.assigned_to.blank?
382 assert_equal @project, issue.project
397 assert_equal @project, issue.project
383 end
398 end
384 end
399 end
385
400
386 should "change the new issues to use the copied version" do
401 should "change the new issues to use the copied version" do
387 assigned_version = Version.generate!(:name => "Assigned Issues")
402 assigned_version = Version.generate!(:name => "Assigned Issues")
388 @source_project.versions << assigned_version
403 @source_project.versions << assigned_version
389 assert_equal 1, @source_project.versions.size
404 assert_equal 1, @source_project.versions.size
390 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
405 @source_project.issues << Issue.generate!(:fixed_version_id => assigned_version.id,
391 :subject => "change the new issues to use the copied version",
406 :subject => "change the new issues to use the copied version",
392 :tracker_id => 1,
407 :tracker_id => 1,
393 :project_id => @source_project.id)
408 :project_id => @source_project.id)
394
409
395 assert @project.copy(@source_project)
410 assert @project.copy(@source_project)
396 @project.reload
411 @project.reload
397 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
412 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
398
413
399 assert copied_issue
414 assert copied_issue
400 assert copied_issue.fixed_version
415 assert copied_issue.fixed_version
401 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
416 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
402 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
417 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
403 end
418 end
404
419
405 should "copy members" do
420 should "copy members" do
406 assert @project.valid?
421 assert @project.valid?
407 assert @project.members.empty?
422 assert @project.members.empty?
408 assert @project.copy(@source_project)
423 assert @project.copy(@source_project)
409
424
410 assert_equal @source_project.members.size, @project.members.size
425 assert_equal @source_project.members.size, @project.members.size
411 @project.members.each do |member|
426 @project.members.each do |member|
412 assert member
427 assert member
413 assert_equal @project, member.project
428 assert_equal @project, member.project
414 end
429 end
415 end
430 end
416
431
417 should "copy project specific queries" do
432 should "copy project specific queries" do
418 assert @project.valid?
433 assert @project.valid?
419 assert @project.queries.empty?
434 assert @project.queries.empty?
420 assert @project.copy(@source_project)
435 assert @project.copy(@source_project)
421
436
422 assert_equal @source_project.queries.size, @project.queries.size
437 assert_equal @source_project.queries.size, @project.queries.size
423 @project.queries.each do |query|
438 @project.queries.each do |query|
424 assert query
439 assert query
425 assert_equal @project, query.project
440 assert_equal @project, query.project
426 end
441 end
427 end
442 end
428
443
429 should "copy versions" do
444 should "copy versions" do
430 @source_project.versions << Version.generate!
445 @source_project.versions << Version.generate!
431 @source_project.versions << Version.generate!
446 @source_project.versions << Version.generate!
432
447
433 assert @project.versions.empty?
448 assert @project.versions.empty?
434 assert @project.copy(@source_project)
449 assert @project.copy(@source_project)
435
450
436 assert_equal @source_project.versions.size, @project.versions.size
451 assert_equal @source_project.versions.size, @project.versions.size
437 @project.versions.each do |version|
452 @project.versions.each do |version|
438 assert version
453 assert version
439 assert_equal @project, version.project
454 assert_equal @project, version.project
440 end
455 end
441 end
456 end
442
457
443 should "copy wiki" do
458 should "copy wiki" do
444 assert @project.copy(@source_project)
459 assert @project.copy(@source_project)
445
460
446 assert @project.wiki
461 assert @project.wiki
447 assert_not_equal @source_project.wiki, @project.wiki
462 assert_not_equal @source_project.wiki, @project.wiki
448 assert_equal "Start page", @project.wiki.start_page
463 assert_equal "Start page", @project.wiki.start_page
449 end
464 end
450
465
451 should "copy wiki pages and content" do
466 should "copy wiki pages and content" do
452 assert @project.copy(@source_project)
467 assert @project.copy(@source_project)
453
468
454 assert @project.wiki
469 assert @project.wiki
455 assert_equal 1, @project.wiki.pages.length
470 assert_equal 1, @project.wiki.pages.length
456
471
457 @project.wiki.pages.each do |wiki_page|
472 @project.wiki.pages.each do |wiki_page|
458 assert wiki_page.content
473 assert wiki_page.content
459 assert !@source_project.wiki.pages.include?(wiki_page)
474 assert !@source_project.wiki.pages.include?(wiki_page)
460 end
475 end
461 end
476 end
462
477
463 should "copy custom fields"
478 should "copy custom fields"
464
479
465 should "copy issue categories" do
480 should "copy issue categories" do
466 assert @project.copy(@source_project)
481 assert @project.copy(@source_project)
467
482
468 assert_equal 2, @project.issue_categories.size
483 assert_equal 2, @project.issue_categories.size
469 @project.issue_categories.each do |issue_category|
484 @project.issue_categories.each do |issue_category|
470 assert !@source_project.issue_categories.include?(issue_category)
485 assert !@source_project.issue_categories.include?(issue_category)
471 end
486 end
472 end
487 end
473
488
474 should "change the new issues to use the copied issue categories" do
489 should "change the new issues to use the copied issue categories" do
475 issue = Issue.find(4)
490 issue = Issue.find(4)
476 issue.update_attribute(:category_id, 3)
491 issue.update_attribute(:category_id, 3)
477
492
478 assert @project.copy(@source_project)
493 assert @project.copy(@source_project)
479
494
480 @project.issues.each do |issue|
495 @project.issues.each do |issue|
481 assert issue.category
496 assert issue.category
482 assert_equal "Stock management", issue.category.name # Same name
497 assert_equal "Stock management", issue.category.name # Same name
483 assert_not_equal IssueCategory.find(3), issue.category # Different record
498 assert_not_equal IssueCategory.find(3), issue.category # Different record
484 end
499 end
485 end
500 end
486
501
487 should "copy issue relations"
502 should "copy issue relations"
488 should "link issue relations if cross project issue relations are valid"
503 should "link issue relations if cross project issue relations are valid"
489
504
490 end
505 end
491
506
492 end
507 end
General Comments 0
You need to be logged in to leave comments. Login now