Allows project to be changed from the regular issue update action (#4769, #9803).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@8531 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2012-01-07 12:34:52 +00:00
parent 3dd97a87c6
commit 81cf6b2343
8 changed files with 160 additions and 23 deletions

View File

@ -135,7 +135,17 @@ class IssuesController < ApplicationController
def new def new
respond_to do |format| respond_to do |format|
format.html { render :action => 'new', :layout => !request.xhr? } format.html { render :action => 'new', :layout => !request.xhr? }
format.js { render :partial => 'attributes' } format.js {
render(:update) { |page|
if params[:project_change]
page.replace_html 'all_attributes', :partial => 'form'
else
page.replace_html 'attributes', :partial => 'attributes'
end
m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
page << "if ($('log_time')) {Element.#{m}('log_time');}"
}
}
end end
end end
@ -274,7 +284,7 @@ private
end end
def find_project def find_project
project_id = (params[:issue] && params[:issue][:project_id]) || params[:project_id] project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
@project = Project.find(project_id) @project = Project.find(project_id)
rescue ActiveRecord::RecordNotFound rescue ActiveRecord::RecordNotFound
render_404 render_404

View File

@ -147,7 +147,9 @@ class Issue < ActiveRecord::Base
issue.init_journal(User.current, options[:notes]) issue.init_journal(User.current, options[:notes])
issue.project = new_project # Preserve previous behaviour
# #move_to_project doesn't change tracker automatically
issue.send :project=, new_project, true
if new_tracker if new_tracker
issue.tracker = new_tracker issue.tracker = new_tracker
end end
@ -169,6 +171,16 @@ class Issue < ActiveRecord::Base
write_attribute(:priority_id, pid) write_attribute(:priority_id, pid)
end end
def category_id=(cid)
self.category = nil
write_attribute(:category_id, cid)
end
def fixed_version_id=(vid)
self.fixed_version = nil
write_attribute(:fixed_version_id, vid)
end
def tracker_id=(tid) def tracker_id=(tid)
self.tracker = nil self.tracker = nil
result = write_attribute(:tracker_id, tid) result = write_attribute(:tracker_id, tid)
@ -182,11 +194,14 @@ class Issue < ActiveRecord::Base
end end
end end
def project=(project) def project=(project, keep_tracker=false)
project_was = self.project project_was = self.project
write_attribute(:project_id, project ? project.id : nil) write_attribute(:project_id, project ? project.id : nil)
association_instance_set('project', project) association_instance_set('project', project)
if project_was && project && project_was != project if project_was && project && project_was != project
unless keep_tracker || project.trackers.include?(tracker)
self.tracker = project.trackers.first
end
# Reassign to the category with same name if any # Reassign to the category with same name if any
if category if category
self.category = project.issue_categories.find_by_name(category.name) self.category = project.issue_categories.find_by_name(category.name)
@ -229,6 +244,12 @@ class Issue < ActiveRecord::Base
write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
end end
safe_attributes 'project_id',
:if => lambda {|issue, user|
projects = Issue.allowed_target_projects_on_move(user)
projects.include?(issue.project) && projects.size > 1
}
safe_attributes 'tracker_id', safe_attributes 'tracker_id',
'status_id', 'status_id',
'category_id', 'category_id',
@ -278,7 +299,11 @@ class Issue < ActiveRecord::Base
attrs = delete_unsafe_attributes(attrs, user) attrs = delete_unsafe_attributes(attrs, user)
return if attrs.empty? return if attrs.empty?
# Tracker must be set before since new_statuses_allowed_to depends on it. # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
if p = attrs.delete('project_id')
self.project_id = p
end
if t = attrs.delete('tracker_id') if t = attrs.delete('tracker_id')
self.tracker_id = t self.tracker_id = t
end end
@ -725,16 +750,16 @@ class Issue < ActiveRecord::Base
# End ReportsController extraction # End ReportsController extraction
# Returns an array of projects that current user can move issues to # Returns an array of projects that current user can move issues to
def self.allowed_target_projects_on_move def self.allowed_target_projects_on_move(user=User.current)
projects = [] projects = []
if User.current.admin? if user.admin?
# admin is allowed to move issues to any active (visible) project # admin is allowed to move issues to any active (visible) project
projects = Project.visible.all projects = Project.visible(user).all
elsif User.current.logged? elsif user.logged?
if Role.non_member.allowed_to?(:move_issues) if Role.non_member.allowed_to?(:move_issues)
projects = Project.visible.all projects = Project.visible(user).all
else else
User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}} user.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
end end
end end
projects projects
@ -754,7 +779,8 @@ class Issue < ActiveRecord::Base
# Move subtasks # Move subtasks
children.each do |child| children.each do |child|
child.project = project # Change project and keep project
child.send :project=, project, true
unless child.save unless child.save
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end

View File

