How to correctly to download file with django requests - django

I am running a Django app where I can upload files. Now I want to download the files using requests. I was trying to create a view where the file is downloaded, so I can then make a call with requests. But it doesn't quite work
My model:
class FileCollection(models.Model):
name = models.CharField(max_length=120, null=True, blank=True)
store_file = models.FileField(storage=PrivateMediaStorage(), null=True, blank=True)
creation_date = models.DateTimeField(null=True, blank=True)
My views
def fileview(request, *args, **kwargs):
file = FileCollection.objects.first()
file_path = file.store_file
print(file_path)
FilePointer = open(file_path, "r")
response = HttpResponse(FilePointer, content_type='application/msexcel')
response['Content-Disposition'] = 'attachment; filename=NameOfFile'
return response
It tells me that TypeError: expected str, bytes or os.PathLike object, not FieldFile
If I pass in the url provided in the apiview/admin I get: FileNotFoundError: [Errno 2] No such file or directory
Also tried:
def fileview(request):
path = FileCollection.objects.first()
obj = path.store_file
o = str(obj)
file_path = os.path.join(settings.MEDIA_ROOT, o)
print(file_path)
if os.path.exists(file_path):
with open(file_path, 'rb') as fh:
response = HttpResponse(fh.read(),
content_type="application/vnd.ms-excel")
response[
'Content-Disposition'] = 'inline; filename=' + os.path.basename(
file_path)
return response
but this gives me ValueError: The view file_storage_api.api.v1.views.fileview didn't return an HttpResponse object. It returned None instead.
Is that the right way to go?
I'm very grateful for help or hints.
Thanks so much

file_path = file.store_file
is not a file path but an instance of a FileField
try using
file_path = file.store_file.name
and use the second snippet
EDIT: here is the code I use:
l_targetFile is the path to the actual file
l_prjPath = os.path.realpath(os.path.dirname(__file__)).replace(<adapt the path here>)
l_userFileName= <file field>.name.replace('<upload to sub-dir>','')
l_targetFile = l_prjPath + '/media/' + l_fileObj.file_obj.name
#return the file
response = FileResponse(open(l_targetFile, 'rb'),\
(l_responseDisposition == 'attachment'))
#process the filename as stored on the local machine in case of download
try:
#check if it will throw
l_tmpUserName = l_userFileName.encode('ascii')
#no error use the non-encoded filename
l_fileExpr = 'filename="{0}"'.format(l_userFileName)
except UnicodeEncodeError:
# Handle a non-ASCII filename
l_fileExpr = "filename*=utf-8''{}".format(quote(l_userFileName))
response['Content-Disposition'] = '{0};{1};'.format(l_responseDisposition,l_fileExpr)
if '.pdf' in l_userFileName:
response['Content-Type'] = 'application/pdf'
elif l_dataSource == CMSArchiveEntry.SOURCE_TAG:
response['Content-Type'] = 'text/html; charset=utf-8'
return response

Related

Django Error: 'utf-8' codec can't decode byte when trying to create downloadable file

I have created an app, to do some process and zip some files. Now I need to make the zip file downloadable for users, so they can download the zip file.
I am working with Django and here is the in views.py:
def download(request):
context = {}
if request.method == 'POST':
if form.is_valid():
userInput = form.cleaned_data['userInput']
createFiles(userInput)
filename = 'reports.zip'
filepath = '/home/download/'
fl = open(filepath, 'r')
mime_type, _ = mimetypes.guess_type(filepath)
response = HttpResponse(fl, content_type=mime_type)
response['Content-Disposition'] = "attachment; filename=%s" % filename
return response
return render(request, 'download.html', context)
But I am getting an error:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x83 in position 11: invalid start byte
Which is breaking on this line:
response = HttpResponse(fl, content_type=mime_type)
Any suggestions how to fix this?
I would guess, that you should open the zip file with 'rb' flags (binary).
Here's a sample from my working code:
zip_fullpath = os.path.join(zip_path, zip_filename)
zip_file = open(zip_fullpath, 'rb')
resp = FileResponse(
zip_file,
content_type="application/force-download"
)
resp['Content-Disposition'] = 'attachment; filename=%s' % zip_filename
os.remove(zip_fullpath)
return resp

Django : How to upload csv file in unit test case using APIClient

