Django - Custom header to application/x-zip-compressed HttpResponse - django

I have a Django App which contains a view that returns a .zip file using HttpResponse
resp = HttpResponse(s.getvalue(), content_type="application/x-zip-compressed")
resp['Content-Disposition'] = 'attachment; filename=%s' % zip_filename
return resp
The .zip is created inside the view where I also calculate its checksum.
On the client-side I use a requests.get() to get the zip file.
How can I send the checksum in the same HttpResponse along with the zip.
I tried adding the checksum to the header by
resp['hash-key'] = sha_checksum
but on the client requests.headers['hash-key'] seems to be None
How can I do this?
Edit:
As it seems my problem is located in the calculation of the hash which results in None.
The strange thing is that the same function is used on the client-side and works fine, but I guess this is another question.

Since hash-key is in the response, it sounds like this line is working.
resp['hash-key'] = sha_checksum
Try printing the value of sha_checksum before you assign it, to make sure it is not None.

Related

Django PIPE youtube-dl to view for download

TL;DR: I want to pipe the output of youtube-dl to the user's browser on a button click, without having to save the video on my server's disk.
So I'm trying to have a "download" button on a page (django backend) where the user is able to download the video they're watching.
I am using the latest version of youtube-dl.
In my download view I have this piece of code:
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
file = ydl.download([f"https://clips.twitch.tv/{pk}"])
And it works, to some extend. It does download the file to my machine, but I am not sure how to allow users to download the file.
I thought of a few ways to achieve this, but the only one that really works for me would be a way to pipe the download to user(client) without needing to store any video on my disk. I found this issue on the same matter, but I am not sure how to make it work. I successfully piped the download to stdout using ydl_opts = {'outtmpl': '-'}, but I'm not sure how to pipe that to my view's response. One of the responses from a maintainer mentions a subprocess.Popen, I looked it up but couldn't make out how it should be implemented in my case.
I did a workaround.
I download the file with a specific name, I return the view with HttpResponse, with force-download content-type, and then delete the file using python.
It's not what I originally had in mind, but it's the second best solution that I could come up with. I will select this answer as accepted solution until a Python wizard gives a solution to the original question.
The code that I have right now:
def download_clip(request, pk):
ydl_opts = {
'outtmpl': f"{pk}.mp4"
}
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
ydl.download([f"https://clips.twitch.tv/{pk}"])
path = f"{pk}.mp4"
file_path = os.path.join(path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(), content_type="application/force-download")
response['Content-Disposition'] = 'inline; filename=' + os.path.basename(file_path)
os.remove(file_path)
return response
raise Http404

Stop spinner after Django FileResponse Save

