Duplicate documentable code and rename for imageable

This commit is contained in:
Senén Rodero Rodríguez
2017-09-12 18:12:49 +02:00
parent 614ff79ba1
commit 6f71da07ee
59 changed files with 1362 additions and 520 deletions

View File

@@ -63,6 +63,7 @@
//= require followable
//= require flaggable
//= require documentable
//= require imageable
//= require tree_navigator
//= require custom
//= require tag_autocomplete
@@ -100,6 +101,7 @@ var initialize_modules = function() {
App.WatchFormChanges.initialize();
App.TreeNavigator.initialize();
App.Documentable.initialize();
App.Imageable.initialize();
App.TagAutocomplete.initialize();
};

View File

@@ -0,0 +1,101 @@
App.Imageable =
initialize: ->
@initializeDirectUploads()
@initializeInterface()
initializeDirectUploads: ->
$('input.image_ajax_attachment[type=file]').fileupload
paramName: "image[attachment]"
formData: null
add: (e, data) ->
wrapper = $(e.target).closest('.image')
index = $(e.target).data('index')
is_nested_image = $(e.target).data('nested-image')
$(wrapper).find('.progress-bar-placeholder').empty()
data.progressBar = $(wrapper).find('.progress-bar-placeholder').html('<div class="progress-bar"><div class="loading-bar uploading"></div></div>')
$(wrapper).find('.progress-bar-placeholder').css('display','block')
data.formData = {
"image[title]": $(wrapper).find('input.image-title').val() || data.files[0].name
"index": index,
"nested_image": is_nested_image
}
data.submit()
change: (e, data) ->
wrapper = $(e.target).parent()
$.each(data.files, (index, file)->
$(wrapper).find('.file-name').text(file.name)
)
progress: (e, data) ->
progress = parseInt(data.loaded / data.total * 100, 10)
$(data.progressBar).find('.loading-bar').css 'width', progress + '%'
return
initializeInterface: ->
input_files = $('input.image_ajax_attachment[type=file]')
$.each input_files, (index, file) ->
wrapper = $(file).parent()
App.Imageable.watchRemoveImagebutton(wrapper)
watchRemoveImagebutton: (wrapper) ->
remove_image_button = $(wrapper).find('.remove-image')
$(remove_image_button).on 'click', (e) ->
e.preventDefault()
$(wrapper).remove()
$('#new_image_link').show()
$('.max-images-notice').hide()
uploadNestedImage: (id, nested_image, result) ->
$('#' + id).replaceWith(nested_image)
@updateLoadingBar(id, result)
@initialize()
uploadPlainImage: (id, nested_image, result) ->
$('#' + id).replaceWith(nested_image)
@updateLoadingBar(id, result)
@initialize()
updateLoadingBar: (id, result) ->
if result
$('#' + id).find('.loading-bar').addClass 'complete'
else
$('#' + id).find('.loading-bar').addClass 'errors'
$('#' + id).find('.progress-bar-placeholder').css('display','block')
new: (nested_fields) ->
$(".images-list").append(nested_fields)
@initialize()
destroyNestedImage: (id, notice) ->
$('#' + id).remove()
@updateNotice(notice)
replacePlainImage: (id, notice, plain_image) ->
$('#' + id).replaceWith(plain_image)
@updateNotice(notice)
@initialize()
updateNotice: (notice) ->
if $('[data-alert]').length > 0
$('[data-alert]').replaceWith(notice)
else
$("body").append(notice)
updateNewImageButton: (link) ->
if $('.image').length >= $('.images').data('max-images')
$('#new_image_link').hide()
$('.max-images-notice').removeClass('hide')
$('.max-images-notice').show()
else if $('#new_image_link').length > 0
$('#new_image_link').replaceWith(link)
$('.max-images-notice').hide()
else
$('.max-images-notice').hide()
$(link).insertBefore('.images hr:last')

View File

@@ -19,3 +19,4 @@
@import 'jquery-ui/autocomplete';
@import 'autocomplete_overrides';
@import 'documentable';
@import 'imageable';

View File

@@ -197,7 +197,7 @@
content: '\53';
}
.icon-budget-investment-image::before {
.icon-image::before {
content: '\68';
}

View File

@@ -0,0 +1,64 @@
.progress-bar-placeholder {
display: none;
}
.image-form {
.image .file-name {
margin-top: 0;
}
.progress-bar-placeholder {
margin-bottom: 15px;
}
.image .loading-bar.errors {
margin-top: $line-height * 2;
}
}
.cached-image{
max-width: 150px;
max-height: 150px;
}
.image {
.button {
font-weight: normal;
}
.progress-bar {
width: 100%;
background-color: $light-gray;
}
input.image_ajax_attachment[type=file]{
display: none;
}
.file-name {
margin-top: 0px;
}
.loading-bar {
height: 5px;
width: 0;
transition: width 500ms ease-out;
&.uploading {
background-color: $dark-gray;
}
&.complete {
background-color: $success-color;
width: 100%;
}
&.errors {
background-color: $alert-color;
width: 100%;
margin-top: $line-height / 2;
}
}
.loading-bar.no-transition {
transition: none;
}
}

View File

@@ -2282,3 +2282,59 @@ table {
}
}
// 19. Images
.image-form form {
.source-option-link {
input {
padding-bottom: 0;
}
.error {
margin-bottom: $line-height;
}
label {
&.error {
margin-bottom: 0;
}
}
}
.source-option-file {
.file-name {
label {
@include breakpoint(small medium) {
float: none;
}
@include breakpoint(large) {
float: left;
}
}
p {
@include breakpoint(small medium) {
float: none;
margin-top: 0;
margin-left: 0;
margin-bottom: 0;
}
@include breakpoint(large) {
float: left;
margin-bottom: 0;
margin-top: $line-height / 2;
margin-left: $line-height;
}
}
}
}
.attachment-errors {
margin-bottom: $line-height;
}
}

View File