I would like to write a unit test for a view on a Django REST Framework application. The test should upload a CSV file using the POST.
#staticmethod
def _file_upload(client, string, args, file_name):
base_path = os.path.dirname(os.path.realpath(__file__))
with open(base_path + file_name, 'rb') as data:
data = {
'file': data
}
response = client.post(reverse(string, args=[args]), data, format = "multipart")
return response.status_code, response.data
The above code I used which obviously doesn't work it shows the following error
Missing filename. Request should include a Content-Disposition header with a filename parameter.
The following code is the one that I want to test via unit testing.
class ChartOfAccounts(views.APIView):
parser_classes = (JSONParser, FileUploadParser)
def post(self, request, pk, *args, **kwargs):
request.FILES['file'].seek(0)
csv_data = CSVUtils.format_request_csv(request.FILES['file'])
try:
coa_data = CSVUtils.process_chart_of_accounts_csv(company, csv_data)
serializer = CoASerializer(coa_data, many=True)
if len(serializer.data) > 0:
return Utils.dispatch_success(request, serializer.data)
except Exception as e:
error = ["%s" % e]
return Utils.dispatch_failure(request, 'DATA_PARSING_ISSUE', error)
Any help regarding this is welcome. Thanks in advance
I have fixed my issue using the different approach with HTTP headers HTTP_CONTENT_DISPOSITION, HTTP_CONTENT_TYPE by this reference
And here is my code
#staticmethod
def _file_upload_csv( string, args, file_name):
base_path = os.path.dirname(os.path.realpath(__file__))
data = open(base_path + file_name, 'rb')
data = SimpleUploadedFile(content = data.read(),name = data.name,content_type='multipart/form-data')
factory = RequestFactory()
user = User.objects.get(username=UserConstant.ADMIN_USERNAME)
view = ChartOfAccounts.as_view()
content_type = 'multipart/form-data'
headers= {
'HTTP_CONTENT_TYPE': content_type,
'HTTP_CONTENT_DISPOSITION': 'attachment; filename='+file_name}
request = factory.post(reverse(string, args=[args]),{'file': data},
**headers)
force_authenticate(request, user=user)
response = view(request, args)
return response.status_code, response.data
**headers done the trick...
Here's what i did
#patch("pandas.read_csv")
#patch("pandas.DataFrame.to_sql")
def test_upload_csv_success(self, mock_read_csv, mock_to_sql) -> None:
"""Test uploading a csv file"""
file_name = "test.csv"
# Open file in write mode (Arrange)
with open(file_name, "w") as file:
writer = csv.writer(file)
# Add some rows in csv file
writer.writerow(["name", "area", "country_code2", "country_code3"])
writer.writerow(
["Albania", 28748, "AL", "ALB"],
)
writer.writerow(
["Algeria", 2381741, "DZ", "DZA"],
)
writer.writerow(
["Andorra", 468, "AD", "AND"],
)
# open file in read mode
data = open(file_name, "rb")
# Create a simple uploaded file
data = SimpleUploadedFile(
content=data.read(), name=data.name, content_type="multipart/form-data"
)
# Perform put request (Act)
res = self.client.put(CSV_URL, {"file_name": data}, format="multipart")
# Mock read_csv() and to_sql() functions provided by pandas module
mock_read_csv.return_value = True
mock_to_sql.return_value = True
# Assert
self.assertEqual(res.status_code, status.HTTP_201_CREATED)
self.assertEqual(res.data, "Data set uploaded")
# Delete the test csv file
os.remove(file_name)

Django Redirect after downloading a file

I want to redirect to another view after downloading a file, but when I finish the download nothing happens and it remains in the same page. The dwonloading works perfectly. Any idea??
Views.py:
#The function that downloads the file
def download_file(path, format, fileName):
path = path+"."+format
filename = os.path.basename(path)
mimetype, encoding = mimetypes.guess_type(filename)
if fileName==None: fileName = filename
else: fileName = fileName+"."+format
response = HttpResponse(mimetype=mimetype)
response['Content-Disposition'] = 'attachment; filename=%s' %fileName
response.write(file(path, "rb").read())
return response
def download_downloaded_track(request, downloadedTrack_id):
dt = get_object_or_404(DownloadedTrack, id=downloadedTrack_id)
if request.method=='POST':
form = DownloadDownloadedTrackForm(request.POST)
if form.is_valid():
...
return download_file(downloadedTrackRoute+dt.fileName,format, name)
return HttpResponseRedirect(reverse('profile_detail'))
form = DownloadDownloadedTrackForm(initial={'format':'gpx'})
return render(request,'principal/downloadedTrack.html',{'form':form,'zone':dt.zone,'downloadedTrack':dt, 'layer':'downloadDownloadedTrack'})

Django TestCase: SimpleUploadedFile shows wrong filetype

