Feature #1739 » redmine_changeable_author_r21387.patch
app/helpers/issues_helper.rb | ||
---|---|---|
534 | 534 |
old_value = format_date(detail.old_value.to_date) if detail.old_value |
535 | 535 | |
536 | 536 |
when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id', |
537 |
'priority_id', 'category_id', 'fixed_version_id' |
|
537 |
'priority_id', 'category_id', 'fixed_version_id', 'author_id'
|
|
538 | 538 |
value = find_name_by_reflection(field, detail.value) |
539 | 539 |
old_value = find_name_by_reflection(field, detail.old_value) |
540 | 540 | |
... | ... | |
772 | 772 |
issue.allowed_target_projects(User.current) |
773 | 773 |
end |
774 | 774 |
end |
775 | ||
776 |
def author_options_for_select(issue, project) |
|
777 |
users = issue.assignable_users.select {|m| m.is_a?(User) && m.allowed_to?(:add_issues, project) } |
|
778 | ||
779 |
if issue.new_record? |
|
780 |
if users.include?(User.current) |
|
781 |
principals_options_for_select(users, issue.author) |
|
782 |
else |
|
783 |
principals_options_for_select([User.current] + users) |
|
784 |
end |
|
785 |
elsif issue.persisted? |
|
786 |
if users.include?(issue.author) |
|
787 |
principals_options_for_select(users, issue.author) |
|
788 |
else |
|
789 |
author_principal = Principal.find(issue.author_id) |
|
790 |
principals_options_for_select([author_principal] + users, author_principal) |
|
791 |
end |
|
792 |
end |
|
793 |
end |
|
775 | 794 |
end |
app/models/issue.rb | ||
---|---|---|
109 | 109 |
before_validation :clear_disabled_fields |
110 | 110 |
before_save :close_duplicates, :update_done_ratio_from_issue_status, |
111 | 111 |
:force_updated_on_change, :update_closed_on |
112 |
before_create :set_author_journal |
|
112 | 113 |
after_save do |issue| |
113 | 114 |
if !issue.saved_change_to_id? && issue.saved_change_to_project_id? |
114 | 115 |
issue.send :after_project_change |
... | ... | |
517 | 518 |
safe_attributes( |
518 | 519 |
'deleted_attachment_ids', |
519 | 520 |
:if => lambda {|issue, user| issue.attachments_deletable?(user)}) |
521 |
safe_attributes( |
|
522 |
'author_id', |
|
523 |
:if => lambda {|issue, user| user.allowed_to?(:change_issue_author, issue.project)}) |
|
520 | 524 | |
521 | 525 |
def safe_attribute_names(user=nil) |
522 | 526 |
names = super |
... | ... | |
2013 | 2017 |
end |
2014 | 2018 |
end |
2015 | 2019 | |
2020 |
def set_author_journal |
|
2021 |
return unless new_record? |
|
2022 |
return unless self.author.present? && User.current.present? && self.author != User.current |
|
2023 | ||
2024 |
self.init_journal(User.current) |
|
2025 |
self.current_journal.__send__(:add_attribute_detail, 'author_id', User.current.id, self.author.id) |
|
2026 |
end |
|
2027 | ||
2016 | 2028 |
def send_notification |
2017 | 2029 |
if notify? && Setting.notified_events.include?('issue_added') |
2018 | 2030 |
Mailer.deliver_issue_add(self) |
app/views/issues/_attributes.html.erb | ||
---|---|---|
3 | 3 |
<div class="splitcontent"> |
4 | 4 |
<div class="splitcontentleft"> |
5 | 5 |
<% if @issue.safe_attribute?('status_id') && @allowed_statuses.present? %> |
6 |
<% if User.current.allowed_to?(:change_issue_author, @project) %> |
|
7 |
<p><%= f.select :author_id, author_options_for_select(@issue, @project), :include_blank => false, :required => true %></p> |
|
8 |
<% end %> |
|
6 | 9 |
<p> |
7 | 10 |
<%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), {:required => true}, |
8 | 11 |
:onchange => "updateIssueFrom('#{escape_javascript(update_issue_form_path(@project, @issue))}', this)" %> |
config/locales/en.yml | ||
---|---|---|
533 | 533 |
permission_view_private_notes: View private notes |
534 | 534 |
permission_set_notes_private: Set notes as private |
535 | 535 |
permission_delete_issues: Delete issues |
536 |
permission_change_issue_author: Change issue author |
|
536 | 537 |
permission_manage_public_queries: Manage public queries |
537 | 538 |
permission_save_queries: Save queries |
538 | 539 |
permission_view_gantt: View gantt chart |
lib/redmine/preparation.rb | ||
---|---|---|
71 | 71 |
map.permission :view_private_notes, {}, :read => true, :require => :member |
72 | 72 |
map.permission :set_notes_private, {}, :require => :member |
73 | 73 |
map.permission :delete_issues, {:issues => :destroy}, :require => :member |
74 |
map.permission :change_issue_author, {:issues => [:edit, :update]} |
|
74 | 75 |
# Watchers |
75 | 76 |
map.permission :view_issue_watchers, {}, :read => true |
76 | 77 |
map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]} |
test/functional/issues_controller_test.rb | ||
---|---|---|
8250 | 8250 |
end |
8251 | 8251 | |
8252 | 8252 |
def test_destroy_child_issue |
8253 |
User.current = User.find(1) |
|
8253 | 8254 |
parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue') |
8254 | 8255 |
child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id) |
8255 | 8256 |
assert child.is_descendant_of?(parent.reload) |
test/functional/versions_controller_test.rb | ||
---|---|---|
100 | 100 |
end |
101 | 101 | |
102 | 102 |
def test_index_should_show_issue_assignee |
103 |
User.current = User.find_by_login('jsmith') |
|
103 | 104 |
with_settings :gravatar_enabled => '1' do |
104 | 105 |
Issue.generate!(:project_id => 3, :fixed_version_id => 4, :assigned_to => User.find_by_login('jsmith')) |
105 | 106 |
Issue.generate!(:project_id => 3, :fixed_version_id => 4) |
test/helpers/issues_helper_test.rb | ||
---|---|---|
435 | 435 |
assert_include "<a href=\"/issues?issue_id=#{open_issue.id}%2C#{closed_issue.id}&set_filter=true&status_id=o\">1 open</a>", html |
436 | 436 |
assert_include "<a href=\"/issues?issue_id=#{open_issue.id}%2C#{closed_issue.id}&set_filter=true&status_id=c\">1 closed</a>", html |
437 | 437 |
end |
438 | ||
439 |
def test_author_options_for_select_if_new_record_and_users_includes_current_user |
|
440 |
User.current = User.find(2) |
|
441 |
issue = Issue.new(project_id: 1) |
|
442 |
assignable_users = [User.find(3), User.find(2)] |
|
443 | ||
444 |
assert_includes assignable_users, User.current |
|
445 |
assert_equal( |
|
446 |
principals_options_for_select(assignable_users, nil), |
|
447 |
author_options_for_select(issue, issue.project)) |
|
448 |
end |
|
449 | ||
450 |
def test_author_options_for_select_if_new_record_and_users_not_includes_current_user |
|
451 |
User.current = User.find(1) |
|
452 |
issue = Issue.new(project_id: 1) |
|
453 |
assignable_users = [User.find(3), User.find(2)] |
|
454 |
assert_not_includes assignable_users, User.current |
|
455 | ||
456 |
assert_equal( |
|
457 |
principals_options_for_select([User.current] + assignable_users, nil), |
|
458 |
author_options_for_select(issue, issue.project)) |
|
459 |
end |
|
460 | ||
461 |
def test_author_options_for_select_if_persisted_record_and_users_includes_author |
|
462 |
User.current = User.find(2) |
|
463 |
issue = Issue.find(1) |
|
464 |
issue.update(author_id: 2) |
|
465 |
assignable_users = [User.find(3), User.find(2)] |
|
466 | ||
467 |
assert_includes assignable_users, issue.author |
|
468 |
assert_equal( |
|
469 |
principals_options_for_select(assignable_users, issue.author), |
|
470 |
author_options_for_select(issue, issue.project)) |
|
471 |
end |
|
472 | ||
473 |
def test_author_options_for_select_if_persisted_record_and_users_not_includes_author |
|
474 |
User.current = User.find(2) |
|
475 |
issue = Issue.find(1) |
|
476 |
issue.update(author_id: 1) |
|
477 |
assignable_users = [User.find(3), User.find(2)] |
|
478 | ||
479 |
assert_not_includes assignable_users, issue.author |
|
480 |
assert_equal( |
|
481 |
principals_options_for_select([User.find(1)] + assignable_users, issue.author), |
|
482 |
author_options_for_select(issue, issue.project)) |
|
483 |
end |
|
484 | ||
485 |
def test_author_options_for_select_if_persisted_record_and_author_is_anonymous |
|
486 |
User.current = User.find(2) |
|
487 |
issue = Issue.find(1) |
|
488 |
issue.update(author_id: User.anonymous.id) |
|
489 |
assignable_users = [User.find(3), User.find(2)] |
|
490 | ||
491 |
assert_not_includes assignable_users, issue.author |
|
492 |
assert_equal( |
|
493 |
principals_options_for_select([User.anonymous] + assignable_users, issue.author), |
|
494 |
author_options_for_select(issue, issue.project)) |
|
495 |
end |
|
438 | 496 |
end |
test/helpers/journals_helper_test.rb | ||
---|---|---|
22 | 22 |
class JournalsHelperTest < Redmine::HelperTest |
23 | 23 |
include JournalsHelper |
24 | 24 | |
25 |
fixtures :projects, :trackers, :issue_statuses, :issues, :journals, |
|
25 |
fixtures :projects, :trackers, :issue_statuses, :issues, :journals, :journal_details,
|
|
26 | 26 |
:enumerations, :issue_categories, |
27 | 27 |
:projects_trackers, |
28 | 28 |
:users, :roles, :member_roles, :members, |
test/object_helpers.rb | ||
---|---|---|
95 | 95 |
issue.project ||= Project.find(1) |
96 | 96 |
issue.tracker ||= issue.project.trackers.first |
97 | 97 |
issue.subject = 'Generated' if issue.subject.blank? |
98 |
issue.author ||= User.find(2)
|
|
98 |
issue.author ||= (User.current || User.find(2))
|
|
99 | 99 |
yield issue if block_given? |
100 | 100 |
issue |
101 | 101 |
end |
test/unit/issue_nested_set_test.rb | ||
---|---|---|
60 | 60 |
end |
61 | 61 | |
62 | 62 |
def test_creating_a_child_in_a_subproject_should_validate |
63 |
User.current = User.find(1) |
|
63 | 64 |
issue = Issue.generate! |
64 | 65 |
child = nil |
65 | 66 |
assert_difference 'Journal.count', 1 do |
test/unit/issue_test.rb | ||
---|---|---|
2751 | 2751 |
end |
2752 | 2752 | |
2753 | 2753 |
def test_journalized_multi_custom_field |
2754 |
User.current = User.find(1) |
|
2754 | 2755 |
field = IssueCustomField.create!(:name => 'filter', :field_format => 'list', |
2755 | 2756 |
:is_filter => true, :is_for_all => true, |
2756 | 2757 |
:tracker_ids => [1], |
... | ... | |
3427 | 3428 |
r = Issue.like('issue today') |
3428 | 3429 |
assert_include Issue.find(7), r |
3429 | 3430 |
end |
3431 | ||
3432 |
def test_author_should_be_changed_when_user_with_permission_change_issue_author |
|
3433 |
Role.all.each do |r| |
|
3434 |
r.add_permission! :change_issue_author |
|
3435 |
end |
|
3436 |
User.current = User.find(2) |
|
3437 | ||
3438 |
issue = Issue.generate!(:author => User.find(3)) |
|
3439 |
assert_equal 3, issue.author_id |
|
3440 | ||
3441 |
issue.safe_attributes = { 'author_id' => 4 } |
|
3442 |
assert_equal 4, issue.author_id |
|
3443 |
assert_not_equal 3, issue.author_id |
|
3444 |
end |
|
3445 | ||
3446 |
def test_author_should_not_be_changed_when_user_without_permission_change_issue_author |
|
3447 |
Role.all.each do |r| |
|
3448 |
r.remove_permission! :change_issue_author |
|
3449 |
end |
|
3450 |
User.current = User.find(2) |
|
3451 | ||
3452 |
issue = Issue.generate!(:author => User.find(3)) |
|
3453 |
assert_equal 3, issue.author_id |
|
3454 | ||
3455 |
issue.safe_attributes = { 'author_id' => 4 } |
|
3456 |
assert_not_equal 4, issue.author_id |
|
3457 |
assert_equal 3, issue.author_id |
|
3458 |
end |
|
3459 | ||
3460 |
def test_create_should_create_journal_if_user_other_than_current_user_is_set_as_the_author |
|
3461 |
User.current = User.find(1) |
|
3462 |
issue = nil |
|
3463 |
assert_difference 'Journal.count' do |
|
3464 |
issue = Issue.generate!(author: User.find(2)) |
|
3465 |
end |
|
3466 | ||
3467 |
first_journal_detail = issue.journals.first.details.first |
|
3468 |
assert_equal 'author_id', first_journal_detail.prop_key |
|
3469 |
assert_equal '1', first_journal_detail.old_value |
|
3470 |
assert_equal '2', first_journal_detail.value |
|
3471 |
end |
|
3472 | ||
3473 |
def test_create_should_create_journal_if_current_user_is_set_as_the_author |
|
3474 |
User.current = User.find(1) |
|
3475 |
issue = nil |
|
3476 |
assert_no_difference 'Journal.count' do |
|
3477 |
issue = Issue.generate!(author: User.current) |
|
3478 |
end |
|
3479 | ||
3480 |
assert_not issue.journals.present? |
|
3481 |
end |
|
3430 | 3482 |
end |