@@ -252,13 +252,13 @@
.document-form,
.topic-new,
.topic-form,
.budget-investment-image-form {
.image-form {
.icon-debates,
.icon-proposals,
.icon-budget,
.icon-documents,
.icon-budget-investment-image {
.icon-image {
font-size: rem-calc(50);
line-height: $line-height;
opacity: 0.5;
@@ -277,7 +277,7 @@
color: $budget;
}
.icon-budget-investment-image {
.icon-image {
color: $budget-investment-image;
}
}
@@ -315,7 +315,7 @@
}
}
.budget-investment-image-form {
.image-form {
.recommendations li::before {
color: $budget-investment-image;
@@ -955,7 +955,7 @@
}
}
.budget-investment-image-form {
.image-form {
@include image-upload;

View File

@@ -45,11 +45,13 @@ module Budgets
load_investment_votes(@investment)
@investment_ids = [@investment.id]
@document = Document.new(documentable: @investment)
@image = Image.new(imageable: @investment)
end
def create
@investment.author = current_user
recover_documents_from_cache(@investment)
recover_image_from_cache(@investment)
if @investment.save
Mailer.budget_investment_created(@investment).deliver_later
@@ -80,24 +82,6 @@ module Budgets
super
end
def edit_image
end
def update_image
if @investment.update(investment_params)
redirect_to budget_investment_path(@investment.budget, @investment),
notice: t("flash.actions.update_image.budget_investment")
else
render :edit_image
end
end
def remove_image
@investment.image.destroy!
redirect_to budget_investment_path(@investment.budget, @investment),
notice: t("flash.actions.remove_image.budget_investment")
end
private
def resource_model
@@ -124,9 +108,9 @@ module Budgets
def investment_params
params.require(:budget_investment)
.permit(:title, :description, :external_url, :heading_id,
:tag_list, :organization_name, :location, :terms_of_service,
image_attributes: [:title, :attachment],
.permit(:title, :description, :external_url, :heading_id, :tag_list,
:organization_name, :location, :terms_of_service,
image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id],
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id])
end

View File

@@ -59,6 +59,7 @@ module CommentableActions
def update
resource.assign_attributes(strong_params)
recover_documents_from_cache(resource)
recover_image_from_cache(resource)
if resource.save
redirect_to resource, notice: t("flash.actions.update.#{resource_name.underscore}")
@@ -119,4 +120,10 @@ module CommentableActions
end
end
def recover_image_from_cache(resource)
return false unless resource.try(:image)
resource.image.attachment = resource.image.set_attachment_from_cached_attachment if resource.image.cached_attachment.present?
end
end

View File

@@ -0,0 +1,106 @@
class ImagesController < ApplicationController
before_action :authenticate_user!
before_filter :find_imageable, except: :destroy
before_filter :prepare_new_image, only: [:new, :new_nested]
before_filter :prepare_image_for_creation, only: :create
before_filter :find_image, only: :destroy
load_and_authorize_resource except: :upload
skip_authorization_check only: :upload
def new
end
def new_nested
end
def create
recover_attachments_from_cache
if @image.save
flash[:notice] = t "images.actions.create.notice"
redirect_to params[:from]
else
flash[:alert] = t "images.actions.create.alert"
render :new
end
end
def destroy
respond_to do |format|
format.html do
if @image.destroy
flash[:notice] = t "images.actions.destroy.notice"
else
flash[:alert] = t "images.actions.destroy.alert"
end
redirect_to params[:from]
end
format.js do
if @image.destroy
flash.now[:notice] = t "images.actions.destroy.notice"
else
flash.now[:alert] = t "images.actions.destroy.alert"
end
end
end
end
def destroy_upload
@image = Image.new(cached_attachment: params[:path])
@image.set_attachment_from_cached_attachment
@image.cached_attachment = nil
@image.imageable = @imageable
if @image.attachment.destroy
flash.now[:notice] = t "images.actions.destroy.notice"
else
flash.now[:alert] = t "images.actions.destroy.alert"
end
render :destroy
end
def upload
@image = Image.new(image_params.merge(user: current_user))
@image.imageable = @imageable
if @image.valid?
@image.attachment.save
@image.set_cached_attachment_from_attachment(URI(request.url))
else
@image.attachment.destroy
end
end
private
def image_params
params.require(:image).permit(:title, :imageable_type, :imageable_id,
:attachment, :cached_attachment, :user_id)
end
def find_imageable
@imageable = params[:imageable_type].constantize.find_or_initialize_by(id: params[:imageable_id])
end
def find_image
@image = Image.find(params[:id])
end
def prepare_new_image
@image = Image.new(imageable: @imageable)
end
def prepare_image_for_creation
@image = Image.new(image_params)
@image.imageable = @imageable
@image.user = current_user
end
def recover_attachments_from_cache
if @image.attachment.blank? && @image.cached_attachment.present?
@image.set_attachment_from_cached_attachment
end
end
end

View File

@@ -20,12 +20,14 @@ class ProposalsController < ApplicationController
super
@notifications = @proposal.notifications
@document = Document.new(documentable: @proposal)
@image = Image.new(imageable: @proposal)
redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal)
end
def create
@proposal = Proposal.new(proposal_params.merge(author: current_user))
recover_documents_from_cache(@proposal)
recover_image_from_cache(@proposal)
if @proposal.save
redirect_to share_proposal_path(@proposal), notice: I18n.t('flash.actions.create.proposal')
@@ -78,7 +80,8 @@ class ProposalsController < ApplicationController
def proposal_params
params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url,
:responsible_name, :tag_list, :terms_of_service, :geozone_id,
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id])
image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id],
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id] )
end
def retired_params

View File

@@ -22,7 +22,7 @@ module DocumentablesHelper
.join(",")
end
def humanized_accepted_content_types(documentable)
def documentable_humanized_accepted_content_types(documentable)
documentable.class.accepted_content_types
.collect{ |content_type| content_type.split("/").last }
.join(", ")
@@ -30,7 +30,7 @@ module DocumentablesHelper
def documentables_note(documentable)
t "documents.form.note", max_documents_allowed: max_documents_allowed(documentable),
accepted_content_types: humanized_accepted_content_types(documentable),
accepted_content_types: documentable_humanized_accepted_content_types(documentable),
max_file_size: max_file_size(documentable)
end

View File

@@ -4,7 +4,7 @@ module DocumentsHelper
document.attachment_file_name
end
def errors_on_attachment(document)
def document_errors_on_attachment(document)
document.errors[:attachment].join(', ') if document.errors.key?(:attachment)
end
@@ -71,7 +71,7 @@ module DocumentsHelper
class: "button hollow #{klass}"
if document.errors[:attachment].any?
html += content_tag :small, class: "error" do
errors_on_attachment(document)
document_errors_on_attachment(document)
end
end
end

View File

@@ -0,0 +1,36 @@
module ImageablesHelper
def imageable_class(imageable)
imageable.class.name.parameterize('_')
end
def imageable_max_file_size
bytesToMeg(Image::MAX_IMAGE_SIZE)
end
def bytesToMeg(bytes)
bytes / Numeric::MEGABYTE
end
def imageable_accepted_content_types
Image::ACCEPTED_CONTENT_TYPE
end
def imageable_accepted_content_types_extensions
Image::ACCEPTED_CONTENT_TYPE
.collect{ |content_type| ".#{content_type.split("/").last}" }
.join(",")
end
def imageable_humanized_accepted_content_types
Image::ACCEPTED_CONTENT_TYPE
.collect{ |content_type| content_type.split("/").last }
.join(", ")
end
def imageables_note(imageable)
t "images.form.note", accepted_content_types: imageable_humanized_accepted_content_types,
max_file_size: max_file_size(imageable)
end
end

View File

@@ -0,0 +1,98 @@
module ImagesHelper
def image_attachment_file_name(image)
image.attachment_file_name
end
def image_errors_on_attachment(image)
image.errors[:attachment].join(', ') if image.errors.key?(:attachment)
end
def image_bytesToMeg(bytes)
bytes / Numeric::MEGABYTE
end
def image_nested_field_name(image, field)
parent = image.imageable_type.parameterize.underscore
"#{parent.parameterize}[image_attributes]#{field}"
end
def image_nested_field_id(image, field)
parent = image.imageable_type.parameterize.underscore
"#{parent.parameterize}_image_attributes_#{field}"
end
def image_nested_field_wrapper_id
"nested_image"
end
def image_class(image)
image.persisted? ? "image" : "cached-image"
end
def render_destroy_image_link(image)
if image.persisted?
link_to t('images.form.delete_button'),
image_path(image, nested_image: true),
method: :delete,
remote: true,
data: { confirm: t('images.actions.destroy.confirm') },
class: "delete float-right"
elsif !image.persisted? && image.cached_attachment.present?
link_to t('images.form.delete_button'),
destroy_upload_images_path(path: image.cached_attachment,
nested_image: true,
imageable_type: image.imageable_type,
imageable_id: image.imageable_id),
method: :delete,
remote: true,
class: "delete float-right"
else
link_to t('images.form.delete_button'),
"#",
class: "delete float-right remove-image"
end
end
def render_image_attachment(image)
html = file_field_tag :attachment,
accept: imageable_accepted_content_types_extensions,
class: 'image_ajax_attachment',
data: {
url: image_direct_upload_url(image),
cached_attachment_input_field: image_nested_field_id(image, :cached_attachment),
multiple: false,
nested_image: true
},
name: image_nested_field_name(image, :attachment),
id: image_nested_field_id(image, :attachment)
if image.attachment.blank? && image.cached_attachment.blank?
klass = image.errors[:attachment].any? ? "error" : ""
html += label_tag image_nested_field_id(image, :attachment),
t("images.form.attachment_label"),
class: "button hollow #{klass}"
if image.errors[:attachment].any?
html += content_tag :small, class: "error" do
image_errors_on_attachment(image)
end
end
end
html
end
def render_image(image, version, show_caption = true)
version = image.persisted? ? version : :original
render partial: "images/image", locals: { image: image,
version: version,
show_caption: show_caption }
end
def image_direct_upload_url(image)
upload_images_url(
imageable_type: image.imageable_type,
imageable_id: image.imageable_id,
format: :js
)
end
end

