Django httpresponse stripping CR - python

When I open my text file attachment that is generated using the below code, the HTTP response always seems to strip out the CR from each line, the users of this file will be using Notepad so I need CR/LF on each line.
the_file = tempfile.TemporaryFile(mode='w+b')
<procedure call to generate lines of text in "the_file">
the_file.seek(0)
filestring = the_file.read()
response = HttpResponse(filestring,
mimetype="text/plain")
response['Content-Length'] = the_file.tell()
response['Content-Disposition'] = 'attachment; filename="4cos_example.txt"'
return response
If I use this method, I get CR/LF in my files but I'd like to avoid having to write the file to disk at all, so it doesn't seem to be a good solution:
the_file = open('myfile.txt','w+')
<procedure call to generate lines of text in "the_file">
the_file.close
the_file = open('myfile.txt','rb')
filestring = the_file.read()
response = HttpResponse(filestring,
mimetype="text/plain")
response['Content-Length'] = the_file.tell()
response['Content-Disposition'] = 'attachment; filename="4cos_example.txt"'
return response
I feel like the solution should be obvious. but I can't close a tempfile and re-open it in binary mode (preserving the CR/LR). Heck I'm not even sure i'm in the right ballpark regarding how to correctly do this :) None the less, I'd like to pass this data as an attachment to the user after the configuration is assembled and have it display correctly in notepad. Is tempfile the wrong solution here or is there a mechanic of tempfile that will solve this issue for me without having to use file IO on disk.

Instead of using TemporaryFile, just use HttpResponse:
response = HttpResponse('', content_type='text/plain')
response['Content-Disposition'] = 'attachment; filename="4cos_example.txt"'
response.write('first line\r\n')
response.write('second line\r\n')
return response
FYI, if this is a very large response, you can also use StreamingHttpResponse. But only do that if required, since headers like Content-Length will not be able to be added automatically.

Related

Django download a BinaryField as a CSV file

I am working with a legacy project and we need to implement a Django Admin that helps download a csv report that was stored as a BinaryField.
The model is something like this:
class MyModel(models.Model):
csv_report = models.BinaryField(blank=True,null=True)
Everything seems to being stored as expected but I have no clue how to decode the field back to a csv file for later use.
I am using something like these (as an admin action on MyModelAdmin class)
class MyModelAdmin(admin.ModelAdmin):
...
...
actions = ["download_file",]
def download_file(self, request,queryset):
# just getting one for testing
contents = queryset[0].csv_report
encoded_data = base64.b64encode(contents).decode()
with open("report.csv", "wb") as binary_file:
# Write bytes to file
decoded_image_data = base64.decodebytes(encoded_data)
binary_file.write(decoded_image_data)
response = HttpResponse(encoded_data)
response['Content-Disposition'] = 'attachment; filename=report.csv'
return response
download_file.short_description = "file"
But all I download is a scrambled csv file. I don't seem to understand if it is a problem of the format I am using to decode (.decode('utf-8') does nothing either )
PD:
I know it is a bad practice to use BinaryField for this. But requirements are requirements. Nothing to do about it.
EDIT:
As #TimRoberts pointed out, encoding and then decoding is REALLY silly :$. I've changed the method like so:
def download_file(self, request,queryset):
# print(self,request)
contents = queryset[0].csv_report
# print(type(contents))
encoded_data = base64.b64decode(contents)
with open("my_file.csv", "wb") as binary_file:
binary_file.write(encoded_data)
response = HttpResponse(encoded_data)
response['Content-Disposition'] = 'attachment; filename=blob.csv'
return response
download_file.short_description = "file"
Still I am getting a csv file with something like this:
A big fat case of the old RTFM: I was getting carried away by the all base64.. Obviously I didn't have any idea of what I was doing.
After tampering with the shell and reading the docs, I just changed my method to:
def download_file(self, request,queryset):
**contents = bytes(queryset[0].csv_report)**
response = HttpResponse(contents)
response['ContentDisposition']='attachment;filename=report.csv'
return response
Note that I was scrambling the data on purpose by doing the encoded_data = base64.b64decode(contents) stuff. I just needed to apply bytes on my BinaryField and voilá

