How can I post FileInfo to a web service using Yesod and Http-Conduit? - web-services

I am working with the default Yesod scaffolding project.
I have created a page that displays a simple form to upload files.
(The form will likely be created on the client using Javascript.)
For brevity, the form has a single file input:
<form method="post" action=#{UploadR}>
<input type="file" name="myfile">
<button type="submit">
My objective is to process the form data and then upload the file to a web service.
I have no trouble processing the form, my concern is interacting with the web service.
For example, given the following Yesod handler:
postUploadR :: Handler Html
postUploadR = do
mgr <- fmap httpManager getYesod
fi <- runInputPost $ ireq fileField "myfile"
let fSource = fileSource fi
fName = fileName fi
req <- parseUrl "http://webservice/upload"
let areq = req { method = methodPost
, requestBody = requestBodySourceChunked fSource
}
res <- httpLbs areq mgr
defaultLayout $ do
setTitle "file uploaded"
[whamlet|
<h3> Success
<p> You uploaded #{fName}.
|]
The webservice returns the error: fail post content-length, but everything else works as expected. Perhaps the server doesn't support a chunked request body?

I think your guess about chunked request body is correct. What you need to do is:
Stream the uploaded contents into a temporary file.
Get the size of that file.
Use requestBodySource and provide the file length and its contents.
Fortunately, steps (1) and (2) can be handled quite easily by the sinkCacheLength function. You'll end up with something like:
(fSize, fSource) <- fileSource fi $$ sinkCacheLength

Related

Remove uploaded files with filepond

I really enjoy the filepond library and would like to implement it in my flask app. Since I was not able to find any useful examples online, I started to write my own, small, proof of concept web application. I would like to upload multiple images to the server and save the filenames in the database. Furthermore, I would like to edit an entry and add additional files or remove the existing ones.
So far I figured out how to upload and revert files before the form is submitted. I am also able to load existing files inside the edit form. Just when I click the 'x' button on a loaded image inside the edit form the image is removed from the filepond window and a 'removefile' event is fired, but the file still remains on the server. Is it possible to trigger the revert request on a loaded file or is there a better solution altogether?
x-button does not remove the file from the server
Here are the relevant snippets from my js file:
FilePond.registerPlugin(
FilePondPluginFileValidateSize,
FilePondPluginImagePreview,
FilePondPluginFileRename,
FilePondPluginFileValidateType
);
inputElement = document.querySelector(".filepond");
token = document
.querySelector('input[name="csrf_token"]')
.getAttribute("value");
FilePond.setOptions({
server: {
headers: { "X-CSRF-TOKEN": token },
process: "./process",
revert: "./revert",
load: {
url: "../",
}
},
});
const filepond = FilePond.create(inputElement, {
// Here I pass the files to my edit form in the following format:
//
// files: [
// {
// source: 'static/images/some_name.png',
// options: {
// type: 'local'
// }
// }]
});
The relevant code from .py file:
#app.route("/process", methods=["POST"])
#app.route("/edit/process", methods=["POST"])
def process():
upload_dir = "static/images"
file_names = []
for key in request.files:
file = request.files[key]
picture_fn = file.filename
file_names.append(picture_fn)
picture_path = os.path.join(upload_dir, picture_fn)
try:
file.save(picture_path)
except:
print("save fail: " + picture_path)
return json.dumps({"filename": [f for f in file_names]})
#app.route("/revert", methods=["DELETE"])
#app.route("/edit/revert", methods=["DELETE"])
def revert():
upload_dir = "static/images"
parsed = json.loads(request.data)
picture_fn = parsed["filename"][0]
picture_path = os.path.join(upload_dir, picture_fn)
try:
os.remove(picture_path)
except:
print("delete fail: " + picture_path)
return json.dumps({"filename": picture_fn})
Here is the repository to my full flask-filepond app:
https://github.com/AugeJevgenij/flask_filepond
Please excuse me if the question is unclear, does not make sense or the code is written poorly.
I just started programming a few months ago.
Acording to filepond documentation you can remove a file stored locally on the server like this:
FilePond.setOptions({
server: {
remove: (source, load, error) {
// 'source' is the path of the file and should be sent to a server endpoint via http
// call the load method before ending the function
load()
}
}
})
then on your server where you receive the source (path), use it to delete the file. Keep in mind that this is a risky approach to get your website hacked!

