@project.enabled_module_names = Setting.default_projects_modules
else
@project.enabled_module_names = params[:enabled_modules]
- if @project.save
+ if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
# Add current user as a project member if he is not admin
unless User.current.admin?
else
@project = Project.new(params[:project])
@project.enabled_module_names = params[:enabled_modules]
- if @project.copy(@source_project, :only => params[:only])
+ if validate_parent_id && @project.copy(@source_project, :only => params[:only])
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'admin', :action => 'projects'
def edit
if request.post?
@project.attributes = params[:project]
- if @project.save
+ if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s }
end
end
+
+ # Validates parent_id param according to user's permissions
+ # TODO: move it to Project model in a validation that depends on User.current
+ def validate_parent_id
+ return true if User.current.admin?
+ parent_id = params[:project] && params[:project][:parent_id]
+ if parent_id || @project.new_record?
+ parent = parent_id.blank? ? nil : Project.find_by_id(parent_id.to_i)
+ unless @project.allowed_parents.include?(parent)
+ @project.errors.add :parent_id, :invalid
+ return false
+ end
+ end
+ true
+ end
end
end
def parent_project_select_tag(project)
- options = '<option></option>' + project_tree_options_for_select(project.allowed_parents, :selected => project.parent)
+ selected = project.parent
+ # retrieve the requested parent project
+ parent_id = (params[:project] && params[:project][:parent_id]) || params[:parent_id]
+ if parent_id
+ selected = (parent_id.blank? ? nil : Project.find(parent_id))
+ end
+
+ options = ''
+ options << "<option value=''></option>" if project.allowed_parents.include?(nil)
+ options << project_tree_options_for_select(project.allowed_parents.compact, :selected => selected)
content_tag('select', options, :name => 'project[parent_id]')
end
# by the current user
def allowed_parents
return @allowed_parents if @allowed_parents
- @allowed_parents = (Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_project, :member => true)) - self_and_descendants)
+ @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
+ @allowed_parents = @allowed_parents - self_and_descendants
+ if User.current.allowed_to?(:add_project, nil, :global => true)
+ @allowed_parents << nil
+ end
unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
@allowed_parents << parent
end
<!--[form:project]-->
<p><%= f.text_field :name, :required => true %><br /><em><%= l(:text_caracters_maximum, 30) %></em></p>
-<% unless @project.allowed_parents.empty? %>
+<% unless @project.allowed_parents.compact.empty? %>
<p><label><%= l(:field_parent) %></label><%= parent_project_select_tag(@project) %></p>
<% end %>
+<div class="contextual">
+ <% if User.current.allowed_to?(:add_subprojects, @project) %>
+ <%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => 'icon icon-add' %>
+ <% end %>
+</div>
+
<h2><%=l(:label_overview)%></h2>
<div class="splitcontentleft">
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
label_missing_api_access_key: Missing an API access key
label_missing_feeds_access_key: Missing a RSS access key
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
setting_rest_api_enabled: Enable REST web service
permission_add_project: Create project
+ permission_add_subprojects: Create subprojects
permission_edit_project: Edit project
permission_select_project_modules: Select project modules
permission_manage_members: Manage members
label_auth_source_new: New authentication mode
label_auth_source_plural: Authentication modes
label_subproject_plural: Subprojects
+ label_subproject_new: New subproject
label_and_its_subprojects: "{{value}} and its subprojects"
label_min_max_length: Min - Max length
label_list: List
button_show: Mostrar
text_line_separated: Múltiples valores permitidos (un valor en cada línea).
setting_mail_handler_body_delimiters: Truncar correos tras una de estas líneas
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
setting_rest_api_enabled: Activer l'API REST
permission_add_project: Créer un projet
+ permission_add_subprojects: Créer des sous-projets
permission_edit_project: Modifier le projet
permission_select_project_modules: Choisir les modules
permission_manage_members: Gérer les members
label_auth_source_new: Nouveau mode d'authentification
label_auth_source_plural: Modes d'authentification
label_subproject_plural: Sous-projets
+ label_subproject_new: Nouveau sous-projet
label_and_its_subprojects: "{{value}} et ses sous-projets"
label_min_max_length: Longueurs mini - maxi
label_list: Liste
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
error_workflow_copy_source: Please select a source tracker or role
setting_start_of_week: Start calendars on
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
label_missing_feeds_access_key: Chave de acesso ao RSS faltando
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
label_missing_api_access_key: Missing an API access key
label_missing_feeds_access_key: Missing a RSS access key
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
enumeration_doc_categories: 文件分類
enumeration_activities: 活動 (時間追蹤)
enumeration_system_activity: 系統活動
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
label_missing_api_access_key: Missing an API access key
label_missing_feeds_access_key: Missing a RSS access key
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
+ permission_add_subprojects: Create subprojects
+ label_subproject_new: New subproject
map.permission :select_project_modules, {:projects => :modules}, :require => :member
map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
+ map.permission :add_subprojects, {:projects => :add}, :require => :member
map.project_module :issue_tracking do |map|
# Issue categories
)
end
- def test_get_add
- @request.session[:user_id] = 1
- get :add
- assert_response :success
- assert_template 'add'
- end
-
- def test_get_add_by_non_admin
- @request.session[:user_id] = 2
- get :add
- assert_response :success
- assert_template 'add'
- end
-
- def test_post_add
- @request.session[:user_id] = 1
- post :add, :project => { :name => "blog",
- :description => "weblog",
- :identifier => "blog",
- :is_public => 1,
- :custom_field_values => { '3' => 'Beta' }
- }
- assert_redirected_to '/projects/blog/settings'
-
- project = Project.find_by_name('blog')
- assert_kind_of Project, project
- assert_equal 'weblog', project.description
- assert_equal true, project.is_public?
- assert_nil project.parent
- end
-
- def test_post_add_subproject
- @request.session[:user_id] = 1
- post :add, :project => { :name => "blog",
- :description => "weblog",
- :identifier => "blog",
- :is_public => 1,
- :custom_field_values => { '3' => 'Beta' },
- :parent_id => 1
- }
- assert_redirected_to '/projects/blog/settings'
-
- project = Project.find_by_name('blog')
- assert_kind_of Project, project
- assert_equal Project.find(1), project.parent
- end
-
- def test_post_add_by_non_admin
- @request.session[:user_id] = 2
- post :add, :project => { :name => "blog",
- :description => "weblog",
- :identifier => "blog",
- :is_public => 1,
- :custom_field_values => { '3' => 'Beta' }
- }
- assert_redirected_to '/projects/blog/settings'
+ context "#add" do
+ context "by admin user" do
+ setup do
+ @request.session[:user_id] = 1
+ end
+
+ should "accept get" do
+ get :add
+ assert_response :success
+ assert_template 'add'
+ end
+
+ should "accept post" do
+ post :add, :project => { :name => "blog",
+ :description => "weblog",
+ :identifier => "blog",
+ :is_public => 1,
+ :custom_field_values => { '3' => 'Beta' }
+ }
+ assert_redirected_to '/projects/blog/settings'
+
+ project = Project.find_by_name('blog')
+ assert_kind_of Project, project
+ assert_equal 'weblog', project.description
+ assert_equal true, project.is_public?
+ assert_nil project.parent
+ end
+
+ should "accept post with parent" do
+ post :add, :project => { :name => "blog",
+ :description => "weblog",
+ :identifier => "blog",
+ :is_public => 1,
+ :custom_field_values => { '3' => 'Beta' },
+ :parent_id => 1
+ }
+ assert_redirected_to '/projects/blog/settings'
+
+ project = Project.find_by_name('blog')
+ assert_kind_of Project, project
+ assert_equal Project.find(1), project.parent
+ end
+ end
- project = Project.find_by_name('blog')
- assert_kind_of Project, project
- assert_equal 'weblog', project.description
- assert_equal true, project.is_public?
+ context "by non-admin user with add_project permission" do
+ setup do
+ Role.non_member.add_permission! :add_project
+ @request.session[:user_id] = 9
+ end
+
+ should "accept get" do
+ get :add
+ assert_response :success
+ assert_template 'add'
+ assert_no_tag :select, :attributes => {:name => 'project[parent_id]'}
+ end
+
+ should "accept post" do
+ post :add, :project => { :name => "blog",
+ :description => "weblog",
+ :identifier => "blog",
+ :is_public => 1,
+ :custom_field_values => { '3' => 'Beta' }
+ }
+
+ assert_redirected_to '/projects/blog/settings'
+
+ project = Project.find_by_name('blog')
+ assert_kind_of Project, project
+ assert_equal 'weblog', project.description
+ assert_equal true, project.is_public?
+
+ # User should be added as a project member
+ assert User.find(9).member_of?(project)
+ assert_equal 1, project.members.size
+ end
+
+ should "fail with parent_id" do
+ assert_no_difference 'Project.count' do
+ post :add, :project => { :name => "blog",
+ :description => "weblog",
+ :identifier => "blog",
+ :is_public => 1,
+ :custom_field_values => { '3' => 'Beta' },
+ :parent_id => 1
+ }
+ end
+ assert_response :success
+ project = assigns(:project)
+ assert_kind_of Project, project
+ assert_not_nil project.errors.on(:parent_id)
+ end
+ end
- # User should be added as a project member
- assert User.find(2).member_of?(project)
- assert_equal 1, project.members.size
+ context "by non-admin user with add_subprojects permission" do
+ setup do
+ Role.find(1).remove_permission! :add_project
+ Role.find(1).add_permission! :add_subprojects
+ @request.session[:user_id] = 2
+ end
+
+ should "accept get" do
+ get :add, :parent_id => 'ecookbook'
+ assert_response :success
+ assert_template 'add'
+ # parent project selected
+ assert_tag :select, :attributes => {:name => 'project[parent_id]'},
+ :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}}
+ # no empty value
+ assert_no_tag :select, :attributes => {:name => 'project[parent_id]'},
+ :child => {:tag => 'option', :attributes => {:value => ''}}
+ end
+
+ should "accept post with parent_id" do
+ post :add, :project => { :name => "blog",
+ :description => "weblog",
+ :identifier => "blog",
+ :is_public => 1,
+ :custom_field_values => { '3' => 'Beta' },
+ :parent_id => 1
+ }
+ assert_redirected_to '/projects/blog/settings'
+ project = Project.find_by_name('blog')
+ end
+
+ should "fail without parent_id" do
+ assert_no_difference 'Project.count' do
+ post :add, :project => { :name => "blog",
+ :description => "weblog",
+ :identifier => "blog",
+ :is_public => 1,
+ :custom_field_values => { '3' => 'Beta' }
+ }
+ end
+ assert_response :success
+ project = assigns(:project)
+ assert_kind_of Project, project
+ assert_not_nil project.errors.on(:parent_id)
+ end
+
+ should "fail with unauthorized parent_id" do
+ assert !User.find(2).member_of?(Project.find(6))
+ assert_no_difference 'Project.count' do
+ post :add, :project => { :name => "blog",
+ :description => "weblog",
+ :identifier => "blog",
+ :is_public => 1,
+ :custom_field_values => { '3' => 'Beta' },
+ :parent_id => 6
+ }
+ end
+ assert_response :success
+ project = assigns(:project)
+ assert_kind_of Project, project
+ assert_not_nil project.errors.on(:parent_id)
+ end
+ end
end
def test_show_routing
user = User.find(9)
assert user.memberships.empty?
User.current = user
- assert Project.new.allowed_parents.empty?
+ assert Project.new.allowed_parents.compact.empty?
end
def test_users_by_role