Shrine with Rails multiple polymorphic image uploads - image-uploading

I've been struggling for about 5 hours trying to understand why Shrine is blocking my uploads. I either get errors like "Shrine: Invalid file", or "Expected Array but got string" in strong params. If there aren't errors, the images aren't actually saved.
require "image_processing/mini_magick"
class ImageUploader < Shrine
include ImageProcessing::MiniMagick
plugin :activerecord
plugin :backgrounding
plugin :cached_attachment_data
plugin :determine_mime_type
plugin :delete_raw
plugin :direct_upload
plugin :logging, logger: Rails.logger
plugin :processing
plugin :remove_attachment
plugin :store_dimensions
plugin :validation_helpers
plugin :versions
Attacher.validate do
validate_max_size 2.megabytes, message: 'is too large (max is 2 MB)'
validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png', 'image/gif']
end
def process(io, context)
case context[:phase]
when :store
thumb = resize_to_limit!(io.download, 200, 200)
{ original: io, thumb: thumb }
end
end
end
class Image < ActiveRecord::Base
include ImageUploader[:image]
belongs_to :imageable, polymorphic: true
end
class Product < ApplicationRecord
has_many :images, as: :imageable, dependent: :destroy
accepts_nested_attributes_for :images, allow_destroy: true
...
# Strong Params:
def product_params
params.require(:product).permit(
:name, :brand_id, :category_id, :price, :compare_price, :description,
images_attributes: { image: [] },
product_properties_attributes: [:id, :property_id, :value]
)
...
And my view:
<%= f.fields_for :images do |image_form| %>
<%= image_form.file_field :image, multiple: true %>
<% end %>
According to everything I've read on the docs or from gorails, this should work. Do I need to restructure the images_attributes hash? I also tried using direct_uploads, but struggled to get the presigned_url to work with S3.
Refile makes this really easy, so I'll probably run crying back to that.
Is there something I'm obviously doing wrong?