Trouble creating HttpResponse for xml download, Django

I'm trying to let the user download an xml file that i have generated.
This is my code:
tree.write('output.xml', encoding="utf-16")
# Pathout is the path to the output.xml
xmlFile = open(pathout, 'r')
myfile = FileWrapper(xmlFile.read())
response = HttpResponse(myfile, content_type='application/xml')
response['Content-Disposition'] = 'attachment; filename='+filename
return response
When I try to create my response I get this exception:
'\\'str\\' object has no attribute \\'read\\''
Can't figure out what I'm doing wrong. Any ideas?
Edit:
When I use this code I get no errors but instead the downloaded file is empty
tree.write('output.xml', encoding="utf-16")
xmlFile = open(pathout, 'r')
myfile = FileWrapper(xmlFile)
response = HttpResponse(myfile, content_type='application/xml')
response['Content-Disposition'] = 'attachment; filename='+filename
return response
You are calling xmlFile.read() - which yields a string - and passing the result to FileWrapper() which expects a readable file-like object. You should either just pass xmlFile to FileWrapper, or not use FileWrapper at all and pass the result of xmlFile.read() as your HttpResponse body.
Note that if you are creating the xml dynamically (which seems to be the case according to your snippet's first line), writing it to disk only to read it back a couple lines later is both a waste of time and resources and a potential cause of race conditions. You perhaps want to have a look at https://docs.python.org/2/library/xml.etree.elementtree.html#xml.etree.ElementTree.tostring
You're reading the file and passing the resulting string to FileWrapper, instead of passing the actual file object.
myfile = FileWrapper(xmlFile)
Alternatively from the other answers, I would recommend to completely go around the problem by utilizing the Django template system:
from django.http import HttpResponse
from django.template import Context, loader
def my_view(request):
# View code here...
t = loader.get_template('myapp/myfile.xml')
c = Context({'foo': 'bar'})
response = HttpResponse(t.render(c), content_type="application/xml")
response['Content-Disposition'] = 'attachment; filename=...'
return response
In this way create a myfile.xml template which is used to render the proper xml response without having to deal with writing any files to the filesystem. This is cleaner and faster, given that there is no other need to indeed create the xml and store it permanently.

Serving Excel(xlsx) file to the user for download in Django(Python)

I'm trying create and serve excel files using Django. I have a jar file which gets parameters and produces an excel file according to parameters and it works with no problem. But when i'm trying to get the produced file and serve it to the user for download the file comes out broken. It has 0kb size. This is the code piece I'm using for excel generation and serving.
def generateExcel(request,id):
if os.path.exists('./%s_Report.xlsx' % id):
excel = open("%s_Report.xlsx" % id, "r")
output = StringIO.StringIO(excel.read())
out_content = output.getvalue()
output.close()
response = HttpResponse(out_content,content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=%s_Report.xlsx' % id
return response
else:
args = ['ServerExcel.jar', id]
result = jarWrapper(*args) # this creates the excel file with no problem
if result:
excel = open("%s_Report.xlsx" % id, "r")
output = StringIO.StringIO(excel.read())
out_content = output.getvalue()
output.close()
response = HttpResponse(out_content,content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=%s_Report.xlsx' % id
return response
else:
return HttpResponse(json.dumps({"no":"excel","no one": "cries"}))
I have searched for possible solutions and tried to use File Wrapper also but the result did not changed. I assume i have problem with reading the xlsx file into StringIO object. But dont have any idea about how to fix it
Why on earth are you passing your file's content to a StringIO just to assign StringIO.get_value() to a local variable ? What's wrong with assigning file.read() to your variable directly ?
def generateExcel(request,id):
path = './%s_Report.xlsx' % id # this should live elsewhere, definitely
if os.path.exists(path):
with open(path, "r") as excel:
data = excel.read()
response = HttpResponse(data,content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=%s_Report.xlsx' % id
return response
else:
# quite some duplication to fix down there
Now you may want to check weither you actually had any content in your file - the fact that the file exists doesn't mean it has anything in it. Remember that you're in a concurrent context, you can have one thread or process trying to read the file while another (=>another request) is trying to write it.
In addition to what Bruno says, you probably need to open the file in binary mode:
excel = open("%s_Report.xlsx" % id, "rb")
You can use this library to create excel sheets on the fly.
http://xlsxwriter.readthedocs.io/
For more information see this page. Thanks to #alexcxe
XlsxWriter object save as http response to create download in Django
my answer is:
def generateExcel(request,id):
if os.path.exists('./%s_Report.xlsx' % id):
with open('./%s_Report.xlsx' % id, "rb") as file:
response = HttpResponse(file.read(),content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=%s_Report.xlsx' % id
return response
else:
# quite some duplication to fix down there
why using "rb"? because HttpResponse class init parameters is (self, content=b'', *args, **kwargs), so we should using "rb" and using .read() to get the bytes.

Function to create in-memory zip file and return as http response

I am avoiding the creation of files on disk, this is what I have got so far:
def get_zip(request):
import zipfile, StringIO
i = open('picture.jpg', 'rb').read()
o = StringIO.StringIO()
zf = zipfile.ZipFile(o, mode='w')
zf.writestr('picture.jpg', i)
zf.close()
o.seek(0)
response = HttpResponse(o.read())
o.close()
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = "attachment; filename=\"picture.zip\""
return response
Do you think is correct-elegant-pythonic enough? Any better way to do it?
Thanks!
For StringIO you should generally use o.getvalue() to get the result. Also, if you want to add a normal file to the zip file, you can use zf.write('picture.jpg'). You don't need to manually read it.
Avoiding disk files can slow your server to a crawl, but it will certainly work.
You'll exhaust memory if you serve too many of these requests concurrently.

Confused about making a CSV file into a ZIP file in django

I have a view that takes data from my site and then makes it into a zip compressed csv file. Here is my working code sans zip:
def backup_to_csv(request):
response = HttpResponse(mimetype='text/csv')
response['Content-Disposition'] = 'attachment; filename=backup.csv'
writer = csv.writer(response, dialect='excel')
#code for writing csv file go here...
return response
and it works great. Now I want that file to be compressed before it gets sent out. This is where I get stuck.
def backup_to_csv(request):
output = StringIO.StringIO() ## temp output file
writer = csv.writer(output, dialect='excel')
#code for writing csv file go here...
response = HttpResponse(mimetype='application/zip')
response['Content-Disposition'] = 'attachment; filename=backup.csv.zip'
z = zipfile.ZipFile(response,'w') ## write zip to response
z.writestr("filename.csv", output) ## write csv file to zip
return response
But thats not it and I have no idea how to do this.
OK I got it. Here is my new function:
def backup_to_csv(request):
output = StringIO.StringIO() ## temp output file
writer = csv.writer(output, dialect='excel')
#code for writing csv file go here...
response = HttpResponse(mimetype='application/zip')
response['Content-Disposition'] = 'attachment; filename=backup.csv.zip'
z = zipfile.ZipFile(response,'w') ## write zip to response
z.writestr("filename.csv", output.getvalue()) ## write csv file to zip
return response
Note how, in the working case, you return response... and in the NON-working case you return z, which is NOT an HttpResponse of course (while it should be!).
So: use your csv_writer NOT on response but on a temporary file; zip the temporary file; and write THAT zipped bytestream into the response!
zipfile.ZipFile(response,'w')
doesn't seem to work in python 2.7.9. The response is a django.HttpResponse object (which is said to be file-like) but it gives an error "HttpResponse object does not have an attribute 'seek'. When the same code is run in python 2.7.0 or 2.7.6 (I haven't tested it in other versions) it is OK... So you'd better test it with python 2.7.9 and see if you get the same behaviour.

Categories

Resources