@ -15,14 +15,14 @@
<p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p> <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
<% end %> <% end %>
<% if @issue.safe_attribute?('category_id') && @project.issue_categories.any? %> <% if @issue.safe_attribute?('category_id') && @issue.project.issue_categories.any? %>
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %> <p><%= f.select :category_id, (@issue.project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'), <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
l(:label_issue_category_new), l(:label_issue_category_new),
'issue_category[name]', 'issue_category[name]',
{:controller => 'issue_categories', :action => 'create', :project_id => @project}, {:controller => 'issue_categories', :action => 'create', :project_id => @issue.project},
:title => l(:label_issue_category_new), :title => l(:label_issue_category_new),
:tabindex => 199) if authorize_for('issue_categories', 'new') %></p> :tabindex => 199) if User.current.allowed_to?(:manage_categories, @issue.project) %></p>
<% end %> <% end %>
<% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %> <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
@ -30,9 +30,9 @@
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'), <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
l(:label_version_new), l(:label_version_new),
'version[name]', 'version[name]',
{:controller => 'versions', :action => 'create', :project_id => @project}, {:controller => 'versions', :action => 'create', :project_id => @issue.project},
:title => l(:label_version_new), :title => l(:label_version_new),
:tabindex => 200) if authorize_for('versions', 'new') %> :tabindex => 200) if User.current.allowed_to?(:manage_versions, @issue.project) %>
</p> </p>
<% end %> <% end %>
</div> </div>
@ -41,7 +41,7 @@
<% if @issue.safe_attribute? 'parent_issue_id' %> <% if @issue.safe_attribute? 'parent_issue_id' %>
<p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p> <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
<div id="parent_issue_candidates" class="autocomplete"></div> <div id="parent_issue_candidates" class="autocomplete"></div>
<%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %> <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @issue.project) }')" %>
<% end %> <% end %>
<% if @issue.safe_attribute? 'start_date' %> <% if @issue.safe_attribute? 'start_date' %>

View File

@ -3,7 +3,9 @@
<div class="box"> <div class="box">
<% if @edit_allowed || !@allowed_statuses.empty? %> <% if @edit_allowed || !@allowed_statuses.empty? %>
<fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend> <fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend>
<div id="all_attributes">
<%= render :partial => 'form', :locals => {:f => f} %> <%= render :partial => 'form', :locals => {:f => f} %>
</div>
</fieldset> </fieldset>
<% end %> <% end %>
<% if User.current.allowed_to?(:log_time, @project) %> <% if User.current.allowed_to?(:log_time, @project) %>

View File

@ -1,3 +1,4 @@
<% labelled_fields_for :issue, @issue do |f| %>
<%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %> <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
<% if @issue.safe_attribute? 'is_private' %> <% if @issue.safe_attribute? 'is_private' %>
@ -6,10 +7,15 @@
</p> </p>
<% end %> <% end %>
<% if !@issue.new_record? && @issue.safe_attribute?('project_id') %>
<p><%= f.select :project_id, Issue.allowed_target_projects_on_move.collect {|t| [t.name, t.id]}, :required => true %></p>
<%= observe_field :issue_project_id, :url => project_issue_form_path(@project, :id => @issue, :project_change => '1'),
:with => "Form.serialize('issue-form')" %>
<% end %>
<% if @issue.safe_attribute? 'tracker_id' %> <% if @issue.safe_attribute? 'tracker_id' %>
<p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p> <p><%= f.select :tracker_id, @issue.project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
<%= observe_field :issue_tracker_id, :url => project_issue_form_path(@project, :id => @issue), <%= observe_field :issue_tracker_id, :url => project_issue_form_path(@project, :id => @issue),
:update => :attributes,
:with => "Form.serialize('issue-form')" %> :with => "Form.serialize('issue-form')" %>
<% end %> <% end %>
@ -39,3 +45,4 @@
</div> </div>
<%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %> <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
<% end %>

View File

@ -152,7 +152,6 @@ roles_004:
- :edit_issues - :edit_issues
- :manage_issue_relations - :manage_issue_relations
- :add_issue_notes - :add_issue_notes
- :move_issues
- :save_queries - :save_queries
- :view_gantt - :view_gantt
- :view_calendar - :view_calendar

View File