According to the fields_for documentation, the provided block will be called for each image in the project.images collection. So if your product currently doesn't have any images, the block won't be called (according to the docs).
For nested attributes to work, you need to forward the following parameters when creating the Product:
product[images_attributes][0][image] = <file object or data hash>
product[images_attributes][1][image] = <file object or data hash>
product[images_attributes][2][image] = <file object or data hash>
...
If you look at the "Multiple Files" Shrine guide, it's recommended that you just have a single file field which accepts multiple files:
<input type="file" name="file" multiple>
And then setup direct uploads for this field using Uppy, dynamically generating the image field for each uploaded file populated with the uploaded file data hash:
<input type="hidden" name="product[images_attributes][0][image]" value='{"id":"...","storage":"cache","metadata":{...}}'>
<input type="hidden" name="product[images_attributes][1][image]" value='{"id":"...","storage":"cache","metadata":{...}}'>
....
Alternatively you can just let users attach multiple files, which are all submitted to the app, and then destructure them in the controller:
class ProductsController < ApplicationController
def create
images_attributes = params["files"].map { |file| {image: file} }
Product.create(product_params.merge(images_attributes: images_attributes))
end
end
In that case you have to make sure your HTML form has the enctype="multipart/form-data" attribute set (otherwise only the files' filenames will get submitted, not files themselves).

Related

django form to upload file returns error as form not valid

I am developing an app in Django.
My users are allowed to save data by compiling a form like this
Tool:
acronym:
definition:
defined by the following function, in forms.py:
class tool_form(forms.ModelForm):
class Meta:
model=tool
fields=["Tool", "Acronym", "Definition"]
That saves the data into a model like this:
class tool(models.Model):
Tool = models.CharField(max_length=256, blank=True, null=True)
Acronym = models.CharField(max_length=25, blank=True, null=True)
Definition = models.TextField(blank=True, null=True)
The view function allowing this, is:
def add_tool(request):
if request.method=='POST':
form = tool_form(request.POST or None)
if form.is_valid():
form.save()
messages.success(request, ("Submit succeed!"))
return redirect('adding_tools')
else:
messages.error(request, ('ERROR: submit failed'))
return render(request, 'adding_tools.html', {})
else:
return render(request, 'adding_tools.html', {})
Now I want my users to be able to copile many times of the same form, all at once.
In order to achieve this, I am allowing my users to upload a file copiled with the data to insert.
So I am allowing my users to download a template xlsx file with colums with given name
Column 1 name (cell A1): Tool
Column 2 name (cell B1): acronym
Column 3 name (cell C1): definition
To compile it, inserting many records, and then to upload it back.
So I want my code to save this data into the same model declared before (tool)
I am trying to achieve this by:
in template add_tool_sheet.html:
<form class="container" method="POST" enctype="multipart/form-data" >
{% csrf_token %}
<div class="file-upload-wrapper" id="input-file-now">
<small id="inputHelp" class="form-text text-muted">Select file to upload.</small>
<input type="file" name="uploaded_file" id="input-file-now" data-max-file-size="5M" class="file-upload">
<br><br>
<div class="form-group">
<input name="Date" type="hidden" class="form-control" id="date_to_turn_into_toda">
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
in forms.py:
class tool_file_form(forms.ModelForm):
class Meta:
model=tool_file
fields=["Tool_file", "Date"]
In models.py
class tool_file(models.Model):
Tool_file = models.FileField(upload_to='uploaded_sheets/', blank=False, null=False)
Date = models.DateField(blank=False, null=False, default=timezone.now().date() )
class Meta:
ordering = ['Date', 'Tool_file']
def clean(self):
if not (self.Tool_file or self.Date):
raise ValidationError("something went wrong")
def __str__(self):
return "%s ----- [%s]" % (self.Tool_file, self.Date)
in views.py:
def add_tool_sheet(request):
if request.method=='POST':
form = tool_file_form(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, ("upload succeeded"))
return redirect('add_tool_sheet')
else:
messages.error(request, ('ERROR n1'))
return render(request, 'add_tool_sheet.html', {})
else:
return render(request, 'add_tool_sheet.html', {})
When I try to add new objects in the model tool_file from admin section, it works.
But when I try to add new objects from the user interface (template add_tool_sheet.html), it returns
ERROR n1
as message, and my console returns
GET /admin/ HTTP/1.1" 200 7381
Why?
Please note:
The upload from admin section works, the upload from user interface does not.
SOLVED
In template I put
name="uploaded_file"
but in order to match with the information in forms.py and model.py, it has to be:
name="Tool_file"
Now it works!

Add online video to 'textmedia' element

In an extension, I would basically like to do what the core does when you click the button "Add media by URL" on an existing textmedia element.
I tried to look in the source code but did not find any convenient API function I could use. Perhaps I could use DataHandler for this?
What the core does is create a textfile (e.g. .youtube) which contains the video id. It then creates a record for this file in sys_file and a file reference (sys_file_reference) between the record in tt_content and the file record in sys_file.
I am using the latest TYPO3 8.
I don't have a complete solution, but I recently had to do something similar:
Create a file from the Video URL and create the corresponding sys_file record:
The class OnlineMediaController::createAction does what you are looking for. Specifically, the function OnlineMediaHelperRegistry::transformUrlToFile will transform the video URL to a file (creating it if necessary).
To use the existing action, you can use the Ajax Route online_media_create.
Or, you can use the existing action to model your own code.
Create a relation between existing records in sys_file and tt_content:
See Creating a file reference (TYPO3 documentation)
Sample code: (Most of the code taken from Creating a file reference)
use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Backend\Utility\BackendUtility;
...
protected function addMediaByUrl($url, $uid)
{
$targetFolder = $GLOBALS['BE_USER']->getDefaultUploadFolder();
$file = OnlineMediaHelperRegistry::getInstance()->transformUrlToFile(
$url,
$targetFolder
);
$contentElement = BackendUtility::getRecord('tt_content', $uid);
$newId = 'NEW1234';
$data = array();
$data['sys_file_reference'][$newId] = [
'table_local' => 'sys_file',
'uid_local' => $file->getUid(),
'tablenames' => 'tt_content',
'uid_foreign' => $contentElement['uid'],
'fieldname' => 'assets',
'pid' => $contentElement['pid']
];
$data['tt_content'][$contentElement['uid']] = [
'assets' => $newId
];
// Get an instance of the DataHandler and process the data
/** #var DataHandler $dataHandler */
$dataHandler = GeneralUtility::makeInstance(DataHandler::class);
$dataHandler->start($data, array());
$dataHandler->process_datamap();
// Error or success reporting
if (count($dataHandler->errorLog) === 0) {
// Handle success
} else {
// Handle error
}
}
Maybe you look here for good examples
YoutubeVideo, Vimeo, Video-Files, More Informations you can find at the vhs Docs
Or you could do something like this:
<flux:form id="youtubevideo" label="YouTubeVideo" description="Einbetten eines
YouTube Videos als iframe">
<flux:form.option name="optionsettings">
<flux:form.option.group value="Content" />
<flux:form.option.icon
value="EXT:extension_key/Resources/Public/Icons/Content/YouTubeVideo.svg" />
</flux:form.option>
<flux:field.input name="settings.videoid" label="YouTube Video ID. Entnehmen
Sie die ID aus der YouTube URL. Beispiel: https://www.youtube.com/watch?
v=UX12345678 Die ID ist UX12345678" />
<flux:field.select name="settings.videoformat" label="Video Format"
maxItems="1" multiple="0" default="YouTubeVideo--normal" items="{YouTubeVideo--
normal: 'Normal (4:3)', YouTubeVideo--widescreen: 'Widescreen (16:9)'}"/>
<flux:field.checkbox name="settings.gridPull" label="Bleed Outside (Randlos
glücklich)" default="0" />
</flux:form>
...
<div class="YouTubeVideo {settings.videoformat}">
<iframe width="640" height="480" src="//www.youtube-
nocookie.com/embed/{settings.videoid}?rel=0&showinfo=0" allowfullscreen>
</iframe>
</div>
Finally found the solution:
The key is to set the correct "allowed extensions": add "youtube" or "vimeo".
This will automatically add the missing "Add media by URL" button.
Example using Flux Inline FAL:
<f:section name="Configuration">
<flux:form id="myCustomVideoContentElement">
<flux:field.inline.fal name="settings.records" label="video"
multiple="false" minItems="1" maxItems="1"
allowedExtensions="mp4,mkv,youtube,vimeo"
/>
</flux:form>
</f:section>
(Typo3 9.5)