I've been trying to get the tests running on upload forms. But, whenever I run the tests, it says that file is of wrong type.
Upload form saves the file with a randomly generated file name:
class Video(models.Model):
def get_generated_path(self):
# Generates random path for video file upload
original_file = models.CharField(null=True)
uploaded_file = models.FileField(storage=FileSystemStorage(location=
settings.MEDIA_ROOT), upload_to=get_generated_path)
video_name = models.TextField()
And form looks like:
class VideoForm(forms.Form):
video_file = forms.FileField()
video_name = forms.CharField()
def clean_video_file(forms.Form):
content = self.cleaned_data['video_file']
content_type = content.content_type.split('/')[0]
if content_type in settings.CONTENT_TYPES:
if content._size > settings.MAX_UPLOAD_SIZE:
raise forms.ValidationError(_('Please keep filesize under %s.
Current filesize %s') % (filesizeformat(
settings.MAX_UPLOAD_SIZE), filesizeformat(content._size)))
else:
raise forms.ValidationError(_('File type is not supported,
content type is: %s' % content_type))
return content
Most of the remaining logic is in views:
def upload_video(request):
try:
# Check if user is authenticated
if form.is_valid():
video_file = request.FILES['video_file']
video_name = form.cleaned_data['video_name']
save_video = Video.objects.create(
original_file = 'uploaded_videos' + user.username,
video_name = video_name)
return HTTPResponseRedirect('next-page')
except Exception, e:
...
The tests are written as:
def test_video_form(TestCase):
user = #Create a dummy user
test_video = SimpleUploadedFile('test_video.flv', open(
'path/to/test/video', 'rb'))
form = VideoForm(user, {'video_name': test_video}, )
self.assertTrue(form.is_valid())
The above test always fails since it says that the file type of 'test_video.flv' is plain/text. I've checked the 'test_video.flv' and its of correct type.
How to pass these files to the upload form and test it.
SimpleUploadedFile has the content_type text/plain by default. It is why your testing code goes fail in clean_video_file and this line:
raise forms.ValidationError(_('File type is not supported,
content type is: %s' % content_type))
is printing:
File type is not supported,
content type is: text/plain
Pass the content_type = 'video'in the SimpleUploadedFile line as shown below.
def test_video_form(TestCase):
user = #Create a dummy user
test_video = SimpleUploadedFile('test_video.flv', open(
'path/to/test/video', 'rb'), content_type='video') # Notice the change here.
form = VideoForm(user, {'video_name': test_video}, )
self.assertTrue(form.is_valid())

django download file from server to user's machine,or read online

I am uploading some .doc and .txt documents on my server, and i'd like to have two options:
-the user to be able to download the document
-the user to be able to read it online
i've read some code for the download function, but it doesn't seem to work.
my code:
def download_course(request, id):
course = Courses.objects.get(pk = id)
response = HttpResponse(mimetype='application/force-download')
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
response['X-Sendfile'] = smart_str(/root/)
return response
def save_course(request, classname):
classroom = Classroom.objects.get(classname = classname)
if request.method == 'POST':
form = CoursesForm(request.POST, request.FILES)
if form.is_valid():
handle_uploaded_file(request.FILES['course'])
new_obj = form.save(commit=False)
new_obj.creator = request.user
new_obj.classroom = classroom
new_obj.save()
return HttpResponseRedirect('.')
else:
form = CoursesForm()
return render_to_response('courses/new_course.html', {
'form': form,
},
context_instance=RequestContext(request))
def handle_uploaded_file(f):
destination = open('root', 'wb+')
for chunk in f.chunks():
destination.write(chunk)
destination.close()
any clue?
thanks!
You can open a File object to read the actual file, and then start download the file like this code:
path_to_file = os.path.realpath("random.xls")
f = open(path_to_file, 'r')
myfile = File(f)
response = HttpResponse(myfile, content_type='application/vnd.ms-excel')
response['Content-Disposition'] = 'attachment; filename=' + name
return response
path_to_file: is where the file is located on the server.
f = open(path_to_file, 'r') .. to read the file
the rest is to download the file.
Should the response['X-Sendfile'] be pointing to the file? It looks like it's only pointing at '/root/', which I'm guessing is just a directory. Maybe it should look more like this:
def download_course(request, id):
course = Courses.objects.get(pk = id)
path_to_file = get_path_to_course_download(course)
response = HttpResponse(mimetype='application/force-download')
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
response['X-Sendfile'] = smart_str(path_to_file)
return response
Where get_path_to_course_download returns the location of the download in the file system (ex: /path/to/where/handle_uploaded_files/saves/files/the_file.doc)