I have a template link of a url that runs a view function that generates a file and returns a FileResponse. Works great, but in cases it can take a while to generate the file so I'd like to start and stop a spinner before and after.
I've tried using a click event on the link to run and $.ajax() or $.get() function that sends the url, and in this way I can start the spinner. But the FileResponse doesn't generate a Save window in this case. (code below)
Is there a way to generate and save a file in a Django view via JavaScript? The following never opens the file save window.
$("#ds_downloads a").click(function(e){
urly='/datasets/{{ds.id}}/augmented/'+$(this).attr('ref')
startDownloadSpinner()
$.ajax({
type: 'GET',
url: urly
}).done(function() {
spinner_dl.stop();
})
})
Adding the function below that creates the FileResponse. This generates a local system file save window allowing the user to save the file locally. The event of either opening or closing (save) that window would be the time to stop the spinner, but I can't seem to access it via javascript.
with open(fn, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile, delimiter='\t',
quotechar='', quoting=csv.QUOTE_NONE)
writer.writerow(['col1','col2','col3'])
for f in features:
geoms = f.geoms.all()
gobj = augGeom(geoms)
row = [
gobj['col1'],
gobj['col2'],
gobj['col3']]
writer.writerow(row)
response = FileResponse(open(fn, 'rb'),content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="'+os.path.basename(fn)+'"'
Just use HttpResponse, the browser adds the spinner automatically. No need to add extra spinner.
from csv import writer
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response.status_code = 200
response["Content-Disposition"] = "attachment; filename={}".format(filename)
try:
csv_writer = writer(response)
# define headers
csv_writer.writerow(headers)
[csv_writer.writerow(row) for row in features.values_list(*headers)]
except Exception:
raise
return response
I came up with a somewhat klugey solution: I generate a POST request with $.ajax to a function-based view that initiates a Celery task to perform the file creation and save it to the filesystem. It returns the Celery task_id to the browser while the task runs, feeding that to a celery_progress.js routine that checks the progress of the task and on completion returns the filename; an href link is created with that in the markup, and a click() generated on it. That ajax function starts and stops a spinner with spinner.js at the appropriate points.
Not posting code b/c it is a kluge and I wouldn't recommend it. I'm guessing there's a better way to do this with a django form, but this works, the project is overdue, and I haven't got the time to look further.

Save pdf from django-wkhtmltopdf to server (instead of returning as a response)

I'm writing a Django function that takes some user input, and generates a pdf for the user. However, the process for generating the pdf is quite intensive, and I'll get a lot of repeated requests so I'd like to store the generated pdfs on the server and check if they already exist before generating them.
The problem is that django-wkhtmltopdf (which I'm using for generation) is meant to return to the user directly, and I'm not sure how to store it on the file.
I have the following, which works for returning a pdf at /pdf:
urls.py
urlpatterns = [
url(r'^pdf$', views.createPDF.as_view(template_name='site/pdftemplate.html', filename='my_pdf.pdf'))
]
views.py
class createPDF(PDFTemplateView):
filename = 'my_pdf.pdf'
template_name = 'site/pdftemplate.html'
So that works fine to create a pdf. What I'd like is to call that view from another view and save the result. Here's what I've got so far:
#Create pdf
pdf = createPDF.as_view(template_name='site/pdftemplate.html', filename='my_pdf.pdf')
pdf = pdf(request).render()
pdfPath = os.path.join(settings.TEMP_DIR,'temp.pdf')
with open(pdfPath, 'w') as f:
f.write(pdf.content)
This creates temp.pdf and is about the size I'd expect but the file isn't valid (it renders as a single completely blank page).
Any suggestions?
Elaborating on the previous answer given: to generate a pdf file and save to disk do this anywhere in your view:
...
context = {...} # build your context
# generate response
response = PDFTemplateResponse(
request=self.request,
template=self.template_name,
filename='file.pdf',
context=context,
cmd_options={'load-error-handling': 'ignore'})
# write the rendered content to a file
with open("file.pdf", "wb") as f:
f.write(response.rendered_content)
...
I have used this code in a TemplateView class so request and template fields were set like that, you may have to set it to whatever is appropriate in your particular case.
Well, you need to take a look to the code of wkhtmltopdf, first you need to use the class PDFTemplateResponse in wkhtmltopdf.views to get access to the rendered_content property, this property get us access to the pdf file:
response = PDFTemplateResponse(
request=<your_view_request>,
template=<your_template_to_render>,
filename=<pdf_filename.pdf>,
context=<a_dcitionary_to_render>,
cmd_options={'load-error-handling': 'ignore'})
Now you could use the rendered_content property to get access to the pdf file:
mail.attach('pdf_filename.pdf', response.rendered_content, 'application/pdf')
In my case I'm using this pdf to attach to an email, you could store it.

django - iterating over return render_to_response

I would like to read a file, update the website, read more lines, update the site, etc ...The logic is below but it's not working.
It only shows the first line from the logfile and stops. Is there a way to iterate over 'return render_to_response'?
#django view calling a remote python script that appends output to the logfile
proc = subprocess.Popen([program, branch, service, version, nodelist])
logfile = 'text.log'
fh = open(logfile, 'r')
while proc.poll() == None:
where = fh.tell()
line = fh.read()
if not line:
time.sleep(1)
fh.seek(where,os.SEEK_SET)
else:
output = cgi.escape(line)
output = line.replace('\n\r', '<br>')
return render_to_response('hostinfo/deployservices.html', {'response': output})
Thank you for your help.
You can actually do this, by making your function a generator - that is, using 'yield' to return each line.
However, you would need to create the response directly, rather than using render to response.
render_to_response will render the first batch to the website and stop. Then the website must call this view again somehow if you want to send the next batch. You will also have to maintain a record of where you were in the log file so that the second batch can be read from that point.
I assume that you have some logic in the templates so that the second post to render_to_response doesnt overwrite the first
If your data is not humongous, you should explore sending over the entire contents you want to show on the webpage each time you read some new lines.
Instead of re-inventing the wheel, use django_logtail

How to process an uploaded KML file in GeoDjango

I wrote a cmd line routine to import a kml file into a geoDjango application, which works fine when you feed it a locally saved KML file path (using the datasource object).
Now I am writing a web file upload dialog, to achieve the same thing. This is the beginning of the code that I have, problem is, that the GDAL DataSource object does not seem to understand Djangos UploadedFile format. It is held in memory and not a file path as expected.
What would be the best strategy to convert the UploadedFile to a normal file, and access this through a path? I dont want to keep the file after processing.
def createFeatureSet(request):
if request.method == 'POST':
inMemoryFile = request.FILES['myfile']
name = inMemoryFile.name
POSTGIS_SRID = 900913
ds = DataSource(inMemoryFile) #This line doesnt work!!!
for layer in ds:
if layer.geom_type in (OGRGeomType('Point'), OGRGeomType('Point25D'), OGRGeomType('MultiPoint'), OGRGeomType('MultiPoint25D')):
layerGeomType = OGRGeomType('MultiPoint').django
elif layer.geom_type in (OGRGeomType('LineString'),OGRGeomType('LineString25D'), OGRGeomType('MultiLineString'), OGRGeomType('MultiLineString25D')):
layerGeomType = OGRGeomType('MultiLineString').django
elif layer.geom_type in (OGRGeomType('Polygon'), OGRGeomType('Polygon25D'), OGRGeomType('MultiPolygon'), OGRGeomType('MultiPolygon25D')):
layerGeomType = OGRGeomType('MultiPolygon').django
DataSource is a wrapper around GDAL's C API and needs an actual file. You'll need to write your upload somewhere on the disk, for insance using a tempfile. Then you can pass the file to DataSource.
Here is a suggested solution using a tempfile. I put the processing code in its own function which is now called.
f = request.FILES['myfile']
temp = tempfile.NamedTemporaryFile(delete=False)
temp.write(f.read())
temp.close()
createFeatureSet(temp.name, source_SRID= 900913)