Rails 4 form_tag submitting to wrong controller action

This feels dumb, but so far everything I've read makes me feel like I'm doing it right but it's still not working.
I'm using a form_tag to submit params to a custom controller action. Instead of the action I indicate, it seems intents on submitting to the show action in my controller, which I need to reserve for profiles. Anyways here's the code (please excuse it's un-refactored state):
Doctors controller:
class DoctorsController < ApplicationController
def new
end
def show
#doctor_list = Doctor.find(params[:id])
end
def index
#doctor_list = Doctor.all
end
def search
end
def results
if params[:zip] && params[:zip].length === 5 && params[:zip]
#doctor_list = Doctor.where("zip = ?", params[:zip])
elsif params[:id]
begin
#doctor_list = []
#doctor_list<<Doctor.find(params[:id])
rescue
flash.now[:errors] = "That doctor does not exist!"
render 'search'
end
else
flash.now[:errors] = "That is not a valid zipcode!"
render 'search'
end
end
end
Routes:
resources :users
resources :doctors
root 'doctors#search'
get 'doctors/results' => 'doctors#results'
search.html.erb:
<% provide(:title, "Home") %>
<div class="hero">
<h1>Find an M.D.</h1>
<%= form_tag(doctors_results_path, method: "get") do %>
<%= label_tag("Zipcode: ") %>
<%= text_field_tag(:zip) %><br>
<%= submit_tag "FIND", class: "button"%>
<% end %>
</div>
Again, the issue is that I'm getting an error (Couldn't find Doctor with 'id'=results) because the form is using my show action vs. my results action. The application trace says the error is located at app/controllers/doctors_controller.rb:6:in 'show'. As an added confusion, I don't really understand why it's sending "id"=>"results" as part of the params hash on submit, but it seems like that might be a non-issue if it would use the correct controller action to begin with.
Thanks for any thoughts.
Yes its a priority issue. Since the resources :doctors is first, the GET show will be given the priority than get 'doctors/results' => 'doctors#results'
Moving the get 'doctors/results' => 'doctors#results' above resources :doctors should solve your problem
#routes.rb
root 'doctors#search'
get 'doctors/results' => 'doctors#results'
resources :users
resources :doctors

Jquery tokeninput and unobtrusive validation in a MVC 4 application

I am stuck here and would very much appreciate help. I have a form in a razor view with a input field for current city which looks like this:
#Html.LabelFor(x => x.UserModel.CurrentCity)
#Html.TextBoxFor(x => x.UserModel.CurrentCity, new { #data_bind = "value: UserModel.CurrentCity ", #class = "city", #data_val = "true", #data_val_required="City is required" })
#Html.ValidationMessageFor(x => x.UserModel.CurrentCity)
I want autocomplete for this field and am using jquery token input plugin for this like:
$(".city").tokenInput('#Url.Action("AutocompleteCity", "Settings")',{ minChars: 2, tokenLimit: 1, hintText: "Type in a city" });
$(".city").tokenInput("add", {name: viewModel.UserModel.CurrentCity()});
Everything works fine except the clientside unobtrusive validation. The form gets posted even if CurrentCity is empty.
I also tried to change the MVC helpers to plain html:
<input data-val="true" data-val-required="City is required" type="text" class="city" data-bind = "value: UserModel.CurrentCity, attr: { name: 'UserModel.CurrentCity', id: 'UserModel.CurrentCity'}" />
<span class="field-validation-valid" data-valmsg-for="UserModel.CurrentCity" data-valmsg-replace="true"></span>
This approach prevents the form from being submitted but the validation-error class is not injected into the span and the error message does not show up.
Any suggestions?
The original input element you created is hidden. You will likely need to enable validation of hidden elements: jquery.validate v. 1.9 ignores some hidden inputs or https://stackoverflow.com/a/13295938/173225.