@ -721,6 +721,7 @@ class IssuesControllerTest < ActionController::TestCase
assert_tag 'form', :attributes => {:id => 'issue-form'} assert_tag 'form', :attributes => {:id => 'issue-form'}
assert_tag 'input', :attributes => {:name => 'issue[is_private]'} assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
assert_tag 'input', :attributes => {:name => 'issue[subject]'} assert_tag 'input', :attributes => {:name => 'issue[subject]'}
assert_tag 'textarea', :attributes => {:name => 'issue[description]'} assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
@ -748,6 +749,7 @@ class IssuesControllerTest < ActionController::TestCase
assert_tag 'form', :attributes => {:id => 'issue-form'} assert_tag 'form', :attributes => {:id => 'issue-form'}
assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'} assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
assert_no_tag 'input', :attributes => {:name => 'issue[subject]'} assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'} assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
@ -774,6 +776,7 @@ class IssuesControllerTest < ActionController::TestCase
assert_tag 'form', :attributes => {:id => 'issue-form'} assert_tag 'form', :attributes => {:id => 'issue-form'}
assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'} assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
assert_no_tag 'input', :attributes => {:name => 'issue[subject]'} assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'} assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
@ -1014,6 +1017,7 @@ class IssuesControllerTest < ActionController::TestCase
assert_template 'new' assert_template 'new'
assert_tag 'input', :attributes => {:name => 'issue[is_private]'} assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
assert_tag 'input', :attributes => {:name => 'issue[subject]'} assert_tag 'input', :attributes => {:name => 'issue[subject]'}
assert_tag 'textarea', :attributes => {:name => 'issue[description]'} assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
@ -1045,6 +1049,7 @@ class IssuesControllerTest < ActionController::TestCase
assert_template 'new' assert_template 'new'
assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'} assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'} assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
assert_tag 'input', :attributes => {:name => 'issue[subject]'} assert_tag 'input', :attributes => {:name => 'issue[subject]'}
assert_tag 'textarea', :attributes => {:name => 'issue[description]'} assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
@ -1636,7 +1641,7 @@ class IssuesControllerTest < ActionController::TestCase
def test_update_edit_form def test_update_edit_form
@request.session[:user_id] = 2 @request.session[:user_id] = 2
xhr :post, :new, :project_id => 1, xhr :put, :new, :project_id => 1,
:id => 1, :id => 1,
:issue => {:tracker_id => 2, :issue => {:tracker_id => 2,
:subject => 'This is the test_new issue', :subject => 'This is the test_new issue',
@ -1653,6 +1658,27 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal 'This is the test_new issue', issue.subject assert_equal 'This is the test_new issue', issue.subject
end end
def test_update_edit_form_with_project_change
@request.session[:user_id] = 2
xhr :put, :new, :project_id => 1,
:id => 1,
:project_change => '1',
:issue => {:project_id => 2,
:tracker_id => 2,
:subject => 'This is the test_new issue',
:description => 'This is the description',
:priority_id => 5}
assert_response :success
assert_template 'form'
issue = assigns(:issue)
assert_kind_of Issue, issue
assert_equal 1, issue.id
assert_equal 2, issue.project_id
assert_equal 2, issue.tracker_id
assert_equal 'This is the test_new issue', issue.subject
end
def test_update_using_invalid_http_verbs def test_update_using_invalid_http_verbs
@request.session[:user_id] = 2 @request.session[:user_id] = 2
subject = 'Updated by an invalid http verb' subject = 'Updated by an invalid http verb'
@ -1696,6 +1722,57 @@ class IssuesControllerTest < ActionController::TestCase
assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}") assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
end end
def test_put_update_with_project_change
@request.session[:user_id] = 2
ActionMailer::Base.deliveries.clear
assert_difference('Journal.count') do
assert_difference('JournalDetail.count', 3) do
put :update, :id => 1, :issue => {:project_id => '2',
:tracker_id => '1', # no change
:priority_id => '6',
:category_id => '3'
}
end
end
assert_redirected_to :action => 'show', :id => '1'
issue = Issue.find(1)
assert_equal 2, issue.project_id
assert_equal 1, issue.tracker_id
assert_equal 6, issue.priority_id
assert_equal 3, issue.category_id
mail = ActionMailer::Base.deliveries.last
assert_not_nil mail
assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
assert mail.body.include?("Project changed from eCookbook to OnlineStore")
end
def test_put_update_with_tracker_change
@request.session[:user_id] = 2
ActionMailer::Base.deliveries.clear
assert_difference('Journal.count') do
assert_difference('JournalDetail.count', 2) do
put :update, :id => 1, :issue => {:project_id => '1',
:tracker_id => '2',
:priority_id => '6'
}
end
end
assert_redirected_to :action => 'show', :id => '1'
issue = Issue.find(1)
assert_equal 1, issue.project_id
assert_equal 2, issue.tracker_id
assert_equal 6, issue.priority_id
assert_equal 1, issue.category_id
mail = ActionMailer::Base.deliveries.last
assert_not_nil mail
assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
assert mail.body.include?("Tracker changed from Bug to Feature request")
end
def test_put_update_with_custom_field_change def test_put_update_with_custom_field_change
@request.session[:user_id] = 2 @request.session[:user_id] = 2
issue = Issue.find(1) issue = Issue.find(1)

View File

@ -455,6 +455,22 @@ class ApiTest::IssuesTest < ActionController::IntegrationTest
end end
end end
context "PUT /issues/3.xml with project change" do
setup do
@parameters = {:issue => {:project_id => 2, :subject => 'Project changed'}}
end
should "update project" do
assert_no_difference('Issue.count') do
put '/issues/3.xml', @parameters, credentials('jsmith')
end
issue = Issue.find(3)
assert_equal 2, issue.project_id
assert_equal 'Project changed', issue.subject
end
end
context "PUT /issues/6.xml with failed update" do context "PUT /issues/6.xml with failed update" do
setup do setup do
@parameters = {:issue => {:subject => ''}} @parameters = {:issue => {:subject => ''}}