##// END OF EJS Templates
Added the ability to copy a project in the Project Administration panel....
Eric Davis -
r2608:fa7bd1c71dca
parent child
Show More
@@ -0,0 +1,16
1 <h2><%=l(:label_project_copy)%></h2>
2
3 <% labelled_tabular_form_for :project, @project, :url => { :action => "copy" } do |f| %>
4 <%= render :partial => 'form', :locals => { :f => f } %>
5
6 <fieldset class="box"><legend><%= l(:label_module_plural) %></legend>
7 <% Redmine::AccessControl.available_project_modules.each do |m| %>
8 <label class="floating">
9 <%= check_box_tag 'enabled_modules[]', m, @project.module_enabled?(m) %>
10 <%= l_or_humanize(m, :prefix => "project_module_") %>
11 </label>
12 <% end %>
13 </fieldset>
14
15 <%= submit_tag l(:button_copy) %>
16 <% end %>
@@ -1,295 +1,319
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class ProjectsController < ApplicationController
19 19 menu_item :overview
20 20 menu_item :activity, :only => :activity
21 21 menu_item :roadmap, :only => :roadmap
22 22 menu_item :files, :only => [:list_files, :add_file]
23 23 menu_item :settings, :only => :settings
24 24 menu_item :issues, :only => [:changelog]
25 25
26 before_filter :find_project, :except => [ :index, :list, :add, :activity ]
26 before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
27 27 before_filter :find_optional_project, :only => :activity
28 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy, :activity ]
29 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
28 before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
29 before_filter :require_admin, :only => [ :add, :copy, :archive, :unarchive, :destroy ]
30 30 accept_key_auth :activity
31 31
32 32 after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
33 33 if controller.request.post?
34 34 controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
35 35 end
36 36 end
37 37
38 38 helper :sort
39 39 include SortHelper
40 40 helper :custom_fields
41 41 include CustomFieldsHelper
42 42 helper :issues
43 43 helper IssuesHelper
44 44 helper :queries
45 45 include QueriesHelper
46 46 helper :repositories
47 47 include RepositoriesHelper
48 48 include ProjectsHelper
49 49
50 50 # Lists visible projects
51 51 def index
52 52 respond_to do |format|
53 53 format.html {
54 54 @projects = Project.visible.find(:all, :order => 'lft')
55 55 }
56 56 format.atom {
57 57 projects = Project.visible.find(:all, :order => 'created_on DESC',
58 58 :limit => Setting.feeds_limit.to_i)
59 59 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 60 }
61 61 end
62 62 end
63 63
64 64 # Add a new project
65 65 def add
66 66 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 67 @trackers = Tracker.all
68 68 @project = Project.new(params[:project])
69 69 if request.get?
70 70 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
71 71 @project.trackers = Tracker.all
72 72 @project.is_public = Setting.default_projects_public?
73 73 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
74 74 else
75 75 @project.enabled_module_names = params[:enabled_modules]
76 76 if @project.save
77 77 @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
78 78 flash[:notice] = l(:notice_successful_create)
79 79 redirect_to :controller => 'admin', :action => 'projects'
80 80 end
81 81 end
82 82 end
83
84 def copy
85 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
86 @trackers = Tracker.all
87 @root_projects = Project.find(:all,
88 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
89 :order => 'name')
90 if request.get?
91 @project = Project.copy_from(params[:id])
92 if @project
93 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
94 else
95 redirect_to :controller => 'admin', :action => 'projects'
96 end
97 else
98 @project = Project.new(params[:project])
99 @project.enabled_module_names = params[:enabled_modules]
100 if @project.copy(params[:id])
101 flash[:notice] = l(:notice_successful_create)
102 redirect_to :controller => 'admin', :action => 'projects'
103 end
104 end
105 end
106
83 107
84 108 # Show @project
85 109 def show
86 110 if params[:jump]
87 111 # try to redirect to the requested menu item
88 112 redirect_to_project_menu_item(@project, params[:jump]) && return
89 113 end
90 114
91 115 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
92 116 @subprojects = @project.children.visible
93 117 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
94 118 @trackers = @project.rolled_up_trackers
95 119
96 120 cond = @project.project_condition(Setting.display_subprojects_issues?)
97 121
98 122 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
99 123 :include => [:project, :status, :tracker],
100 124 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
101 125 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
102 126 :include => [:project, :status, :tracker],
103 127 :conditions => cond)
104 128
105 129 TimeEntry.visible_by(User.current) do
106 130 @total_hours = TimeEntry.sum(:hours,
107 131 :include => :project,
108 132 :conditions => cond).to_f
109 133 end
110 134 @key = User.current.rss_key
111 135 end
112 136
113 137 def settings
114 138 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
115 139 @issue_category ||= IssueCategory.new
116 140 @member ||= @project.members.new
117 141 @trackers = Tracker.all
118 142 @repository ||= @project.repository
119 143 @wiki ||= @project.wiki
120 144 end
121 145
122 146 # Edit @project
123 147 def edit
124 148 if request.post?
125 149 @project.attributes = params[:project]
126 150 if @project.save
127 151 @project.set_parent!(params[:project]['parent_id']) if User.current.admin? && params[:project].has_key?('parent_id')
128 152 flash[:notice] = l(:notice_successful_update)
129 153 redirect_to :action => 'settings', :id => @project
130 154 else
131 155 settings
132 156 render :action => 'settings'
133 157 end
134 158 end
135 159 end
136 160
137 161 def modules
138 162 @project.enabled_module_names = params[:enabled_modules]
139 163 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
140 164 end
141 165
142 166 def archive
143 167 @project.archive if request.post? && @project.active?
144 168 redirect_to :controller => 'admin', :action => 'projects'
145 169 end
146 170
147 171 def unarchive
148 172 @project.unarchive if request.post? && !@project.active?
149 173 redirect_to :controller => 'admin', :action => 'projects'
150 174 end
151 175
152 176 # Delete @project
153 177 def destroy
154 178 @project_to_destroy = @project
155 179 if request.post? and params[:confirm]
156 180 @project_to_destroy.destroy
157 181 redirect_to :controller => 'admin', :action => 'projects'
158 182 end
159 183 # hide project in layout
160 184 @project = nil
161 185 end
162 186
163 187 # Add a new issue category to @project
164 188 def add_issue_category
165 189 @category = @project.issue_categories.build(params[:category])
166 190 if request.post? and @category.save
167 191 respond_to do |format|
168 192 format.html do
169 193 flash[:notice] = l(:notice_successful_create)
170 194 redirect_to :action => 'settings', :tab => 'categories', :id => @project
171 195 end
172 196 format.js do
173 197 # IE doesn't support the replace_html rjs method for select box options
174 198 render(:update) {|page| page.replace "issue_category_id",
175 199 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]')
176 200 }
177 201 end
178 202 end
179 203 end
180 204 end
181 205
182 206 # Add a new version to @project
183 207 def add_version
184 208 @version = @project.versions.build(params[:version])
185 209 if request.post? and @version.save
186 210 flash[:notice] = l(:notice_successful_create)
187 211 redirect_to :action => 'settings', :tab => 'versions', :id => @project
188 212 end
189 213 end
190 214
191 215 def add_file
192 216 if request.post?
193 217 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
194 218 attachments = attach_files(container, params[:attachments])
195 219 if !attachments.empty? && Setting.notified_events.include?('file_added')
196 220 Mailer.deliver_attachments_added(attachments)
197 221 end
198 222 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
199 223 return
200 224 end
201 225 @versions = @project.versions.sort
202 226 end
203 227
204 228 def list_files
205 229 sort_init 'filename', 'asc'
206 230 sort_update 'filename' => "#{Attachment.table_name}.filename",
207 231 'created_on' => "#{Attachment.table_name}.created_on",
208 232 'size' => "#{Attachment.table_name}.filesize",
209 233 'downloads' => "#{Attachment.table_name}.downloads"
210 234
211 235 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
212 236 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
213 237 render :layout => !request.xhr?
214 238 end
215 239
216 240 # Show changelog for @project
217 241 def changelog
218 242 @trackers = @project.trackers.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
219 243 retrieve_selected_tracker_ids(@trackers)
220 244 @versions = @project.versions.sort
221 245 end
222 246
223 247 def roadmap
224 248 @trackers = @project.trackers.find(:all, :conditions => ["is_in_roadmap=?", true])
225 249 retrieve_selected_tracker_ids(@trackers)
226 250 @versions = @project.versions.sort
227 251 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
228 252 end
229 253
230 254 def activity
231 255 @days = Setting.activity_days_default.to_i
232 256
233 257 if params[:from]
234 258 begin; @date_to = params[:from].to_date + 1; rescue; end
235 259 end
236 260
237 261 @date_to ||= Date.today + 1
238 262 @date_from = @date_to - @days
239 263 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
240 264 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
241 265
242 266 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
243 267 :with_subprojects => @with_subprojects,
244 268 :author => @author)
245 269 @activity.scope_select {|t| !params["show_#{t}"].nil?}
246 270 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
247 271
248 272 events = @activity.events(@date_from, @date_to)
249 273
250 274 respond_to do |format|
251 275 format.html {
252 276 @events_by_day = events.group_by(&:event_date)
253 277 render :layout => false if request.xhr?
254 278 }
255 279 format.atom {
256 280 title = l(:label_activity)
257 281 if @author
258 282 title = @author.name
259 283 elsif @activity.scope.size == 1
260 284 title = l("label_#{@activity.scope.first.singularize}_plural")
261 285 end
262 286 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
263 287 }
264 288 end
265 289
266 290 rescue ActiveRecord::RecordNotFound
267 291 render_404
268 292 end
269 293
270 294 private
271 295 # Find project of id params[:id]
272 296 # if not found, redirect to project list
273 297 # Used as a before_filter
274 298 def find_project
275 299 @project = Project.find(params[:id])
276 300 rescue ActiveRecord::RecordNotFound
277 301 render_404
278 302 end
279 303
280 304 def find_optional_project
281 305 return true unless params[:id]
282 306 @project = Project.find(params[:id])
283 307 authorize
284 308 rescue ActiveRecord::RecordNotFound
285 309 render_404
286 310 end
287 311
288 312 def retrieve_selected_tracker_ids(selectable_trackers)
289 313 if ids = params[:tracker_ids]
290 314 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
291 315 else
292 316 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
293 317 end
294 318 end
295 319 end
@@ -1,337 +1,397
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Project < ActiveRecord::Base
19 19 # Project statuses
20 20 STATUS_ACTIVE = 1
21 21 STATUS_ARCHIVED = 9
22 22
23 23 has_many :members, :include => :user, :conditions => "#{User.table_name}.status=#{User::STATUS_ACTIVE}"
24 24 has_many :users, :through => :members
25 25 has_many :enabled_modules, :dependent => :delete_all
26 26 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
27 27 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
28 28 has_many :issue_changes, :through => :issues, :source => :journals
29 29 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
30 30 has_many :time_entries, :dependent => :delete_all
31 31 has_many :queries, :dependent => :delete_all
32 32 has_many :documents, :dependent => :destroy
33 33 has_many :news, :dependent => :delete_all, :include => :author
34 34 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
35 35 has_many :boards, :dependent => :destroy, :order => "position ASC"
36 36 has_one :repository, :dependent => :destroy
37 37 has_many :changesets, :through => :repository
38 38 has_one :wiki, :dependent => :destroy
39 39 # Custom field for the project issues
40 40 has_and_belongs_to_many :issue_custom_fields,
41 41 :class_name => 'IssueCustomField',
42 42 :order => "#{CustomField.table_name}.position",
43 43 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
44 44 :association_foreign_key => 'custom_field_id'
45 45
46 46 acts_as_nested_set :order => 'name', :dependent => :destroy
47 47 acts_as_attachable :view_permission => :view_files,
48 48 :delete_permission => :manage_files
49 49
50 50 acts_as_customizable
51 51 acts_as_searchable :columns => ['name', 'description'], :project_key => 'id', :permission => nil
52 52 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
53 53 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o.id}},
54 54 :author => nil
55 55
56 56 attr_protected :status, :enabled_module_names
57 57
58 58 validates_presence_of :name, :identifier
59 59 validates_uniqueness_of :name, :identifier
60 60 validates_associated :repository, :wiki
61 61 validates_length_of :name, :maximum => 30
62 62 validates_length_of :homepage, :maximum => 255
63 63 validates_length_of :identifier, :in => 1..20
64 64 validates_format_of :identifier, :with => /^[a-z0-9\-]*$/
65 65
66 66 before_destroy :delete_all_members
67 67
68 68 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] } }
69 69 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
70 70 named_scope :public, { :conditions => { :is_public => true } }
71 71 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
72 72
73 73 def identifier=(identifier)
74 74 super unless identifier_frozen?
75 75 end
76 76
77 77 def identifier_frozen?
78 78 errors[:identifier].nil? && !(new_record? || identifier.blank?)
79 79 end
80 80
81 81 def issues_with_subprojects(include_subprojects=false)
82 82 conditions = nil
83 83 if include_subprojects
84 84 ids = [id] + descendants.collect(&:id)
85 85 conditions = ["#{Project.table_name}.id IN (#{ids.join(',')}) AND #{Project.visible_by}"]
86 86 end
87 87 conditions ||= ["#{Project.table_name}.id = ?", id]
88 88 # Quick and dirty fix for Rails 2 compatibility
89 89 Issue.send(:with_scope, :find => { :conditions => conditions }) do
90 90 Version.send(:with_scope, :find => { :conditions => conditions }) do
91 91 yield
92 92 end
93 93 end
94 94 end
95 95
96 96 # returns latest created projects
97 97 # non public projects will be returned only if user is a member of those
98 98 def self.latest(user=nil, count=5)
99 99 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
100 100 end
101 101
102 102 # Returns a SQL :conditions string used to find all active projects for the specified user.
103 103 #
104 104 # Examples:
105 105 # Projects.visible_by(admin) => "projects.status = 1"
106 106 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
107 107 def self.visible_by(user=nil)
108 108 user ||= User.current
109 109 if user && user.admin?
110 110 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
111 111 elsif user && user.memberships.any?
112 112 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(',')}))"
113 113 else
114 114 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
115 115 end
116 116 end
117 117
118 118 def self.allowed_to_condition(user, permission, options={})
119 119 statements = []
120 120 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
121 121 if perm = Redmine::AccessControl.permission(permission)
122 122 unless perm.project_module.nil?
123 123 # If the permission belongs to a project module, make sure the module is enabled
124 124 base_statement << " AND EXISTS (SELECT em.id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}' AND em.project_id=#{Project.table_name}.id)"
125 125 end
126 126 end
127 127 if options[:project]
128 128 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
129 129 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
130 130 base_statement = "(#{project_statement}) AND (#{base_statement})"
131 131 end
132 132 if user.admin?
133 133 # no restriction
134 134 else
135 135 statements << "1=0"
136 136 if user.logged?
137 137 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}" if Role.non_member.allowed_to?(permission)
138 138 allowed_project_ids = user.memberships.select {|m| m.role.allowed_to?(permission)}.collect {|m| m.project_id}
139 139 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
140 140 elsif Role.anonymous.allowed_to?(permission)
141 141 # anonymous user allowed on public project
142 142 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
143 143 else
144 144 # anonymous user is not authorized
145 145 end
146 146 end
147 147 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
148 148 end
149 149
150 150 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
151 151 #
152 152 # Examples:
153 153 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
154 154 # project.project_condition(false) => "projects.id = 1"
155 155 def project_condition(with_subprojects)
156 156 cond = "#{Project.table_name}.id = #{id}"
157 157 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
158 158 cond
159 159 end
160 160
161 161 def self.find(*args)
162 162 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
163 163 project = find_by_identifier(*args)
164 164 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
165 165 project
166 166 else
167 167 super
168 168 end
169 169 end
170 170
171 171 def to_param
172 172 # id is used for projects with a numeric identifier (compatibility)
173 173 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
174 174 end
175 175
176 176 def active?
177 177 self.status == STATUS_ACTIVE
178 178 end
179 179
180 180 # Archives the project and its descendants recursively
181 181 def archive
182 182 # Archive subprojects if any
183 183 children.each do |subproject|
184 184 subproject.archive
185 185 end
186 186 update_attribute :status, STATUS_ARCHIVED
187 187 end
188 188
189 189 # Unarchives the project
190 190 # All its ancestors must be active
191 191 def unarchive
192 192 return false if ancestors.detect {|a| !a.active?}
193 193 update_attribute :status, STATUS_ACTIVE
194 194 end
195 195
196 196 # Returns an array of projects the project can be moved to
197 197 def possible_parents
198 198 @possible_parents ||= (Project.active.find(:all) - self_and_descendants)
199 199 end
200 200
201 201 # Sets the parent of the project
202 202 # Argument can be either a Project, a String, a Fixnum or nil
203 203 def set_parent!(p)
204 204 unless p.nil? || p.is_a?(Project)
205 205 if p.to_s.blank?
206 206 p = nil
207 207 else
208 208 p = Project.find_by_id(p)
209 209 return false unless p
210 210 end
211 211 end
212 212 if p == parent && !p.nil?
213 213 # Nothing to do
214 214 true
215 215 elsif p.nil? || (p.active? && move_possible?(p))
216 216 # Insert the project so that target's children or root projects stay alphabetically sorted
217 217 sibs = (p.nil? ? self.class.roots : p.children)
218 218 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
219 219 if to_be_inserted_before
220 220 move_to_left_of(to_be_inserted_before)
221 221 elsif p.nil?
222 222 if sibs.empty?
223 223 # move_to_root adds the project in first (ie. left) position
224 224 move_to_root
225 225 else
226 226 move_to_right_of(sibs.last) unless self == sibs.last
227 227 end
228 228 else
229 229 # move_to_child_of adds the project in last (ie.right) position
230 230 move_to_child_of(p)
231 231 end
232 232 true
233 233 else
234 234 # Can not move to the given target
235 235 false
236 236 end
237 237 end
238 238
239 239 # Returns an array of the trackers used by the project and its active sub projects
240 240 def rolled_up_trackers
241 241 @rolled_up_trackers ||=
242 242 Tracker.find(:all, :include => :projects,
243 243 :select => "DISTINCT #{Tracker.table_name}.*",
244 244 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
245 245 :order => "#{Tracker.table_name}.position")
246 246 end
247 247
248 248 # Deletes all project's members
249 249 def delete_all_members
250 250 Member.delete_all(['project_id = ?', id])
251 251 end
252 252
253 253 # Users issues can be assigned to
254 254 def assignable_users
255 255 members.select {|m| m.role.assignable?}.collect {|m| m.user}.sort
256 256 end
257 257
258 258 # Returns the mail adresses of users that should be always notified on project events
259 259 def recipients
260 260 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user.mail}
261 261 end
262 262
263 263 # Returns an array of all custom fields enabled for project issues
264 264 # (explictly associated custom fields and custom fields enabled for all projects)
265 265 def all_issue_custom_fields
266 266 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
267 267 end
268 268
269 269 def project
270 270 self
271 271 end
272 272
273 273 def <=>(project)
274 274 name.downcase <=> project.name.downcase
275 275 end
276 276
277 277 def to_s
278 278 name
279 279 end
280 280
281 281 # Returns a short description of the projects (first lines)
282 282 def short_description(length = 255)
283 283 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
284 284 end
285 285
286 286 # Return true if this project is allowed to do the specified action.
287 287 # action can be:
288 288 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
289 289 # * a permission Symbol (eg. :edit_project)
290 290 def allows_to?(action)
291 291 if action.is_a? Hash
292 292 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
293 293 else
294 294 allowed_permissions.include? action
295 295 end
296 296 end
297 297
298 298 def module_enabled?(module_name)
299 299 module_name = module_name.to_s
300 300 enabled_modules.detect {|m| m.name == module_name}
301 301 end
302 302
303 303 def enabled_module_names=(module_names)
304 304 if module_names && module_names.is_a?(Array)
305 305 module_names = module_names.collect(&:to_s)
306 306 # remove disabled modules
307 307 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
308 308 # add new modules
309 309 module_names.each {|name| enabled_modules << EnabledModule.new(:name => name)}
310 310 else
311 311 enabled_modules.clear
312 312 end
313 313 end
314 314
315 315 # Returns an auto-generated project identifier based on the last identifier used
316 316 def self.next_identifier
317 317 p = Project.find(:first, :order => 'created_on DESC')
318 318 p.nil? ? nil : p.identifier.to_s.succ
319 319 end
320 320
321 # Copies and saves the Project instance based on the +project+.
322 # Will duplicate the source project's:
323 # * Issues
324 # * Members
325 # * Queries
326 def copy(project)
327 project = project.is_a?(Project) ? project : Project.find(project)
328
329 Project.transaction do
330 # Issues
331 project.issues.each do |issue|
332 new_issue = Issue.new
333 new_issue.copy_from(issue)
334 self.issues << new_issue
335 end
336
337 # Members
338 project.members.each do |member|
339 new_member = Member.new
340 new_member.attributes = member.attributes.dup.except("project_id")
341 new_member.project = self
342 self.members << new_member
343 end
344
345 # Queries
346 project.queries.each do |query|
347 new_query = Query.new
348 new_query.attributes = query.attributes.dup.except("project_id", "sort_criteria")
349 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
350 new_query.project = self
351 self.queries << new_query
352 end
353
354 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
355 self.save
356 end
357 end
358
359
360 # Copies +project+ and returns the new instance. This will not save
361 # the copy
362 def self.copy_from(project)
363 begin
364 project = project.is_a?(Project) ? project : Project.find(project)
365 if project
366 # clear unique attributes
367 attributes = project.attributes.dup.except('name', 'identifier', 'id', 'status')
368 copy = Project.new(attributes)
369 copy.enabled_modules = project.enabled_modules
370 copy.trackers = project.trackers
371 copy.custom_values = project.custom_values.collect {|v| v.clone}
372 return copy
373 else
374 return nil
375 end
376 rescue ActiveRecord::RecordNotFound
377 return nil
378 end
379 end
380
321 381 protected
322 382 def validate
323 383 errors.add(:identifier, :invalid) if !identifier.blank? && identifier.match(/^\d*$/)
324 384 end
325 385
326 386 private
327 387 def allowed_permissions
328 388 @allowed_permissions ||= begin
329 389 module_names = enabled_modules.collect {|m| m.name}
330 390 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
331 391 end
332 392 end
333 393
334 394 def allowed_actions
335 395 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
336 396 end
337 397 end
@@ -1,48 +1,52
1 1 <div class="contextual">
2 2 <%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add' %>
3 3 </div>
4 4
5 5 <h2><%=l(:label_project_plural)%></h2>
6 6
7 7 <% form_tag({}, :method => :get) do %>
8 8 <fieldset><legend><%= l(:label_filter_plural) %></legend>
9 9 <label><%= l(:field_status) %> :</label>
10 10 <%= select_tag 'status', project_status_options_for_select(@status), :class => "small", :onchange => "this.form.submit(); return false;" %>
11 11 <label><%= l(:label_project) %>:</label>
12 12 <%= text_field_tag 'name', params[:name], :size => 30 %>
13 13 <%= submit_tag l(:button_apply), :class => "small", :name => nil %>
14 14 </fieldset>
15 15 <% end %>
16 16 &nbsp;
17 17
18 18 <table class="list">
19 19 <thead><tr>
20 20 <th><%=l(:label_project)%></th>
21 21 <th><%=l(:field_description)%></th>
22 22 <th><%=l(:field_is_public)%></th>
23 23 <th><%=l(:field_created_on)%></th>
24 24 <th></th>
25 25 <th></th>
26 <th></th>
26 27 </tr></thead>
27 28 <tbody>
28 29 <% for project in @projects %>
29 30 <tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %>">
30 31 <td class="name" style="padding-left: <%= project.level %>em;"><%= project.active? ? link_to(h(project.name), :controller => 'projects', :action => 'settings', :id => project) : h(project.name) %></td>
31 32 <td><%= textilizable project.short_description, :project => project %></td>
32 33 <td align="center"><%= image_tag 'true.png' if project.is_public? %></td>
33 34 <td align="center"><%= format_date(project.created_on) %></td>
34 35 <td align="center" style="width:10%">
35 36 <small>
36 37 <%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
37 38 <%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %>
38 39 </small>
39 40 </td>
40 41 <td align="center" style="width:10%">
42 <%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %>
43 </td>
44 <td align="center" style="width:10%">
41 45 <small><%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %></small>
42 46 </td>
43 47 </tr>
44 48 <% end %>
45 49 </tbody>
46 50 </table>
47 51
48 52 <% html_title(l(:label_project_plural)) -%>
@@ -1,795 +1,796
1 1 en:
2 2 date:
3 3 formats:
4 4 # Use the strftime parameters for formats.
5 5 # When no format has been given, it uses default.
6 6 # You can provide other formats here if you like!
7 7 default: "%m/%d/%Y"
8 8 short: "%b %d"
9 9 long: "%B %d, %Y"
10 10
11 11 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
12 12 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
13 13
14 14 # Don't forget the nil at the beginning; there's no such thing as a 0th month
15 15 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
16 16 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
17 17 # Used in date_select and datime_select.
18 18 order: [ :year, :month, :day ]
19 19
20 20 time:
21 21 formats:
22 22 default: "%m/%d/%Y %I:%M %p"
23 23 time: "%I:%M %p"
24 24 short: "%d %b %H:%M"
25 25 long: "%B %d, %Y %H:%M"
26 26 am: "am"
27 27 pm: "pm"
28 28
29 29 datetime:
30 30 distance_in_words:
31 31 half_a_minute: "half a minute"
32 32 less_than_x_seconds:
33 33 one: "less than 1 second"
34 34 other: "less than {{count}} seconds"
35 35 x_seconds:
36 36 one: "1 second"
37 37 other: "{{count}} seconds"
38 38 less_than_x_minutes:
39 39 one: "less than a minute"
40 40 other: "less than {{count}} minutes"
41 41 x_minutes:
42 42 one: "1 minute"
43 43 other: "{{count}} minutes"
44 44 about_x_hours:
45 45 one: "about 1 hour"
46 46 other: "about {{count}} hours"
47 47 x_days:
48 48 one: "1 day"
49 49 other: "{{count}} days"
50 50 about_x_months:
51 51 one: "about 1 month"
52 52 other: "about {{count}} months"
53 53 x_months:
54 54 one: "1 month"
55 55 other: "{{count}} months"
56 56 about_x_years:
57 57 one: "about 1 year"
58 58 other: "about {{count}} years"
59 59 over_x_years:
60 60 one: "over 1 year"
61 61 other: "over {{count}} years"
62 62
63 63 # Used in array.to_sentence.
64 64 support:
65 65 array:
66 66 sentence_connector: "and"
67 67 skip_last_comma: false
68 68
69 69 activerecord:
70 70 errors:
71 71 messages:
72 72 inclusion: "is not included in the list"
73 73 exclusion: "is reserved"
74 74 invalid: "is invalid"
75 75 confirmation: "doesn't match confirmation"
76 76 accepted: "must be accepted"
77 77 empty: "can't be empty"
78 78 blank: "can't be blank"
79 79 too_long: "is too long (maximum is {{count}} characters)"
80 80 too_short: "is too short (minimum is {{count}} characters)"
81 81 wrong_length: "is the wrong length (should be {{count}} characters)"
82 82 taken: "has already been taken"
83 83 not_a_number: "is not a number"
84 84 not_a_date: "is not a valid date"
85 85 greater_than: "must be greater than {{count}}"
86 86 greater_than_or_equal_to: "must be greater than or equal to {{count}}"
87 87 equal_to: "must be equal to {{count}}"
88 88 less_than: "must be less than {{count}}"
89 89 less_than_or_equal_to: "must be less than or equal to {{count}}"
90 90 odd: "must be odd"
91 91 even: "must be even"
92 92 greater_than_start_date: "must be greater than start date"
93 93 not_same_project: "doesn't belong to the same project"
94 94 circular_dependency: "This relation would create a circular dependency"
95 95
96 96 actionview_instancetag_blank_option: Please select
97 97
98 98 general_text_No: 'No'
99 99 general_text_Yes: 'Yes'
100 100 general_text_no: 'no'
101 101 general_text_yes: 'yes'
102 102 general_lang_name: 'English'
103 103 general_csv_separator: ','
104 104 general_csv_decimal_separator: '.'
105 105 general_csv_encoding: ISO-8859-1
106 106 general_pdf_encoding: ISO-8859-1
107 107 general_first_day_of_week: '7'
108 108
109 109 notice_account_updated: Account was successfully updated.
110 110 notice_account_invalid_creditentials: Invalid user or password
111 111 notice_account_password_updated: Password was successfully updated.
112 112 notice_account_wrong_password: Wrong password
113 113 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
114 114 notice_account_unknown_email: Unknown user.
115 115 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
116 116 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
117 117 notice_account_activated: Your account has been activated. You can now log in.
118 118 notice_successful_create: Successful creation.
119 119 notice_successful_update: Successful update.
120 120 notice_successful_delete: Successful deletion.
121 121 notice_successful_connection: Successful connection.
122 122 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
123 123 notice_locking_conflict: Data has been updated by another user.
124 124 notice_not_authorized: You are not authorized to access this page.
125 125 notice_email_sent: "An email was sent to {{value}}"
126 126 notice_email_error: "An error occurred while sending mail ({{value}})"
127 127 notice_feeds_access_key_reseted: Your RSS access key was reset.
128 128 notice_failed_to_save_issues: "Failed to save {{count}} issue(s) on {{total}} selected: {{ids}}."
129 129 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
130 130 notice_account_pending: "Your account was created and is now pending administrator approval."
131 131 notice_default_data_loaded: Default configuration successfully loaded.
132 132 notice_unable_delete_version: Unable to delete version.
133 133
134 134 error_can_t_load_default_data: "Default configuration could not be loaded: {{value}}"
135 135 error_scm_not_found: "The entry or revision was not found in the repository."
136 136 error_scm_command_failed: "An error occurred when trying to access the repository: {{value}}"
137 137 error_scm_annotate: "The entry does not exist or can not be annotated."
138 138 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
139 139
140 140 warning_attachments_not_saved: "{{count}} file(s) could not be saved."
141 141
142 142 mail_subject_lost_password: "Your {{value}} password"
143 143 mail_body_lost_password: 'To change your password, click on the following link:'
144 144 mail_subject_register: "Your {{value}} account activation"
145 145 mail_body_register: 'To activate your account, click on the following link:'
146 146 mail_body_account_information_external: "You can use your {{value}} account to log in."
147 147 mail_body_account_information: Your account information
148 148 mail_subject_account_activation_request: "{{value}} account activation request"
149 149 mail_body_account_activation_request: "A new user ({{value}}) has registered. The account is pending your approval:"
150 150 mail_subject_reminder: "{{count}} issue(s) due in the next days"
151 151 mail_body_reminder: "{{count}} issue(s) that are assigned to you are due in the next {{days}} days:"
152 152
153 153 gui_validation_error: 1 error
154 154 gui_validation_error_plural: "{{count}} errors"
155 155
156 156 field_name: Name
157 157 field_description: Description
158 158 field_summary: Summary
159 159 field_is_required: Required
160 160 field_firstname: Firstname
161 161 field_lastname: Lastname
162 162 field_mail: Email
163 163 field_filename: File
164 164 field_filesize: Size
165 165 field_downloads: Downloads
166 166 field_author: Author
167 167 field_created_on: Created
168 168 field_updated_on: Updated
169 169 field_field_format: Format
170 170 field_is_for_all: For all projects
171 171 field_possible_values: Possible values
172 172 field_regexp: Regular expression
173 173 field_min_length: Minimum length
174 174 field_max_length: Maximum length
175 175 field_value: Value
176 176 field_category: Category
177 177 field_title: Title
178 178 field_project: Project
179 179 field_issue: Issue
180 180 field_status: Status
181 181 field_notes: Notes
182 182 field_is_closed: Issue closed
183 183 field_is_default: Default value
184 184 field_tracker: Tracker
185 185 field_subject: Subject
186 186 field_due_date: Due date
187 187 field_assigned_to: Assigned to
188 188 field_priority: Priority
189 189 field_fixed_version: Target version
190 190 field_user: User
191 191 field_role: Role
192 192 field_homepage: Homepage
193 193 field_is_public: Public
194 194 field_parent: Subproject of
195 195 field_is_in_chlog: Issues displayed in changelog
196 196 field_is_in_roadmap: Issues displayed in roadmap
197 197 field_login: Login
198 198 field_mail_notification: Email notifications
199 199 field_admin: Administrator
200 200 field_last_login_on: Last connection
201 201 field_language: Language
202 202 field_effective_date: Date
203 203 field_password: Password
204 204 field_new_password: New password
205 205 field_password_confirmation: Confirmation
206 206 field_version: Version
207 207 field_type: Type
208 208 field_host: Host
209 209 field_port: Port
210 210 field_account: Account
211 211 field_base_dn: Base DN
212 212 field_attr_login: Login attribute
213 213 field_attr_firstname: Firstname attribute
214 214 field_attr_lastname: Lastname attribute
215 215 field_attr_mail: Email attribute
216 216 field_onthefly: On-the-fly user creation
217 217 field_start_date: Start
218 218 field_done_ratio: % Done
219 219 field_auth_source: Authentication mode
220 220 field_hide_mail: Hide my email address
221 221 field_comments: Comment
222 222 field_url: URL
223 223 field_start_page: Start page
224 224 field_subproject: Subproject
225 225 field_hours: Hours
226 226 field_activity: Activity
227 227 field_spent_on: Date
228 228 field_identifier: Identifier
229 229 field_is_filter: Used as a filter
230 230 field_issue_to_id: Related issue
231 231 field_delay: Delay
232 232 field_assignable: Issues can be assigned to this role
233 233 field_redirect_existing_links: Redirect existing links
234 234 field_estimated_hours: Estimated time
235 235 field_column_names: Columns
236 236 field_time_zone: Time zone
237 237 field_searchable: Searchable
238 238 field_default_value: Default value
239 239 field_comments_sorting: Display comments
240 240 field_parent_title: Parent page
241 241 field_editable: Editable
242 242 field_watcher: Watcher
243 243 field_identity_url: OpenID URL
244 244 field_content: Content
245 245 field_group_by: Group results by
246 246
247 247 setting_app_title: Application title
248 248 setting_app_subtitle: Application subtitle
249 249 setting_welcome_text: Welcome text
250 250 setting_default_language: Default language
251 251 setting_login_required: Authentication required
252 252 setting_self_registration: Self-registration
253 253 setting_attachment_max_size: Attachment max. size
254 254 setting_issues_export_limit: Issues export limit
255 255 setting_mail_from: Emission email address
256 256 setting_bcc_recipients: Blind carbon copy recipients (bcc)
257 257 setting_plain_text_mail: Plain text mail (no HTML)
258 258 setting_host_name: Host name and path
259 259 setting_text_formatting: Text formatting
260 260 setting_wiki_compression: Wiki history compression
261 261 setting_feeds_limit: Feed content limit
262 262 setting_default_projects_public: New projects are public by default
263 263 setting_autofetch_changesets: Autofetch commits
264 264 setting_sys_api_enabled: Enable WS for repository management
265 265 setting_commit_ref_keywords: Referencing keywords
266 266 setting_commit_fix_keywords: Fixing keywords
267 267 setting_autologin: Autologin
268 268 setting_date_format: Date format
269 269 setting_time_format: Time format
270 270 setting_cross_project_issue_relations: Allow cross-project issue relations
271 271 setting_issue_list_default_columns: Default columns displayed on the issue list
272 272 setting_repositories_encodings: Repositories encodings
273 273 setting_commit_logs_encoding: Commit messages encoding
274 274 setting_emails_footer: Emails footer
275 275 setting_protocol: Protocol
276 276 setting_per_page_options: Objects per page options
277 277 setting_user_format: Users display format
278 278 setting_activity_days_default: Days displayed on project activity
279 279 setting_display_subprojects_issues: Display subprojects issues on main projects by default
280 280 setting_enabled_scm: Enabled SCM
281 281 setting_mail_handler_api_enabled: Enable WS for incoming emails
282 282 setting_mail_handler_api_key: API key
283 283 setting_sequential_project_identifiers: Generate sequential project identifiers
284 284 setting_gravatar_enabled: Use Gravatar user icons
285 285 setting_diff_max_lines_displayed: Max number of diff lines displayed
286 286 setting_file_max_size_displayed: Max size of text files displayed inline
287 287 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
288 288 setting_openid: Allow OpenID login and registration
289 289 setting_password_min_length: Minimum password length
290 290
291 291 permission_edit_project: Edit project
292 292 permission_select_project_modules: Select project modules
293 293 permission_manage_members: Manage members
294 294 permission_manage_versions: Manage versions
295 295 permission_manage_categories: Manage issue categories
296 296 permission_add_issues: Add issues
297 297 permission_edit_issues: Edit issues
298 298 permission_manage_issue_relations: Manage issue relations
299 299 permission_add_issue_notes: Add notes
300 300 permission_edit_issue_notes: Edit notes
301 301 permission_edit_own_issue_notes: Edit own notes
302 302 permission_move_issues: Move issues
303 303 permission_delete_issues: Delete issues
304 304 permission_manage_public_queries: Manage public queries
305 305 permission_save_queries: Save queries
306 306 permission_view_gantt: View gantt chart
307 307 permission_view_calendar: View calender
308 308 permission_view_issue_watchers: View watchers list
309 309 permission_add_issue_watchers: Add watchers
310 310 permission_log_time: Log spent time
311 311 permission_view_time_entries: View spent time
312 312 permission_edit_time_entries: Edit time logs
313 313 permission_edit_own_time_entries: Edit own time logs
314 314 permission_manage_news: Manage news
315 315 permission_comment_news: Comment news
316 316 permission_manage_documents: Manage documents
317 317 permission_view_documents: View documents
318 318 permission_manage_files: Manage files
319 319 permission_view_files: View files
320 320 permission_manage_wiki: Manage wiki
321 321 permission_rename_wiki_pages: Rename wiki pages
322 322 permission_delete_wiki_pages: Delete wiki pages
323 323 permission_view_wiki_pages: View wiki
324 324 permission_view_wiki_edits: View wiki history
325 325 permission_edit_wiki_pages: Edit wiki pages
326 326 permission_delete_wiki_pages_attachments: Delete attachments
327 327 permission_protect_wiki_pages: Protect wiki pages
328 328 permission_manage_repository: Manage repository
329 329 permission_browse_repository: Browse repository
330 330 permission_view_changesets: View changesets
331 331 permission_commit_access: Commit access
332 332 permission_manage_boards: Manage boards
333 333 permission_view_messages: View messages
334 334 permission_add_messages: Post messages
335 335 permission_edit_messages: Edit messages
336 336 permission_edit_own_messages: Edit own messages
337 337 permission_delete_messages: Delete messages
338 338 permission_delete_own_messages: Delete own messages
339 339
340 340 project_module_issue_tracking: Issue tracking
341 341 project_module_time_tracking: Time tracking
342 342 project_module_news: News
343 343 project_module_documents: Documents
344 344 project_module_files: Files
345 345 project_module_wiki: Wiki
346 346 project_module_repository: Repository
347 347 project_module_boards: Boards
348 348
349 349 label_user: User
350 350 label_user_plural: Users
351 351 label_user_new: New user
352 352 label_project: Project
353 353 label_project_new: New project
354 label_project_copy: Copy project
354 355 label_project_plural: Projects
355 356 label_x_projects:
356 357 zero: no projects
357 358 one: 1 project
358 359 other: "{{count}} projects"
359 360 label_project_all: All Projects
360 361 label_project_latest: Latest projects
361 362 label_issue: Issue
362 363 label_issue_new: New issue
363 364 label_issue_plural: Issues
364 365 label_issue_view_all: View all issues
365 366 label_issues_by: "Issues by {{value}}"
366 367 label_issue_added: Issue added
367 368 label_issue_updated: Issue updated
368 369 label_document: Document
369 370 label_document_new: New document
370 371 label_document_plural: Documents
371 372 label_document_added: Document added
372 373 label_role: Role
373 374 label_role_plural: Roles
374 375 label_role_new: New role
375 376 label_role_and_permissions: Roles and permissions
376 377 label_member: Member
377 378 label_member_new: New member
378 379 label_member_plural: Members
379 380 label_tracker: Tracker
380 381 label_tracker_plural: Trackers
381 382 label_tracker_new: New tracker
382 383 label_workflow: Workflow
383 384 label_issue_status: Issue status
384 385 label_issue_status_plural: Issue statuses
385 386 label_issue_status_new: New status
386 387 label_issue_category: Issue category
387 388 label_issue_category_plural: Issue categories
388 389 label_issue_category_new: New category
389 390 label_custom_field: Custom field
390 391 label_custom_field_plural: Custom fields
391 392 label_custom_field_new: New custom field
392 393 label_enumerations: Enumerations
393 394 label_enumeration_new: New value
394 395 label_information: Information
395 396 label_information_plural: Information
396 397 label_please_login: Please log in
397 398 label_register: Register
398 399 label_login_with_open_id_option: or login with OpenID
399 400 label_password_lost: Lost password
400 401 label_home: Home
401 402 label_my_page: My page
402 403 label_my_account: My account
403 404 label_my_projects: My projects
404 405 label_administration: Administration
405 406 label_login: Sign in
406 407 label_logout: Sign out
407 408 label_help: Help
408 409 label_reported_issues: Reported issues
409 410 label_assigned_to_me_issues: Issues assigned to me
410 411 label_last_login: Last connection
411 412 label_registered_on: Registered on
412 413 label_activity: Activity
413 414 label_overall_activity: Overall activity
414 415 label_user_activity: "{{value}}'s activity"
415 416 label_new: New
416 417 label_logged_as: Logged in as
417 418 label_environment: Environment
418 419 label_authentication: Authentication
419 420 label_auth_source: Authentication mode
420 421 label_auth_source_new: New authentication mode
421 422 label_auth_source_plural: Authentication modes
422 423 label_subproject_plural: Subprojects
423 424 label_and_its_subprojects: "{{value}} and its subprojects"
424 425 label_min_max_length: Min - Max length
425 426 label_list: List
426 427 label_date: Date
427 428 label_integer: Integer
428 429 label_float: Float
429 430 label_boolean: Boolean
430 431 label_string: Text
431 432 label_text: Long text
432 433 label_attribute: Attribute
433 434 label_attribute_plural: Attributes
434 435 label_download: "{{count}} Download"
435 436 label_download_plural: "{{count}} Downloads"
436 437 label_no_data: No data to display
437 438 label_change_status: Change status
438 439 label_history: History
439 440 label_attachment: File
440 441 label_attachment_new: New file
441 442 label_attachment_delete: Delete file
442 443 label_attachment_plural: Files
443 444 label_file_added: File added
444 445 label_report: Report
445 446 label_report_plural: Reports
446 447 label_news: News
447 448 label_news_new: Add news
448 449 label_news_plural: News
449 450 label_news_latest: Latest news
450 451 label_news_view_all: View all news
451 452 label_news_added: News added
452 453 label_change_log: Change log
453 454 label_settings: Settings
454 455 label_overview: Overview
455 456 label_version: Version
456 457 label_version_new: New version
457 458 label_version_plural: Versions
458 459 label_confirmation: Confirmation
459 460 label_export_to: 'Also available in:'
460 461 label_read: Read...
461 462 label_public_projects: Public projects
462 463 label_open_issues: open
463 464 label_open_issues_plural: open
464 465 label_closed_issues: closed
465 466 label_closed_issues_plural: closed
466 467 label_x_open_issues_abbr_on_total:
467 468 zero: 0 open / {{total}}
468 469 one: 1 open / {{total}}
469 470 other: "{{count}} open / {{total}}"
470 471 label_x_open_issues_abbr:
471 472 zero: 0 open
472 473 one: 1 open
473 474 other: "{{count}} open"
474 475 label_x_closed_issues_abbr:
475 476 zero: 0 closed
476 477 one: 1 closed
477 478 other: "{{count}} closed"
478 479 label_total: Total
479 480 label_permissions: Permissions
480 481 label_current_status: Current status
481 482 label_new_statuses_allowed: New statuses allowed
482 483 label_all: all
483 484 label_none: none
484 485 label_nobody: nobody
485 486 label_next: Next
486 487 label_previous: Previous
487 488 label_used_by: Used by
488 489 label_details: Details
489 490 label_add_note: Add a note
490 491 label_per_page: Per page
491 492 label_calendar: Calendar
492 493 label_months_from: months from
493 494 label_gantt: Gantt
494 495 label_internal: Internal
495 496 label_last_changes: "last {{count}} changes"
496 497 label_change_view_all: View all changes
497 498 label_personalize_page: Personalize this page
498 499 label_comment: Comment
499 500 label_comment_plural: Comments
500 501 label_x_comments:
501 502 zero: no comments
502 503 one: 1 comment
503 504 other: "{{count}} comments"
504 505 label_comment_add: Add a comment
505 506 label_comment_added: Comment added
506 507 label_comment_delete: Delete comments
507 508 label_query: Custom query
508 509 label_query_plural: Custom queries
509 510 label_query_new: New query
510 511 label_filter_add: Add filter
511 512 label_filter_plural: Filters
512 513 label_equals: is
513 514 label_not_equals: is not
514 515 label_in_less_than: in less than
515 516 label_in_more_than: in more than
516 517 label_greater_or_equal: '>='
517 518 label_less_or_equal: '<='
518 519 label_in: in
519 520 label_today: today
520 521 label_all_time: all time
521 522 label_yesterday: yesterday
522 523 label_this_week: this week
523 524 label_last_week: last week
524 525 label_last_n_days: "last {{count}} days"
525 526 label_this_month: this month
526 527 label_last_month: last month
527 528 label_this_year: this year
528 529 label_date_range: Date range
529 530 label_less_than_ago: less than days ago
530 531 label_more_than_ago: more than days ago
531 532 label_ago: days ago
532 533 label_contains: contains
533 534 label_not_contains: doesn't contain
534 535 label_day_plural: days
535 536 label_repository: Repository
536 537 label_repository_plural: Repositories
537 538 label_browse: Browse
538 539 label_modification: "{{count}} change"
539 540 label_modification_plural: "{{count}} changes"
540 541 label_revision: Revision
541 542 label_revision_plural: Revisions
542 543 label_associated_revisions: Associated revisions
543 544 label_added: added
544 545 label_modified: modified
545 546 label_copied: copied
546 547 label_renamed: renamed
547 548 label_deleted: deleted
548 549 label_latest_revision: Latest revision
549 550 label_latest_revision_plural: Latest revisions
550 551 label_view_revisions: View revisions
551 552 label_max_size: Maximum size
552 553 label_sort_highest: Move to top
553 554 label_sort_higher: Move up
554 555 label_sort_lower: Move down
555 556 label_sort_lowest: Move to bottom
556 557 label_roadmap: Roadmap
557 558 label_roadmap_due_in: "Due in {{value}}"
558 559 label_roadmap_overdue: "{{value}} late"
559 560 label_roadmap_no_issues: No issues for this version
560 561 label_search: Search
561 562 label_result_plural: Results
562 563 label_all_words: All words
563 564 label_wiki: Wiki
564 565 label_wiki_edit: Wiki edit
565 566 label_wiki_edit_plural: Wiki edits
566 567 label_wiki_page: Wiki page
567 568 label_wiki_page_plural: Wiki pages
568 569 label_index_by_title: Index by title
569 570 label_index_by_date: Index by date
570 571 label_current_version: Current version
571 572 label_preview: Preview
572 573 label_feed_plural: Feeds
573 574 label_changes_details: Details of all changes
574 575 label_issue_tracking: Issue tracking
575 576 label_spent_time: Spent time
576 577 label_f_hour: "{{value}} hour"
577 578 label_f_hour_plural: "{{value}} hours"
578 579 label_time_tracking: Time tracking
579 580 label_change_plural: Changes
580 581 label_statistics: Statistics
581 582 label_commits_per_month: Commits per month
582 583 label_commits_per_author: Commits per author
583 584 label_view_diff: View differences
584 585 label_diff_inline: inline
585 586 label_diff_side_by_side: side by side
586 587 label_options: Options
587 588 label_copy_workflow_from: Copy workflow from
588 589 label_permissions_report: Permissions report
589 590 label_watched_issues: Watched issues
590 591 label_related_issues: Related issues
591 592 label_applied_status: Applied status
592 593 label_loading: Loading...
593 594 label_relation_new: New relation
594 595 label_relation_delete: Delete relation
595 596 label_relates_to: related to
596 597 label_duplicates: duplicates
597 598 label_duplicated_by: duplicated by
598 599 label_blocks: blocks
599 600 label_blocked_by: blocked by
600 601 label_precedes: precedes
601 602 label_follows: follows
602 603 label_end_to_start: end to start
603 604 label_end_to_end: end to end
604 605 label_start_to_start: start to start
605 606 label_start_to_end: start to end
606 607 label_stay_logged_in: Stay logged in
607 608 label_disabled: disabled
608 609 label_show_completed_versions: Show completed versions
609 610 label_me: me
610 611 label_board: Forum
611 612 label_board_new: New forum
612 613 label_board_plural: Forums
613 614 label_topic_plural: Topics
614 615 label_message_plural: Messages
615 616 label_message_last: Last message
616 617 label_message_new: New message
617 618 label_message_posted: Message added
618 619 label_reply_plural: Replies
619 620 label_send_information: Send account information to the user
620 621 label_year: Year
621 622 label_month: Month
622 623 label_week: Week
623 624 label_date_from: From
624 625 label_date_to: To
625 626 label_language_based: Based on user's language
626 627 label_sort_by: "Sort by {{value}}"
627 628 label_send_test_email: Send a test email
628 629 label_feeds_access_key_created_on: "RSS access key created {{value}} ago"
629 630 label_module_plural: Modules
630 631 label_added_time_by: "Added by {{author}} {{age}} ago"
631 632 label_updated_time_by: "Updated by {{author}} {{age}} ago"
632 633 label_updated_time: "Updated {{value}} ago"
633 634 label_jump_to_a_project: Jump to a project...
634 635 label_file_plural: Files
635 636 label_changeset_plural: Changesets
636 637 label_default_columns: Default columns
637 638 label_no_change_option: (No change)
638 639 label_bulk_edit_selected_issues: Bulk edit selected issues
639 640 label_theme: Theme
640 641 label_default: Default
641 642 label_search_titles_only: Search titles only
642 643 label_user_mail_option_all: "For any event on all my projects"
643 644 label_user_mail_option_selected: "For any event on the selected projects only..."
644 645 label_user_mail_option_none: "Only for things I watch or I'm involved in"
645 646 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
646 647 label_registration_activation_by_email: account activation by email
647 648 label_registration_manual_activation: manual account activation
648 649 label_registration_automatic_activation: automatic account activation
649 650 label_display_per_page: "Per page: {{value}}"
650 651 label_age: Age
651 652 label_change_properties: Change properties
652 653 label_general: General
653 654 label_more: More
654 655 label_scm: SCM
655 656 label_plugins: Plugins
656 657 label_ldap_authentication: LDAP authentication
657 658 label_downloads_abbr: D/L
658 659 label_optional_description: Optional description
659 660 label_add_another_file: Add another file
660 661 label_preferences: Preferences
661 662 label_chronological_order: In chronological order
662 663 label_reverse_chronological_order: In reverse chronological order
663 664 label_planning: Planning
664 665 label_incoming_emails: Incoming emails
665 666 label_generate_key: Generate a key
666 667 label_issue_watchers: Watchers
667 668 label_example: Example
668 669 label_display: Display
669 670 label_sort: Sort
670 671 label_ascending: Ascending
671 672 label_descending: Descending
672 673 label_date_from_to: From {{start}} to {{end}}
673 674
674 675 button_login: Login
675 676 button_submit: Submit
676 677 button_save: Save
677 678 button_check_all: Check all
678 679 button_uncheck_all: Uncheck all
679 680 button_delete: Delete
680 681 button_create: Create
681 682 button_create_and_continue: Create and continue
682 683 button_test: Test
683 684 button_edit: Edit
684 685 button_add: Add
685 686 button_change: Change
686 687 button_apply: Apply
687 688 button_clear: Clear
688 689 button_lock: Lock
689 690 button_unlock: Unlock
690 691 button_download: Download
691 692 button_list: List
692 693 button_view: View
693 694 button_move: Move
694 695 button_back: Back
695 696 button_cancel: Cancel
696 697 button_activate: Activate
697 698 button_sort: Sort
698 699 button_log_time: Log time
699 700 button_rollback: Rollback to this version
700 701 button_watch: Watch
701 702 button_unwatch: Unwatch
702 703 button_reply: Reply
703 704 button_archive: Archive
704 705 button_unarchive: Unarchive
705 706 button_reset: Reset
706 707 button_rename: Rename
707 708 button_change_password: Change password
708 709 button_copy: Copy
709 710 button_annotate: Annotate
710 711 button_update: Update
711 712 button_configure: Configure
712 713 button_quote: Quote
713 714
714 715 status_active: active
715 716 status_registered: registered
716 717 status_locked: locked
717 718
718 719 text_select_mail_notifications: Select actions for which email notifications should be sent.
719 720 text_regexp_info: eg. ^[A-Z0-9]+$
720 721 text_min_max_length_info: 0 means no restriction
721 722 text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
722 723 text_subprojects_destroy_warning: "Its subproject(s): {{value}} will be also deleted."
723 724 text_workflow_edit: Select a role and a tracker to edit the workflow
724 725 text_are_you_sure: Are you sure ?
725 726 text_journal_changed: "changed from {{old}} to {{new}}"
726 727 text_journal_set_to: "set to {{value}}"
727 728 text_journal_deleted: deleted
728 729 text_tip_task_begin_day: task beginning this day
729 730 text_tip_task_end_day: task ending this day
730 731 text_tip_task_begin_end_day: task beginning and ending this day
731 732 text_project_identifier_info: 'Only lower case letters (a-z), numbers and dashes are allowed.<br />Once saved, the identifier can not be changed.'
732 733 text_caracters_maximum: "{{count}} characters maximum."
733 734 text_caracters_minimum: "Must be at least {{count}} characters long."
734 735 text_length_between: "Length between {{min}} and {{max}} characters."
735 736 text_tracker_no_workflow: No workflow defined for this tracker
736 737 text_unallowed_characters: Unallowed characters
737 738 text_comma_separated: Multiple values allowed (comma separated).
738 739 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
739 740 text_issue_added: "Issue {{id}} has been reported by {{author}}."
740 741 text_issue_updated: "Issue {{id}} has been updated by {{author}}."
741 742 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
742 743 text_issue_category_destroy_question: "Some issues ({{count}}) are assigned to this category. What do you want to do ?"
743 744 text_issue_category_destroy_assignments: Remove category assignments
744 745 text_issue_category_reassign_to: Reassign issues to this category
745 746 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)."
746 747 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."
747 748 text_load_default_configuration: Load the default configuration
748 749 text_status_changed_by_changeset: "Applied in changeset {{value}}."
749 750 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s) ?'
750 751 text_select_project_modules: 'Select modules to enable for this project:'
751 752 text_default_administrator_account_changed: Default administrator account changed
752 753 text_file_repository_writable: Attachments directory writable
753 754 text_plugin_assets_writable: Plugin assets directory writable
754 755 text_rmagick_available: RMagick available (optional)
755 756 text_destroy_time_entries_question: "{{hours}} hours were reported on the issues you are about to delete. What do you want to do ?"
756 757 text_destroy_time_entries: Delete reported hours
757 758 text_assign_time_entries_to_project: Assign reported hours to the project
758 759 text_reassign_time_entries: 'Reassign reported hours to this issue:'
759 760 text_user_wrote: "{{value}} wrote:"
760 761 text_enumeration_destroy_question: "{{count}} objects are assigned to this value."
761 762 text_enumeration_category_reassign_to: 'Reassign them to this value:'
762 763 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."
763 764 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."
764 765 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
765 766 text_custom_field_possible_values_info: 'One line for each value'
766 767 text_wiki_page_destroy_question: "This page has {{descendants}} child page(s) and descendant(s). What do you want to do?"
767 768 text_wiki_page_nullify_children: "Keep child pages as root pages"
768 769 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
769 770 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
770 771
771 772 default_role_manager: Manager
772 773 default_role_developper: Developer
773 774 default_role_reporter: Reporter
774 775 default_tracker_bug: Bug
775 776 default_tracker_feature: Feature
776 777 default_tracker_support: Support
777 778 default_issue_status_new: New
778 779 default_issue_status_assigned: Assigned
779 780 default_issue_status_resolved: Resolved
780 781 default_issue_status_feedback: Feedback
781 782 default_issue_status_closed: Closed
782 783 default_issue_status_rejected: Rejected
783 784 default_doc_category_user: User documentation
784 785 default_doc_category_tech: Technical documentation
785 786 default_priority_low: Low
786 787 default_priority_normal: Normal
787 788 default_priority_high: High
788 789 default_priority_urgent: Urgent
789 790 default_priority_immediate: Immediate
790 791 default_activity_design: Design
791 792 default_activity_development: Development
792 793
793 794 enumeration_issue_priorities: Issue priorities
794 795 enumeration_doc_categories: Document categories
795 796 enumeration_activities: Activities (time tracking)
@@ -1,128 +1,128
1 1 ---
2 2 issues_001:
3 3 created_on: <%= 3.days.ago.to_date.to_s(:db) %>
4 4 project_id: 1
5 5 updated_on: <%= 1.day.ago.to_date.to_s(:db) %>
6 6 priority_id: 4
7 7 subject: Can't print recipes
8 8 id: 1
9 9 fixed_version_id:
10 10 category_id: 1
11 11 description: Unable to print recipes
12 12 tracker_id: 1
13 13 assigned_to_id:
14 14 author_id: 2
15 15 status_id: 1
16 16 start_date: <%= 1.day.ago.to_date.to_s(:db) %>
17 17 due_date: <%= 10.day.from_now.to_date.to_s(:db) %>
18 18 issues_002:
19 19 created_on: 2006-07-19 21:04:21 +02:00
20 20 project_id: 1
21 21 updated_on: 2006-07-19 21:09:50 +02:00
22 22 priority_id: 5
23 23 subject: Add ingredients categories
24 24 id: 2
25 25 fixed_version_id: 2
26 26 category_id:
27 27 description: Ingredients of the recipe should be classified by categories
28 28 tracker_id: 2
29 29 assigned_to_id: 3
30 30 author_id: 2
31 31 status_id: 2
32 32 start_date: <%= 2.day.ago.to_date.to_s(:db) %>
33 33 due_date:
34 34 issues_003:
35 35 created_on: 2006-07-19 21:07:27 +02:00
36 36 project_id: 1
37 37 updated_on: 2006-07-19 21:07:27 +02:00
38 38 priority_id: 4
39 39 subject: Error 281 when updating a recipe
40 40 id: 3
41 41 fixed_version_id:
42 42 category_id:
43 43 description: Error 281 is encountered when saving a recipe
44 44 tracker_id: 1
45 45 assigned_to_id: 3
46 46 author_id: 2
47 47 status_id: 1
48 48 start_date: <%= 1.day.from_now.to_date.to_s(:db) %>
49 49 due_date: <%= 40.day.ago.to_date.to_s(:db) %>
50 50 issues_004:
51 51 created_on: <%= 5.days.ago.to_date.to_s(:db) %>
52 52 project_id: 2
53 53 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
54 54 priority_id: 4
55 55 subject: Issue on project 2
56 56 id: 4
57 57 fixed_version_id:
58 58 category_id:
59 59 description: Issue on project 2
60 60 tracker_id: 1
61 assigned_to_id:
61 assigned_to_id: 2
62 62 author_id: 2
63 63 status_id: 1
64 64 issues_005:
65 65 created_on: <%= 5.days.ago.to_date.to_s(:db) %>
66 66 project_id: 3
67 67 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
68 68 priority_id: 4
69 69 subject: Subproject issue
70 70 id: 5
71 71 fixed_version_id:
72 72 category_id:
73 73 description: This is an issue on a cookbook subproject
74 74 tracker_id: 1
75 75 assigned_to_id:
76 76 author_id: 2
77 77 status_id: 1
78 78 issues_006:
79 79 created_on: <%= 1.minute.ago.to_date.to_s(:db) %>
80 80 project_id: 5
81 81 updated_on: <%= 1.minute.ago.to_date.to_s(:db) %>
82 82 priority_id: 4
83 83 subject: Issue of a private subproject
84 84 id: 6
85 85 fixed_version_id:
86 86 category_id:
87 87 description: This is an issue of a private subproject of cookbook
88 88 tracker_id: 1
89 89 assigned_to_id:
90 90 author_id: 2
91 91 status_id: 1
92 92 start_date: <%= Date.today.to_s(:db) %>
93 93 due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
94 94 issues_007:
95 95 created_on: <%= 10.days.ago.to_date.to_s(:db) %>
96 96 project_id: 1
97 97 updated_on: <%= 10.days.ago.to_date.to_s(:db) %>
98 98 priority_id: 3
99 99 subject: Issue due today
100 100 id: 7
101 101 fixed_version_id:
102 102 category_id:
103 103 description: This is an issue that is due today
104 104 tracker_id: 1
105 105 assigned_to_id:
106 106 author_id: 2
107 107 status_id: 1
108 108 start_date: <%= 10.days.ago.to_s(:db) %>
109 109 due_date: <%= Date.today.to_s(:db) %>
110 110 lock_version: 0
111 111 issues_008:
112 112 created_on: <%= 10.days.ago.to_date.to_s(:db) %>
113 113 project_id: 1
114 114 updated_on: <%= 10.days.ago.to_date.to_s(:db) %>
115 115 priority_id: 3
116 116 subject: Closed issue
117 117 id: 8
118 118 fixed_version_id:
119 119 category_id:
120 120 description: This is a closed issue.
121 121 tracker_id: 1
122 122 assigned_to_id:
123 123 author_id: 2
124 124 status_id: 5
125 125 start_date:
126 126 due_date:
127 127 lock_version: 0
128 No newline at end of file
128
@@ -1,109 +1,137
1 1 ---
2 2 queries_001:
3 3 id: 1
4 4 project_id: 1
5 5 is_public: true
6 6 name: Multiple custom fields query
7 7 filters: |
8 8 ---
9 9 cf_1:
10 10 :values:
11 11 - MySQL
12 12 :operator: "="
13 13 status_id:
14 14 :values:
15 15 - "1"
16 16 :operator: o
17 17 cf_2:
18 18 :values:
19 19 - "125"
20 20 :operator: "="
21 21
22 22 user_id: 1
23 23 column_names:
24 24 queries_002:
25 25 id: 2
26 26 project_id: 1
27 27 is_public: false
28 28 name: Private query for cookbook
29 29 filters: |
30 30 ---
31 31 tracker_id:
32 32 :values:
33 33 - "3"
34 34 :operator: "="
35 35 status_id:
36 36 :values:
37 37 - "1"
38 38 :operator: o
39 39
40 40 user_id: 3
41 41 column_names:
42 42 queries_003:
43 43 id: 3
44 44 project_id:
45 45 is_public: false
46 46 name: Private query for all projects
47 47 filters: |
48 48 ---
49 49 tracker_id:
50 50 :values:
51 51 - "3"
52 52 :operator: "="
53 53
54 54 user_id: 3
55 55 column_names:
56 56 queries_004:
57 57 id: 4
58 58 project_id:
59 59 is_public: true
60 60 name: Public query for all projects
61 61 filters: |
62 62 ---
63 63 tracker_id:
64 64 :values:
65 65 - "3"
66 66 :operator: "="
67 67
68 68 user_id: 2
69 69 column_names:
70 70 queries_005:
71 71 id: 5
72 72 project_id:
73 73 is_public: true
74 74 name: Open issues by priority and tracker
75 75 filters: |
76 76 ---
77 77 status_id:
78 78 :values:
79 79 - "1"
80 80 :operator: o
81 81
82 82 user_id: 1
83 83 column_names:
84 84 sort_criteria: |
85 85 ---
86 86 - - priority
87 87 - desc
88 88 - - tracker
89 89 - asc
90 90 queries_006:
91 91 id: 6
92 92 project_id:
93 93 is_public: true
94 94 name: Open issues grouped by tracker
95 95 filters: |
96 96 ---
97 97 status_id:
98 98 :values:
99 99 - "1"
100 100 :operator: o
101 101
102 102 user_id: 1
103 103 column_names:
104 104 group_by: tracker
105 105 sort_criteria: |
106 106 ---
107 107 - - priority
108 108 - desc
109 No newline at end of file
109 queries_007:
110 id: 7
111 project_id: 2
112 is_public: true
113 name: Public query for project 2
114 filters: |
115 ---
116 tracker_id:
117 :values:
118 - "3"
119 :operator: "="
120
121 user_id: 2
122 column_names:
123 queries_008:
124 id: 8
125 project_id: 2
126 is_public: false
127 name: Private query for project 2
128 filters: |
129 ---
130 tracker_id:
131 :values:
132 - "3"
133 :operator: "="
134
135 user_id: 2
136 column_names:
137
@@ -1,501 +1,517
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19 require 'projects_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class ProjectsController; def rescue_action(e) raise e end; end
23 23
24 24 class ProjectsControllerTest < Test::Unit::TestCase
25 25 fixtures :projects, :versions, :users, :roles, :members, :issues, :journals, :journal_details,
26 26 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
27 27 :attachments
28 28
29 29 def setup
30 30 @controller = ProjectsController.new
31 31 @request = ActionController::TestRequest.new
32 32 @response = ActionController::TestResponse.new
33 33 @request.session[:user_id] = nil
34 34 Setting.default_language = 'en'
35 35 end
36 36
37 37 def test_index_routing
38 38 assert_routing(
39 39 {:method => :get, :path => '/projects'},
40 40 :controller => 'projects', :action => 'index'
41 41 )
42 42 end
43 43
44 44 def test_index
45 45 get :index
46 46 assert_response :success
47 47 assert_template 'index'
48 48 assert_not_nil assigns(:projects)
49 49
50 50 assert_tag :ul, :child => {:tag => 'li',
51 51 :descendant => {:tag => 'a', :content => 'eCookbook'},
52 52 :child => { :tag => 'ul',
53 53 :descendant => { :tag => 'a',
54 54 :content => 'Child of private child'
55 55 }
56 56 }
57 57 }
58 58
59 59 assert_no_tag :a, :content => /Private child of eCookbook/
60 60 end
61 61
62 62 def test_index_atom_routing
63 63 assert_routing(
64 64 {:method => :get, :path => '/projects.atom'},
65 65 :controller => 'projects', :action => 'index', :format => 'atom'
66 66 )
67 67 end
68 68
69 69 def test_index_atom
70 70 get :index, :format => 'atom'
71 71 assert_response :success
72 72 assert_template 'common/feed.atom.rxml'
73 73 assert_select 'feed>title', :text => 'Redmine: Latest projects'
74 74 assert_select 'feed>entry', :count => Project.count(:conditions => Project.visible_by(User.current))
75 75 end
76 76
77 77 def test_add_routing
78 78 assert_routing(
79 79 {:method => :get, :path => '/projects/new'},
80 80 :controller => 'projects', :action => 'add'
81 81 )
82 82 assert_recognizes(
83 83 {:controller => 'projects', :action => 'add'},
84 84 {:method => :post, :path => '/projects/new'}
85 85 )
86 86 assert_recognizes(
87 87 {:controller => 'projects', :action => 'add'},
88 88 {:method => :post, :path => '/projects'}
89 89 )
90 90 end
91 91
92 92 def test_show_routing
93 93 assert_routing(
94 94 {:method => :get, :path => '/projects/test'},
95 95 :controller => 'projects', :action => 'show', :id => 'test'
96 96 )
97 97 end
98 98
99 99 def test_show_by_id
100 100 get :show, :id => 1
101 101 assert_response :success
102 102 assert_template 'show'
103 103 assert_not_nil assigns(:project)
104 104 end
105 105
106 106 def test_show_by_identifier
107 107 get :show, :id => 'ecookbook'
108 108 assert_response :success
109 109 assert_template 'show'
110 110 assert_not_nil assigns(:project)
111 111 assert_equal Project.find_by_identifier('ecookbook'), assigns(:project)
112 112 end
113 113
114 114 def test_private_subprojects_hidden
115 115 get :show, :id => 'ecookbook'
116 116 assert_response :success
117 117 assert_template 'show'
118 118 assert_no_tag :tag => 'a', :content => /Private child/
119 119 end
120 120
121 121 def test_private_subprojects_visible
122 122 @request.session[:user_id] = 2 # manager who is a member of the private subproject
123 123 get :show, :id => 'ecookbook'
124 124 assert_response :success
125 125 assert_template 'show'
126 126 assert_tag :tag => 'a', :content => /Private child/
127 127 end
128 128
129 129 def test_settings_routing
130 130 assert_routing(
131 131 {:method => :get, :path => '/projects/4223/settings'},
132 132 :controller => 'projects', :action => 'settings', :id => '4223'
133 133 )
134 134 assert_routing(
135 135 {:method => :get, :path => '/projects/4223/settings/members'},
136 136 :controller => 'projects', :action => 'settings', :id => '4223', :tab => 'members'
137 137 )
138 138 end
139 139
140 140 def test_settings
141 141 @request.session[:user_id] = 2 # manager
142 142 get :settings, :id => 1
143 143 assert_response :success
144 144 assert_template 'settings'
145 145 end
146 146
147 147 def test_edit
148 148 @request.session[:user_id] = 2 # manager
149 149 post :edit, :id => 1, :project => {:name => 'Test changed name',
150 150 :issue_custom_field_ids => ['']}
151 151 assert_redirected_to 'projects/ecookbook/settings'
152 152 project = Project.find(1)
153 153 assert_equal 'Test changed name', project.name
154 154 end
155 155
156 156 def test_add_version_routing
157 157 assert_routing(
158 158 {:method => :get, :path => 'projects/64/versions/new'},
159 159 :controller => 'projects', :action => 'add_version', :id => '64'
160 160 )
161 161 assert_routing(
162 162 #TODO: use PUT
163 163 {:method => :post, :path => 'projects/64/versions/new'},
164 164 :controller => 'projects', :action => 'add_version', :id => '64'
165 165 )
166 166 end
167 167
168 168 def test_add_issue_category_routing
169 169 assert_routing(
170 170 {:method => :get, :path => 'projects/test/categories/new'},
171 171 :controller => 'projects', :action => 'add_issue_category', :id => 'test'
172 172 )
173 173 assert_routing(
174 174 #TODO: use PUT and update form
175 175 {:method => :post, :path => 'projects/64/categories/new'},
176 176 :controller => 'projects', :action => 'add_issue_category', :id => '64'
177 177 )
178 178 end
179 179
180 180 def test_destroy_routing
181 181 assert_routing(
182 182 {:method => :get, :path => '/projects/567/destroy'},
183 183 :controller => 'projects', :action => 'destroy', :id => '567'
184 184 )
185 185 assert_routing(
186 186 #TODO: use DELETE and update form
187 187 {:method => :post, :path => 'projects/64/destroy'},
188 188 :controller => 'projects', :action => 'destroy', :id => '64'
189 189 )
190 190 end
191 191
192 192 def test_get_destroy
193 193 @request.session[:user_id] = 1 # admin
194 194 get :destroy, :id => 1
195 195 assert_response :success
196 196 assert_template 'destroy'
197 197 assert_not_nil Project.find_by_id(1)
198 198 end
199 199
200 200 def test_post_destroy
201 201 @request.session[:user_id] = 1 # admin
202 202 post :destroy, :id => 1, :confirm => 1
203 203 assert_redirected_to 'admin/projects'
204 204 assert_nil Project.find_by_id(1)
205 205 end
206 206
207 207 def test_add_file
208 208 set_tmp_attachments_directory
209 209 @request.session[:user_id] = 2
210 210 Setting.notified_events = ['file_added']
211 211 ActionMailer::Base.deliveries.clear
212 212
213 213 assert_difference 'Attachment.count' do
214 214 post :add_file, :id => 1, :version_id => '',
215 215 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
216 216 end
217 217 assert_redirected_to 'projects/ecookbook/files'
218 218 a = Attachment.find(:first, :order => 'created_on DESC')
219 219 assert_equal 'testfile.txt', a.filename
220 220 assert_equal Project.find(1), a.container
221 221
222 222 mail = ActionMailer::Base.deliveries.last
223 223 assert_kind_of TMail::Mail, mail
224 224 assert_equal "[eCookbook] New file", mail.subject
225 225 assert mail.body.include?('testfile.txt')
226 226 end
227 227
228 228 def test_add_file_routing
229 229 assert_routing(
230 230 {:method => :get, :path => '/projects/33/files/new'},
231 231 :controller => 'projects', :action => 'add_file', :id => '33'
232 232 )
233 233 assert_routing(
234 234 {:method => :post, :path => '/projects/33/files/new'},
235 235 :controller => 'projects', :action => 'add_file', :id => '33'
236 236 )
237 237 end
238 238
239 239 def test_add_version_file
240 240 set_tmp_attachments_directory
241 241 @request.session[:user_id] = 2
242 242 Setting.notified_events = ['file_added']
243 243
244 244 assert_difference 'Attachment.count' do
245 245 post :add_file, :id => 1, :version_id => '2',
246 246 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
247 247 end
248 248 assert_redirected_to 'projects/ecookbook/files'
249 249 a = Attachment.find(:first, :order => 'created_on DESC')
250 250 assert_equal 'testfile.txt', a.filename
251 251 assert_equal Version.find(2), a.container
252 252 end
253 253
254 254 def test_list_files
255 255 get :list_files, :id => 1
256 256 assert_response :success
257 257 assert_template 'list_files'
258 258 assert_not_nil assigns(:containers)
259 259
260 260 # file attached to the project
261 261 assert_tag :a, :content => 'project_file.zip',
262 262 :attributes => { :href => '/attachments/download/8/project_file.zip' }
263 263
264 264 # file attached to a project's version
265 265 assert_tag :a, :content => 'version_file.zip',
266 266 :attributes => { :href => '/attachments/download/9/version_file.zip' }
267 267 end
268 268
269 269 def test_list_files_routing
270 270 assert_routing(
271 271 {:method => :get, :path => '/projects/33/files'},
272 272 :controller => 'projects', :action => 'list_files', :id => '33'
273 273 )
274 274 end
275 275
276 276 def test_changelog_routing
277 277 assert_routing(
278 278 {:method => :get, :path => '/projects/44/changelog'},
279 279 :controller => 'projects', :action => 'changelog', :id => '44'
280 280 )
281 281 end
282 282
283 283 def test_changelog
284 284 get :changelog, :id => 1
285 285 assert_response :success
286 286 assert_template 'changelog'
287 287 assert_not_nil assigns(:versions)
288 288 end
289 289
290 290 def test_roadmap_routing
291 291 assert_routing(
292 292 {:method => :get, :path => 'projects/33/roadmap'},
293 293 :controller => 'projects', :action => 'roadmap', :id => '33'
294 294 )
295 295 end
296 296
297 297 def test_roadmap
298 298 get :roadmap, :id => 1
299 299 assert_response :success
300 300 assert_template 'roadmap'
301 301 assert_not_nil assigns(:versions)
302 302 # Version with no date set appears
303 303 assert assigns(:versions).include?(Version.find(3))
304 304 # Completed version doesn't appear
305 305 assert !assigns(:versions).include?(Version.find(1))
306 306 end
307 307
308 308 def test_roadmap_with_completed_versions
309 309 get :roadmap, :id => 1, :completed => 1
310 310 assert_response :success
311 311 assert_template 'roadmap'
312 312 assert_not_nil assigns(:versions)
313 313 # Version with no date set appears
314 314 assert assigns(:versions).include?(Version.find(3))
315 315 # Completed version appears
316 316 assert assigns(:versions).include?(Version.find(1))
317 317 end
318 318
319 319 def test_project_activity_routing
320 320 assert_routing(
321 321 {:method => :get, :path => '/projects/1/activity'},
322 322 :controller => 'projects', :action => 'activity', :id => '1'
323 323 )
324 324 end
325 325
326 326 def test_project_activity_atom_routing
327 327 assert_routing(
328 328 {:method => :get, :path => '/projects/1/activity.atom'},
329 329 :controller => 'projects', :action => 'activity', :id => '1', :format => 'atom'
330 330 )
331 331 end
332 332
333 333 def test_project_activity
334 334 get :activity, :id => 1, :with_subprojects => 0
335 335 assert_response :success
336 336 assert_template 'activity'
337 337 assert_not_nil assigns(:events_by_day)
338 338
339 339 assert_tag :tag => "h3",
340 340 :content => /#{2.days.ago.to_date.day}/,
341 341 :sibling => { :tag => "dl",
342 342 :child => { :tag => "dt",
343 343 :attributes => { :class => /issue-edit/ },
344 344 :child => { :tag => "a",
345 345 :content => /(#{IssueStatus.find(2).name})/,
346 346 }
347 347 }
348 348 }
349 349 end
350 350
351 351 def test_previous_project_activity
352 352 get :activity, :id => 1, :from => 3.days.ago.to_date
353 353 assert_response :success
354 354 assert_template 'activity'
355 355 assert_not_nil assigns(:events_by_day)
356 356
357 357 assert_tag :tag => "h3",
358 358 :content => /#{3.day.ago.to_date.day}/,
359 359 :sibling => { :tag => "dl",
360 360 :child => { :tag => "dt",
361 361 :attributes => { :class => /issue/ },
362 362 :child => { :tag => "a",
363 363 :content => /#{Issue.find(1).subject}/,
364 364 }
365 365 }
366 366 }
367 367 end
368 368
369 369 def test_global_activity_routing
370 370 assert_routing({:method => :get, :path => '/activity'}, :controller => 'projects', :action => 'activity', :id => nil)
371 371 end
372 372
373 373 def test_global_activity
374 374 get :activity
375 375 assert_response :success
376 376 assert_template 'activity'
377 377 assert_not_nil assigns(:events_by_day)
378 378
379 379 assert_tag :tag => "h3",
380 380 :content => /#{5.day.ago.to_date.day}/,
381 381 :sibling => { :tag => "dl",
382 382 :child => { :tag => "dt",
383 383 :attributes => { :class => /issue/ },
384 384 :child => { :tag => "a",
385 385 :content => /#{Issue.find(5).subject}/,
386 386 }
387 387 }
388 388 }
389 389 end
390 390
391 391 def test_user_activity
392 392 get :activity, :user_id => 2
393 393 assert_response :success
394 394 assert_template 'activity'
395 395 assert_not_nil assigns(:events_by_day)
396 396
397 397 assert_tag :tag => "h3",
398 398 :content => /#{3.day.ago.to_date.day}/,
399 399 :sibling => { :tag => "dl",
400 400 :child => { :tag => "dt",
401 401 :attributes => { :class => /issue/ },
402 402 :child => { :tag => "a",
403 403 :content => /#{Issue.find(1).subject}/,
404 404 }
405 405 }
406 406 }
407 407 end
408 408
409 409 def test_global_activity_atom_routing
410 410 assert_routing({:method => :get, :path => '/activity.atom'}, :controller => 'projects', :action => 'activity', :id => nil, :format => 'atom')
411 411 end
412 412
413 413 def test_activity_atom_feed
414 414 get :activity, :format => 'atom'
415 415 assert_response :success
416 416 assert_template 'common/feed.atom.rxml'
417 417 end
418 418
419 419 def test_archive_routing
420 420 assert_routing(
421 421 #TODO: use PUT to project path and modify form
422 422 {:method => :post, :path => 'projects/64/archive'},
423 423 :controller => 'projects', :action => 'archive', :id => '64'
424 424 )
425 425 end
426 426
427 427 def test_archive
428 428 @request.session[:user_id] = 1 # admin
429 429 post :archive, :id => 1
430 430 assert_redirected_to 'admin/projects'
431 431 assert !Project.find(1).active?
432 432 end
433 433
434 434 def test_unarchive_routing
435 435 assert_routing(
436 436 #TODO: use PUT to project path and modify form
437 437 {:method => :post, :path => '/projects/567/unarchive'},
438 438 :controller => 'projects', :action => 'unarchive', :id => '567'
439 439 )
440 440 end
441 441
442 442 def test_unarchive
443 443 @request.session[:user_id] = 1 # admin
444 444 Project.find(1).archive
445 445 post :unarchive, :id => 1
446 446 assert_redirected_to 'admin/projects'
447 447 assert Project.find(1).active?
448 448 end
449 449
450 450 def test_project_breadcrumbs_should_be_limited_to_3_ancestors
451 451 CustomField.delete_all
452 452 parent = nil
453 453 6.times do |i|
454 454 p = Project.create!(:name => "Breadcrumbs #{i}", :identifier => "breadcrumbs-#{i}")
455 455 p.set_parent!(parent)
456
457 456 get :show, :id => p
458 457 assert_tag :h1, :parent => { :attributes => {:id => 'header'}},
459 458 :children => { :count => [i, 3].min,
460 459 :only => { :tag => 'a' } }
461 460
462 461 parent = p
463 462 end
464 463 end
465
464
465 def test_copy_with_project
466 @request.session[:user_id] = 1 # admin
467 get :copy, :id => 1
468 assert_response :success
469 assert_template 'copy'
470 assert assigns(:project)
471 assert_equal Project.find(1).description, assigns(:project).description
472 assert_nil assigns(:project).id
473 end
474
475 def test_copy_without_project
476 @request.session[:user_id] = 1 # admin
477 get :copy
478 assert_response :redirect
479 assert_redirected_to :controller => 'admin', :action => 'projects'
480 end
481
466 482 def test_jump_should_redirect_to_active_tab
467 483 get :show, :id => 1, :jump => 'issues'
468 484 assert_redirected_to 'projects/ecookbook/issues'
469 485 end
470 486
471 487 def test_jump_should_not_redirect_to_inactive_tab
472 488 get :show, :id => 3, :jump => 'documents'
473 489 assert_response :success
474 490 assert_template 'show'
475 491 end
476 492
477 493 def test_jump_should_not_redirect_to_unknown_tab
478 494 get :show, :id => 3, :jump => 'foobar'
479 495 assert_response :success
480 496 assert_template 'show'
481 497 end
482 498
483 499 # A hook that is manually registered later
484 500 class ProjectBasedTemplate < Redmine::Hook::ViewListener
485 501 def view_layouts_base_html_head(context)
486 502 # Adds a project stylesheet
487 503 stylesheet_link_tag(context[:project].identifier) if context[:project]
488 504 end
489 505 end
490 506 # Don't use this hook now
491 507 Redmine::Hook.clear_listeners
492 508
493 509 def test_hook_response
494 510 Redmine::Hook.add_listener(ProjectBasedTemplate)
495 511 get :show, :id => 1
496 512 assert_tag :tag => 'link', :attributes => {:href => '/stylesheets/ecookbook.css'},
497 513 :parent => {:tag => 'head'}
498 514
499 515 Redmine::Hook.clear_listeners
500 516 end
501 517 end
@@ -1,236 +1,320
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class ProjectTest < Test::Unit::TestCase
21 21 fixtures :projects, :enabled_modules,
22 22 :issues, :issue_statuses, :journals, :journal_details,
23 :users, :members, :roles, :projects_trackers, :trackers, :boards
23 :users, :members, :roles, :projects_trackers, :trackers, :boards,
24 :queries
24 25
25 26 def setup
26 27 @ecookbook = Project.find(1)
27 28 @ecookbook_sub1 = Project.find(3)
28 29 end
29 30
30 31 def test_truth
31 32 assert_kind_of Project, @ecookbook
32 33 assert_equal "eCookbook", @ecookbook.name
33 34 end
34 35
35 36 def test_update
36 37 assert_equal "eCookbook", @ecookbook.name
37 38 @ecookbook.name = "eCook"
38 39 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
39 40 @ecookbook.reload
40 41 assert_equal "eCook", @ecookbook.name
41 42 end
42 43
43 44 def test_validate
44 45 @ecookbook.name = ""
45 46 assert !@ecookbook.save
46 47 assert_equal 1, @ecookbook.errors.count
47 48 assert_equal I18n.translate('activerecord.errors.messages.blank'), @ecookbook.errors.on(:name)
48 49 end
49 50
50 51 def test_archive
51 52 user = @ecookbook.members.first.user
52 53 @ecookbook.archive
53 54 @ecookbook.reload
54 55
55 56 assert !@ecookbook.active?
56 57 assert !user.projects.include?(@ecookbook)
57 58 # Subproject are also archived
58 59 assert !@ecookbook.children.empty?
59 60 assert @ecookbook.descendants.active.empty?
60 61 end
61 62
62 63 def test_unarchive
63 64 user = @ecookbook.members.first.user
64 65 @ecookbook.archive
65 66 # A subproject of an archived project can not be unarchived
66 67 assert !@ecookbook_sub1.unarchive
67 68
68 69 # Unarchive project
69 70 assert @ecookbook.unarchive
70 71 @ecookbook.reload
71 72 assert @ecookbook.active?
72 73 assert user.projects.include?(@ecookbook)
73 74 # Subproject can now be unarchived
74 75 @ecookbook_sub1.reload
75 76 assert @ecookbook_sub1.unarchive
76 77 end
77 78
78 79 def test_destroy
79 80 # 2 active members
80 81 assert_equal 2, @ecookbook.members.size
81 82 # and 1 is locked
82 83 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
83 84 # some boards
84 85 assert @ecookbook.boards.any?
85 86
86 87 @ecookbook.destroy
87 88 # make sure that the project non longer exists
88 89 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
89 90 # make sure related data was removed
90 91 assert Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
91 92 assert Board.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).empty?
92 93 end
93 94
94 95 def test_move_an_orphan_project_to_a_root_project
95 96 sub = Project.find(2)
96 97 sub.set_parent! @ecookbook
97 98 assert_equal @ecookbook.id, sub.parent.id
98 99 @ecookbook.reload
99 100 assert_equal 4, @ecookbook.children.size
100 101 end
101 102
102 103 def test_move_an_orphan_project_to_a_subproject
103 104 sub = Project.find(2)
104 105 assert sub.set_parent!(@ecookbook_sub1)
105 106 end
106 107
107 108 def test_move_a_root_project_to_a_project
108 109 sub = @ecookbook
109 110 assert sub.set_parent!(Project.find(2))
110 111 end
111 112
112 113 def test_should_not_move_a_project_to_its_children
113 114 sub = @ecookbook
114 115 assert !(sub.set_parent!(Project.find(3)))
115 116 end
116 117
117 118 def test_set_parent_should_add_roots_in_alphabetical_order
118 119 ProjectCustomField.delete_all
119 120 Project.delete_all
120 121 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
121 122 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
122 123 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
123 124 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
124 125
125 126 assert_equal 4, Project.count
126 127 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
127 128 end
128 129
129 130 def test_set_parent_should_add_children_in_alphabetical_order
130 131 ProjectCustomField.delete_all
131 132 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
132 133 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
133 134 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
134 135 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
135 136 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
136 137
137 138 parent.reload
138 139 assert_equal 4, parent.children.size
139 140 assert_equal parent.children.sort_by(&:name), parent.children
140 141 end
141 142
142 143 def test_rebuild_should_sort_children_alphabetically
143 144 ProjectCustomField.delete_all
144 145 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
145 146 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
146 147 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
147 148 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
148 149 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
149 150
150 151 Project.update_all("lft = NULL, rgt = NULL")
151 152 Project.rebuild!
152 153
153 154 parent.reload
154 155 assert_equal 4, parent.children.size
155 156 assert_equal parent.children.sort_by(&:name), parent.children
156 157 end
157 158
158 159 def test_parent
159 160 p = Project.find(6).parent
160 161 assert p.is_a?(Project)
161 162 assert_equal 5, p.id
162 163 end
163 164
164 165 def test_ancestors
165 166 a = Project.find(6).ancestors
166 167 assert a.first.is_a?(Project)
167 168 assert_equal [1, 5], a.collect(&:id)
168 169 end
169 170
170 171 def test_root
171 172 r = Project.find(6).root
172 173 assert r.is_a?(Project)
173 174 assert_equal 1, r.id
174 175 end
175 176
176 177 def test_children
177 178 c = Project.find(1).children
178 179 assert c.first.is_a?(Project)
179 180 assert_equal [5, 3, 4], c.collect(&:id)
180 181 end
181 182
182 183 def test_descendants
183 184 d = Project.find(1).descendants
184 185 assert d.first.is_a?(Project)
185 186 assert_equal [5, 6, 3, 4], d.collect(&:id)
186 187 end
187 188
188 189 def test_rolled_up_trackers
189 190 parent = Project.find(1)
190 191 parent.trackers = Tracker.find([1,2])
191 192 child = parent.children.find(3)
192 193
193 194 assert_equal [1, 2], parent.tracker_ids
194 195 assert_equal [2, 3], child.trackers.collect(&:id)
195 196
196 197 assert_kind_of Tracker, parent.rolled_up_trackers.first
197 198 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
198 199
199 200 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
200 201 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
201 202 end
202 203
203 204 def test_rolled_up_trackers_should_ignore_archived_subprojects
204 205 parent = Project.find(1)
205 206 parent.trackers = Tracker.find([1,2])
206 207 child = parent.children.find(3)
207 208 child.trackers = Tracker.find([1,3])
208 209 parent.children.each(&:archive)
209 210
210 211 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
211 212 end
212 213
213 214 def test_next_identifier
214 215 ProjectCustomField.delete_all
215 216 Project.create!(:name => 'last', :identifier => 'p2008040')
216 217 assert_equal 'p2008041', Project.next_identifier
217 218 end
218 219
219 220 def test_next_identifier_first_project
220 221 Project.delete_all
221 222 assert_nil Project.next_identifier
222 223 end
223 224
225
224 226 def test_enabled_module_names_should_not_recreate_enabled_modules
225 227 project = Project.find(1)
226 228 # Remove one module
227 229 modules = project.enabled_modules.slice(0..-2)
228 230 assert modules.any?
229 231 assert_difference 'EnabledModule.count', -1 do
230 232 project.enabled_module_names = modules.collect(&:name)
231 233 end
232 234 project.reload
233 235 # Ids should be preserved
234 236 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
235 237 end
238
239 def test_copy_from_existing_project
240 source_project = Project.find(1)
241 copied_project = Project.copy_from(1)
242
243 assert copied_project
244 # Cleared attributes
245 assert copied_project.id.blank?
246 assert copied_project.name.blank?
247 assert copied_project.identifier.blank?
248
249 # Duplicated attributes
250 assert_equal source_project.description, copied_project.description
251 assert_equal source_project.enabled_modules, copied_project.enabled_modules
252 assert_equal source_project.trackers, copied_project.trackers
253
254 # Default attributes
255 assert_equal 1, copied_project.status
256 end
257
258 # Context: Project#copy
259 def test_copy_should_copy_issues
260 # Setup
261 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
262 source_project = Project.find(2)
263 Project.destroy_all :identifier => "copy-test"
264 project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
265 project.trackers = source_project.trackers
266 assert project.valid?
267
268 assert project.issues.empty?
269 assert project.copy(source_project)
270
271 # Tests
272 assert_equal source_project.issues.size, project.issues.size
273 project.issues.each do |issue|
274 assert issue.valid?
275 assert ! issue.assigned_to.blank?
276 assert_equal project, issue.project
277 end
278 end
279
280 def test_copy_should_copy_members
281 # Setup
282 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
283 source_project = Project.find(2)
284 project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
285 project.trackers = source_project.trackers
286 project.enabled_modules = source_project.enabled_modules
287 assert project.valid?
288
289 assert project.members.empty?
290 assert project.copy(source_project)
291
292 # Tests
293 assert_equal source_project.members.size, project.members.size
294 project.members.each do |member|
295 assert member
296 assert_equal project, member.project
297 end
298 end
299
300 def test_copy_should_copy_project_level_queries
301 # Setup
302 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
303 source_project = Project.find(2)
304 project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
305 project.trackers = source_project.trackers
306 project.enabled_modules = source_project.enabled_modules
307 assert project.valid?
308
309 assert project.queries.empty?
310 assert project.copy(source_project)
311
312 # Tests
313 assert_equal source_project.queries.size, project.queries.size
314 project.queries.each do |query|
315 assert query
316 assert_equal project, query.project
317 end
318 end
319
236 320 end
General Comments 0
You need to be logged in to leave comments. Login now