Symfony2 Forms: is it possible to bind a form in an "unconventional way"?

Imagine this scenario: in our company there is an employee that "play" around graphic,css,html and so on.
Our new project will born under symfony2 so we're trying some silly - but "real" - stuff (like authentication from db, submit data from a form and persist it to db and so on..)
The problem
As far i know, learnt from symfony2 "book" that i found on the site (you can find it here), there is an "automated" way for creating and rendering forms:
1) Build the form up into a controller in this way
$form = $this->createFormBuilder($task)
->add('task','text'),
->add('dueDate','date'),
->getForm();
return $this->render('pathToBundle:Controller:templateTwig',
array('form'=>$form->createview());
2) Into templateTwig render the template
{{ form_widget(form) }} // or single rows method
3) Into a controller (the same that have a route where you can submit data), take back submitted information
if($rquest->getMethod()=='POST'){
$form->bindRequest($request);
/* and so on */
}
Return to scenario
Our graphic employee don't want to access controllers, write php and other stuff like those. So he'll write a twig template with a "unconventional" (from symfony2 point of view, but conventional from HTML point of view) method:
/* into twig template */
<form action="{{ path('SestanteUserBundle_homepage') }}" method="post" name="userForm">
<div>
USERNAME: <input type="text" name="user_name" value="{{ user.username}}"/>
</div>
<div>
EMAIL: <input type="text" name="user_mail" value="{{ user.email }}"/>
</div>
<input type="hidden" name="user_id" value="{{ id }}" />
<input type="submit" value="modifica i dati">
</form>
Now, if into the controller that handle the submission of data we do something like that
public function indexAction(Request $request)
{
if($request->getMethod() == 'POST'){ // sono arrivato per via di un submit, quindi devo modificare i dati prima di farli vedere a video
$defaultData = array('message'=>'ho visto questa cosa in esempio, ma non capisco se posso farne a meno');
$form = $this->createFormBuilder($defaultData)
->add('user_name','text')
->add('user_mail','email')
->add('user_id','integer')
->getForm();
$form->bindRequest($request); //bindo la form ad una request
$data = $form->getData(); //mi aspetto un'array chiave=>valore
/* .... */
We expected that $data will contain an array with key,value from the submitted form.
We found that it isn't true. After googling for a while and try with other "bad" ideas, we're frozen into that.
So, if you have a "graphic office" that can't handle directly php code, how can we interface from form(s) to controller(s) ?
UPDATE
It seems that Symfony2 use a different convention for form's field name and lookup once you've submitted that.
In particular, if my form's name is addUser and a field is named userName, the field's name will be AddUser[username] so maybe it have a "dynamic" lookup method that will extract form's name, field's name, concat them and lookup for values.
Is it possible?
You can force Symfony2 to set the name of a form field, though I don't suggest it: $formBuilder->add('dummyfield', 'text', array( 'attr' => array('name' => 'yournamehere') ) );
Alternatively (also a bad idea), you can do this, which won't even let you use the form API: $this->getRequest()->get('whatever_the_field_name_is');
OR you can hackily add elements to the request based on the Sf2 generated names before binding it (copying the values that exist).
OR you can make use of the bind method of the form component (instead of bindRequest) as documented here.
But seriously...just use the formbuilder api. Your life will be easier, and isn't that what a framework is for? :)
Symfony 2 is based on twig as templating language. Let him use it :
{{ form_label(form.field) }}
will generate something like this :
<label for="field">field</label>
You can use all the available functions in order to render the form :
{{ form_label() }}
{{ form_widget() }}
{{ form_errors() }}
If you want to customize what is rendered by those functions, you can override twig templates as defined in the Symfony2 documentation.
Otherwise if you really want to something ugly, you can go for this kind of syntax :
{{ myform.vars.value.myField }}