mirror of
https://github.com/meineerde/redmine.git
synced 2025-12-19 15:01:14 +00:00
Adds file custom field format (#6719).
git-svn-id: http://svn.redmine.org/redmine/trunk@15917 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
c91a4391d3
commit
ef45304817
@ -197,6 +197,8 @@ module ApplicationHelper
|
|||||||
l(:general_text_No)
|
l(:general_text_No)
|
||||||
when 'Issue'
|
when 'Issue'
|
||||||
object.visible? && html ? link_to_issue(object) : "##{object.id}"
|
object.visible? && html ? link_to_issue(object) : "##{object.id}"
|
||||||
|
when 'Attachment'
|
||||||
|
html ? link_to_attachment(object, :download => true) : object.filename
|
||||||
when 'CustomValue', 'CustomFieldValue'
|
when 'CustomValue', 'CustomFieldValue'
|
||||||
if object.custom_field
|
if object.custom_field
|
||||||
f = object.custom_field.format.formatted_custom_value(self, object, html)
|
f = object.custom_field.format.formatted_custom_value(self, object, html)
|
||||||
|
|||||||
@ -329,6 +329,7 @@ module IssuesHelper
|
|||||||
def show_detail(detail, no_html=false, options={})
|
def show_detail(detail, no_html=false, options={})
|
||||||
multiple = false
|
multiple = false
|
||||||
show_diff = false
|
show_diff = false
|
||||||
|
no_details = false
|
||||||
|
|
||||||
case detail.property
|
case detail.property
|
||||||
when 'attr'
|
when 'attr'
|
||||||
@ -364,7 +365,9 @@ module IssuesHelper
|
|||||||
custom_field = detail.custom_field
|
custom_field = detail.custom_field
|
||||||
if custom_field
|
if custom_field
|
||||||
label = custom_field.name
|
label = custom_field.name
|
||||||
if custom_field.format.class.change_as_diff
|
if custom_field.format.class.change_no_details
|
||||||
|
no_details = true
|
||||||
|
elsif custom_field.format.class.change_as_diff
|
||||||
show_diff = true
|
show_diff = true
|
||||||
else
|
else
|
||||||
multiple = custom_field.multiple?
|
multiple = custom_field.multiple?
|
||||||
@ -417,7 +420,9 @@ module IssuesHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if show_diff
|
if no_details
|
||||||
|
s = l(:text_journal_changed_no_detail, :label => label).html_safe
|
||||||
|
elsif show_diff
|
||||||
s = l(:text_journal_changed_no_detail, :label => label)
|
s = l(:text_journal_changed_no_detail, :label => label)
|
||||||
unless no_html
|
unless no_html
|
||||||
diff_link = link_to 'diff',
|
diff_link = link_to 'diff',
|
||||||
|
|||||||
@ -163,6 +163,10 @@ class CustomField < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_custom_field_value(custom_field_value, value)
|
||||||
|
format.set_custom_field_value(self, custom_field_value, value)
|
||||||
|
end
|
||||||
|
|
||||||
def cast_value(value)
|
def cast_value(value)
|
||||||
format.cast_value(self, value)
|
format.cast_value(self, value)
|
||||||
end
|
end
|
||||||
@ -254,7 +258,9 @@ class CustomField < ActiveRecord::Base
|
|||||||
# or an empty array if value is a valid value for the custom field
|
# or an empty array if value is a valid value for the custom field
|
||||||
def validate_custom_value(custom_value)
|
def validate_custom_value(custom_value)
|
||||||
value = custom_value.value
|
value = custom_value.value
|
||||||
errs = []
|
errs = format.validate_custom_value(custom_value)
|
||||||
|
|
||||||
|
unless errs.any?
|
||||||
if value.is_a?(Array)
|
if value.is_a?(Array)
|
||||||
if !multiple?
|
if !multiple?
|
||||||
errs << ::I18n.t('activerecord.errors.messages.invalid')
|
errs << ::I18n.t('activerecord.errors.messages.invalid')
|
||||||
@ -267,7 +273,8 @@ class CustomField < ActiveRecord::Base
|
|||||||
errs << ::I18n.t('activerecord.errors.messages.blank')
|
errs << ::I18n.t('activerecord.errors.messages.blank')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
errs += format.validate_custom_value(custom_value)
|
end
|
||||||
|
|
||||||
errs
|
errs
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -281,6 +288,10 @@ class CustomField < ActiveRecord::Base
|
|||||||
validate_field_value(value).empty?
|
validate_field_value(value).empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def after_save_custom_value(custom_value)
|
||||||
|
format.after_save_custom_value(self, custom_value)
|
||||||
|
end
|
||||||
|
|
||||||
def format_in?(*args)
|
def format_in?(*args)
|
||||||
args.include?(field_format)
|
args.include?(field_format)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -48,6 +48,10 @@ class CustomFieldValue
|
|||||||
value.to_s
|
value.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def value=(v)
|
||||||
|
@value = custom_field.set_custom_field_value(self, v)
|
||||||
|
end
|
||||||
|
|
||||||
def validate_value
|
def validate_value
|
||||||
custom_field.validate_custom_value(self).each do |message|
|
custom_field.validate_custom_value(self).each do |message|
|
||||||
customized.errors.add(:base, custom_field.name + ' ' + message)
|
customized.errors.add(:base, custom_field.name + ' ' + message)
|
||||||
|
|||||||
@ -20,6 +20,8 @@ class CustomValue < ActiveRecord::Base
|
|||||||
belongs_to :customized, :polymorphic => true
|
belongs_to :customized, :polymorphic => true
|
||||||
attr_protected :id
|
attr_protected :id
|
||||||
|
|
||||||
|
after_save :custom_field_after_save_custom_value
|
||||||
|
|
||||||
def initialize(attributes=nil, *args)
|
def initialize(attributes=nil, *args)
|
||||||
super
|
super
|
||||||
if new_record? && custom_field && !attributes.key?(:value)
|
if new_record? && custom_field && !attributes.key?(:value)
|
||||||
@ -40,6 +42,10 @@ class CustomValue < ActiveRecord::Base
|
|||||||
custom_field.visible?
|
custom_field.visible?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attachments_visible?(user)
|
||||||
|
visible? && customized && customized.visible?(user)
|
||||||
|
end
|
||||||
|
|
||||||
def required?
|
def required?
|
||||||
custom_field.is_required?
|
custom_field.is_required?
|
||||||
end
|
end
|
||||||
@ -47,4 +53,10 @@ class CustomValue < ActiveRecord::Base
|
|||||||
def to_s
|
def to_s
|
||||||
value.to_s
|
value.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def custom_field_after_save_custom_value
|
||||||
|
custom_field.after_save_custom_value(self)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,29 +1,45 @@
|
|||||||
<span id="attachments_fields">
|
<% attachment_param ||= 'attachments' %>
|
||||||
<% if defined?(container) && container && container.saved_attachments %>
|
<% saved_attachments ||= container.saved_attachments if defined?(container) && container %>
|
||||||
<% container.saved_attachments.each_with_index do |attachment, i| %>
|
<% multiple = true unless defined?(multiple) && multiple == false %>
|
||||||
|
<% show_add = multiple || saved_attachments.blank? %>
|
||||||
|
<% description = (defined?(description) && description == false ? false : true) %>
|
||||||
|
<% css_class = (defined?(filedrop) && filedrop == false ? '' : 'filedrop') %>
|
||||||
|
|
||||||
|
<span class="attachments_form">
|
||||||
|
<span class="attachments_fields">
|
||||||
|
<% if saved_attachments.present? %>
|
||||||
|
<% saved_attachments.each_with_index do |attachment, i| %>
|
||||||
<span id="attachments_p<%= i %>">
|
<span id="attachments_p<%= i %>">
|
||||||
<%= text_field_tag("attachments[p#{i}][filename]", attachment.filename, :class => 'filename') +
|
<%= text_field_tag("#{attachment_param}[p#{i}][filename]", attachment.filename, :class => 'filename') %>
|
||||||
text_field_tag("attachments[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') +
|
<% if attachment.container_id.present? %>
|
||||||
link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %>
|
<%= link_to l(:label_delete), "#", :onclick => "$(this).closest('.attachments_form').find('.add_attachment').show(); $(this).parent().remove(); return false;", :class => 'icon-only icon-del' %>
|
||||||
<%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.token}" %>
|
<%= hidden_field_tag "#{attachment_param}[p#{i}][id]", attachment.id %>
|
||||||
|
<% else %>
|
||||||
|
<%= text_field_tag("#{attachment_param}[p#{i}][description]", attachment.description, :maxlength => 255, :placeholder => l(:label_optional_description), :class => 'description') if description %>
|
||||||
|
<%= link_to(' '.html_safe, attachment_path(attachment, :attachment_id => "p#{i}", :format => 'js'), :method => 'delete', :remote => true, :class => 'remove-upload') %>
|
||||||
|
<%= hidden_field_tag "#{attachment_param}[p#{i}][token]", attachment.token %>
|
||||||
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
<span class="add_attachment">
|
<span class="add_attachment" style="<%= show_add ? nil : 'display:none;' %>">
|
||||||
<%= file_field_tag 'attachments[dummy][file]',
|
<%= file_field_tag "#{attachment_param}[dummy][file]",
|
||||||
:id => nil,
|
:id => nil,
|
||||||
:class => 'file_selector',
|
:class => "file_selector #{css_class}",
|
||||||
:multiple => true,
|
:multiple => multiple,
|
||||||
:onchange => 'addInputFiles(this);',
|
:onchange => 'addInputFiles(this);',
|
||||||
:data => {
|
:data => {
|
||||||
:max_file_size => Setting.attachment_max_size.to_i.kilobytes,
|
:max_file_size => Setting.attachment_max_size.to_i.kilobytes,
|
||||||
:max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)),
|
:max_file_size_message => l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)),
|
||||||
:max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i,
|
:max_concurrent_uploads => Redmine::Configuration['max_concurrent_ajax_uploads'].to_i,
|
||||||
:upload_path => uploads_path(:format => 'js'),
|
:upload_path => uploads_path(:format => 'js'),
|
||||||
|
:param => attachment_param,
|
||||||
|
:description => description,
|
||||||
:description_placeholder => l(:label_optional_description)
|
:description_placeholder => l(:label_optional_description)
|
||||||
} %>
|
} %>
|
||||||
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
|
(<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<% content_for :header_tags do %>
|
<% content_for :header_tags do %>
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
|
$('#attachments_<%= j params[:attachment_id] %>').closest('.attachments_form').find('.add_attachment').show();
|
||||||
$('#attachments_<%= j params[:attachment_id] %>').remove();
|
$('#attachments_<%= j params[:attachment_id] %>').remove();
|
||||||
|
|||||||
@ -3,7 +3,7 @@ var fileSpan = $('#attachments_<%= j params[:attachment_id] %>');
|
|||||||
fileSpan.hide();
|
fileSpan.hide();
|
||||||
alert("<%= escape_javascript @attachment.errors.full_messages.join(', ') %>");
|
alert("<%= escape_javascript @attachment.errors.full_messages.join(', ') %>");
|
||||||
<% else %>
|
<% else %>
|
||||||
$('<input>', { type: 'hidden', name: 'attachments[<%= j params[:attachment_id] %>][token]' } ).val('<%= j @attachment.token %>').appendTo(fileSpan);
|
fileSpan.find('input.token').val('<%= j @attachment.token %>');
|
||||||
fileSpan.find('a.remove-upload')
|
fileSpan.find('a.remove-upload')
|
||||||
.attr({
|
.attr({
|
||||||
"data-remote": true,
|
"data-remote": true,
|
||||||
|
|||||||
@ -28,7 +28,9 @@
|
|||||||
when "IssueCustomField" %>
|
when "IssueCustomField" %>
|
||||||
<p><%= f.check_box :is_required %></p>
|
<p><%= f.check_box :is_required %></p>
|
||||||
<p><%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %></p>
|
<p><%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %></p>
|
||||||
|
<% if @custom_field.format.is_filter_supported %>
|
||||||
<p><%= f.check_box :is_filter %></p>
|
<p><%= f.check_box :is_filter %></p>
|
||||||
|
<% end %>
|
||||||
<% if @custom_field.format.searchable_supported %>
|
<% if @custom_field.format.searchable_supported %>
|
||||||
<p><%= f.check_box :searchable %></p>
|
<p><%= f.check_box :searchable %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
@ -57,7 +59,9 @@ when "IssueCustomField" %>
|
|||||||
<p><%= f.check_box :is_required %></p>
|
<p><%= f.check_box :is_required %></p>
|
||||||
<p><%= f.check_box :visible %></p>
|
<p><%= f.check_box :visible %></p>
|
||||||
<p><%= f.check_box :editable %></p>
|
<p><%= f.check_box :editable %></p>
|
||||||
|
<% if @custom_field.format.is_filter_supported %>
|
||||||
<p><%= f.check_box :is_filter %></p>
|
<p><%= f.check_box :is_filter %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% when "ProjectCustomField" %>
|
<% when "ProjectCustomField" %>
|
||||||
<p><%= f.check_box :is_required %></p>
|
<p><%= f.check_box :is_required %></p>
|
||||||
@ -65,19 +69,27 @@ when "IssueCustomField" %>
|
|||||||
<% if @custom_field.format.searchable_supported %>
|
<% if @custom_field.format.searchable_supported %>
|
||||||
<p><%= f.check_box :searchable %></p>
|
<p><%= f.check_box :searchable %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if @custom_field.format.is_filter_supported %>
|
||||||
<p><%= f.check_box :is_filter %></p>
|
<p><%= f.check_box :is_filter %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% when "VersionCustomField" %>
|
<% when "VersionCustomField" %>
|
||||||
<p><%= f.check_box :is_required %></p>
|
<p><%= f.check_box :is_required %></p>
|
||||||
|
<% if @custom_field.format.is_filter_supported %>
|
||||||
<p><%= f.check_box :is_filter %></p>
|
<p><%= f.check_box :is_filter %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% when "GroupCustomField" %>
|
<% when "GroupCustomField" %>
|
||||||
<p><%= f.check_box :is_required %></p>
|
<p><%= f.check_box :is_required %></p>
|
||||||
|
<% if @custom_field.format.is_filter_supported %>
|
||||||
<p><%= f.check_box :is_filter %></p>
|
<p><%= f.check_box :is_filter %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% when "TimeEntryCustomField" %>
|
<% when "TimeEntryCustomField" %>
|
||||||
<p><%= f.check_box :is_required %></p>
|
<p><%= f.check_box :is_required %></p>
|
||||||
|
<% if @custom_field.format.is_filter_supported %>
|
||||||
<p><%= f.check_box :is_filter %></p>
|
<p><%= f.check_box :is_filter %></p>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% else %>
|
<% else %>
|
||||||
<p><%= f.check_box :is_required %></p>
|
<p><%= f.check_box :is_required %></p>
|
||||||
|
|||||||
@ -34,6 +34,7 @@ module Redmine
|
|||||||
options.merge(:as => :container, :dependent => :destroy, :inverse_of => :container)
|
options.merge(:as => :container, :dependent => :destroy, :inverse_of => :container)
|
||||||
send :include, Redmine::Acts::Attachable::InstanceMethods
|
send :include, Redmine::Acts::Attachable::InstanceMethods
|
||||||
before_save :attach_saved_attachments
|
before_save :attach_saved_attachments
|
||||||
|
after_rollback :detach_saved_attachments
|
||||||
validate :warn_about_failed_attachments
|
validate :warn_about_failed_attachments
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -90,7 +91,7 @@ module Redmine
|
|||||||
if file = attachment['file']
|
if file = attachment['file']
|
||||||
next unless file.size > 0
|
next unless file.size > 0
|
||||||
a = Attachment.create(:file => file, :author => author)
|
a = Attachment.create(:file => file, :author => author)
|
||||||
elsif token = attachment['token']
|
elsif token = attachment['token'].presence
|
||||||
a = Attachment.find_by_token(token)
|
a = Attachment.find_by_token(token)
|
||||||
unless a
|
unless a
|
||||||
@failed_attachment_count += 1
|
@failed_attachment_count += 1
|
||||||
@ -117,6 +118,14 @@ module Redmine
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def detach_saved_attachments
|
||||||
|
saved_attachments.each do |attachment|
|
||||||
|
# TODO: use #reload instead, after upgrading to Rails 5
|
||||||
|
# (after_rollback is called when running transactional tests in Rails 4)
|
||||||
|
attachment.container = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def warn_about_failed_attachments
|
def warn_about_failed_attachments
|
||||||
if @failed_attachment_count && @failed_attachment_count > 0
|
if @failed_attachment_count && @failed_attachment_count > 0
|
||||||
errors.add :base, ::I18n.t('warning_attachments_not_saved', count: @failed_attachment_count)
|
errors.add :base, ::I18n.t('warning_attachments_not_saved', count: @failed_attachment_count)
|
||||||
|
|||||||
@ -68,16 +68,7 @@ module Redmine
|
|||||||
custom_field_values.each do |custom_field_value|
|
custom_field_values.each do |custom_field_value|
|
||||||
key = custom_field_value.custom_field_id.to_s
|
key = custom_field_value.custom_field_id.to_s
|
||||||
if values.has_key?(key)
|
if values.has_key?(key)
|
||||||
value = values[key]
|
custom_field_value.value = values[key]
|
||||||
if value.is_a?(Array)
|
|
||||||
value = value.reject(&:blank?).map(&:to_s).uniq
|
|
||||||
if value.empty?
|
|
||||||
value << ''
|
|
||||||
end
|
|
||||||
else
|
|
||||||
value = value.to_s
|
|
||||||
end
|
|
||||||
custom_field_value.value = value
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@custom_field_values_changed = true
|
@custom_field_values_changed = true
|
||||||
@ -93,11 +84,11 @@ module Redmine
|
|||||||
if values.empty?
|
if values.empty?
|
||||||
values << custom_values.build(:customized => self, :custom_field => field)
|
values << custom_values.build(:customized => self, :custom_field => field)
|
||||||
end
|
end
|
||||||
x.value = values.map(&:value)
|
x.instance_variable_set("@value", values.map(&:value))
|
||||||
else
|
else
|
||||||
cv = custom_values.detect { |v| v.custom_field == field }
|
cv = custom_values.detect { |v| v.custom_field == field }
|
||||||
cv ||= custom_values.build(:customized => self, :custom_field => field)
|
cv ||= custom_values.build(:customized => self, :custom_field => field)
|
||||||
x.value = cv.value
|
x.instance_variable_set("@value", cv.value)
|
||||||
end
|
end
|
||||||
x.value_was = x.value.dup if x.value
|
x.value_was = x.value.dup if x.value
|
||||||
x
|
x
|
||||||
|
|||||||
@ -67,6 +67,10 @@ module Redmine
|
|||||||
class_attribute :multiple_supported
|
class_attribute :multiple_supported
|
||||||
self.multiple_supported = false
|
self.multiple_supported = false
|
||||||
|
|
||||||
|
# Set this to true if the format supports filtering on custom values
|
||||||
|
class_attribute :is_filter_supported
|
||||||
|
self.is_filter_supported = true
|
||||||
|
|
||||||
# Set this to true if the format supports textual search on custom values
|
# Set this to true if the format supports textual search on custom values
|
||||||
class_attribute :searchable_supported
|
class_attribute :searchable_supported
|
||||||
self.searchable_supported = false
|
self.searchable_supported = false
|
||||||
@ -87,6 +91,9 @@ module Redmine
|
|||||||
class_attribute :change_as_diff
|
class_attribute :change_as_diff
|
||||||
self.change_as_diff = false
|
self.change_as_diff = false
|
||||||
|
|
||||||
|
class_attribute :change_no_details
|
||||||
|
self.change_no_details = false
|
||||||
|
|
||||||
def self.add(name)
|
def self.add(name)
|
||||||
self.format_name = name
|
self.format_name = name
|
||||||
Redmine::FieldFormat.add(name, self)
|
Redmine::FieldFormat.add(name, self)
|
||||||
@ -107,6 +114,19 @@ module Redmine
|
|||||||
"label_#{name}"
|
"label_#{name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_custom_field_value(custom_field, custom_field_value, value)
|
||||||
|
if value.is_a?(Array)
|
||||||
|
value = value.map(&:to_s).reject{|v| v==''}.uniq
|
||||||
|
if value.empty?
|
||||||
|
value << ''
|
||||||
|
end
|
||||||
|
else
|
||||||
|
value = value.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
def cast_custom_value(custom_value)
|
def cast_custom_value(custom_value)
|
||||||
cast_value(custom_value.custom_field, custom_value.value, custom_value.customized)
|
cast_value(custom_value.custom_field, custom_value.value, custom_value.customized)
|
||||||
end
|
end
|
||||||
@ -169,6 +189,7 @@ module Redmine
|
|||||||
|
|
||||||
# Returns the validation error messages for custom_value
|
# Returns the validation error messages for custom_value
|
||||||
# Should return an empty array if custom_value is valid
|
# Should return an empty array if custom_value is valid
|
||||||
|
# custom_value is a CustomFieldValue.
|
||||||
def validate_custom_value(custom_value)
|
def validate_custom_value(custom_value)
|
||||||
values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''}
|
values = Array.wrap(custom_value.value).reject {|value| value.to_s == ''}
|
||||||
errors = values.map do |value|
|
errors = values.map do |value|
|
||||||
@ -181,6 +202,10 @@ module Redmine
|
|||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# CustomValue after_save callback
|
||||||
|
def after_save_custom_value(custom_field, custom_value)
|
||||||
|
end
|
||||||
|
|
||||||
def formatted_custom_value(view, custom_value, html=false)
|
def formatted_custom_value(view, custom_value, html=false)
|
||||||
formatted_value(view, custom_value.custom_field, custom_value.value, custom_value.customized, html)
|
formatted_value(view, custom_value.custom_field, custom_value.value, custom_value.customized, html)
|
||||||
end
|
end
|
||||||
@ -830,5 +855,109 @@ module Redmine
|
|||||||
scope.sort.collect{|u| [u.to_s, u.id.to_s] }
|
scope.sort.collect{|u| [u.to_s, u.id.to_s] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class AttachementFormat < Base
|
||||||
|
add 'attachment'
|
||||||
|
self.form_partial = 'custom_fields/formats/attachment'
|
||||||
|
self.is_filter_supported = false
|
||||||
|
self.change_no_details = true
|
||||||
|
|
||||||
|
def set_custom_field_value(custom_field, custom_field_value, value)
|
||||||
|
attachment_present = false
|
||||||
|
|
||||||
|
if value.is_a?(Hash)
|
||||||
|
attachment_present = true
|
||||||
|
value = value.except(:blank)
|
||||||
|
|
||||||
|
if value.values.any? && value.values.all? {|v| v.is_a?(Hash)}
|
||||||
|
value = value.values.first
|
||||||
|
end
|
||||||
|
|
||||||
|
if value.key?(:id)
|
||||||
|
value = set_custom_field_value_by_id(custom_field, custom_field_value, value[:id])
|
||||||
|
elsif value[:token].present?
|
||||||
|
if attachment = Attachment.find_by_token(value[:token])
|
||||||
|
value = attachment.id.to_s
|
||||||
|
else
|
||||||
|
value = ''
|
||||||
|
end
|
||||||
|
elsif value.key?(:file)
|
||||||
|
attachment = Attachment.new(:file => value[:file], :author => User.current)
|
||||||
|
if attachment.save
|
||||||
|
value = attachment.id.to_s
|
||||||
|
else
|
||||||
|
value = ''
|
||||||
|
end
|
||||||
|
else
|
||||||
|
attachment_present = false
|
||||||
|
value = ''
|
||||||
|
end
|
||||||
|
elsif value.is_a?(String)
|
||||||
|
value = set_custom_field_value_by_id(custom_field, custom_field_value, value)
|
||||||
|
end
|
||||||
|
custom_field_value.instance_variable_set "@attachment_present", attachment_present
|
||||||
|
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_custom_field_value_by_id(custom_field, custom_field_value, id)
|
||||||
|
attachment = Attachment.find_by_id(id)
|
||||||
|
if attachment && attachment.container.is_a?(CustomValue) && attachment.container.customized == custom_field_value.customized
|
||||||
|
id.to_s
|
||||||
|
else
|
||||||
|
''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private :set_custom_field_value_by_id
|
||||||
|
|
||||||
|
def cast_single_value(custom_field, value, customized=nil)
|
||||||
|
Attachment.find_by_id(value.to_i) if value.present? && value.respond_to?(:to_i)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_custom_value(custom_value)
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if custom_value.instance_variable_get("@attachment_present") && custom_value.value.blank?
|
||||||
|
errors << ::I18n.t('activerecord.errors.messages.invalid')
|
||||||
|
end
|
||||||
|
|
||||||
|
errors.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_save_custom_value(custom_field, custom_value)
|
||||||
|
if custom_value.value_changed?
|
||||||
|
if custom_value.value.present?
|
||||||
|
attachment = Attachment.where(:id => custom_value.value.to_s).first
|
||||||
|
if attachment
|
||||||
|
attachment.container = custom_value
|
||||||
|
attachment.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if custom_value.value_was.present?
|
||||||
|
attachment = Attachment.where(:id => custom_value.value_was.to_s).first
|
||||||
|
if attachment
|
||||||
|
attachment.destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit_tag(view, tag_id, tag_name, custom_value, options={})
|
||||||
|
attachment = nil
|
||||||
|
if custom_value.value.present? #&& custom_value.value == custom_value.value_was
|
||||||
|
attachment = Attachment.find_by_id(custom_value.value)
|
||||||
|
end
|
||||||
|
|
||||||
|
view.hidden_field_tag("#{tag_name}[blank]", "") +
|
||||||
|
view.render(:partial => 'attachments/form',
|
||||||
|
:locals => {
|
||||||
|
:attachment_param => tag_name,
|
||||||
|
:multiple => false,
|
||||||
|
:description => false,
|
||||||
|
:saved_attachments => [attachment].compact,
|
||||||
|
:filedrop => false
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -2,23 +2,32 @@
|
|||||||
Copyright (C) 2006-2016 Jean-Philippe Lang */
|
Copyright (C) 2006-2016 Jean-Philippe Lang */
|
||||||
|
|
||||||
function addFile(inputEl, file, eagerUpload) {
|
function addFile(inputEl, file, eagerUpload) {
|
||||||
|
var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields');
|
||||||
|
var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment');
|
||||||
|
var maxFiles = ($(inputEl).prop('multiple') == true ? 10 : 1);
|
||||||
|
|
||||||
if ($('#attachments_fields').children().length < 10) {
|
if (attachmentsFields.children().length < maxFiles) {
|
||||||
|
|
||||||
var attachmentId = addFile.nextAttachmentId++;
|
var attachmentId = addFile.nextAttachmentId++;
|
||||||
|
|
||||||
var fileSpan = $('<span>', { id: 'attachments_' + attachmentId });
|
var fileSpan = $('<span>', { id: 'attachments_' + attachmentId });
|
||||||
|
var param = $(inputEl).data('param');
|
||||||
|
if (!param) {param = 'attachments'};
|
||||||
|
|
||||||
fileSpan.append(
|
fileSpan.append(
|
||||||
$('<input>', { type: 'text', 'class': 'filename readonly', name: 'attachments[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name),
|
$('<input>', { type: 'text', 'class': 'filename readonly', name: param +'[' + attachmentId + '][filename]', readonly: 'readonly'} ).val(file.name),
|
||||||
$('<input>', { type: 'text', 'class': 'description', name: 'attachments[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload),
|
$('<input>', { type: 'text', 'class': 'description', name: param + '[' + attachmentId + '][description]', maxlength: 255, placeholder: $(inputEl).data('description-placeholder') } ).toggle(!eagerUpload),
|
||||||
|
$('<input>', { type: 'hidden', 'class': 'token', name: param + '[' + attachmentId + '][token]'} ),
|
||||||
$('<a> </a>').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload)
|
$('<a> </a>').attr({ href: "#", 'class': 'remove-upload' }).click(removeFile).toggle(!eagerUpload)
|
||||||
).appendTo('#attachments_fields');
|
).appendTo(attachmentsFields);
|
||||||
|
|
||||||
|
if ($(inputEl).data('description') == 0) {
|
||||||
|
fileSpan.find('input.description').remove();
|
||||||
|
}
|
||||||
|
|
||||||
if(eagerUpload) {
|
if(eagerUpload) {
|
||||||
ajaxUpload(file, attachmentId, fileSpan, inputEl);
|
ajaxUpload(file, attachmentId, fileSpan, inputEl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addAttachment.toggle(attachmentsFields.children().length < maxFiles);
|
||||||
return attachmentId;
|
return attachmentId;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -118,11 +127,16 @@ function uploadBlob(blob, uploadUrl, attachmentId, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addInputFiles(inputEl) {
|
function addInputFiles(inputEl) {
|
||||||
|
var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields');
|
||||||
|
var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment');
|
||||||
var clearedFileInput = $(inputEl).clone().val('');
|
var clearedFileInput = $(inputEl).clone().val('');
|
||||||
|
var sizeExceeded = false;
|
||||||
|
var param = $(inputEl).data('param');
|
||||||
|
if (!param) {param = 'attachments'};
|
||||||
|
|
||||||
if ($.ajaxSettings.xhr().upload && inputEl.files) {
|
if ($.ajaxSettings.xhr().upload && inputEl.files) {
|
||||||
// upload files using ajax
|
// upload files using ajax
|
||||||
uploadAndAttachFiles(inputEl.files, inputEl);
|
sizeExceeded = uploadAndAttachFiles(inputEl.files, inputEl);
|
||||||
$(inputEl).remove();
|
$(inputEl).remove();
|
||||||
} else {
|
} else {
|
||||||
// browser not supporting the file API, upload on form submission
|
// browser not supporting the file API, upload on form submission
|
||||||
@ -130,11 +144,11 @@ function addInputFiles(inputEl) {
|
|||||||
var aFilename = inputEl.value.split(/\/|\\/);
|
var aFilename = inputEl.value.split(/\/|\\/);
|
||||||
attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false);
|
attachmentId = addFile(inputEl, { name: aFilename[ aFilename.length - 1 ] }, false);
|
||||||
if (attachmentId) {
|
if (attachmentId) {
|
||||||
$(inputEl).attr({ name: 'attachments[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId);
|
$(inputEl).attr({ name: param + '[' + attachmentId + '][file]', style: 'display:none;' }).appendTo('#attachments_' + attachmentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearedFileInput.insertAfter('#attachments_fields');
|
clearedFileInput.prependTo(addAttachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadAndAttachFiles(files, inputEl) {
|
function uploadAndAttachFiles(files, inputEl) {
|
||||||
@ -151,6 +165,7 @@ function uploadAndAttachFiles(files, inputEl) {
|
|||||||
} else {
|
} else {
|
||||||
$.each(files, function() {addFile(inputEl, this, true);});
|
$.each(files, function() {addFile(inputEl, this, true);});
|
||||||
}
|
}
|
||||||
|
return sizeExceeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFileDropEvent(e) {
|
function handleFileDropEvent(e) {
|
||||||
@ -159,7 +174,7 @@ function handleFileDropEvent(e) {
|
|||||||
blockEventPropagation(e);
|
blockEventPropagation(e);
|
||||||
|
|
||||||
if ($.inArray('Files', e.dataTransfer.types) > -1) {
|
if ($.inArray('Files', e.dataTransfer.types) > -1) {
|
||||||
uploadAndAttachFiles(e.dataTransfer.files, $('input:file.file_selector'));
|
uploadAndAttachFiles(e.dataTransfer.files, $('input:file.filedrop').first());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,12 +193,12 @@ function setupFileDrop() {
|
|||||||
|
|
||||||
$.event.fixHooks.drop = { props: [ 'dataTransfer' ] };
|
$.event.fixHooks.drop = { props: [ 'dataTransfer' ] };
|
||||||
|
|
||||||
$('form div.box').has('input:file').each(function() {
|
$('form div.box:not(.filedroplistner)').has('input:file.filedrop').each(function() {
|
||||||
$(this).on({
|
$(this).on({
|
||||||
dragover: dragOverHandler,
|
dragover: dragOverHandler,
|
||||||
dragleave: dragOutHandler,
|
dragleave: dragOutHandler,
|
||||||
drop: handleFileDropEvent
|
drop: handleFileDropEvent
|
||||||
});
|
}).addClass('filedroplistner');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -600,7 +600,7 @@ span.pagination>span {white-space:nowrap;}
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 3px 0 3px 0;
|
padding: 3px 0 3px 0;
|
||||||
padding-left: 180px; /* width of left column containing the label elements */
|
padding-left: 180px; /* width of left column containing the label elements */
|
||||||
min-height: 1.8em;
|
line-height: 2em;
|
||||||
clear:left;
|
clear:left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -626,13 +626,16 @@ html>body .tabular p {overflow:hidden;}
|
|||||||
width: 270px;
|
width: 270px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.block {
|
||||||
|
display: block;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
.tabular label.block{
|
.tabular label.block{
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-left: 0px !important;
|
margin-left: 0px !important;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
float: none;
|
float: none;
|
||||||
display: block;
|
|
||||||
width: auto !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabular label.inline{
|
.tabular label.inline{
|
||||||
@ -687,13 +690,14 @@ span.required {color: #bb0000;}
|
|||||||
.check_box_group.bool_cf {border:0; background:inherit;}
|
.check_box_group.bool_cf {border:0; background:inherit;}
|
||||||
.check_box_group.bool_cf label {display: inline;}
|
.check_box_group.bool_cf label {display: inline;}
|
||||||
|
|
||||||
#attachments_fields input.description, #existing-attachments input.description {margin-left:4px; width:340px;}
|
.attachments_fields input.description, #existing-attachments input.description {margin-left:4px; width:340px;}
|
||||||
#attachments_fields>span, #existing-attachments>span {display:block; white-space:nowrap;}
|
.attachments_fields>span, #existing-attachments>span {display:block; white-space:nowrap;}
|
||||||
#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;}
|
.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;}
|
||||||
#attachments_fields input.filename {height:1.8em;}
|
.tabular input.filename {max-width:75% !important;}
|
||||||
#attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
|
.attachments_fields input.filename {height:1.8em;}
|
||||||
#attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
|
.attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
|
||||||
#attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
|
.attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
|
||||||
|
.attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
|
||||||
a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;}
|
a.remove-upload {background: url(../images/delete.png) no-repeat 1px 50%; width:1px; display:inline-block; padding-left:16px;}
|
||||||
a.remove-upload:hover {text-decoration:none !important;}
|
a.remove-upload:hover {text-decoration:none !important;}
|
||||||
.existing-attachment.deleted .filename {text-decoration:line-through; color:#999 !important;}
|
.existing-attachment.deleted .filename {text-decoration:line-through; color:#999 !important;}
|
||||||
@ -1160,7 +1164,7 @@ a.close-icon:hover {background-image:url('../images/close_hl.png');}
|
|||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
vertical-align: text-bottom;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
.icon-only::after {
|
.icon-only::after {
|
||||||
content: " ";
|
content: " ";
|
||||||
|
|||||||
@ -0,0 +1,156 @@
|
|||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2016 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require File.expand_path('../../../../../test_helper', __FILE__)
|
||||||
|
|
||||||
|
class AttachmentFieldFormatTest < Redmine::IntegrationTest
|
||||||
|
fixtures :projects,
|
||||||
|
:users, :email_addresses,
|
||||||
|
:roles,
|
||||||
|
:members,
|
||||||
|
:member_roles,
|
||||||
|
:trackers,
|
||||||
|
:projects_trackers,
|
||||||
|
:enabled_modules,
|
||||||
|
:issue_statuses,
|
||||||
|
:issues,
|
||||||
|
:enumerations,
|
||||||
|
:custom_fields,
|
||||||
|
:custom_values,
|
||||||
|
:custom_fields_trackers,
|
||||||
|
:attachments
|
||||||
|
|
||||||
|
def setup
|
||||||
|
set_tmp_attachments_directory
|
||||||
|
@field = IssueCustomField.generate!(:name => "File", :field_format => "attachment")
|
||||||
|
log_user "jsmith", "jsmith"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_new_should_include_inputs
|
||||||
|
get '/projects/ecookbook/issues/new'
|
||||||
|
assert_response :success
|
||||||
|
|
||||||
|
assert_select '[name^=?]', "issue[custom_field_values][#{@field.id}]", 2
|
||||||
|
assert_select 'input[name=?][type=hidden][value=""]', "issue[custom_field_values][#{@field.id}][blank]"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_create_with_attachment
|
||||||
|
issue = new_record(Issue) do
|
||||||
|
assert_difference 'Attachment.count' do
|
||||||
|
post '/projects/ecookbook/issues', {
|
||||||
|
:issue => {
|
||||||
|
:subject => "Subject",
|
||||||
|
:custom_field_values => {
|
||||||
|
@field.id => {
|
||||||
|
'blank' => '',
|
||||||
|
'1' => {:file => uploaded_test_file("testfile.txt", "text/plain")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_response 302
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
custom_value = issue.custom_value_for(@field)
|
||||||
|
assert custom_value
|
||||||
|
assert custom_value.value.present?
|
||||||
|
|
||||||
|
attachment = Attachment.find_by_id(custom_value.value)
|
||||||
|
assert attachment
|
||||||
|
assert_equal custom_value, attachment.container
|
||||||
|
|
||||||
|
follow_redirect!
|
||||||
|
assert_response :success
|
||||||
|
|
||||||
|
# link to the attachment
|
||||||
|
link = css_select(".cf_#{@field.id} .value a")
|
||||||
|
assert_equal 1, link.size
|
||||||
|
assert_equal "testfile.txt", link.text
|
||||||
|
|
||||||
|
# download the attachment
|
||||||
|
get link.attr('href')
|
||||||
|
assert_response :success
|
||||||
|
assert_equal "text/plain", response.content_type
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_create_without_attachment
|
||||||
|
issue = new_record(Issue) do
|
||||||
|
assert_no_difference 'Attachment.count' do
|
||||||
|
post '/projects/ecookbook/issues', {
|
||||||
|
:issue => {
|
||||||
|
:subject => "Subject",
|
||||||
|
:custom_field_values => {
|
||||||
|
@field.id => {:blank => ''}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_response 302
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
custom_value = issue.custom_value_for(@field)
|
||||||
|
assert custom_value
|
||||||
|
assert custom_value.value.blank?
|
||||||
|
|
||||||
|
follow_redirect!
|
||||||
|
assert_response :success
|
||||||
|
|
||||||
|
# no links to the attachment
|
||||||
|
assert_select ".cf_#{@field.id} .value a", 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_failure_on_create_should_preserve_attachment
|
||||||
|
attachment = new_record(Attachment) do
|
||||||
|
assert_no_difference 'Issue.count' do
|
||||||
|
post '/projects/ecookbook/issues', {
|
||||||
|
:issue => {
|
||||||
|
:subject => "",
|
||||||
|
:custom_field_values => {
|
||||||
|
@field.id => {:file => uploaded_test_file("testfile.txt", "text/plain")}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_response :success
|
||||||
|
assert_select_error /Subject cannot be blank/
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_nil attachment.container_id
|
||||||
|
assert_select 'input[name=?][value=?][type=hidden]', "issue[custom_field_values][#{@field.id}][p0][token]", attachment.token
|
||||||
|
assert_select 'input[name=?][value=?]', "issue[custom_field_values][#{@field.id}][p0][filename]", 'testfile.txt'
|
||||||
|
|
||||||
|
issue = new_record(Issue) do
|
||||||
|
assert_no_difference 'Attachment.count' do
|
||||||
|
post '/projects/ecookbook/issues', {
|
||||||
|
:issue => {
|
||||||
|
:subject => "Subject",
|
||||||
|
:custom_field_values => {
|
||||||
|
@field.id => {:token => attachment.token}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_response 302
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
custom_value = issue.custom_value_for(@field)
|
||||||
|
assert custom_value
|
||||||
|
assert_equal attachment.id.to_s, custom_value.value
|
||||||
|
assert_equal custom_value, attachment.reload.container
|
||||||
|
end
|
||||||
|
end
|
||||||
163
test/unit/lib/redmine/field_format/attachment_format_test.rb
Normal file
163
test/unit/lib/redmine/field_format/attachment_format_test.rb
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# Redmine - project management software
|
||||||
|
# Copyright (C) 2006-2016 Jean-Philippe Lang
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
require File.expand_path('../../../../../test_helper', __FILE__)
|
||||||
|
require 'redmine/field_format'
|
||||||
|
|
||||||
|
class Redmine::AttachmentFieldFormatTest < ActionView::TestCase
|
||||||
|
include ApplicationHelper
|
||||||
|
include Redmine::I18n
|
||||||
|
|
||||||
|
fixtures :users
|
||||||
|
|
||||||
|
def setup
|
||||||
|
set_language_if_valid 'en'
|
||||||
|
set_tmp_attachments_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_accept_a_hash_with_upload_on_create
|
||||||
|
field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment')
|
||||||
|
group = Group.new(:name => 'Group')
|
||||||
|
attachment = nil
|
||||||
|
|
||||||
|
custom_value = new_record(CustomValue) do
|
||||||
|
attachment = new_record(Attachment) do
|
||||||
|
group.custom_field_values = {field.id => {:file => mock_file}}
|
||||||
|
assert group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal 'a_file.png', attachment.filename
|
||||||
|
assert_equal custom_value, attachment.container
|
||||||
|
assert_equal field, attachment.container.custom_field
|
||||||
|
assert_equal group, attachment.container.customized
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_accept_a_hash_with_no_upload_on_create
|
||||||
|
field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment')
|
||||||
|
group = Group.new(:name => 'Group')
|
||||||
|
attachment = nil
|
||||||
|
|
||||||
|
custom_value = new_record(CustomValue) do
|
||||||
|
assert_no_difference 'Attachment.count' do
|
||||||
|
group.custom_field_values = {field.id => {}}
|
||||||
|
assert group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal '', custom_value.value
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_not_validate_with_invalid_upload_on_create
|
||||||
|
field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment')
|
||||||
|
group = Group.new(:name => 'Group')
|
||||||
|
|
||||||
|
with_settings :attachment_max_size => 0 do
|
||||||
|
assert_no_difference 'CustomValue.count' do
|
||||||
|
assert_no_difference 'Attachment.count' do
|
||||||
|
group.custom_field_values = {field.id => {:file => mock_file}}
|
||||||
|
assert_equal false, group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_accept_a_hash_with_token_on_create
|
||||||
|
field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment')
|
||||||
|
group = Group.new(:name => 'Group')
|
||||||
|
|
||||||
|
attachment = Attachment.create!(:file => mock_file, :author => User.find(2))
|
||||||
|
assert_nil attachment.container
|
||||||
|
|
||||||
|
custom_value = new_record(CustomValue) do
|
||||||
|
assert_no_difference 'Attachment.count' do
|
||||||
|
group.custom_field_values = {field.id => {:token => attachment.token}}
|
||||||
|
assert group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attachment.reload
|
||||||
|
assert_equal custom_value, attachment.container
|
||||||
|
assert_equal field, attachment.container.custom_field
|
||||||
|
assert_equal group, attachment.container.customized
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_not_validate_with_invalid_token_on_create
|
||||||
|
field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment')
|
||||||
|
group = Group.new(:name => 'Group')
|
||||||
|
|
||||||
|
assert_no_difference 'CustomValue.count' do
|
||||||
|
assert_no_difference 'Attachment.count' do
|
||||||
|
group.custom_field_values = {field.id => {:token => "123.0123456789abcdef"}}
|
||||||
|
assert_equal false, group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_replace_attachment_on_update
|
||||||
|
field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment')
|
||||||
|
group = Group.new(:name => 'Group')
|
||||||
|
attachment = nil
|
||||||
|
custom_value = new_record(CustomValue) do
|
||||||
|
attachment = new_record(Attachment) do
|
||||||
|
group.custom_field_values = {field.id => {:file => mock_file}}
|
||||||
|
assert group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
group.reload
|
||||||
|
|
||||||
|
assert_no_difference 'Attachment.count' do
|
||||||
|
assert_no_difference 'CustomValue.count' do
|
||||||
|
group.custom_field_values = {field.id => {:file => mock_file}}
|
||||||
|
assert group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert !Attachment.exists?(attachment.id)
|
||||||
|
assert CustomValue.exists?(custom_value.id)
|
||||||
|
|
||||||
|
new_attachment = Attachment.order(:id => :desc).first
|
||||||
|
custom_value.reload
|
||||||
|
assert_equal custom_value, new_attachment.container
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_should_delete_attachment_on_update
|
||||||
|
field = GroupCustomField.generate!(:name => "File", :field_format => 'attachment')
|
||||||
|
group = Group.new(:name => 'Group')
|
||||||
|
attachment = nil
|
||||||
|
custom_value = new_record(CustomValue) do
|
||||||
|
attachment = new_record(Attachment) do
|
||||||
|
group.custom_field_values = {field.id => {:file => mock_file}}
|
||||||
|
assert group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
group.reload
|
||||||
|
|
||||||
|
assert_difference 'Attachment.count', -1 do
|
||||||
|
assert_no_difference 'CustomValue.count' do
|
||||||
|
group.custom_field_values = {field.id => {}}
|
||||||
|
assert group.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert !Attachment.exists?(attachment.id)
|
||||||
|
assert CustomValue.exists?(custom_value.id)
|
||||||
|
|
||||||
|
custom_value.reload
|
||||||
|
assert_equal '', custom_value.value
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
x
Reference in New Issue
Block a user