View File

@@ -5,7 +5,7 @@ module InvestmentsHelper
end
def investment_image_advice_note(investment)
if investment.image.exists?
if investment.image.present?
t("budgets.investments.edit_image.edit_note", title: investment.title)
else
t("budgets.investments.edit_image.add_note", title: investment.title)
@@ -13,7 +13,7 @@ module InvestmentsHelper
end
def investment_image_button_text(investment)
investment.image.exists? ? t("budgets.investments.show.edit_image") : t("budgets.investments.show.add_image")
investment.image.present? ? t("budgets.investments.show.edit_image") : t("budgets.investments.show.add_image")
end
def errors_on_image(investment)

View File

@@ -74,6 +74,7 @@ module Abilities
cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation]
can [:create, :destroy], Document
can [:create, :destroy], Image
end
end
end

View File

@@ -40,6 +40,9 @@ module Abilities
can [:create, :destroy, :new], Document, documentable: { author_id: user.id }
can [:new_nested, :upload, :destroy_upload], Document
can [:create, :destroy, :new], Image, imageable: { author_id: user.id }
can [:new_nested, :upload, :destroy_upload], Image
unless user.organization?
can :vote, Debate
can :vote, Comment

View File

@@ -7,8 +7,8 @@ class Budget
include Reclassification
include Followable
include Communitable
include Documentable
include Imageable
include Documentable
documentable max_documents_allowed: 3,
max_file_size: 3.megabytes,
accepted_content_types: [ "application/pdf" ]

View File

@@ -15,6 +15,7 @@ module Documentable
@max_file_size = options[:max_file_size]
@accepted_content_types = options[:accepted_content_types]
end
end
end

View File

@@ -8,7 +8,7 @@ class Document < ActiveRecord::Base
belongs_to :documentable, polymorphic: true
# Disable paperclip security validation due to polymorphic configuration
# Paperclip do not allow to user Procs on valiations definition
# Paperclip do not allow to use Procs on valiations definition
do_not_validate_attachment_file_type :attachment
validate :attachment_presence
validate :validate_attachment_content_type, if: -> { attachment.present? }
@@ -64,7 +64,7 @@ class Document < ActiveRecord::Base
!accepted_content_types(documentable).include?(attachment_content_type)
errors[:attachment] = I18n.t("documents.errors.messages.wrong_content_type",
content_type: attachment_content_type,
accepted_content_types: humanized_accepted_content_types(documentable))
accepted_content_types: documentable_humanized_accepted_content_types(documentable))
end
end

View File

