Simple question. I have a .doc file generated in my mojolicious app. I want to download it. That's my question, how do I get the browser to download it?
I'm using the CPAN module MsOffice::Word::HTML::Writer to generate the doc.
Here is the sub routine in my mojolicious app, it is called by an Ajax request in Jquery:
sub down_doc {
my $self = shift;
my $doc = MsOffice::Word::HTML::Writer->new(
title => "My new Doc",
WordDocument => {View => 'Print'},
);
$doc->write("Content and Stuff");
my $save = $doc->save_as("/docs/file.doc");
$self->res->headers->content_disposition("attachment;filename=file.doc");
$self->res->headers->content_type('application/msword');
$self->render(data => $doc->content);
}
Here is my Ajax request in Jquery:
var request = $.ajax({
url: "/down_doc",
type: "post",
data: {'data': data},
});
request.done(function(response, textStatus, jqXHR) {
window.location.href = response;
});
I know that my Ajax "done" handler is wrong, I was just experimenting. How do I make my webpage prompt to save and download the .doc file?
You where pretty close, but I would recommend either of the following options...
File Download Processing with Mojolicious
You can install the plugin Mojolicious::Plugin::RenderFile to make this easy.
Example
plugin 'RenderFile';
sub down_doc {
my $self = shift;
my $doc = MsOffice::Word::HTML::Writer->new(
title => "My new Doc",
WordDocument => {View => 'Print'},
);
$doc->write("Content and Stuff");
my $save = $doc->save_as("/docs/file.doc");
$self->render_file('filepath' => "/docs/file.doc");
}
Or if you want to only use Mojo the following will work, and is explained further at the link below.
use Cwd;
app->static->paths->[0] = getcwd;
sub down_doc {
my $self = shift;
my $doc = MsOffice::Word::HTML::Writer->new(
title => "My new Doc",
WordDocument => {View => 'Print'},
);
$doc->write("Content and Stuff");
my $save = $doc->save_as("/docs/file.doc");
shift->render_static("/docs/file.doc");
}
Reference
This really isn't a problem on the server side, but rather that you can't save the response from an ajax request without using the (relatively new) File API. I would suggest replacing the ajax with a temporary form:
$('<form method="post" action="/down_doc">')
.append(
$('<input type="hidden" name="data">')
.attr("value", JSON.stringify(data))
)
.appendTo('body')
.submit();
When form is submitted and your /down_doc handler replies with the appropriate content-disposition header and the document data, the browser will do the work of handling the file save.
If you're not planning to use the file on the server after the request, this line can be removed:
my $save = $doc->save_as("/docs/file.doc");
Related
I am trying to upload image to an Etsy listing via their API v2. Their documentation have code example of image upload in PHP, in which the POST request body parameter is given like this
$params = array('#image' => '#'.$source_file.';type='.$mimetype);
How would I go about replicating this in JavaScript? I have tried sending the image blob as the image parameter but it does not seem to be working.
Edit: I am using npm oauth package. Here is my complete code that I am using to call the API.
var oauth1 = new OAuth1.OAuth(
'https://openapi.etsy.com/v2/oauth/request_token?scope=email_r%20listings_r%20listings_w%20listings_d',
'https://openapi.etsy.com/v2/oauth/access_token',
'api_key',
'api_secret',
'1.0A',
null,
'HMAC-SHA1'
);
oauth1.post(
'https://openapi.etsy.com/v2/listings/915677000/images',
req.user.etsy.oauth_token,
req.user.etsy.oauth_token_secret,
{
'listing_id': 915677000,
'image': <image has to go here>
},
'multipart/form-data',
function (e, data, response){
if (e) console.error(e);
// let body = JSON.parse(data);
console.log(data);
res.redirect('/create-listings')
});
I'm trying to figure how to download a word document generated with python-docx in my django app (I'm still learning and this is the first time I working with documents); with the help of ajax I send all the information needed to the view and call a function that uses that information and returns the document, then I'm trying to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.
to send this document as response in order to download it with the help of a "Download" button (or show web browser download dialog) in the same template from where I'm submitting the data, but here is where I'm stuck.
What I have until now is:
1) In javascript I'm sending the information as follows:
data = {
categoria: cat,
familia: fam,
Gcas: gcas,
FI: FI,
FF: FF,
Test: test,
Grafica: grafica
},
$.ajax({
type: 'post',
headers: {
"X-CSRFToken": csrftoken
},
url: url,
data: { json_data: JSON.stringify(data) },
success: function (response) {
$('#instrucciones').hide(); //Hide a div with a message
$('#btndesc').show(); //Show the button to download the file generated
}
});
return false;
}
2) In my Django view:
def Documento(request):
if request.method == "GET":
context={}
context['form'] = catForm
return render(request, 'report/report_base.html', context)
if request.method == 'POST':
#Data from ajax
datos = request.POST.get('json_data')
jsondata = json.loads(datos)
Gcas = jsondata['Gcas']
FI = jsondata['FI']
FF = jsondata['FF']
grafica = jsondata['Grafica']
#Using function to create the report
Reporte = ReporteWord(Gcas, FI, FF, grafica)
#Response
response = HttpResponse(content_type='application/vnd.openxmlformats-
officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename = "Reporte.docx"'
response['Content-Encoding'] = 'UTF-8'
Reporte.save(response)
return response
3) My function to create the document looks like:
def ReporteWord( gcas, FI, FF, Chart):
#Cargamos el template
template = finders.find('otros/Template_reporte.docx')
document = Document(template)
#Header
logo = finders.find('otros/logo.png')
header = document.sections[0].header
paragraph = header.paragraphs[0]
r = paragraph.add_run()
r.add_picture(logo)
#Adding title
titulo = document.add_heading('', 0)
titulo.add_run('Mi reporte').bold = True
titulo.style.font.size=Pt(13)
.
Many other steps to add more content
.
.
#IF I SAVE THE FILE NORMALLY ALL WORKS FINE
#document.save(r'C:\tests\new_demo.docx')
return document
I'll be very grateful for any idea or suggestion, many thanks in advance.
NOTE: I've reviewed these answers (and others) without luck.
Q1, Q2, Q3, Q4
UPDATE: Thanks to the feedback received I finally found how to generate the document and show the download dialog:
As was suggested the best way to achieve its using the view and not ajax, so the final updates in the code are:
a) Update view to work as show in feedback
b) JavaScript - Ajax control for POST method was removed and now all is handled directly with python (no extra code needed)
1) View:
def Reporte(request):
if request.method == "GET":
context={}
context['form'] = catForm
return render(request, 'reportes/reporte_base.html', context)
if request.method == 'POST':
#Getting data needed after submit the form in the page
GcasID = request.POST.get('GCASS')
FI = request.POST.get('dp1')
FF = request.POST.get('dp2')
Grafica = request.POST.get('options')
#Function to obtain complete code from GcasID
Gcas = GcasNumber(GcasID)
#Report creation
Reporte = ReporteWord(Gcas, FI, FF, Grafica)
#PART UPDATED TO SHOW DOWNLOAD REPORT DIALOG
bio = io.BytesIO()
Reporte.save(bio) # save to memory stream
bio.seek(0) # rewind the stream
response = HttpResponse(
bio.getvalue(), # use the stream's contents
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
response["Content-Encoding"] = "UTF-8"
return response
With those changes now when I press "Create report" (submit button of form) all works as expected (as a plus no more libraries are necessary). At the end as you suggested its easier do it in this way than using ajax.
Many thanks to all for your kind help.
Python-docx's Document.save() method accepts a stream instead of a filename. Thus, you can initialize an io.BytesIO() object to save the document into, then dump that to the user.
Reporte = ReporteWord(Gcas, FI, FF, grafica)
bio = io.BytesIO()
Reporte.save(bio) # save to memory stream
bio.seek(0) # rewind the stream
response = HttpResponse(
bio.getvalue(), # use the stream's contents
content_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
)
response["Content-Disposition"] = 'attachment; filename = "Reporte.docx"'
response["Content-Encoding"] = "UTF-8"
return response
This will work if you use a regular link or a form to submit the request, but since you're using $.ajax, you may need to do additional work on the browser end to have the client download the file. It would be easier not to use $.ajax.
Yep, a cleaner options, as stated by wardk would be, using https://python-docx.readthedocs.org/:
from docx import Document
from django.http import HttpResponse
def download_docx(request):
document = Document()
document.add_heading('Document Title', 0)
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document')
response['Content-Disposition'] = 'attachment; filename=download.docx'
document.save(response)
return response
Know more
I have the following.
<form method="post" action="/send" enctype="multipart/form-data">
<input type="file" name="filename" id="AttachFile">
</form>
I want to change the name of the file the user uploads.
If the user selects "Document.docx" I want to change it to "Bank - Document.docx".
I still want to read the file the user selected, not some other file, just use a different name for it when sending to the server.
I'm working within bounds of an application which doesn't allow control of the server side, so ideally I need to do this on the client. Furthermore I need this to work within the confines of a form.
I have tried variations of the following without success:
document.getElementById("AttachFile").name = "test.txt"
document.getElementById("AttachFile").files = "test.txt"
document.getElementById("AttachFile").value ="test.txt"
You can do it through the File API. We can also use the Blob API to be compatible with Microsoft edge.
var file = document.getElementById("AttachFile").files[0];
var newFile = new File([file], "Bank - Document.docx", {
type: file.type,
});
Here's a complete example — see comments:
HTML:
<input type="file" id="AttachFile">
<input type="button" id="BtnSend" value="Send">
JavaScript:
document.getElementById("BtnSend").addEventListener("click", function() {
// Get the file the user picked
var files = document.getElementById("AttachFile").files;
if (!files.length) {
return;
}
var file = files[0];
// Create a new one with the data but a new name
var newFile = new File([file], "Bank - Document.docx", {
type: file.type,
});
// Build the FormData to send
var data = new FormData();
data.set("AttachFile", newFile);
// Send it
fetch("/some/url", {
method: "POST",
body: data
})
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
return response.text(); // or response.json() or whatever
})
.then(response => {
// Do something with the response
})
.catch(error => {
// Do something with the error
});
});
You can't rename the file using a standard form submission. The name of the file being uploaded is read-only. To do this, you'd have to do it server-side. (The designers of file uploads seem to have either not considered this rename-on-upload use case or not felt it needed to be addressed by the API.)
However, you can prevent the default form submission and instead submit it programmatically via ajax, which does allow you to rename the file; see man tou's answer.
If you cannot work on the server side then you have to either rename the file BEFORE upload or AFTER download. How you present the name for the user is you to decide.
My current code is:
Play.save = function() {
//Hide the buttons
this.printButton.visible = false;
this.saveButton.visible = false;
this.backButton.visible = false;
//Force the game to re-render.
this.game.cameras.render(); //generally not recommended if you can help it
//Get the canvas information
var img = this.game.stage.canvas.toDataURL("image/octet-stream");
this.saveajax(img);
//Show UI again.
this.printButton.visible = false;
this.saveButton.visible = true;
this.backButton.visible = true;
}
Play.saveajax = function(img){
$.ajax
({
url: "http://localhost/ourthing/character/save.php",
type: "POST",
cache: false,
data: {
img: img
}
});
}
The file 'save.php' works (when i simply open the file). It will execute a query which it has to do. Problem here is: with this script i want to update a user with the given post data (img). But it doesnt execute on this request.
(i create data for var img and send this data to the saveajax function, which will open save.php to execute the query).
Im very new to JS/ajax. Does anyone can help me?
Best regards
apparently I cant comment, so my question is what are you getting on save.php
with command like
error_log(print_r($_REQUEST,true));
I suspect JSON issue here. can we see save.php?
Ok didn't see your previous comment, scrap that - your Jquery is not included.
Answer:
I had to include jQuery:
<script src="assets/js/jquery.js" ></script >
Thanks #Don'tVoteMeDown and #Lixus
Save.php file:
$db = framework::getDBO();
$data = array(
'canvas_character' => $_POST['img'],
);
$db->where('id', 1);
$db->update('ot_users', $data);
(i use my own framework)
I am writing a asp.net web api web service. Along with the service I am also writing a tester so the web service can be tested.
the tester allows you to post and receive either JSON or XML. I am processing the response using jquery ajax. I am fine using JSON and the below works fine.
the JSON response
{"ItemLabel":
{"Requisition":"W44DQ18255TS42 ",
"Nsn":"5999-01-100-5901",
"FscNiin":"5999011005901 ",
"Cage":"1CAY9",
"PartNumber":"",
"Nomen":"CONTACT,ELECTRICAL ",
"Quantity":"1",
"Ui":"EA",
"UiDesc":"",
"PurchOrderNbr":"SPM907-85-5-4444",
"RlseNbr":"TS0042",
"Clin":"0042 ",
"Lot":"",
"Preservation":"",
"DatePreserved":"",
"ShelfType":"",
"Shelf1":"",
"Exp1":"",
"CureDt1":"",
"CureInsp1":"",
"Shelf2":"",
"Exp2":"",
"CureDt2":"",
"CureInsp2":"",
"Serials":"",
"Serial":null,
"SerialInters":null,
"UnitPerInt":"1",
"TypeLbl":"ITEMx1"
},
"filePaths":["https://xxxxxxxxxxx.dir.ad.dla.mil/pdf/email_ITEMLBL__W44DQ18255TS42 _682895.pdf"]
}
and I can process the results using jquery ajax as follows.
success: function (result) {
$('#Spinner129').hide();
self.jsonResponse129(JSON.stringify(result));
self.hasSuccess129(true);
self.successMessage129("Success! Please view the response in the JSON Response tab below.");
$.each(result.filePaths, function (i, path) {
window.open(path, '_blank');
});
},
although I am struggling a bit to do the same thing with the xml response how do I get the values in filepaths?
here is the xml response
<FobOriginWebService129PLabelOutput xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/VSM">
<ItemLabel>
<Cage>1CAY9</Cage>
<Clin>0042 </Clin>
<CureDt1></CureDt1>
<CureDt2></CureDt2>
<CureInsp1></CureInsp1>
<CureInsp2></CureInsp2>
<DatePreserved></DatePreserved>
<Exp1></Exp1>
<Exp2></Exp2>
<FscNiin>5999011005901 </FscNiin>
<Lot></Lot>
<Nomen>CONTACT,ELECTRICAL </Nomen>
<Nsn>5999-01-100-5901</Nsn>
<PartNumber i:nil="true" />
<Preservation></Preservation>
<PurchOrderNbr>SPM907-85-5-4444</PurchOrderNbr>
<Quantity>1</Quantity>
<Requisition>W44DQ18255TS42 </Requisition>
<RlseNbr>TS0042</RlseNbr>
<Serial i:nil="true" />
<SerialInters i:nil="true" />
<Serials></Serials>
<Shelf1></Shelf1>
<Shelf2></Shelf2>
<ShelfType>SHL0</ShelfType>
<TypeLbl>ITEMx1</TypeLbl>
<Ui>EA</Ui>
<UiDesc></UiDesc>
<UnitPerInt>1</UnitPerInt>
</ItemLabel>
<filePaths xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>https://xxxxxxxxxxx.dir.ad.dla.mil/pdf/email_ITEMLBL__W44DQ18255TS42 _405955.pdf</d2p1:string>
</filePaths>
</FobOriginWebService129PLabelOutput>
not sure how to process it in the jquery ajax success section here was my attempt.
success: function (result) {
$('#Spinner129').hide();
self.hasSuccess129(true);
self.successMessage129("Success! Please view the response in the XML Response tab below.");
self.xmlResponse129(JSON.stringify(result));
// xmlDoc = $.parseXML(result),
// $xml = $(xmlDoc),
// $filePath = $xml.find("filePaths");
// now what?
},
The file path is nested within the children of filePaths node.
<filePaths xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>https://xxxxxxxxxxx.dir.ad.dla.mil/pdf/email_ITEMLBL__W44DQ18255TS42 _405955.pdf</d2p1:string>
</filePaths>
Once you have $filePaths as following:
$filePaths = $xml.find("filePaths");
You can access its children and then get its text:
$filePaths.children().each(function () {
//console.log($(this).text()); // print to test
// https://xxxxxxxxxxx.dir.ad.dla.mil/pdf/email_ITEMLBL__W44DQ18255TS42 _405955.pdf
// open in new window
window.open($(this).text(), '_blank');
});
Everything looks fine. To get filePath value try this:
$filePath.text();