@@ -59,6 +59,7 class Issue < ActiveRecord::Base | |||||
59 |
|
59 | |||
60 | DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
|
60 | DONE_RATIO_OPTIONS = %w(issue_field issue_status) | |
61 |
|
61 | |||
|
62 | attr_accessor :deleted_attachment_ids | |||
62 | attr_reader :current_journal |
|
63 | attr_reader :current_journal | |
63 | delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true |
|
64 | delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true | |
64 |
|
65 | |||
@@ -109,7 +110,7 class Issue < ActiveRecord::Base | |||||
109 | :force_updated_on_change, :update_closed_on, :set_assigned_to_was |
|
110 | :force_updated_on_change, :update_closed_on, :set_assigned_to_was | |
110 | after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?} |
|
111 | after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?} | |
111 | after_save :reschedule_following_issues, :update_nested_set_attributes, |
|
112 | after_save :reschedule_following_issues, :update_nested_set_attributes, | |
112 | :update_parent_attributes, :create_journal |
|
113 | :update_parent_attributes, :delete_selected_attachments, :create_journal | |
113 | # Should be after_create but would be called before previous after_save callbacks |
|
114 | # Should be after_create but would be called before previous after_save callbacks | |
114 | after_save :after_create_from_copy |
|
115 | after_save :after_create_from_copy | |
115 | after_destroy :update_parent_attributes |
|
116 | after_destroy :update_parent_attributes | |
@@ -403,6 +404,10 class Issue < ActiveRecord::Base | |||||
403 | write_attribute(:description, arg) |
|
404 | write_attribute(:description, arg) | |
404 | end |
|
405 | end | |
405 |
|
406 | |||
|
407 | def deleted_attachment_ids | |||
|
408 | Array(@deleted_attachment_ids).map(&:to_i) | |||
|
409 | end | |||
|
410 | ||||
406 | # Overrides assign_attributes so that project and tracker get assigned first |
|
411 | # Overrides assign_attributes so that project and tracker get assigned first | |
407 | def assign_attributes_with_project_and_tracker_first(new_attributes, *args) |
|
412 | def assign_attributes_with_project_and_tracker_first(new_attributes, *args) | |
408 | return if new_attributes.nil? |
|
413 | return if new_attributes.nil? | |
@@ -465,6 +470,9 class Issue < ActiveRecord::Base | |||||
465 | :if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) && |
|
470 | :if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) && | |
466 | user.allowed_to?(:manage_subtasks, issue.project)} |
|
471 | user.allowed_to?(:manage_subtasks, issue.project)} | |
467 |
|
472 | |||
|
473 | safe_attributes 'deleted_attachment_ids', | |||
|
474 | :if => lambda {|issue, user| issue.attachments_deletable?(user)} | |||
|
475 | ||||
468 | def safe_attribute_names(user=nil) |
|
476 | def safe_attribute_names(user=nil) | |
469 | names = super |
|
477 | names = super | |
470 | names -= disabled_core_fields |
|
478 | names -= disabled_core_fields | |
@@ -1586,6 +1594,13 class Issue < ActiveRecord::Base | |||||
1586 | end |
|
1594 | end | |
1587 | end |
|
1595 | end | |
1588 |
|
1596 | |||
|
1597 | def delete_selected_attachments | |||
|
1598 | if deleted_attachment_ids.present? | |||
|
1599 | objects = attachments.where(:id => deleted_attachment_ids.map(&:to_i)) | |||
|
1600 | attachments.delete(objects) | |||
|
1601 | end | |||
|
1602 | end | |||
|
1603 | ||||
1589 | # Callback on file attachment |
|
1604 | # Callback on file attachment | |
1590 | def attachment_added(attachment) |
|
1605 | def attachment_added(attachment) | |
1591 | if current_journal && !attachment.new_record? |
|
1606 | if current_journal && !attachment.new_record? |
@@ -40,7 +40,27 | |||||
40 | </fieldset> |
|
40 | </fieldset> | |
41 |
|
41 | |||
42 | <fieldset><legend><%= l(:label_attachment_plural) %></legend> |
|
42 | <fieldset><legend><%= l(:label_attachment_plural) %></legend> | |
43 | <p><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p> |
|
43 | <% if @issue.attachments.any? && @issue.safe_attribute?('deleted_attachment_ids') %> | |
|
44 | <div class="contextual"><%= link_to l(:label_edit_attachments), '#', :onclick => "$('#existing-attachments').toggle(); return false;" %></div> | |||
|
45 | <div id="existing-attachments" style="<%= @issue.deleted_attachment_ids.blank? ? 'display:none;' : '' %>"> | |||
|
46 | <% @issue.attachments.each do |attachment| %> | |||
|
47 | <span class="existing-attachment"> | |||
|
48 | <%= text_field_tag '', attachment.filename, :class => "filename", :disabled => true %> | |||
|
49 | <label> | |||
|
50 | <%= check_box_tag 'issue[deleted_attachment_ids][]', | |||
|
51 | attachment.id, | |||
|
52 | @issue.deleted_attachment_ids.include?(attachment.id), | |||
|
53 | :id => nil, :class => "deleted_attachment" %> <%= l(:button_delete) %> | |||
|
54 | </label> | |||
|
55 | </span> | |||
|
56 | <% end %> | |||
|
57 | <hr /> | |||
|
58 | </div> | |||
|
59 | <% end %> | |||
|
60 | ||||
|
61 | <div id="new-attachments" style="display:inline-block;"> | |||
|
62 | <%= render :partial => 'attachments/form', :locals => {:container => @issue} %> | |||
|
63 | </div> | |||
44 | </fieldset> |
|
64 | </fieldset> | |
45 | <% end %> |
|
65 | <% end %> | |
46 | </div> |
|
66 | </div> |
@@ -189,3 +189,8 function setupFileDrop() { | |||||
189 | } |
|
189 | } | |
190 |
|
190 | |||
191 | $(document).ready(setupFileDrop); |
|
191 | $(document).ready(setupFileDrop); | |
|
192 | $(document).ready(function(){ | |||
|
193 | $("input.deleted_attachment").change(function(){ | |||
|
194 | $(this).parents('.existing-attachment').toggleClass('deleted', $(this).is(":checked")); | |||
|
195 | }).change(); | |||
|
196 | }); |
@@ -363,7 +363,7 div.square { | |||||
363 | overflow: hidden; |
|
363 | overflow: hidden; | |
364 | width: .6em; height: .6em; |
|
364 | width: .6em; height: .6em; | |
365 | } |
|
365 | } | |
366 |
.contextual {float:right; white-space: nowrap; line-height:1.4em;margin |
|
366 | .contextual {float:right; white-space: nowrap; line-height:1.4em;margin:5px 0px; padding-left: 10px; font-size:0.9em;} | |
367 | .contextual input, .contextual select {font-size:0.9em;} |
|
367 | .contextual input, .contextual select {font-size:0.9em;} | |
368 | .message .contextual { margin-top: 0; } |
|
368 | .message .contextual { margin-top: 0; } | |
369 |
|
369 | |||
@@ -673,14 +673,16 span.required {color: #bb0000;} | |||||
673 | .check_box_group.bool_cf {border:0; background:inherit;} |
|
673 | .check_box_group.bool_cf {border:0; background:inherit;} | |
674 | .check_box_group.bool_cf label {display: inline;} |
|
674 | .check_box_group.bool_cf label {display: inline;} | |
675 |
|
675 | |||
676 | #attachments_fields input.description {margin-left:4px; width:340px;} |
|
676 | #attachments_fields input.description, #existing-attachments input.description {margin-left:4px; width:340px;} | |
677 | #attachments_fields span {display:block; white-space:nowrap;} |
|
677 | #attachments_fields>span, #existing-attachments>span {display:block; white-space:nowrap;} | |
678 |
#attachments_fields input.filename {border:0 |
|
678 | #attachments_fields input.filename, #existing-attachments .filename {border:0; width:250px; color:#555; background-color:inherit; background:url(../images/attachment.png) no-repeat 1px 50%; padding-left:18px;} | |
|
679 | #attachments_fields input.filename {height:1.8em;} | |||
679 | #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;} |
|
680 | #attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;} | |
680 | #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;} |
|
681 | #attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;} | |
681 | #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } |
|
682 | #attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; } | |
682 | a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;} |
|
683 | a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;} | |
683 | a.remove-upload:hover {text-decoration:none !important;} |
|
684 | a.remove-upload:hover {text-decoration:none !important;} | |
|
685 | .existing-attachment.deleted .filename {text-decoration:line-through; color:#999 !important;} | |||
684 |
|
686 | |||
685 | div.fileover { background-color: lavender; } |
|
687 | div.fileover { background-color: lavender; } | |
686 |
|
688 | |||
@@ -1137,8 +1139,6 a.close-icon:hover {background-image:url('../images/close_hl.png');} | |||||
1137 | background-position: 0% 50%; |
|
1139 | background-position: 0% 50%; | |
1138 | background-repeat: no-repeat; |
|
1140 | background-repeat: no-repeat; | |
1139 | padding-left: 16px; |
|
1141 | padding-left: 16px; | |
1140 | } |
|
|||
1141 | a.icon-only { |
|
|||
1142 | display: inline-block; |
|
1142 | display: inline-block; | |
1143 | width: 0; |
|
1143 | width: 0; | |
1144 | height: 16px; |
|
1144 | height: 16px; | |
@@ -1148,7 +1148,7 a.icon-only { | |||||
1148 | font-size: 8px; |
|
1148 | font-size: 8px; | |
1149 | vertical-align: text-bottom; |
|
1149 | vertical-align: text-bottom; | |
1150 | } |
|
1150 | } | |
1151 |
|
|
1151 | .icon-only::after { | |
1152 | content: " "; |
|
1152 | content: " "; | |
1153 | } |
|
1153 | } | |
1154 |
|
1154 |
@@ -3761,6 +3761,44 class IssuesControllerTest < ActionController::TestCase | |||||
3761 | end |
|
3761 | end | |
3762 | end |
|
3762 | end | |
3763 |
|
3763 | |||
|
3764 | def test_put_update_with_attachment_deletion_should_create_a_single_journal | |||
|
3765 | set_tmp_attachments_directory | |||
|
3766 | @request.session[:user_id] = 2 | |||
|
3767 | ||||
|
3768 | journal = new_record(Journal) do | |||
|
3769 | assert_difference 'Attachment.count', -2 do | |||
|
3770 | put :update, | |||
|
3771 | :id => 3, | |||
|
3772 | :issue => { | |||
|
3773 | :notes => 'Removing attachments', | |||
|
3774 | :deleted_attachment_ids => ['1', '5'] | |||
|
3775 | } | |||
|
3776 | end | |||
|
3777 | end | |||
|
3778 | assert_equal 'Removing attachments', journal.notes | |||
|
3779 | assert_equal 2, journal.details.count | |||
|
3780 | end | |||
|
3781 | ||||
|
3782 | def test_put_update_with_attachment_deletion_and_failure_should_preserve_selected_attachments | |||
|
3783 | set_tmp_attachments_directory | |||
|
3784 | @request.session[:user_id] = 2 | |||
|
3785 | ||||
|
3786 | assert_no_difference 'Journal.count' do | |||
|
3787 | assert_no_difference 'Attachment.count' do | |||
|
3788 | put :update, | |||
|
3789 | :id => 3, | |||
|
3790 | :issue => { | |||
|
3791 | :subject => '', | |||
|
3792 | :notes => 'Removing attachments', | |||
|
3793 | :deleted_attachment_ids => ['1', '5'] | |||
|
3794 | } | |||
|
3795 | end | |||
|
3796 | end | |||
|
3797 | assert_select 'input[name=?][value="1"][checked=checked]', 'issue[deleted_attachment_ids][]' | |||
|
3798 | assert_select 'input[name=?][value="5"][checked=checked]', 'issue[deleted_attachment_ids][]' | |||
|
3799 | assert_select 'input[name=?][value="6"]:not([checked])', 'issue[deleted_attachment_ids][]' | |||
|
3800 | end | |||
|
3801 | ||||
3764 | def test_put_update_with_no_change |
|
3802 | def test_put_update_with_no_change | |
3765 | issue = Issue.find(1) |
|
3803 | issue = Issue.find(1) | |
3766 | issue.journals.clear |
|
3804 | issue.journals.clear |
General Comments 0
You need to be logged in to leave comments.
Login now