@@ -1,35 +1,62 @@
class Image < ActiveRecord::Base
include ImagesHelper
include ImageablesHelper
TITLE_LEGHT_RANGE = 4..80
MIN_SIZE = 475
MAX_IMAGE_SIZE = 1.megabyte
ACCEPTED_CONTENT_TYPE = %w(image/jpeg image/jpg)
attr_accessor :content_type, :original_filename, :attachment_data, :attachment_urls
has_attached_file :attachment, styles: { large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" },
path: ":rails_root/public/system/:class/:prefix/:style/:filename",
url: "/system/:class/:prefix/:style/:filename"
attr_accessor :cached_attachment
belongs_to :user
belongs_to :imageable, polymorphic: true
before_validation :set_styles
has_attached_file :attachment, styles: { large: "x475", medium: "300x300#", thumb: "140x245#" },
url: "/system/:class/:attachment/:imageable_name_path/:style/:hash.:extension",
hash_secret: Rails.application.secrets.secret_key_base
validates_attachment :attachment, presence: true, content_type: { content_type: %w(image/jpeg image/jpg) },
size: { less_than: 1.megabytes }
# Disable paperclip security validation due to polymorphic configuration
# Paperclip do not allow to use Procs on valiations definition
do_not_validate_attachment_file_type :attachment
validate :attachment_presence
validate :validate_attachment_content_type, if: -> { attachment.present? }
validate :validate_attachment_size, if: -> { attachment.present? }
validates :title, presence: true, length: { in: TITLE_LEGHT_RANGE }
validate :check_image_dimensions
validates :user_id, presence: true
validates :imageable_id, presence: true, if: -> { persisted? }
validates :imageable_type, presence: true, if: -> { persisted? }
validate :validate_image_dimensions, if: -> { attachment.present? && attachment.dirty? }
after_create :redimension_using_origin_styles
accepts_nested_attributes_for :imageable
after_save :remove_cached_image, if: -> { valid? && persisted? && cached_attachment.present? }
# # overwrite default styles for Image class
# def set_image_styles
# { large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" }
# end
def set_styles
if imageable
imageable.set_styles if imageable.respond_to? :set_styles
def set_cached_attachment_from_attachment(prefix)
self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
attachment.path
else
{ large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" }
prefix + attachment.url
end
end
Paperclip.interpolates :imageable_name_path do |attachment, _style|
attachment.instance.imageable.class.to_s.downcase.split('::').map(&:pluralize).join('/')
def set_attachment_from_cached_attachment
self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
File.open(cached_attachment)
else
URI.parse(cached_attachment)
end
end
Paperclip.interpolates :prefix do |attachment, style|
attachment.instance.prefix(attachment, style)
end
def prefix(attachment, style)
if !attachment.instance.persisted?
"cached_attachments/user/#{attachment.instance.user_id}"
else
":attachment/:id_partition"
end
end
private
@@ -38,11 +65,37 @@ class Image < ActiveRecord::Base
attachment.reprocess!
end
def check_image_dimensions
return unless attachment?
def validate_image_dimensions
dimensions = Paperclip::Geometry.from_file(attachment.queued_for_write[:original].path)
errors.add(:attachment, :min_image_width, required_min_width: MIN_SIZE) if dimensions.width < MIN_SIZE
errors.add(:attachment, :min_image_height, required_min_height: MIN_SIZE) if dimensions.height < MIN_SIZE
end
def validate_attachment_size
if imageable.present? &&
attachment_file_size > 1.megabytes
errors[:attachment] = I18n.t("images.errors.messages.in_between",
min: "0 Bytes",
max: "#{imageable_max_file_size} MB")
end
end
def validate_attachment_content_type
if imageable.present? &&
!imageable_accepted_content_types.include?(attachment_content_type)
errors[:attachment] = I18n.t("images.errors.messages.wrong_content_type",
content_type: attachment_content_type,
accepted_content_types: imageable_humanized_accepted_content_types)
end
end
def attachment_presence
if attachment.blank? && cached_attachment.blank?
errors[:attachment] = I18n.t("errors.messages.blank")
end
end
def remove_cached_image
File.delete(cached_attachment) if File.exists?(cached_attachment)
end
end

View File

@@ -10,6 +10,7 @@ class Proposal < ActiveRecord::Base
include Graphqlable
include Followable
include Communitable
include Imageable
include Documentable
documentable max_documents_allowed: 3,
max_file_size: 3.megabytes,

View File

@@ -21,6 +21,10 @@
<%= f.text_field :external_url %>
</div>
<div class="images small-12 column" data-max-images="1">
<%= render 'images/nested_images', imageable: @investment %>
</div>
<div class="documents small-12 column" data-max-documents="<%= Budget::Investment.max_documents_allowed %>">
<%= render 'documents/nested_documents', documentable: @investment %>
</div>
@@ -53,29 +57,6 @@
data: {js_url: suggest_tags_path} %>
</div>
<%= f.fields_for :image do |builder| %>
<div class="small-12 column">
<div class="image-upload">
<%= f.file_field :attachment, accept: 'image/jpg,image/jpeg', label: false, class:'show-for-sr' %>
<br>
<%= f.label :attachment, t("budgets.investments.form.image_label"), class:'button' %>
</div>
</div>
<% if @investment.errors.has_key?(:attachment) %>
<div class="small-12 column">
<div class="image-errors">
<small class="error"><%= errors_on_image(@investment)%></small>
</div>
</div>
<% end %>
<div class="small-12 column">
<%= f.text_field :title %>
</div>
<% end %>
<% unless current_user.manager? %>
<div class="small-12 column">

View File

@@ -1,53 +0,0 @@
<%= form_for([@investment.budget, @investment], url: update_image_budget_investment_path(@investment.budget, @investment), multipart: true, method: :put) do |f| %>
<%= render 'shared/errors', resource: @investment %>
<div class="row">
<div class="small-12 column">
<span class="note-marked">
<p><%= investment_image_advice_note(@investment) %></p>
</span>
</div>
<% if @investment.image.exists? %>
<div class="small-12 column">
<%= image_tag @investment.image_url(:large) %>
</div>
<% end %>
<div class="small-12 column">
<div class="image-upload">
<%= f.file_field :attachment, accept: 'image/jpg,image/jpeg', label: false, class:'show-for-sr' %>
<br>
<%= f.label :attachment, t("budgets.investments.edit_image.form.image_label"), class:'button' %>
</div>
</div>
<% if @investment.errors.has_key?(:image) %>
<div class="small-12 column">
<div class="image-errors">
<small class="error"><%= errors_on_image(@investment)%></small>
</div>
</div>
<% end %>
<div class="small-12 column">
<%= f.label :title, t("budgets.investments.edit_image.form.image_title") %>
<%= f.text_field :title, placeholder: t("budgets.investments.edit_image.form.image_title"), label: false %>
</div>
<div class="actions small-12 column">
<%= f.submit(class: "button", value: t("budgets.investments.edit_image.form.submit_button")) %>
<% if @investment.image.exists? %>
<div class="float-right">
<%= link_to t("budgets.investments.edit_image.form.remove_button"),
remove_image_budget_investment_path(@investment.budget, @investment),
class: "button hollow alert",
method: :delete,
data: { confirm: t("budgets.investments.edit_image.form.remove_alert") } %>
</div>
<% end %>
</div>
</div>
<% end %>

View File

@@ -3,11 +3,8 @@
<div class="row">
<div class="small-12 medium-3 large-2 column">
<% if investment.image.exists? %>
<figure>
<% if investment.image.present? %>
<%= image_tag investment.image_url(:thumb), alt: investment.image.title %>
<figcaption><%= investment.image.title %></figcaption>
</figure>
<% else %>
<div class="no-image"></div>
<% end %>

View File

@@ -10,9 +10,16 @@
class: 'button hollow float-right' %>
<% end %>
<% if can?(:edit_image, @investment) %>
<%= link_to investment_image_button_text(@investment),
edit_image_budget_investment_path(investment.budget, investment),
<% if can?(:create, @image) %>
<%= link_to t("images.upload_image"),
new_image_path(imageable_id:investment, imageable_type: investment.class.name, from: request.url),
class: 'button hollow float-right' %>
<% end %>
<% if @investment.image.present? && can?(:destroy, @investment.image) %>
<%= link_to t("images.remove_image"),
image_path(@investment.image, from: request.url),
method: :delete,
class: 'button hollow float-right' %>
<% end %>
@@ -28,20 +35,7 @@
<br>
<% if investment.image.exists? %>
<div class="row">
<div class="small-12 column text-center">
<figure>
<%= image_tag investment.image_url(:large),
alt: investment.image.title %>
<figcaption class="text-right">
<em><%= investment.image.title %></em>
</figcaption>
</figure>
<hr>
</div>
</div>
<% end %>
<%= render_image(investment.image, :large, true) if investment.image.present? %>
<p id="investment_code">
<%= t("budgets.investments.show.code_html", code: investment.id) %>
@@ -138,7 +132,7 @@
<%= render partial: 'shared/social_share', locals: {
share_title: t("budgets.investments.show.share"),
title: investment.title,
image_url: investment.image.exists? ? investment_image_full_url(investment, :thumb) : '',
image_url: investment.image.present? ? investment_image_full_url(investment, :thumb) : '',
url: budget_investment_url(budget_id: investment.budget_id, id: investment.id)
} %>

View File

@@ -1,19 +0,0 @@
<div class="budget-investment-image-form row">
<div class="small-12 medium-9 column">
<%= render "shared/back_link" %>
<h1><%= t("budgets.investments.edit_image.title") %></h1>
<%= render "image_form" %>
</div>
<div class="small-12 medium-3 column">
<span class="icon-budget-investment-image float-right"></span>
<h2><%= t("budgets.investments.edit_image.recommendation_title") %></h2>
<ul class="recommendations">
<li><%= t("budgets.investments.edit_image.recommendation_one") %></li>
<li><%= t("budgets.investments.edit_image.recommendation_two") %></li>
</ul>
</div>
</div>

View File

@@ -3,8 +3,9 @@
<p class="help-text"><%= documentables_note(documentable) %></p>
<% documentable.documents.each_with_index do |document, index| %>
<%= render 'documents/nested_fields', document: document, index: index, documentable: documentable %>
<%= render 'documents/nested_fields', document: document, index: index %>
<% end %>
</div>
<% unless max_documents_allowed?(documentable) %>

View File

@@ -39,7 +39,7 @@
<% if document.errors.has_key?(:attachment) %>
<div class="small-12 column source-option-file">
<div class="attachment-errors">
<small class="error"><%= errors_on_attachment(document) %></small>
<small class="error"><%= document_errors_on_attachment(document) %></small>
</div>
</div>
<% end %>

View File

@@ -16,7 +16,7 @@
</li>
<li>
<%= t "documents.recommendation_two_html",
accepted_content_types: humanized_accepted_content_types(@document.documentable) %>
accepted_content_types: documentable_humanized_accepted_content_types(@document.documentable) %>
</li>
<li>
<%= t "documents.recommendation_three_html",

View File

@@ -0,0 +1,20 @@
<%= form_for @image,
url: images_path(
imageable_type: @image.imageable_type,
imageable_id: @image.imageable_id,
from: params[:from]
),
html: { multipart: true, class: "imageable"},
data: { direct_upload_url: upload_images_url(imageable_type: @image.imageable_type, imageable_id: @image.imageable_id) } do |f| %>
<%= render 'shared/errors', resource: @image %>
<div class="row">
<%= render 'plain_fields', image: @image %>
<div class="actions small-12 medium-6 large-4 end column">
<%= f.submit(t("images.form.submit_button"), class: "button expanded") %>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,19 @@
<div class="row">
<div class="small-12 column text-center">
<figure>
<%= image_tag image.attachment.url(version),
class: image_class(image),
alt: image.title %>
<% if show_caption %>
<figcaption class="text-right">
<em><%= image.title %></em>
</figcaption>
<% end %>
</figure>
<% if show_caption %>
<hr>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<div id="<%= image_nested_field_wrapper_id %>" class="image">
<%= hidden_field_tag :id,
image.id,
name: image_nested_field_name(image, :id),
id: image_nested_field_id(image, :id) if image.persisted? %>
<%= hidden_field_tag :user_id,
current_user.id,
name: image_nested_field_name(image, :user_id),
id: image_nested_field_id(image, :user_id) %>
<%= hidden_field_tag :cached_attachment,
image.cached_attachment,
name: image_nested_field_name(image, :cached_attachment),
id: image_nested_field_id(image, :cached_attachment) %>
<%= label_tag :title, t("activerecord.attributes.image.title") %>
<%= text_field_tag :title,
image.title,
name: image_nested_field_name(image, :title),
id: image_nested_field_id(image, :title),
class: "image-title" %>
<% if image.errors[:title].any? %>
<small class="error"><%= image.errors[:title].join(", ") %></small>
<% end %>
<%= render_image(image, :thumb, false) if image.attachment.exists? %>
<%= render_image_attachment(image) %>
<%= render_destroy_image_link(image) %>
<p class="file-name"><%= image_attachment_file_name(image) %></p>
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>

View File

@@ -0,0 +1,16 @@
<div class="images-list">
<%= label_tag :image, t("images.form.title") %>
<p class="help-text"><%= imageables_note(imageable) %></p>
<%= render 'images/nested_fields', image: imageable.image if imageable.image.present? %>
</div>
<% if imageable.image.blank? %>
<%= link_to t("images.form.add_new_image"),
new_nested_images_path(imageable_type: imageable.class.name, index: 0),
remote: true,
id: "new_image_link",
class: "button hollow" %>
<% end %>
<hr>

View File

@@ -0,0 +1,50 @@
<div id="plain_image_fields" class="image">
<div class="small-12 column">
<%= label_tag :image_title, t("activerecord.attributes.image.title") %>
<%= text_field_tag :image_title, image.title, name: "image[title]", class: "image-title" %>
<% if image.errors.has_key?(:title) %>
<small class="error"><%= image.errors[:title].join(", ") %></small>
<% end %>
</div>
<div class="small-12 column">
<%= hidden_field_tag :cached_attachment, image.cached_attachment, name: "image[cached_attachment]" %>
<%= file_field_tag :attachment,
accept: imageable_accepted_content_types_extensions,
label: false,
class: 'image_ajax_attachment',
data: {
url: upload_images_url(imageable_type: image.imageable_type, imageable_id: image.imageable_id),
cached_attachment_input_field: "image_cached_attachment",
multiple: false,
nested_image: false
},
id: "image_attachment",
name: "image[attachment]" %>
<% if image.cached_attachment.blank? %>
<%= label_tag :image_attachment, t("images.form.attachment_label"), class: 'button hollow' %>
<% else %>
<%= link_to t('images.form.delete_button'),
destroy_upload_images_path(path: image.cached_attachment,
nested_image: false,
imageable_type: image.imageable_type,
imageable_id: image.imageable_id),
method: :delete,
remote: true,
class: "delete float-right" %>
<% end %>
<% if image.errors.has_key?(:attachment) %>
<div class="small-12 column source-option-file">
<div class="attachment-errors">
<small class="error"><%= image_errors_on_attachment(image) %></small>
</div>
</div>
<% end %>
<p class="file-name"><%= image_attachment_file_name(image) %></p>
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<% if params[:nested_image] == "true" %>
App.Imageable.destroyNestedImage("<%= image_nested_field_wrapper_id %>", "<%= j render('layouts/flash') %>")
<% new_image_link = link_to t("images.form.add_new_image"),
new_nested_images_path(imageable_type: @image.imageable_type),
remote: true,
id: "new_image_link",
class: "button hollow" %>
App.Imageable.updateNewImageButton("<%= j new_image_link %>")
<% else %>
App.Imageable.replacePlainImage("plain_image_fields",
"<%= j render('layouts/flash') %>",
"<%= j render('plain_fields', image: @image) %>")
<% end %>

View File

@@ -0,0 +1,26 @@
<div class="image-form <%= imageable_class(@image.imageable) %> row">
<div class="small-12 medium-9 column">
<%= back_link_to params[:from] %>
<h1><%= t("images.new.title") %></h1>
<%= render "form", form_url: images_url %>
</div>
<div class="small-12 medium-3 column">
<span class="icon-images float-right"></span>
<h2><%= t("images.recommendations_title") %></h2>
<ul class="recommendations">
<li>
<%= t "images.recommendation_one_html" %>
</li>
<li>
<%= t "images.recommendation_two_html",
accepted_content_types: imageable_humanized_accepted_content_types %>
</li>
<li>
<%= t "images.recommendation_three_html",
max_file_size: imageable_max_file_size %>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,9 @@
<%
new_image_link = link_to t("images.form.add_new_image"),
new_nested_images_path(imageable_type: params[:imageable_type]),
remote: true,
id: "new_image_link",
class: "button hollow"
%>
App.Imageable.new("<%= j render('images/nested_fields', image: @image) %>")
App.Imageable.updateNewImageButton("<%= j new_image_link %>")

View File

@@ -0,0 +1,12 @@
<% if params[:nested_image] == "true" %>
App.Imageable.uploadNestedImage("<%= image_nested_field_wrapper_id %>",
"<%= j render('images/nested_fields', image: @image) %>",
<%= @image.cached_attachment.present? %>)
<% else %>
App.Imageable.uploadPlainImage("plain_image_fields",
"<%= j render('images/plain_fields', image: @image) %>",
<%= @image.cached_attachment.present? %>)
<% end %>

View File

@@ -46,6 +46,10 @@
<%= f.text_field :external_url, placeholder: t("proposals.form.proposal_external_url"), label: false %>
</div>
<div class="images small-12 column" data-max-images="1">
<%= render 'images/nested_images', imageable: @proposal %>
</div>
<div class="documents small-12 column" data-max-documents="<%= Proposal.max_documents_allowed %>">
<%= render 'documents/nested_documents', documentable: @proposal %>
</div>

View File

@@ -48,6 +48,8 @@
</div>
<%= render_image(@proposal.image, :large, true) if @proposal.image.present? %>
<br>
<p>
<%= t("proposals.show.code") %>
@@ -106,7 +108,8 @@
</div>
<aside class="small-12 medium-3 column">
<% if can_create_document?(@document, @proposal) || author_of_proposal?(@proposal) || current_editable?(@proposal) %>
<% if can_create_document?(@document, @proposal) || author_of_proposal?(@proposal) || current_editable?(@proposal) ||
can?(:create, @image) || (@proposal.image.present? && can?(:destroy, @proposal.image)) %>
<div class="sidebar-divider"></div>
<h2><%= t("proposals.show.author") %></h2>
<div class="show-actions-menu">
@@ -132,6 +135,22 @@
<%= t("proposals.show.edit_proposal_link") %>
<% end %>
<% end %>
<% if can?(:create, @image) %>
<%= link_to new_image_path(imageable_id: @proposal, imageable_type: @proposal.class.name, from: request.url),
class: 'button hollow float-right' do %>
<%= t("images.upload_image") %>
<% end %>
<% end %>
<% if @proposal.image.present? && can?(:destroy, @proposal.image) %>
<%= link_to image_path(@proposal.image, from: request.url),
method: :delete,
class: 'button hollow float-right',
data: { confirm: 'Are you sure?' } do %>
<% t("images.remove_image") %>
<% end %>
<% end %>
</div>
<% end %>

View File

@@ -40,6 +40,7 @@ data:
- config/locales/%{locale}/legislation.yml
- config/locales/%{locale}/community.yml
- config/locales/%{locale}/documents.yml
- config/locales/%{locale}/images.yml
# Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
# `i18n-tasks normalize -p` will force move the keys according to these rules

View File

@@ -79,6 +79,9 @@ en:
documents:
one: "Document"
other: "Documents"
images:
one: "Image"
other: "Images"
attributes:
budget:
name: "Name"
@@ -205,6 +208,9 @@ en:
document:
title: Title
attachment: Attachment
image:
title: Title
attachment: Attachment
errors:
models:
user:

View File

@@ -181,6 +181,7 @@ en:
signature_sheet: Signature sheet
document: Document
topic: Topic
image: Image
geozones:
none: All city
all: All scopes

View File

@@ -0,0 +1,33 @@
en:
images:
upload_image: Upload image
remove_image: Remove image
form:
title: Descriptive image
attachment_label: Choose image
submit_button: Upload image
delete_button: Remove image
note: "You can upload one image of following content types: %{accepted_content_types}, up to %{max_file_size} MB."
add_new_image: Add image
new:
title: Upload image
recommendations_title: File upload tips
recommendation_one_html: You can upload only one image. Proposals with image attract more attention of the users.
recommendation_two_html: You can upload <strong>%{accepted_content_types}</strong> image.
recommendation_three_html: You can upload one image up to <strong>%{max_file_size} MB</strong>.
actions:
create:
notice: Image was created successfully.
alert: Cannot create image. Check form errors and try again.
destroy:
notice: Image was deleted successfully.
alert: Cannot destroy image.
confirm: Are you sure you want to delete the image? This action cannot be undone!
buttons:
download_image: Dowload file
destroy_image: Destroy
errors:
messages:
in_between: must be in between %{min} and %{max}
wrong_content_type: content type %{content_type} does not match any of accepted content types %{accepted_content_types}

View File

@@ -79,6 +79,9 @@ es:
documents:
one: "Documento"
other: "Documentos"
images:
one: "Imagen"
other: "Imágenes"
attributes:
budget:
name: "Nombre"
@@ -99,8 +102,7 @@ es:
title: "Título"
location: "Ubicación"
organization_name: "Si estás proponiendo en nombre de una organización o colectivo, escribe su nombre"
image: "Imagen descriptiva de la propuesta"
image_title: "Título de la imagen"
image: "Imagen descriptiva de la propuesta de inversión"
comment:
body: "Comentario"
user: "Usuario"
@@ -200,6 +202,9 @@ es:
document:
title: Título
attachment: Archivo adjunto
image:
title: Título
attachment: Archivo adjunto
errors:
models:
user:

View File

@@ -0,0 +1,33 @@
es:
images:
upload_image: Subir imagen
remove_image: Eliminar imagen
form:
title: Imagen descriptiva
attachment_label: Selecciona una imagen
submit_button: Subir imagen
delete_button: Eliminar imagen
note: "Puedes subir una imagen en los formatos: %{accepted_content_types}, y de hasta %{max_file_size} MB por archivo."
add_new_image: Añadir imagen
new:
title: Subir una imagen
recommendations_title: Consejos para subir imágenes
recommendation_one_html: Puedes subir una imagen descriptiva, esta imagen se mostrará en los listados y ayudará a que tu propuesta llame más la atención de los usuarios.
recommendation_two_html: "Sólo puedes subir <strong>imágenes en los formatos: %{accepted_content_types}</strong>."
recommendation_three_html: Puedes subir una imagen de hasta <strong>%{max_file_size} MB</strong>
actions:
create:
notice: "La imagen se ha creado correctamente."
alert: "La imagen no se ha podido crear. Revise los errores del formulario."
destroy:
notice: "La imagen se ha eliminado correctamente."
alert: "La imagen no se ha podido eliminar."
confirm: "¿Está seguro de que desea eliminar la imagen? Esta acción no se puede deshacer!"
buttons:
download_image: Descargar imagen
destroy_image: Eliminar
errors:
messages:
in_between: debe estar entre %{min} y %{max}
wrong_content_type: El tipo de contenido %{content_type} de la imagen no coincide con ninguno de los tipos de contenido aceptados %{accepted_content_types}

View File

@@ -39,7 +39,6 @@ Rails.application.routes.draw do
concern :imageable do
resources :images
end
resources :debates do
member do
post :vote
@@ -84,9 +83,6 @@ Rails.application.routes.draw do
resources :investments, controller: "budgets/investments", only: [:index, :new, :create, :show, :destroy], concerns: :imageable do
member do
post :vote
get :edit_image
put :update_image
delete :remove_image
end
collection { get :suggest }
end
@@ -112,6 +108,14 @@ Rails.application.routes.draw do
end
end
resources :images, only: [:new, :create, :destroy] do
collection do
get :new_nested
delete :destroy_upload
post :upload
end
end
resources :stats, only: [:index]
resources :legacy_legislations, only: [:show], path: 'legislations'

View File

@@ -0,0 +1,5 @@
class AddUserIdToImages < ActiveRecord::Migration
def change
add_reference :images, :user, index: true, foreign_key: true
end
end

View File

@@ -398,9 +398,11 @@ ActiveRecord::Schema.define(version: 20170918231410) do
t.string "attachment_content_type"
t.integer "attachment_file_size"
t.datetime "attachment_updated_at"
t.integer "user_id"
end
add_index "images", ["imageable_type", "imageable_id"], name: "index_images_on_imageable_type_and_imageable_id", using: :btree
add_index "images", ["user_id"], name: "index_images_on_user_id", using: :btree
create_table "legacy_legislations", force: :cascade do |t|
t.string "title"
@@ -1093,6 +1095,7 @@ ActiveRecord::Schema.define(version: 20170918231410) do
add_foreign_key "geozones_polls", "geozones"
add_foreign_key "geozones_polls", "polls"
add_foreign_key "identities", "users"
add_foreign_key "images", "users"
add_foreign_key "legislation_draft_versions", "legislation_processes"
add_foreign_key "locks", "users"
add_foreign_key "managers", "users"

View File

@@ -319,14 +319,20 @@ FactoryGirl.define do
valuation_finished true
end
trait :with_descriptive_image do
association :image, factory: :image
end
end
factory :image, class: 'Image' do
factory :image do
attachment { File.new("spec/fixtures/files/clippy.jpg") }
title "Lorem ipsum dolor sit amet"
association :user, factory: :user
trait :proposal_image do
association :imageable, factory: :proposal
end
trait :budget_investment_image do
association :imageable, factory: :budget_investment
end
end
factory :budget_ballot, class: 'Budget::Ballot' do

View File

@@ -29,17 +29,17 @@ feature 'Budget Investments' do
end
scenario 'Index should show investment descriptive image only when is defined' do
investment = FactoryGirl.create(:budget_investment, heading: heading)
investment_with_image = FactoryGirl.create(:budget_investment, :with_descriptive_image, heading: heading)
investment = create(:budget_investment, heading: heading)
investment_with_image = create(:budget_investment, heading: heading)
image = create(:image, imageable: investment_with_image)
visit budget_investments_path(budget, heading_id: heading.id)
within("#budget_investment_#{investment.id}") do
expect(page).not_to have_css("picture")
expect(page).not_to have_css("img")
end
within("#budget_investment_#{investment_with_image.id}") do
expect(page).to have_css("picture img[alt='#{investment_with_image.image.title}']")
expect(page).to have_css("img[alt='#{investment_with_image.image.title}']")
end
end
@@ -370,16 +370,6 @@ feature 'Budget Investments' do
expect(page).not_to have_selector ".js-follow"
end
scenario "Show descriptive image when exists" do
user = create(:user)
login_as(user)
investment_with_image = create(:budget_investment, :with_descriptive_image, heading: heading)
visit budget_investment_path(budget_id: budget.id, id: investment_with_image.id)
expect(page).to have_css("img[alt='#{investment_with_image.image.title}']")
end
scenario "Show back link contains heading id" do
investment = create(:budget_investment, heading: heading)
visit budget_investment_path(budget, investment)
@@ -387,241 +377,6 @@ feature 'Budget Investments' do
expect(page).to have_link "Go back", href: budget_investments_path(budget, heading_id: investment.heading)
end
context "Show investment image button" do
scenario "Should show add text when investment has not image" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Add image"
end
scenario "Should show edit text when investment has already an image" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(author)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Edit image"
end
scenario "Should not be shown for anonymous users" do
investment = create(:budget_investment, heading: heading)
visit budget_investment_path(budget, investment)
expect(page).not_to have_link "Edit image", href: edit_image_budget_investment_path(budget, investment)
end
scenario "Should not be shown when current user is not investment author" do
investment = create(:budget_investment, heading: heading)
visit budget_investment_path(budget, investment)
expect(page).not_to have_link "Edit image", href: edit_image_budget_investment_path(budget, investment)
end
scenario "Should be shown when current user is investment author" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Add image", href: edit_image_budget_investment_path(budget, investment)
end
scenario "Should be shown when current user is administrator" do
administrator = create(:administrator).user
investment = create(:budget_investment, heading: heading, author: author)
login_as(administrator)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Add image", href: edit_image_budget_investment_path(budget, investment)
end
end
scenario "Create new investment project should show image title validation errors when image selected and title blank" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit new_budget_investment_path(budget)
select 'Health: More hospitals', from: 'budget_investment_heading_id'
fill_in 'budget_investment_title', with: 'Build a skyscraper'
fill_in 'budget_investment_description', with: 'I want to live in a high tower over the clouds'
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
check 'budget_investment_terms_of_service'
click_button "Create Investment"
expect(page).to have_content "can't be blank, is too short (minimum is 4 characters)"
end
scenario "Create new investment project should show image validation error when image selected is too small" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit new_budget_investment_path(budget)
select 'Health: More hospitals', from: 'budget_investment_heading_id'
fill_in 'budget_investment_title', with: 'Build a skyscraper'
fill_in 'budget_investment_description', with: 'I want to live in a high tower over the clouds'
attach_file :budget_investment_image, "spec/fixtures/files/logo_header.jpg"
check 'budget_investment_terms_of_service'
click_button "Create Investment"
expect(page).to have_content "Image dimensions are too small. For a good quality please upload a larger image. Minimum width: 475px, minimum height: 475px."
end
scenario "Edit image page should show image title validation errors when image selected and title blank" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
click_button "Save image"
expect(page).to have_content "can't be blank, is too short (minimum is 4 characters)"
end
scenario "Edit image page should show image validation error when image selected is too small" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
attach_file :budget_investment_image, "spec/fixtures/files/logo_header.jpg"
click_button "Save image"
expect(page).to have_content "Image dimensions are too small. For a good quality please upload a larger image. Minimum width: 475px, minimum height: 475px."
end
scenario "Edit image page should not be accesible when there is no logged user" do
investment = create(:budget_investment, heading: heading, author: author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "You must sign in or register to continue"
end
scenario "Edit image page should redirect to investment show page if logged user is not the author" do
other_author = create(:user, :level_two, username: 'Manuel')
investment = create(:budget_investment, heading: heading, author: author)
login_as(other_author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "You do not have permission to carry out the action 'edit_image' on budget/investment."
end
scenario "Edit image page should be accesible when author is currently logged" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "Change your project image"
end
scenario "Edit image page should be accesible when there is logged any administrator" do
administrator = create(:administrator).user
investment = create(:budget_investment, heading: heading, author: author)
login_as(administrator)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "Change your project image"
end
scenario "Remove image button should not be present when investment image does not exists" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).not_to have_link "Remove image"
end
scenario "Remove image button should be present when investment has an image defined" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_link "Remove image"
end
scenario "Remove image button should be possible for administrators" do
administrator = create(:administrator).user
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(administrator)
visit edit_image_budget_investment_path(budget, investment)
click_link "Remove image"
expect(page).to have_content "Investment project image removed succesfully."
end
scenario "Remove image should be possible for investment author" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
click_link "Remove image"
expect(page).to have_content "Investment project image removed succesfully."
end
scenario "Remove image should not be possible for any other logged users (except administrators and author)" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(create(:user))
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "You do not have permission to carry out the action 'edit_image' on budget/investment."
end
scenario "Update image should not be possible if logged user is not the author" do
other_author = create(:user, :level_two, username: 'Manuel')
investment = create(:budget_investment, heading: heading, author: author)
login_as(other_author)
visit edit_image_budget_investment_path(investment.budget, investment)
expect(current_path).not_to eq(edit_image_budget_investment_path(investment.budget, investment))
expect(page).to have_content 'You do not have permission'
end
scenario "Update image should be possible for authors" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(investment.budget, investment)
fill_in :budget_investment_image_title, with: "New image title"
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
click_on "Save image"
within ".budget-investment-show" do
expect(page).to have_css("img[src*='clippy.jpg']")
end
expect(page).to have_content 'Investment project image updated succesfully. '
end
scenario "Update image should be possible for administrators" do
administrator = create(:administrator).user
investment = create(:budget_investment, heading: heading, author: author)
login_as(administrator)
visit edit_image_budget_investment_path(investment.budget, investment)
fill_in :budget_investment_image_title, with: "New image title"
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
click_on "Save image"
within ".budget-investment-show" do
expect(page).to have_css("img[src*='clippy.jpg']")
end
expect(page).to have_content 'Investment project image updated succesfully. '
end
context "Show (feasible budget investment)" do
let(:investment) do
create(:budget_investment,
@@ -707,6 +462,8 @@ feature 'Budget Investments' do
end
end
it_behaves_like "imageable", "budget_investment", "budget_investment_path", { "budget_id": "budget_id", "id": "id" }
it_behaves_like "followable", "budget_investment", "budget_investment_path", { "budget_id": "budget_id", "id": "id" }
it_behaves_like "documentable", "budget_investment", "budget_investment_path", {"budget_id": "budget_id", "id": "id"}

View File

@@ -1272,6 +1272,8 @@ feature 'Proposals' do
expect(Flag.flagged?(user, proposal)).to_not be
end
it_behaves_like "imageable", "proposal", "proposal_path", { "id": "id" }
it_behaves_like "followable", "proposal", "proposal_path", { "id": "id" }
it_behaves_like "documentable", "proposal", "proposal_path", { "id": "id" }

View File

@@ -20,6 +20,9 @@ describe "Abilities::Administrator" do
let(:budget_investment_document) { build(:document, documentable: budget_investment) }
let(:poll_question_document) { build(:document, documentable: poll_question) }
let(:proposal_image) { build(:image, imageable: proposal) }
let(:budget_investment_image) { build(:image, imageable: budget_investment) }
let(:hidden_debate) { create(:debate, :hidden) }
let(:hidden_comment) { create(:comment, :hidden) }
let(:hidden_proposal) { create(:proposal, :hidden) }
@@ -82,6 +85,10 @@ describe "Abilities::Administrator" do
it { should be_able_to(:create, proposal_document) }
it { should be_able_to(:destroy, proposal_document) }
it { should be_able_to(:new, proposal_image) }
it { should be_able_to(:create, proposal_image) }
it { should be_able_to(:destroy, proposal_image) }
it { should be_able_to(:new, budget_investment_document) }
it { should be_able_to(:create, budget_investment_document) }
it { should be_able_to(:destroy, budget_investment_document) }
@@ -89,4 +96,9 @@ describe "Abilities::Administrator" do
it { should be_able_to(:new, poll_question_document) }
it { should be_able_to(:create, poll_question_document) }
it { should be_able_to(:destroy, poll_question_document) }
it { should be_able_to(:new, budget_investment_image) }
it { should be_able_to(:create, budget_investment_image) }
it { should be_able_to(:destroy, budget_investment_image) }
end

View File

@@ -29,82 +29,7 @@ describe Budget::Investment do
end
end
describe "#image and #image_title", :investment_image do
let(:investment_with_image) { build(:budget_investment, :with_descriptive_image) }
it "should be valid" do
expect(investment_with_image).to be_valid
end
describe "file extension" do
it "should not be valid with '.png' extension" do
investment_with_image.image = File.new("spec/fixtures/files/clippy.png")
expect(investment_with_image).to_not be_valid
expect(investment_with_image.errors[:image].size).to eq(1)
end
it "should not be valid with '.gif' extension" do
investment_with_image.image = File.new("spec/fixtures/files/clippy.gif")
expect(investment_with_image).to_not be_valid
expect(investment_with_image.errors[:image].size).to eq(1)
end
it "should be valid with '.jpg' extension" do
investment_with_image.image = File.new("spec/fixtures/files/clippy.jpg")
expect(investment_with_image).to be_valid
end
end
describe "image dimmessions" do
it "should be valid when image dimmessions are 475X475 at least" do
expect(investment_with_image).to be_valid
end
it "should not be valid when image dimmensions are smaller than 475X475" do
investment_with_image.image = File.new("spec/fixtures/files/logo_header.jpg")
expect(investment_with_image).not_to be_valid
end
end
describe "title" do
it "should be valid when image and image_title are not defined" do
investment_with_image.image = nil
investment_with_image.image_title = nil
expect(investment_with_image).to be_valid
end
it "should not be valid when correct image attached but no image title provided" do
investment_with_image.image_title = ''
expect(investment_with_image).to_not be_valid
end
it "should not be valid when image title is too short" do
investment_with_image.image_title = 'a'*3
expect(investment_with_image).to_not be_valid
end
it "should not be valid when image title is too long" do
investment_with_image.image_title = 'a'*81
expect(investment_with_image).to_not be_valid
end
end
it "image destroy should remove image from file storage" do
image_url = investment_with_image.image.url
expect{ investment_with_image.image.destroy }.to change{ investment_with_image.image.url }.from(image_url).to("/images/original/missing.png")
end
end
it_behaves_like "acts as imageable", "budget_investment_image"
it "sanitizes description" do
investment.description = "<script>alert('danger');</script>"

View File

@@ -304,7 +304,7 @@ shared_examples "documentable" do |documentable_factory_name, documentable_path,
from: send(documentable_path, arguments))
expect(page).to have_content "You can upload up to a maximum of #{max_file_size(documentable)} documents."
expect(page).to have_content "You can upload #{humanized_accepted_content_types(documentable)} files."
expect(page).to have_content "You can upload #{documentable_humanized_accepted_content_types(documentable)} files."
expect(page).to have_content "You can upload files up to #{max_file_size(documentable)} MB."
end

View File

@@ -0,0 +1,331 @@
shared_examples "imageable" do |imageable_factory_name, imageable_path, imageable_path_arguments|
include ActionView::Helpers
include ImagesHelper
include ImageablesHelper
let!(:administrator) { create(:user) }
let!(:user) { create(:user) }
let!(:arguments) { {} }
let!(:imageable) { create(imageable_factory_name, author: user) }
let!(:imageable_dom_name) { imageable_factory_name.parameterize }
before do
create(:administrator, user: administrator)
imageable_path_arguments.each do |argument_name, path_to_value|
arguments.merge!("#{argument_name}": imageable.send(path_to_value))
end
end
context "Show" do
scenario "Show descriptive image when exists", :js do
image = create(:image, imageable: imageable)
visit send(imageable_path, arguments)
expect(page).to have_css("img[alt='#{image.title}']")
end
scenario "Show image title when image exists" do
image = create(:image, imageable: imageable)
visit send(imageable_path, arguments)
expect(page).to have_content image.title
end
scenario "Should not display upload image button when there is no logged user" do
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).not_to have_link("Upload image")
end
end
scenario "Should not display upload image button when maximum number of images reached " do
create_list(:image, 3, imageable: imageable)
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).not_to have_link("Upload image")
end
end
scenario "Should display upload image button when user is logged in and is imageable owner" do
login_as(user)
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).to have_link("Upload image")
end
end
scenario "Should display upload image button when admin is logged in" do
login_as(administrator)
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).to have_link("Upload image")
end
end
scenario "Should navigate to new image page when click un upload button" do
login_as(user)
visit send(imageable_path, arguments)
click_link "Upload image"
expect(page).to have_selector("h1", text: "Upload image")
end
end
context "New" do
scenario "Should not be able for unathenticated users" do
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
expect(page).to have_content("You must sign in or register to continue.")
end
scenario "Should not be able for other users" do
login_as create(:user)
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
expect(page).to have_content("You do not have permission to carry out the action 'new' on image. ")
end
scenario "Should be able to imageable author" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
expect(page).to have_selector("h1", text: "Upload image")
end
scenario "Should display file name after file selection", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/empty.pdf", make_visible: true
sleep 1
expect(page).to have_content "empty.pdf"
end
scenario "Should not display file name after file selection", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/logo_header.png", make_visible: true
sleep 1
expect(page).not_to have_content "logo_header.jpg"
end
scenario "Should update loading bar style after valid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(page).to have_selector ".loading-bar.complete"
end
scenario "Should update loading bar style after unvalid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/logo_header.png", make_visible: true
sleep 1
expect(page).to have_selector ".loading-bar.errors"
end
scenario "Should update image title with attachment original file name after file selection if no title defined by user", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(find("input[name='image[title]']").value).to eq("clippy.jpg")
end
scenario "Should not update image title with attachment original file name after file selection when title already defined by user", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
fill_in :image_title, with: "My custom title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(find("input[name='image[title]']").value).to eq("My custom title")
end
scenario "Should update image cached_attachment field after valid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(find("input[name='image[cached_attachment]']", visible: false).value).to include("clippy.jpg")
end
scenario "Should not update image cached_attachment field after unvalid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/logo_header.png", make_visible: true
sleep 1
expect(find("input[name='image[cached_attachment]']", visible: false).value).to eq ""
end
scenario "Should show imageable custom recomentations" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
expect(page).to have_content "You can upload only one image. Proposals with image attract more attention of the users."
expect(page).to have_content "You can upload #{imageable_humanized_accepted_content_types} image."
expect(page).to have_content "You can upload one image up to #{imageable_max_file_size} MB."
end
end
context "Create" do
scenario "Should show validation errors" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
click_on "Upload image"
expect(page).to have_content "3 errors prevented this Image from being saved: "
expect(page).to have_selector "small.error", text: "can't be blank", count: 2
end
scenario "Should show error notice after unsuccessfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
attach_file :image_attachment, "spec/fixtures/files/empty.pdf"
sleep 1
click_on "Upload image"
expect(page).to have_content "Cannot create image. Check form errors and try again."
end
scenario "Should show success notice after successfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
fill_in :image_title, with: "Image title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg"
sleep 1
click_on "Upload image"
expect(page).to have_content "Image was created successfully."
end
scenario "Should redirect to imageable path after successfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
fill_in :image_title, with: "Image title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg"
sleep 1
click_on "Upload image"
within "##{dom_id(imageable)}" do
expect(page).to have_selector "h1", text: imageable.title
end
end
scenario "Should show new image on imageable images tab after successfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
fill_in :image_title, with: "Image title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg"
sleep 1
click_on "Upload image"
expect(page).to have_content "Image title"
end
end
context "Destroy" do
let!(:image) { create(:image, imageable: imageable, user: imageable.author) }
scenario "Should show success notice after successfull deletion by an admin" do
login_as administrator
visit send(imageable_path, arguments)
click_on "Remove image"
expect(page).to have_content "Image was deleted successfully."
end
scenario "Should show success notice after successfull deletion" do
login_as imageable.author
visit send(imageable_path, arguments)
click_on "Remove image"
expect(page).to have_content "Image was deleted successfully."
end
scenario "Should not show image after successful deletion" do
login_as imageable.author
visit send(imageable_path, arguments)
click_on "Remove image"
expect(page).not_to have_selector "figure img"
end
scenario "Should redirect to imageable path after successful deletion" do
login_as imageable.author
visit send(imageable_path, arguments)
click_on "Remove image"
within "##{dom_id(imageable)}" do
expect(page).to have_selector "h1", text: imageable.title
end
end
end
end