Getting actual files to send to backend from dropzone

I am having difficulties grabbing files from Dropzone object (using Vue-Dropzone) and appending them to a custom formData object I am building with other params too.
What I am trying to achive is a form with a Dropzone in it which submits via ajax and I am trying to grab all files the user selected and create an object to pass to backend in the form of
files[file_1] = file_1
files[file_2] = file_2
and so on. I have used the below code but no success
let files = {};
_.each(this.$refs.dropzoneID.getQueuedFiles(), (file, index) => {
files[index] = file;
});
// console.log(files);
this.formData.append('files', files);
process_form_via_axios_call
What i get in the backend is:
params={"files"=>"[object Object]"}
While I expect something like this:
{"files" => {"file_1"=>#<ActionDispatch::Http::UploadedFile:0x007fd14c9ec940 #tempfile=#<Tempfile:/var/folders/lk/bhps4r5d3s1fzmywxlp59f480000gn/T/RackMultipart20171002-87936-1tnd839.jpg>, #original_filename="restaurant-person-woman-coffee-medium.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"file\"; filename=\"restaurant-person-woman-coffee-medium.jpg\"\r\nContent-Type: image/jpeg\r\n">......}
How can i achieve this?
Actually was able to resolve this by doing this:
_.each(this.$refs.dropzoneID.getQueuedFiles(), (file, index) => {
this.formData.append('files[file_' + index +']', file);
});
Worked like a charm.

Virtuemart: get PDF invoice link on order_done step

I have Joomla 2.5.17 an Virtuemart 2.0.26d. I want the PDF invoice download link on the order_done step which is rendered by order_done.php view.
I already configured the virtuemart so the order status is on "CONFIRMED" - "C" meaning as the order is paced, the PDF invoice had been already generated.
Ok, i got it. So this is what you have to do if you want the download link in order_done step. Assuming your PDFs are stored in "media/vmfiles/invoices/" you need to add some code to components/com_virtuemart/controllers/cart.php line about 477 where "else if($task=='confirm')" block starts:
...
$cart->confirmDone();
$view = $this->getView('cart', 'html');
$securePath = VmConfig::get('forSale_path',0);
$segments = explode('/', $securePath);
$folderOnServer = $segments[sizeof($segments)-3].'/'.$segments[sizeof($segments)-2];
$orderModel = VmModel::getModel('orders');
$invoiceId = $orderModel->getInvoiceNumber($cart->virtuemart_order_id);
$pdfName = "{$folderOnServer}/invoices/vminvoice_{$invoiceId}.pdf";
$view->setLayout('order_done');
$view->pdfName = $pdfName;
$view->display();
...
also a bit code in view templates/{yourtheme}/html/com_virtuemart/cart/order_done.php:
<div class="get-pdf"><?php echo JText::_('get_your_pdf'); ?> <?php echo JText::_('DOWNLOAD_PDF') ?> <br/></div>

Django: redirect after file download

I'm working on a view in my Django 1.5 that allow me to download a file. The download process it's triggered by a button in the HTML page like this:
<input type="button" value="Download!" />
The url point to a view that manage the download:
def filedownload(request, filename):
down_file = File.objects.get(name = filename)
file_path = MEDIA_ROOT+str(down_file.file)
file_name = down_file.filecomplete()
if not Transaction.objects.filter(user = request.user, file = down_file):
transaction = Transaction.objects.create(date = datetime.now(), user = request.user, file = down_file, vote = False)
transaction.save()
fp = open(file_path, 'rb')
response = HttpResponse(fp.read())
fp.close()
type, encoding = mimetypes.guess_type(file_name)
if type is None:
type = 'application/octet-stream'
response['Content-Type'] = type
response['Content-Length'] = str(os.stat(file_path).st_size)
if encoding is not None:
response['Content-Encoding'] = encoding
if u'WebKit' in request.META['HTTP_USER_AGENT']:
filename_header = 'filename=%s' % file_name.encode('utf-8')
elif u'MSIE' in request.META['HTTP_USER_AGENT']:
filename_header = ''
else:
filename_header = 'filename*=UTF-8\'\'%s' % urllib.quote(file_name.encode('utf-8'))
response['Content-Disposition'] = 'attachment; ' + filename_header
return response
What I wanted to do it's to redirect the user to a success page right after they hit the downlad button but I can't find a way to do it.
I'm not concerned about interrupted or otherwise unsuccessful downloads since it's a school project.
He are all steps that you have to follow to run your code :
get the jQuery File Download which allows downloads with OnSuccess and OnFailure callbacks.
Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.
$.fileDownload('some/file.pdf')
.done(function () { //redirect });
Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.
You could set the href for the input to the download confirmation page you want to display, passing along the file name, then within the template for the confirmation page, set the onload event to redirect to actually do the download.
<body onload=window.location='/file/download/{{ file.name }}/'>
You can do it using ajax request waiting until the download fully successful.
in your view :
$.fileDownload('some/file.pdf')
.done(function () { //redirect
window.location = '/link';
})
.fail(function () { alert('File download failed!'); });
How to use the previous code:
first add a name or id or class to your link
download link
next: here i use id to identify the link #a_d*
<script type="text/javascript">
$(document).on("click", "#a_d", function () {
$.fileDownload(.done(function () { //redirect
window.location = '/link';})
});
});
</script>
done !!

Assetic: Writer do not generate all files on debug mode

I'm trying to connect assetic with Twig (on Zend). It seems to work ok for debug=false, but I cannot understand what it does for development.
Basically, when calling this:
{% javascripts 'static/js/*.js' %}
<p>{{asset_url}}</p>
{% endjavascripts %}
it outputs a list of generated javascript file names (which is nice):
js/d19cc07_part_1_jquery-1.7.2.min_6.js
js/d19cc07_part_1_jquery.cookie_7.js
js/d19cc07_part_1_jquery.jeditable.mini_8.js
but these files are not generated by writter (it only generates js/d19cc07.js). For debug=false it outputs just one file name and also renders it correctly.
So what am I missing here?
Here's how I initialize it:
//Assetic
$factory = new \Assetic\Factory\AssetFactory(APP_BASE_PATH . '/public/');
$factory->setDebug(true);
$am = new \Assetic\Factory\LazyAssetManager($factory);
//enable loading assets from twig templates
$loader = new \Twig_Loader_Filesystem(array());
$loader->addPath(APP_BASE_PATH.'/application/templates/default');
//Init twig
$twig = new \Twig_Environment($loader);
$twig->addExtension(new \Assetic\Extension\Twig\AsseticExtension($factory));
$am->setLoader('twig', new \Assetic\Extension\Twig\TwigFormulaLoader($twig));
$templates = array('/index/index.html'); //An array containing full paths to my templates
foreach ($templates as $template) {
$resource = new \Assetic\Extension\Twig\TwigResource($loader, $template);
$am->addResource($resource, 'twig');
}
//Writer
$writer = new \Assetic\AssetWriter(APP_BASE_PATH . '/public/static/assetic');
$writer->writeManagerAssets($am);
echo $twig->render('index/index.html');
I ended up connecting Symfony console to my Zend project and slightly adopting DumpCommand from assetic to make this work (by passing my version of DI containter with assigned AsseticManager).