##// END OF EJS Templates
XML REST API for Projects (#296)....
Jean-Philippe Lang -
r3199:68a4cd38f543
parent child
Show More
@@ -0,0 +1,18
1 xml.instruct!
2 xml.projects :type => 'array' do
3 @projects.each do |project|
4 xml.project :id => project.id do
5 xml.name project.name
6 xml.identifier project.identifier
7 xml.description project.description
8 xml.parent(:id => project.parent_id, :name => project.parent.name) unless project.parent.nil?
9 xml.custom_fields do
10 project.custom_field_values.each do |custom_value|
11 xml.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
12 end
13 end unless project.custom_field_values.empty?
14 xml.created_on project.created_on
15 xml.updated_on project.updated_on
16 end
17 end
18 end
@@ -0,0 +1,16
1 xml.instruct!
2 xml.project :id => @project.id do
3 xml.name @project.name
4 xml.identifier @project.identifier
5 xml.description @project.description
6 xml.homepage @project.homepage
7
8 xml.custom_fields do
9 @project.custom_field_values.each do |custom_value|
10 xml.custom_field custom_value.value, :id => custom_value.custom_field_id, :name => custom_value.custom_field.name
11 end
12 end unless @project.custom_field_values.empty?
13
14 xml.created_on @project.created_on
15 xml.updated_on @project.updated_on
16 end
@@ -0,0 +1,134
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require "#{File.dirname(__FILE__)}/../test_helper"
19
20 class ProjectsApiTest < ActionController::IntegrationTest
21 fixtures :projects, :versions, :users, :roles, :members, :member_roles, :issues, :journals, :journal_details,
22 :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations, :boards, :messages,
23 :attachments, :custom_fields, :custom_values, :time_entries
24
25 def setup
26 Setting.rest_api_enabled = '1'
27 end
28
29 def test_index_routing
30 assert_routing(
31 {:method => :get, :path => '/projects.xml'},
32 :controller => 'projects', :action => 'index', :format => 'xml'
33 )
34 end
35
36 def test_index
37 get '/projects.xml'
38 assert_response :success
39 assert_equal 'application/xml', @response.content_type
40 end
41
42 def test_show_routing
43 assert_routing(
44 {:method => :get, :path => '/projects/1.xml'},
45 :controller => 'projects', :action => 'show', :id => '1', :format => 'xml'
46 )
47 end
48
49 def test_show
50 get '/projects/1.xml'
51 assert_response :success
52 assert_equal 'application/xml', @response.content_type
53 end
54
55 def test_create_routing
56 assert_routing(
57 {:method => :post, :path => '/projects.xml'},
58 :controller => 'projects', :action => 'add', :format => 'xml'
59 )
60 end
61
62 def test_create
63 attributes = {:name => 'API test', :identifier => 'api-test'}
64 assert_difference 'Project.count' do
65 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
66 end
67 assert_response :created
68 assert_equal 'application/xml', @response.content_type
69 project = Project.first(:order => 'id DESC')
70 attributes.each do |attribute, value|
71 assert_equal value, project.send(attribute)
72 end
73 end
74
75 def test_create_failure
76 attributes = {:name => 'API test'}
77 assert_no_difference 'Project.count' do
78 post '/projects.xml', {:project => attributes}, :authorization => credentials('admin')
79 end
80 assert_response :unprocessable_entity
81 assert_equal 'application/xml', @response.content_type
82 assert_tag :errors, :child => {:tag => 'error', :content => "Identifier can't be blank"}
83 end
84
85 def test_update_routing
86 assert_routing(
87 {:method => :put, :path => '/projects/1.xml'},
88 :controller => 'projects', :action => 'edit', :id => '1', :format => 'xml'
89 )
90 end
91
92 def test_update
93 attributes = {:name => 'API update'}
94 assert_no_difference 'Project.count' do
95 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
96 end
97 assert_response :ok
98 assert_equal 'application/xml', @response.content_type
99 project = Project.find(1)
100 attributes.each do |attribute, value|
101 assert_equal value, project.send(attribute)
102 end
103 end
104
105 def test_update_failure
106 attributes = {:name => ''}
107 assert_no_difference 'Project.count' do
108 put '/projects/1.xml', {:project => attributes}, :authorization => credentials('jsmith')
109 end
110 assert_response :unprocessable_entity
111 assert_equal 'application/xml', @response.content_type
112 assert_tag :errors, :child => {:tag => 'error', :content => "Name can't be blank"}
113 end
114
115 def test_destroy_routing
116 assert_routing(
117 {:method => :delete, :path => '/projects/1.xml'},
118 :controller => 'projects', :action => 'destroy', :id => '1', :format => 'xml'
119 )
120 end
121
122 def test_destroy
123 assert_difference 'Project.count', -1 do
124 delete '/projects/2.xml', {}, :authorization => credentials('admin')
125 end
126 assert_response :ok
127 assert_equal 'application/xml', @response.content_type
128 assert_nil Project.find_by_id(2)
129 end
130
131 def credentials(user, password=nil)
132 ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)
133 end
134 end
@@ -1,310 +1,311
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 'uri'
19 19 require 'cgi'
20 20
21 21 class ApplicationController < ActionController::Base
22 22 include Redmine::I18n
23 23
24 24 layout 'base'
25 exempt_from_layout 'builder'
25 26
26 27 # Remove broken cookie after upgrade from 0.8.x (#4292)
27 28 # See https://rails.lighthouseapp.com/projects/8994/tickets/3360
28 29 # TODO: remove it when Rails is fixed
29 30 before_filter :delete_broken_cookies
30 31 def delete_broken_cookies
31 32 if cookies['_redmine_session'] && cookies['_redmine_session'] !~ /--/
32 33 cookies.delete '_redmine_session'
33 34 redirect_to home_path
34 35 return false
35 36 end
36 37 end
37 38
38 39 before_filter :user_setup, :check_if_login_required, :set_localization
39 40 filter_parameter_logging :password
40 41 protect_from_forgery
41 42
42 43 rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_authenticity_token
43 44
44 45 include Redmine::Search::Controller
45 46 include Redmine::MenuManager::MenuController
46 47 helper Redmine::MenuManager::MenuHelper
47 48
48 49 REDMINE_SUPPORTED_SCM.each do |scm|
49 50 require_dependency "repository/#{scm.underscore}"
50 51 end
51 52
52 53 def user_setup
53 54 # Check the settings cache for each request
54 55 Setting.check_cache
55 56 # Find the current user
56 57 User.current = find_current_user
57 58 end
58 59
59 60 # Returns the current user or nil if no user is logged in
60 61 # and starts a session if needed
61 62 def find_current_user
62 63 if session[:user_id]
63 64 # existing session
64 65 (User.active.find(session[:user_id]) rescue nil)
65 66 elsif cookies[:autologin] && Setting.autologin?
66 67 # auto-login feature starts a new session
67 68 user = User.try_to_autologin(cookies[:autologin])
68 69 session[:user_id] = user.id if user
69 70 user
70 71 elsif params[:format] == 'atom' && params[:key] && accept_key_auth_actions.include?(params[:action])
71 72 # RSS key authentication does not start a session
72 73 User.find_by_rss_key(params[:key])
73 74 elsif Setting.rest_api_enabled? && ['xml', 'json'].include?(params[:format])
74 75 if params[:key].present? && accept_key_auth_actions.include?(params[:action])
75 76 # Use API key
76 77 User.find_by_api_key(params[:key])
77 78 else
78 79 # HTTP Basic, either username/password or API key/random
79 80 authenticate_with_http_basic do |username, password|
80 81 User.try_to_login(username, password) || User.find_by_api_key(username)
81 82 end
82 83 end
83 84 end
84 85 end
85 86
86 87 # Sets the logged in user
87 88 def logged_user=(user)
88 89 reset_session
89 90 if user && user.is_a?(User)
90 91 User.current = user
91 92 session[:user_id] = user.id
92 93 else
93 94 User.current = User.anonymous
94 95 end
95 96 end
96 97
97 98 # check if login is globally required to access the application
98 99 def check_if_login_required
99 100 # no check needed if user is already logged in
100 101 return true if User.current.logged?
101 102 require_login if Setting.login_required?
102 103 end
103 104
104 105 def set_localization
105 106 lang = nil
106 107 if User.current.logged?
107 108 lang = find_language(User.current.language)
108 109 end
109 110 if lang.nil? && request.env['HTTP_ACCEPT_LANGUAGE']
110 111 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.downcase
111 112 if !accept_lang.blank?
112 113 lang = find_language(accept_lang) || find_language(accept_lang.split('-').first)
113 114 end
114 115 end
115 116 lang ||= Setting.default_language
116 117 set_language_if_valid(lang)
117 118 end
118 119
119 120 def require_login
120 121 if !User.current.logged?
121 122 # Extract only the basic url parameters on non-GET requests
122 123 if request.get?
123 124 url = url_for(params)
124 125 else
125 126 url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
126 127 end
127 128 respond_to do |format|
128 129 format.html { redirect_to :controller => "account", :action => "login", :back_url => url }
129 130 format.atom { redirect_to :controller => "account", :action => "login", :back_url => url }
130 131 format.xml { head :unauthorized }
131 132 format.json { head :unauthorized }
132 133 end
133 134 return false
134 135 end
135 136 true
136 137 end
137 138
138 139 def require_admin
139 140 return unless require_login
140 141 if !User.current.admin?
141 142 render_403
142 143 return false
143 144 end
144 145 true
145 146 end
146 147
147 148 def deny_access
148 149 User.current.logged? ? render_403 : require_login
149 150 end
150 151
151 152 # Authorize the user for the requested action
152 153 def authorize(ctrl = params[:controller], action = params[:action], global = false)
153 154 allowed = User.current.allowed_to?({:controller => ctrl, :action => action}, @project, :global => global)
154 155 allowed ? true : deny_access
155 156 end
156 157
157 158 # Authorize the user for the requested action outside a project
158 159 def authorize_global(ctrl = params[:controller], action = params[:action], global = true)
159 160 authorize(ctrl, action, global)
160 161 end
161 162
162 163 # make sure that the user is a member of the project (or admin) if project is private
163 164 # used as a before_filter for actions that do not require any particular permission on the project
164 165 def check_project_privacy
165 166 if @project && @project.active?
166 167 if @project.is_public? || User.current.member_of?(@project) || User.current.admin?
167 168 true
168 169 else
169 170 User.current.logged? ? render_403 : require_login
170 171 end
171 172 else
172 173 @project = nil
173 174 render_404
174 175 false
175 176 end
176 177 end
177 178
178 179 def redirect_back_or_default(default)
179 180 back_url = CGI.unescape(params[:back_url].to_s)
180 181 if !back_url.blank?
181 182 begin
182 183 uri = URI.parse(back_url)
183 184 # do not redirect user to another host or to the login or register page
184 185 if (uri.relative? || (uri.host == request.host)) && !uri.path.match(%r{/(login|account/register)})
185 186 redirect_to(back_url)
186 187 return
187 188 end
188 189 rescue URI::InvalidURIError
189 190 # redirect to default
190 191 end
191 192 end
192 193 redirect_to default
193 194 end
194 195
195 196 def render_403
196 197 @project = nil
197 198 respond_to do |format|
198 199 format.html { render :template => "common/403", :layout => (request.xhr? ? false : 'base'), :status => 403 }
199 200 format.atom { head 403 }
200 201 format.xml { head 403 }
201 202 format.json { head 403 }
202 203 end
203 204 return false
204 205 end
205 206
206 207 def render_404
207 208 respond_to do |format|
208 209 format.html { render :template => "common/404", :layout => !request.xhr?, :status => 404 }
209 210 format.atom { head 404 }
210 211 format.xml { head 404 }
211 212 format.json { head 404 }
212 213 end
213 214 return false
214 215 end
215 216
216 217 def render_error(msg)
217 218 respond_to do |format|
218 219 format.html {
219 220 flash.now[:error] = msg
220 221 render :text => '', :layout => !request.xhr?, :status => 500
221 222 }
222 223 format.atom { head 500 }
223 224 format.xml { head 500 }
224 225 format.json { head 500 }
225 226 end
226 227 end
227 228
228 229 def invalid_authenticity_token
229 230 render_error "Invalid form authenticity token."
230 231 end
231 232
232 233 def render_feed(items, options={})
233 234 @items = items || []
234 235 @items.sort! {|x,y| y.event_datetime <=> x.event_datetime }
235 236 @items = @items.slice(0, Setting.feeds_limit.to_i)
236 237 @title = options[:title] || Setting.app_title
237 238 render :template => "common/feed.atom.rxml", :layout => false, :content_type => 'application/atom+xml'
238 239 end
239 240
240 241 def self.accept_key_auth(*actions)
241 242 actions = actions.flatten.map(&:to_s)
242 243 write_inheritable_attribute('accept_key_auth_actions', actions)
243 244 end
244 245
245 246 def accept_key_auth_actions
246 247 self.class.read_inheritable_attribute('accept_key_auth_actions') || []
247 248 end
248 249
249 250 # TODO: move to model
250 251 def attach_files(obj, attachments)
251 252 attached = []
252 253 unsaved = []
253 254 if attachments && attachments.is_a?(Hash)
254 255 attachments.each_value do |attachment|
255 256 file = attachment['file']
256 257 next unless file && file.size > 0
257 258 a = Attachment.create(:container => obj,
258 259 :file => file,
259 260 :description => attachment['description'].to_s.strip,
260 261 :author => User.current)
261 262 a.new_record? ? (unsaved << a) : (attached << a)
262 263 end
263 264 if unsaved.any?
264 265 flash[:warning] = l(:warning_attachments_not_saved, unsaved.size)
265 266 end
266 267 end
267 268 attached
268 269 end
269 270
270 271 # Returns the number of objects that should be displayed
271 272 # on the paginated list
272 273 def per_page_option
273 274 per_page = nil
274 275 if params[:per_page] && Setting.per_page_options_array.include?(params[:per_page].to_s.to_i)
275 276 per_page = params[:per_page].to_s.to_i
276 277 session[:per_page] = per_page
277 278 elsif session[:per_page]
278 279 per_page = session[:per_page]
279 280 else
280 281 per_page = Setting.per_page_options_array.first || 25
281 282 end
282 283 per_page
283 284 end
284 285
285 286 # qvalues http header parser
286 287 # code taken from webrick
287 288 def parse_qvalues(value)
288 289 tmp = []
289 290 if value
290 291 parts = value.split(/,\s*/)
291 292 parts.each {|part|
292 293 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
293 294 val = m[1]
294 295 q = (m[2] or 1).to_f
295 296 tmp.push([val, q])
296 297 end
297 298 }
298 299 tmp = tmp.sort_by{|val, q| -q}
299 300 tmp.collect!{|val, q| val}
300 301 end
301 302 return tmp
302 303 rescue
303 304 nil
304 305 end
305 306
306 307 # Returns a string that can be used as filename value in Content-Disposition header
307 308 def filename_for_content_disposition(name)
308 309 request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
309 310 end
310 311 end
@@ -1,419 +1,455
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 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
25 25 before_filter :find_project, :except => [ :index, :list, :add, :copy, :activity ]
26 26 before_filter :find_optional_project, :only => :activity
27 27 before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy, :activity ]
28 28 before_filter :authorize_global, :only => :add
29 29 before_filter :require_admin, :only => [ :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 format.xml {
57 @projects = Project.visible.find(:all, :order => 'lft')
58 }
56 59 format.atom {
57 60 projects = Project.visible.find(:all, :order => 'created_on DESC',
58 61 :limit => Setting.feeds_limit.to_i)
59 62 render_feed(projects, :title => "#{Setting.app_title}: #{l(:label_project_latest)}")
60 63 }
61 64 end
62 65 end
63 66
64 67 # Add a new project
65 68 def add
66 69 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
67 70 @trackers = Tracker.all
68 71 @project = Project.new(params[:project])
69 72 if request.get?
70 73 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
71 74 @project.trackers = Tracker.all
72 75 @project.is_public = Setting.default_projects_public?
73 76 @project.enabled_module_names = Setting.default_projects_modules
74 77 else
75 78 @project.enabled_module_names = params[:enabled_modules]
76 79 if validate_parent_id && @project.save
77 80 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
78 81 # Add current user as a project member if he is not admin
79 82 unless User.current.admin?
80 83 r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
81 84 m = Member.new(:user => User.current, :roles => [r])
82 85 @project.members << m
83 86 end
87 respond_to do |format|
88 format.html {
84 89 flash[:notice] = l(:notice_successful_create)
85 90 redirect_to :controller => 'projects', :action => 'settings', :id => @project
91 }
92 format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
93 end
94 else
95 respond_to do |format|
96 format.html
97 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
98 end
86 99 end
87 100 end
88 101 end
89 102
90 103 def copy
91 104 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
92 105 @trackers = Tracker.all
93 106 @root_projects = Project.find(:all,
94 107 :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}",
95 108 :order => 'name')
96 109 @source_project = Project.find(params[:id])
97 110 if request.get?
98 111 @project = Project.copy_from(@source_project)
99 112 if @project
100 113 @project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
101 114 else
102 115 redirect_to :controller => 'admin', :action => 'projects'
103 116 end
104 117 else
105 118 @project = Project.new(params[:project])
106 119 @project.enabled_module_names = params[:enabled_modules]
107 120 if validate_parent_id && @project.copy(@source_project, :only => params[:only])
108 121 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
109 122 flash[:notice] = l(:notice_successful_create)
110 123 redirect_to :controller => 'admin', :action => 'projects'
111 124 elsif !@project.new_record?
112 125 # Project was created
113 126 # But some objects were not copied due to validation failures
114 127 # (eg. issues from disabled trackers)
115 128 # TODO: inform about that
116 129 redirect_to :controller => 'admin', :action => 'projects'
117 130 end
118 131 end
119 132 rescue ActiveRecord::RecordNotFound
120 133 redirect_to :controller => 'admin', :action => 'projects'
121 134 end
122 135
123 136 # Show @project
124 137 def show
125 138 if params[:jump]
126 139 # try to redirect to the requested menu item
127 140 redirect_to_project_menu_item(@project, params[:jump]) && return
128 141 end
129 142
130 143 @users_by_role = @project.users_by_role
131 144 @subprojects = @project.children.visible
132 145 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
133 146 @trackers = @project.rolled_up_trackers
134 147
135 148 cond = @project.project_condition(Setting.display_subprojects_issues?)
136 149
137 150 @open_issues_by_tracker = Issue.visible.count(:group => :tracker,
138 151 :include => [:project, :status, :tracker],
139 152 :conditions => ["(#{cond}) AND #{IssueStatus.table_name}.is_closed=?", false])
140 153 @total_issues_by_tracker = Issue.visible.count(:group => :tracker,
141 154 :include => [:project, :status, :tracker],
142 155 :conditions => cond)
143 156
144 157 TimeEntry.visible_by(User.current) do
145 158 @total_hours = TimeEntry.sum(:hours,
146 159 :include => :project,
147 160 :conditions => cond).to_f
148 161 end
149 162 @key = User.current.rss_key
163
164 respond_to do |format|
165 format.html
166 format.xml
167 end
150 168 end
151 169
152 170 def settings
153 171 @issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
154 172 @issue_category ||= IssueCategory.new
155 173 @member ||= @project.members.new
156 174 @trackers = Tracker.all
157 175 @repository ||= @project.repository
158 176 @wiki ||= @project.wiki
159 177 end
160 178
161 179 # Edit @project
162 180 def edit
163 if request.post?
181 if request.get?
182 else
164 183 @project.attributes = params[:project]
165 184 if validate_parent_id && @project.save
166 185 @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
186 respond_to do |format|
187 format.html {
167 188 flash[:notice] = l(:notice_successful_update)
168 189 redirect_to :action => 'settings', :id => @project
190 }
191 format.xml { head :ok }
192 end
169 193 else
194 respond_to do |format|
195 format.html {
170 196 settings
171 197 render :action => 'settings'
198 }
199 format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
200 end
172 201 end
173 202 end
174 203 end
175 204
176 205 def modules
177 206 @project.enabled_module_names = params[:enabled_modules]
178 207 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
179 208 end
180 209
181 210 def archive
182 211 if request.post?
183 212 unless @project.archive
184 213 flash[:error] = l(:error_can_not_archive_project)
185 214 end
186 215 end
187 216 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
188 217 end
189 218
190 219 def unarchive
191 220 @project.unarchive if request.post? && !@project.active?
192 221 redirect_to(url_for(:controller => 'admin', :action => 'projects', :status => params[:status]))
193 222 end
194 223
195 224 # Delete @project
196 225 def destroy
197 226 @project_to_destroy = @project
198 if request.post? and params[:confirm]
227 if request.get?
228 # display confirmation view
229 else
230 if params[:format] == 'xml' || params[:confirm]
199 231 @project_to_destroy.destroy
200 redirect_to :controller => 'admin', :action => 'projects'
232 respond_to do |format|
233 format.html { redirect_to :controller => 'admin', :action => 'projects' }
234 format.xml { head :ok }
235 end
236 end
201 237 end
202 238 # hide project in layout
203 239 @project = nil
204 240 end
205 241
206 242 # Add a new issue category to @project
207 243 def add_issue_category
208 244 @category = @project.issue_categories.build(params[:category])
209 245 if request.post?
210 246 if @category.save
211 247 respond_to do |format|
212 248 format.html do
213 249 flash[:notice] = l(:notice_successful_create)
214 250 redirect_to :action => 'settings', :tab => 'categories', :id => @project
215 251 end
216 252 format.js do
217 253 # IE doesn't support the replace_html rjs method for select box options
218 254 render(:update) {|page| page.replace "issue_category_id",
219 255 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]')
220 256 }
221 257 end
222 258 end
223 259 else
224 260 respond_to do |format|
225 261 format.html
226 262 format.js do
227 263 render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
228 264 end
229 265 end
230 266 end
231 267 end
232 268 end
233 269
234 270 # Add a new version to @project
235 271 def add_version
236 272 @version = @project.versions.build
237 273 if params[:version]
238 274 attributes = params[:version].dup
239 275 attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
240 276 @version.attributes = attributes
241 277 end
242 278 if request.post?
243 279 if @version.save
244 280 respond_to do |format|
245 281 format.html do
246 282 flash[:notice] = l(:notice_successful_create)
247 283 redirect_to :action => 'settings', :tab => 'versions', :id => @project
248 284 end
249 285 format.js do
250 286 # IE doesn't support the replace_html rjs method for select box options
251 287 render(:update) {|page| page.replace "issue_fixed_version_id",
252 288 content_tag('select', '<option></option>' + version_options_for_select(@project.shared_versions.open, @version), :id => 'issue_fixed_version_id', :name => 'issue[fixed_version_id]')
253 289 }
254 290 end
255 291 end
256 292 else
257 293 respond_to do |format|
258 294 format.html
259 295 format.js do
260 296 render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
261 297 end
262 298 end
263 299 end
264 300 end
265 301 end
266 302
267 303 def add_file
268 304 if request.post?
269 305 container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
270 306 attachments = attach_files(container, params[:attachments])
271 307 if !attachments.empty? && Setting.notified_events.include?('file_added')
272 308 Mailer.deliver_attachments_added(attachments)
273 309 end
274 310 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
275 311 return
276 312 end
277 313 @versions = @project.versions.sort
278 314 end
279 315
280 316 def save_activities
281 317 if request.post? && params[:enumerations]
282 318 Project.transaction do
283 319 params[:enumerations].each do |id, activity|
284 320 @project.update_or_create_time_entry_activity(id, activity)
285 321 end
286 322 end
287 323 end
288 324
289 325 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
290 326 end
291 327
292 328 def reset_activities
293 329 @project.time_entry_activities.each do |time_entry_activity|
294 330 time_entry_activity.destroy(time_entry_activity.parent)
295 331 end
296 332 redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
297 333 end
298 334
299 335 def list_files
300 336 sort_init 'filename', 'asc'
301 337 sort_update 'filename' => "#{Attachment.table_name}.filename",
302 338 'created_on' => "#{Attachment.table_name}.created_on",
303 339 'size' => "#{Attachment.table_name}.filesize",
304 340 'downloads' => "#{Attachment.table_name}.downloads"
305 341
306 342 @containers = [ Project.find(@project.id, :include => :attachments, :order => sort_clause)]
307 343 @containers += @project.versions.find(:all, :include => :attachments, :order => sort_clause).sort.reverse
308 344 render :layout => !request.xhr?
309 345 end
310 346
311 347 def roadmap
312 348 @trackers = @project.trackers.find(:all, :order => 'position')
313 349 retrieve_selected_tracker_ids(@trackers, @trackers.select {|t| t.is_in_roadmap?})
314 350 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
315 351 project_ids = @with_subprojects ? @project.self_and_descendants.collect(&:id) : [@project.id]
316 352
317 353 @versions = @project.shared_versions.sort
318 354 @versions.reject! {|version| version.closed? || version.completed? } unless params[:completed]
319 355
320 356 @issues_by_version = {}
321 357 unless @selected_tracker_ids.empty?
322 358 @versions.each do |version|
323 359 conditions = {:tracker_id => @selected_tracker_ids}
324 360 if !@project.versions.include?(version)
325 361 conditions.merge!(:project_id => project_ids)
326 362 end
327 363 issues = version.fixed_issues.visible.find(:all,
328 364 :include => [:project, :status, :tracker, :priority],
329 365 :conditions => conditions,
330 366 :order => "#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
331 367 @issues_by_version[version] = issues
332 368 end
333 369 end
334 370 @versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].empty?}
335 371 end
336 372
337 373 def activity
338 374 @days = Setting.activity_days_default.to_i
339 375
340 376 if params[:from]
341 377 begin; @date_to = params[:from].to_date + 1; rescue; end
342 378 end
343 379
344 380 @date_to ||= Date.today + 1
345 381 @date_from = @date_to - @days
346 382 @with_subprojects = params[:with_subprojects].nil? ? Setting.display_subprojects_issues? : (params[:with_subprojects] == '1')
347 383 @author = (params[:user_id].blank? ? nil : User.active.find(params[:user_id]))
348 384
349 385 @activity = Redmine::Activity::Fetcher.new(User.current, :project => @project,
350 386 :with_subprojects => @with_subprojects,
351 387 :author => @author)
352 388 @activity.scope_select {|t| !params["show_#{t}"].nil?}
353 389 @activity.scope = (@author.nil? ? :default : :all) if @activity.scope.empty?
354 390
355 391 events = @activity.events(@date_from, @date_to)
356 392
357 393 if events.empty? || stale?(:etag => [events.first, User.current])
358 394 respond_to do |format|
359 395 format.html {
360 396 @events_by_day = events.group_by(&:event_date)
361 397 render :layout => false if request.xhr?
362 398 }
363 399 format.atom {
364 400 title = l(:label_activity)
365 401 if @author
366 402 title = @author.name
367 403 elsif @activity.scope.size == 1
368 404 title = l("label_#{@activity.scope.first.singularize}_plural")
369 405 end
370 406 render_feed(events, :title => "#{@project || Setting.app_title}: #{title}")
371 407 }
372 408 end
373 409 end
374 410
375 411 rescue ActiveRecord::RecordNotFound
376 412 render_404
377 413 end
378 414
379 415 private
380 416 # Find project of id params[:id]
381 417 # if not found, redirect to project list
382 418 # Used as a before_filter
383 419 def find_project
384 420 @project = Project.find(params[:id])
385 421 rescue ActiveRecord::RecordNotFound
386 422 render_404
387 423 end
388 424
389 425 def find_optional_project
390 426 return true unless params[:id]
391 427 @project = Project.find(params[:id])
392 428 authorize
393 429 rescue ActiveRecord::RecordNotFound
394 430 render_404
395 431 end
396 432
397 433 def retrieve_selected_tracker_ids(selectable_trackers, default_trackers=nil)
398 434 if ids = params[:tracker_ids]
399 435 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
400 436 else
401 437 @selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
402 438 end
403 439 end
404 440
405 441 # Validates parent_id param according to user's permissions
406 442 # TODO: move it to Project model in a validation that depends on User.current
407 443 def validate_parent_id
408 444 return true if User.current.admin?
409 445 parent_id = params[:project] && params[:project][:parent_id]
410 446 if parent_id || @project.new_record?
411 447 parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
412 448 unless @project.allowed_parents.include?(parent)
413 449 @project.errors.add :parent_id, :invalid
414 450 return false
415 451 end
416 452 end
417 453 true
418 454 end
419 455 end
@@ -1,280 +1,287
1 1 ActionController::Routing::Routes.draw do |map|
2 2 # Add your own custom routes here.
3 3 # The priority is based upon order of creation: first created -> highest priority.
4 4
5 5 # Here's a sample route:
6 6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 7 # Keep in mind you can assign values other than :controller and :action
8 8
9 9 map.home '', :controller => 'welcome'
10 10
11 11 map.signin 'login', :controller => 'account', :action => 'login'
12 12 map.signout 'logout', :controller => 'account', :action => 'logout'
13 13
14 14 map.connect 'roles/workflow/:id/:role_id/:tracker_id', :controller => 'roles', :action => 'workflow'
15 15 map.connect 'help/:ctrl/:page', :controller => 'help'
16 16
17 17 map.connect 'time_entries/:id/edit', :action => 'edit', :controller => 'timelog'
18 18 map.connect 'projects/:project_id/time_entries/new', :action => 'edit', :controller => 'timelog'
19 19 map.connect 'projects/:project_id/issues/:issue_id/time_entries/new', :action => 'edit', :controller => 'timelog'
20 20
21 21 map.with_options :controller => 'timelog' do |timelog|
22 22 timelog.connect 'projects/:project_id/time_entries', :action => 'details'
23 23
24 24 timelog.with_options :action => 'details', :conditions => {:method => :get} do |time_details|
25 25 time_details.connect 'time_entries'
26 26 time_details.connect 'time_entries.:format'
27 27 time_details.connect 'issues/:issue_id/time_entries'
28 28 time_details.connect 'issues/:issue_id/time_entries.:format'
29 29 time_details.connect 'projects/:project_id/time_entries.:format'
30 30 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries'
31 31 time_details.connect 'projects/:project_id/issues/:issue_id/time_entries.:format'
32 32 end
33 33 timelog.connect 'projects/:project_id/time_entries/report', :action => 'report'
34 34 timelog.with_options :action => 'report',:conditions => {:method => :get} do |time_report|
35 35 time_report.connect 'time_entries/report'
36 36 time_report.connect 'time_entries/report.:format'
37 37 time_report.connect 'projects/:project_id/time_entries/report.:format'
38 38 end
39 39
40 40 timelog.with_options :action => 'edit', :conditions => {:method => :get} do |time_edit|
41 41 time_edit.connect 'issues/:issue_id/time_entries/new'
42 42 end
43 43
44 44 timelog.connect 'time_entries/:id/destroy', :action => 'destroy', :conditions => {:method => :post}
45 45 end
46 46
47 47 map.connect 'projects/:id/wiki', :controller => 'wikis', :action => 'edit', :conditions => {:method => :post}
48 48 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :get}
49 49 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis', :action => 'destroy', :conditions => {:method => :post}
50 50 map.with_options :controller => 'wiki' do |wiki_routes|
51 51 wiki_routes.with_options :conditions => {:method => :get} do |wiki_views|
52 52 wiki_views.connect 'projects/:id/wiki/:page', :action => 'special', :page => /page_index|date_index|export/i
53 53 wiki_views.connect 'projects/:id/wiki/:page', :action => 'index', :page => nil
54 54 wiki_views.connect 'projects/:id/wiki/:page/edit', :action => 'edit'
55 55 wiki_views.connect 'projects/:id/wiki/:page/rename', :action => 'rename'
56 56 wiki_views.connect 'projects/:id/wiki/:page/history', :action => 'history'
57 57 wiki_views.connect 'projects/:id/wiki/:page/diff/:version/vs/:version_from', :action => 'diff'
58 58 wiki_views.connect 'projects/:id/wiki/:page/annotate/:version', :action => 'annotate'
59 59 end
60 60
61 61 wiki_routes.connect 'projects/:id/wiki/:page/:action',
62 62 :action => /edit|rename|destroy|preview|protect/,
63 63 :conditions => {:method => :post}
64 64 end
65 65
66 66 map.with_options :controller => 'messages' do |messages_routes|
67 67 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
68 68 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
69 69 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
70 70 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
71 71 end
72 72 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
73 73 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
74 74 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
75 75 messages_actions.connect 'boards/:board_id/topics/:id/:action', :action => /edit|destroy/
76 76 end
77 77 end
78 78
79 79 map.with_options :controller => 'boards' do |board_routes|
80 80 board_routes.with_options :conditions => {:method => :get} do |board_views|
81 81 board_views.connect 'projects/:project_id/boards', :action => 'index'
82 82 board_views.connect 'projects/:project_id/boards/new', :action => 'new'
83 83 board_views.connect 'projects/:project_id/boards/:id', :action => 'show'
84 84 board_views.connect 'projects/:project_id/boards/:id.:format', :action => 'show'
85 85 board_views.connect 'projects/:project_id/boards/:id/edit', :action => 'edit'
86 86 end
87 87 board_routes.with_options :conditions => {:method => :post} do |board_actions|
88 88 board_actions.connect 'projects/:project_id/boards', :action => 'new'
89 89 board_actions.connect 'projects/:project_id/boards/:id/:action', :action => /edit|destroy/
90 90 end
91 91 end
92 92
93 93 map.with_options :controller => 'documents' do |document_routes|
94 94 document_routes.with_options :conditions => {:method => :get} do |document_views|
95 95 document_views.connect 'projects/:project_id/documents', :action => 'index'
96 96 document_views.connect 'projects/:project_id/documents/new', :action => 'new'
97 97 document_views.connect 'documents/:id', :action => 'show'
98 98 document_views.connect 'documents/:id/edit', :action => 'edit'
99 99 end
100 100 document_routes.with_options :conditions => {:method => :post} do |document_actions|
101 101 document_actions.connect 'projects/:project_id/documents', :action => 'new'
102 102 document_actions.connect 'documents/:id/:action', :action => /destroy|edit/
103 103 end
104 104 end
105 105
106 106 map.with_options :controller => 'issues' do |issues_routes|
107 107 issues_routes.with_options :conditions => {:method => :get} do |issues_views|
108 108 issues_views.connect 'issues', :action => 'index'
109 109 issues_views.connect 'issues.:format', :action => 'index'
110 110 issues_views.connect 'projects/:project_id/issues', :action => 'index'
111 111 issues_views.connect 'projects/:project_id/issues.:format', :action => 'index'
112 112 issues_views.connect 'projects/:project_id/issues/new', :action => 'new'
113 113 issues_views.connect 'projects/:project_id/issues/gantt', :action => 'gantt'
114 114 issues_views.connect 'projects/:project_id/issues/calendar', :action => 'calendar'
115 115 issues_views.connect 'projects/:project_id/issues/:copy_from/copy', :action => 'new'
116 116 issues_views.connect 'issues/:id', :action => 'show', :id => /\d+/
117 117 issues_views.connect 'issues/:id.:format', :action => 'show', :id => /\d+/
118 118 issues_views.connect 'issues/:id/edit', :action => 'edit', :id => /\d+/
119 119 issues_views.connect 'issues/:id/move', :action => 'move', :id => /\d+/
120 120 end
121 121 issues_routes.with_options :conditions => {:method => :post} do |issues_actions|
122 122 issues_actions.connect 'issues', :action => 'index'
123 123 issues_actions.connect 'projects/:project_id/issues', :action => 'new'
124 124 issues_actions.connect 'issues/:id/quoted', :action => 'reply', :id => /\d+/
125 125 issues_actions.connect 'issues/:id/:action', :action => /edit|move|destroy/, :id => /\d+/
126 126 issues_actions.connect 'issues.:format', :action => 'new', :format => /xml/
127 127 end
128 128 issues_routes.with_options :conditions => {:method => :put} do |issues_actions|
129 129 issues_actions.connect 'issues/:id.:format', :action => 'edit', :id => /\d+/, :format => /xml/
130 130 end
131 131 issues_routes.with_options :conditions => {:method => :delete} do |issues_actions|
132 132 issues_actions.connect 'issues/:id.:format', :action => 'destroy', :id => /\d+/, :format => /xml/
133 133 end
134 134 issues_routes.connect 'issues/:action'
135 135 end
136 136
137 137 map.with_options :controller => 'issue_relations', :conditions => {:method => :post} do |relations|
138 138 relations.connect 'issues/:issue_id/relations/:id', :action => 'new'
139 139 relations.connect 'issues/:issue_id/relations/:id/destroy', :action => 'destroy'
140 140 end
141 141
142 142 map.with_options :controller => 'reports', :action => 'issue_report', :conditions => {:method => :get} do |reports|
143 143 reports.connect 'projects/:id/issues/report'
144 144 reports.connect 'projects/:id/issues/report/:detail'
145 145 end
146 146
147 147 map.with_options :controller => 'news' do |news_routes|
148 148 news_routes.with_options :conditions => {:method => :get} do |news_views|
149 149 news_views.connect 'news', :action => 'index'
150 150 news_views.connect 'projects/:project_id/news', :action => 'index'
151 151 news_views.connect 'projects/:project_id/news.:format', :action => 'index'
152 152 news_views.connect 'news.:format', :action => 'index'
153 153 news_views.connect 'projects/:project_id/news/new', :action => 'new'
154 154 news_views.connect 'news/:id', :action => 'show'
155 155 news_views.connect 'news/:id/edit', :action => 'edit'
156 156 end
157 157 news_routes.with_options do |news_actions|
158 158 news_actions.connect 'projects/:project_id/news', :action => 'new'
159 159 news_actions.connect 'news/:id/edit', :action => 'edit'
160 160 news_actions.connect 'news/:id/destroy', :action => 'destroy'
161 161 end
162 162 end
163 163
164 164 map.connect 'projects/:id/members/new', :controller => 'members', :action => 'new'
165 165
166 166 map.with_options :controller => 'users' do |users|
167 167 users.with_options :conditions => {:method => :get} do |user_views|
168 168 user_views.connect 'users', :action => 'index'
169 169 user_views.connect 'users/:id', :action => 'show', :id => /\d+/
170 170 user_views.connect 'users/new', :action => 'add'
171 171 user_views.connect 'users/:id/edit/:tab', :action => 'edit', :tab => nil
172 172 end
173 173 users.with_options :conditions => {:method => :post} do |user_actions|
174 174 user_actions.connect 'users', :action => 'add'
175 175 user_actions.connect 'users/new', :action => 'add'
176 176 user_actions.connect 'users/:id/edit', :action => 'edit'
177 177 user_actions.connect 'users/:id/memberships', :action => 'edit_membership'
178 178 user_actions.connect 'users/:id/memberships/:membership_id', :action => 'edit_membership'
179 179 user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
180 180 end
181 181 end
182 182
183 183 map.with_options :controller => 'projects' do |projects|
184 184 projects.with_options :conditions => {:method => :get} do |project_views|
185 185 project_views.connect 'projects', :action => 'index'
186 186 project_views.connect 'projects.:format', :action => 'index'
187 187 project_views.connect 'projects/new', :action => 'add'
188 188 project_views.connect 'projects/:id', :action => 'show'
189 project_views.connect 'projects/:id.:format', :action => 'show'
189 190 project_views.connect 'projects/:id/:action', :action => /roadmap|destroy|settings/
190 191 project_views.connect 'projects/:id/files', :action => 'list_files'
191 192 project_views.connect 'projects/:id/files/new', :action => 'add_file'
192 193 project_views.connect 'projects/:id/versions/new', :action => 'add_version'
193 194 project_views.connect 'projects/:id/categories/new', :action => 'add_issue_category'
194 195 project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
195 196 end
196 197
197 198 projects.with_options :action => 'activity', :conditions => {:method => :get} do |activity|
198 199 activity.connect 'projects/:id/activity'
199 200 activity.connect 'projects/:id/activity.:format'
200 201 activity.connect 'activity', :id => nil
201 202 activity.connect 'activity.:format', :id => nil
202 203 end
203 204
204 205 projects.with_options :conditions => {:method => :post} do |project_actions|
205 206 project_actions.connect 'projects/new', :action => 'add'
206 207 project_actions.connect 'projects', :action => 'add'
208 project_actions.connect 'projects.:format', :action => 'add', :format => /xml/
207 209 project_actions.connect 'projects/:id/:action', :action => /destroy|archive|unarchive/
208 210 project_actions.connect 'projects/:id/files/new', :action => 'add_file'
209 211 project_actions.connect 'projects/:id/versions/new', :action => 'add_version'
210 212 project_actions.connect 'projects/:id/categories/new', :action => 'add_issue_category'
211 213 project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
212 214 end
213 215
216 projects.with_options :conditions => {:method => :put} do |project_actions|
217 project_actions.conditions 'projects/:id.:format', :action => 'edit', :format => /xml/
218 end
219
214 220 projects.with_options :conditions => {:method => :delete} do |project_actions|
221 project_actions.conditions 'projects/:id.:format', :action => 'destroy', :format => /xml/
215 222 project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
216 223 end
217 224 end
218 225
219 226 map.with_options :controller => 'versions' do |versions|
220 227 versions.with_options :conditions => {:method => :post} do |version_actions|
221 228 version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed'
222 229 end
223 230 end
224 231
225 232 map.with_options :controller => 'repositories' do |repositories|
226 233 repositories.with_options :conditions => {:method => :get} do |repository_views|
227 234 repository_views.connect 'projects/:id/repository', :action => 'show'
228 235 repository_views.connect 'projects/:id/repository/edit', :action => 'edit'
229 236 repository_views.connect 'projects/:id/repository/statistics', :action => 'stats'
230 237 repository_views.connect 'projects/:id/repository/revisions', :action => 'revisions'
231 238 repository_views.connect 'projects/:id/repository/revisions.:format', :action => 'revisions'
232 239 repository_views.connect 'projects/:id/repository/revisions/:rev', :action => 'revision'
233 240 repository_views.connect 'projects/:id/repository/revisions/:rev/diff', :action => 'diff'
234 241 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format', :action => 'diff'
235 242 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path', :action => 'entry', :format => 'raw', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
236 243 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path', :requirements => { :rev => /[a-z0-9\.\-_]+/ }
237 244 repository_views.connect 'projects/:id/repository/raw/*path', :action => 'entry', :format => 'raw'
238 245 # TODO: why the following route is required?
239 246 repository_views.connect 'projects/:id/repository/entry/*path', :action => 'entry'
240 247 repository_views.connect 'projects/:id/repository/:action/*path'
241 248 end
242 249
243 250 repositories.connect 'projects/:id/repository/:action', :conditions => {:method => :post}
244 251 end
245 252
246 253 map.connect 'attachments/:id', :controller => 'attachments', :action => 'show', :id => /\d+/
247 254 map.connect 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/
248 255 map.connect 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/
249 256
250 257 map.resources :groups
251 258
252 259 #left old routes at the bottom for backwards compat
253 260 map.connect 'projects/:project_id/issues/:action', :controller => 'issues'
254 261 map.connect 'projects/:project_id/documents/:action', :controller => 'documents'
255 262 map.connect 'projects/:project_id/boards/:action/:id', :controller => 'boards'
256 263 map.connect 'boards/:board_id/topics/:action/:id', :controller => 'messages'
257 264 map.connect 'wiki/:id/:page/:action', :page => nil, :controller => 'wiki'
258 265 map.connect 'issues/:issue_id/relations/:action/:id', :controller => 'issue_relations'
259 266 map.connect 'projects/:project_id/news/:action', :controller => 'news'
260 267 map.connect 'projects/:project_id/timelog/:action/:id', :controller => 'timelog', :project_id => /.+/
261 268 map.with_options :controller => 'repositories' do |omap|
262 269 omap.repositories_show 'repositories/browse/:id/*path', :action => 'browse'
263 270 omap.repositories_changes 'repositories/changes/:id/*path', :action => 'changes'
264 271 omap.repositories_diff 'repositories/diff/:id/*path', :action => 'diff'
265 272 omap.repositories_entry 'repositories/entry/:id/*path', :action => 'entry'
266 273 omap.repositories_entry 'repositories/annotate/:id/*path', :action => 'annotate'
267 274 omap.connect 'repositories/revision/:id/:rev', :action => 'revision'
268 275 end
269 276
270 277 map.with_options :controller => 'sys' do |sys|
271 278 sys.connect 'sys/projects.:format', :action => 'projects', :conditions => {:method => :get}
272 279 sys.connect 'sys/projects/:id/repository.:format', :action => 'create_project_repository', :conditions => {:method => :post}
273 280 end
274 281
275 282 # Install the default route as the lowest priority.
276 283 map.connect ':controller/:action/:id'
277 284 map.connect 'robots.txt', :controller => 'welcome', :action => 'robots'
278 285 # Used for OpenID
279 286 map.root :controller => 'account', :action => 'login'
280 287 end
@@ -1,131 +1,131
1 1 ---
2 2 custom_fields_001:
3 3 name: Database
4 4 min_length: 0
5 5 regexp: ""
6 6 is_for_all: true
7 7 is_filter: true
8 8 type: IssueCustomField
9 9 max_length: 0
10 10 possible_values:
11 11 - MySQL
12 12 - PostgreSQL
13 13 - Oracle
14 14 id: 1
15 15 is_required: false
16 16 field_format: list
17 17 default_value: ""
18 18 editable: true
19 19 custom_fields_002:
20 20 name: Searchable field
21 21 min_length: 1
22 22 regexp: ""
23 23 is_for_all: true
24 24 type: IssueCustomField
25 25 max_length: 100
26 26 possible_values: ""
27 27 id: 2
28 28 is_required: false
29 29 field_format: string
30 30 searchable: true
31 31 default_value: "Default string"
32 32 editable: true
33 33 custom_fields_003:
34 34 name: Development status
35 35 min_length: 0
36 36 regexp: ""
37 37 is_for_all: false
38 38 is_filter: true
39 39 type: ProjectCustomField
40 40 max_length: 0
41 41 possible_values:
42 42 - Stable
43 43 - Beta
44 44 - Alpha
45 45 - Planning
46 46 id: 3
47 is_required: true
47 is_required: false
48 48 field_format: list
49 49 default_value: ""
50 50 editable: true
51 51 custom_fields_004:
52 52 name: Phone number
53 53 min_length: 0
54 54 regexp: ""
55 55 is_for_all: false
56 56 type: UserCustomField
57 57 max_length: 0
58 58 possible_values: ""
59 59 id: 4
60 60 is_required: false
61 61 field_format: string
62 62 default_value: ""
63 63 editable: true
64 64 custom_fields_005:
65 65 name: Money
66 66 min_length: 0
67 67 regexp: ""
68 68 is_for_all: false
69 69 type: UserCustomField
70 70 max_length: 0
71 71 possible_values: ""
72 72 id: 5
73 73 is_required: false
74 74 field_format: float
75 75 default_value: ""
76 76 editable: true
77 77 custom_fields_006:
78 78 name: Float field
79 79 min_length: 0
80 80 regexp: ""
81 81 is_for_all: true
82 82 type: IssueCustomField
83 83 max_length: 0
84 84 possible_values: ""
85 85 id: 6
86 86 is_required: false
87 87 field_format: float
88 88 default_value: ""
89 89 editable: true
90 90 custom_fields_007:
91 91 name: Billable
92 92 min_length: 0
93 93 regexp: ""
94 94 is_for_all: false
95 95 is_filter: true
96 96 type: TimeEntryActivityCustomField
97 97 max_length: 0
98 98 possible_values: ""
99 99 id: 7
100 100 is_required: false
101 101 field_format: bool
102 102 default_value: ""
103 103 editable: true
104 104 custom_fields_008:
105 105 name: Custom date
106 106 min_length: 0
107 107 regexp: ""
108 108 is_for_all: true
109 109 is_filter: false
110 110 type: IssueCustomField
111 111 max_length: 0
112 112 possible_values: ""
113 113 id: 8
114 114 is_required: false
115 115 field_format: date
116 116 default_value: ""
117 117 editable: true
118 118 custom_fields_009:
119 119 name: Project 1 cf
120 120 min_length: 0
121 121 regexp: ""
122 122 is_for_all: false
123 123 is_filter: true
124 124 type: IssueCustomField
125 125 max_length: 0
126 126 possible_values: ""
127 127 id: 9
128 128 is_required: false
129 129 field_format: date
130 130 default_value: ""
131 131 editable: true
General Comments 0
You need to be logged in to leave comments. Login now