View File

@@ -0,0 +1,76 @@
shared_examples "acts as imageable" do |imageable_factory|
let!(:image) { build(:image, imageable_factory.to_sym) }
let!(:imageable) { image.imageable }
it "should be valid" do
expect(image).to be_valid
end
describe "file extension" do
it "should not be valid with '.png' extension" do
image.attachment = File.new("spec/fixtures/files/clippy.png")
expect(image).to_not be_valid
expect(image.errors[:attachment].size).to eq(1)
end
it "should not be valid with '.gif' extension" do
image.attachment = File.new("spec/fixtures/files/clippy.gif")
expect(image).to_not be_valid
expect(image.errors[:attachment].size).to eq(1)
end
it "should be valid with '.jpg' extension" do
image.attachment = File.new("spec/fixtures/files/clippy.jpg")
expect(image).to be_valid
end
end
describe "image dimmessions" do
it "should be valid when image dimmessions are 475X475 at least" do
expect(image).to be_valid
end
it "should not be valid when image dimmensions are smaller than 475X475" do
image.attachment = File.new("spec/fixtures/files/logo_header.jpg")
expect(image).not_to be_valid
end
end
describe "title" do
it "should not be valid when correct image attached but no image title provided" do
image.title = ''
expect(image).to_not be_valid
end
it "should not be valid when image title is too short" do
image.title = 'a' * 3
expect(image).to_not be_valid
end
it "should not be valid when image title is too long" do
image.title = 'a' * 81
expect(image).to_not be_valid
end
end
it "image destroy should remove image from file storage" do
image.save
image_url = image.attachment.url
expect{ image.attachment.destroy }.to change{ image.attachment.url }.from(image_url).to("/attachments/original/missing.png")
end
end