POST multiple files using Django test client in same request - python

I am trying to build some tests for uploads on my Django site. It allows multiple files to be uploaded so I need to test when multiple files are uploaded.
Testing one file works great:
from django.test import Client
def test_stuff(self):
with open('....\file.csv','rb') as fp:
c = Client()
response = c.post('/', {'name': 'Some Name', 'email': 'some#email.com', 'file': fp})
But trying it with a list of files doesn't work.
def test_stuff(self):
file_list = # get list of file paths to open
myfiles = []
for file in file_list:
with open('....\file.csv','rb') as fp:
myfiles.append(fp)
c = Client()
response = c.post('/', {'name': 'Some Name', 'email': 'some#email.com', 'file':myfiles})
And neither does:
def test_stuff(self):
file_list = # get list of file paths to open
myfiles = []
for file in file_list:
with open('....\file.csv','rb') as fp:
myfiles.append(fp)
c = Client()
response = c.post('/', {'name': 'Some Name', 'email': 'some#email.com',}, files={'file':myfiles})
or
def test_stuff(self):
file_list = # get list of file paths to open
myfiles = []
for file in file_list:
with open('....\file.csv','rb') as fp:
myfiles.append(fp)
c = Client()
response = c.post('/', {'name': 'Some Name', 'email': 'some#email.com'}, files=myfiles)
My view gets the files from request.POST.get('myfiles'), but FILES is empty.
Is there a way to POST multiple files with django test client or should I use something else?
Edited to make more accurate

Part of the problem was that with with, the file is immediately closed exiting the statement. Unsurprisingly, the other part was getting the data in the correct format. Django's test client wants ALL the data as a dictionary so, since i was also sending the username and email, it needed to be formatted like:
def test_stuff(self):
file_list = # get list of file paths to open
data = {}
files = []
for file in file_list:
fp = open('....\file.csv','rb')
files.append(fp)
data['myfiles'] = files
data['name'] = 'Some Name'
data['email'] = 'some#email.com'
c = Client()
response = c.post('/', data)

Also, if you use SimpleUploadedFile you can pass multiple files:
file1 = SimpleUploadedFile('file1.txt', b'file-1')
file2 = SimpleUploadedFile('file2.txt', b'file-2')
response = self.client.post('some url', data={
'file': [ file1, file2]
})
And in the view it could be smth like:
class UploadFormView(FormView):
template_name = '...'
form_class = YourForm
# We require to override the post method to manage multiple files.
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('file')
if form.is_valid():
for f in files:
# do smth with file
return self.form_valid(form)
else:
return self.form_invalid(form)

Related

Download a few files in Django without zip

I want to download multiple files in Django without creating zip acrhive.
I have a valid which uses zip (create 1 zip file and download it)
But I have to implement downloading several files without zip archive creating. How can I modify my code?
class DocumentView(GenericAPIView):
def get(self, request, *args, **kwargs):
document_type = kwargs.get("document_type", None)
user_id = kwargs.get("user_id", None)
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
raise NotFound("This is user not found.")
if document_type == 'vehicle_photo':
user_vehicle = user.vehicle.select_related().first()
documents = user_vehicle.photos.all()
else:
documents = user.document_owner.select_related().filter(document_type=document_type)
in_memory = BytesIO()
zip_filename = f"{document_type}_{user_id}.zip"
zip_archive = ZipFile(in_memory, "w")
for document in documents:
f_dir, f_name = os.path.split(document.photo.url if document_type == "vehicle_photo" else
document.file.url)
zip_path = f"{settings.ROOT_DIR}{f_dir}"
zip_archive.write(zip_path+"/"+f_name, f_name)
# Save zip file
zip_archive.close()
response = HttpResponse(content_type="application/zip")
response['Content-Disposition'] = f'attachment; filename={zip_filename}'
in_memory.seek(0)
response.write(in_memory.read())
return response

DRF How to test file uploads?

I have a simple model, a serializer and a view. I want to upload a file over the view but no method I found worked.
Here's my code:
def test_api_post(self):
lesson = self.Create_lesson()
file = SimpleUploadedFile(
"file.txt",
"".join(random.choices(string.ascii_letters + string.digits, k=1024 * 5)).encode(),
"text/plain"
)
response = self.client.post(
"/api/submission/",
{
"lesson": lesson.id,
"file": file
},
format="multipart"
)
self.assertStatusOk(response.status_code) # Error
I tried it using with open() as file and I also tried using path.read_bytes(). Nothing worked.
How can I test binary file uploading with django-rest-framework's test client? doesn't work, https://gist.github.com/guillaumepiot/817a70706587da3bd862835c59ef584e doesn't work and how to unit test file upload in django also doesn't work.
I have fixed the problem with that:
import io
from django.test import TestCase
class test(TestCase):
def test_upload_file(self):
with open('/path/to/file.txt', 'rb') as fp :
fio = io.FileIO(fp.fileno())
fio.name = 'file.txt'
r = self.client.post('/url/', {'filename': fio, 'extraparameter': 5})
self.assertEqual(r.headers['Content-Type'], 'application/json')
url.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'/url/$', views.serverside_method, name="serverside_method")
]
Example on the server-side (view.py)
def serverside_method(request):
if 'filename' in request.FILES:
file_request = request.FILES['filename']
file_size = file_request.size
file_request_name = file_request.name
return JsonResponse({'Success': True})
else:
return JsonResponse({'Success': False})
source: https://gist.github.com/nghiaht/682c2d8d40272c52dbf7adf214f1c0f1
work for me
from rest_framework.test import APIRequestFactory
from apps.Some.SomeViewSet import SomeViewSet
c = APIRequestFactory()
url = 'someurl/'
view = SomeViewSet.as_view()
file = 'path/to/file'
q = {'filename': file}
request = c.post(url, q)
response = view(request)

Python Flask - Does not return output if the file extension is xls

I have a small Flask project that gets some inputs from the user and extracts some data from the database based on the input back to the user and returns an output file.
The code works just fine if the file format is csv. However when the file format is xls, I see the output being generated but the flask app does not return the file.
Edited:
Given below is the code for views.py
#app.route('/data', methods=['GET','POST'])
def data():
form = DataForm()
if form.validate_on_submit():
name = form.name.data
start_date = form.start_date_field.data
end_date = form.end_date_field.data
file_extension = form.file_extension_field.data
rep_func(name=name, start_date=start_date, end_date=end_date, exten=file_extension)
current_directory = path.abspath(path.join(__file__, ".."))
base = os.path.join(current_directory, 'files')
if file_extension == 'csv':
data = pd.read_csv(base + f'/final_output/{name}_{end_date}.{file_extension}', sep=r',(?!\s|\Z)', engine='python')
resp = make_response(data.to_csv(index=False))
resp.headers["Content-Disposition"] = f'attachment; filename={name}_{end_date}.{file_extension}'
resp.headers["Content-Type"] = "text/csv"
elif file_extension == 'xls':
data = pd.read_excel(base + f'/final_output/{name}_{end_date}.{file_extension}')
resp = make_response(data.to_excel(index=False))
resp.headers["Content-Disposition"] = f'attachment; filename={name}_{end_date}.{file_extension}'
resp.headers["Content-Type"] = "application/vnd.ms-excel"
return resp
return render_template('file.html', form=form)
Could anyone advise on where am I going wrong with this. Thanks

clean() method causes files to lose data using POST form

I have set up a form and view to upload multiple *.gpx files to my website at once. These files are validated using a clean() method on the form and then once validated passed to a function for processing.
When I upload some invalid files the clean() method catches them and informs the user as expected.
When I upload some valid files the processing function crashes with an error saying the files are empty.
If I comment out the clean() method then the valid files are uploaded fine.
What can be happening to the form during the clean() method than means the files are being blanked?
here is my form:
class UploadGpxForm(forms.Form):
gpx_file = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
here is my view:
class UploadGpxView(FormView):
form_class = UploadGpxForm
template_name = 'dashboard/upload.html' # Replace with your template.
success_url = reverse_lazy('dashboard:index') # Replace with your URL or reverse().
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
files = request.FILES.getlist('gpx_file')
if form.is_valid():
for f in files:
SaveGPXtoPostGIS(f)
return self.form_valid(form)
else:
return self.form_invalid(form)
Here is my clean method for the UploadGpxForm:
def clean(self):
file_errors=[]
files = list(self.files.getlist('gpx_file'))
for f in list(files):
#check file has only one full stop in it.
if len(f.name.split('.')) != 2:
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded:'\
'File type is not supported')
, params = { 'file_name': f.name }
, code = 'file_type')
)
#check file doesn't breach the file size listed in settings
if f.content_type in settings.DASHBOARD_UPLOAD_FILE_TYPES:
if f._size > settings.DASHBOARD_UPLOAD_FILE_MAX_SIZE:
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded: File too big.'\
'Please keep filesize under %(setting_size)s.'\
'Current filesize %(file_size)s') ,
params = {
'file_name': f.name,
'setting_size': filesizeformat(
settings.DASHBOARD_UPLOAD_FILE_MAX_SIZE),
'file_size': filesizeformat(f._size)
},
code = 'file_size'
)
)
#check it is one of our allowed file types
else:
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded:'\
'File type is not supported')
, params = { 'file_name' : f.name }
, code = 'file_type'
)
)
#next check the file hasn't been uploaded before
#generate MD5
md5hash = md5()
for chunk in f.chunks():
md5hash.update(chunk)
file_hash = md5hash.hexdigest()
if gpxTrack.objects.filter(file_hash=file_hash).exists():
file_errors.append(ValidationError(
_('%(file_name)s has not been uploaded as a identical file'\
'has already been uploaded previously'),
params = { 'file_name' : f.name },
code = 'file_hash'))
#finally raise errors if there are any
if len(file_errors) > 0:
raise ValidationError(file_errors)
else:
return files
When you read the file content (for calculating md5 hash) you need to move the file object’s position to the beginning (0th byte) using file.seek:
md5hash = md5()
for chunk in f.chunks():
md5hash.update(chunk)
file_hash = md5hash.hexdigest()
f.seek(0) #<-- add this line

Want to prompt browser to save csv

Want to prompt browser to save csv using pyramid.response.Response searched for clues and found here's a link Django answer but i can't use it with Pyramid wsgi my code looks like this:
from pyramid.response import Response
def get_list_names_emails(request):
session, env = request.db, request.client_env
response = Response(content_type='text/csv')
output = StringIO()
writer = csv.writer(output)
writer.writerow(['SomeName', 'SomeEmail', 'CompanyName])
csv_output = output.getvalue()
return csv_output
As a cleaner way to do that, you can register a renderer.
In your configuration set-up, add:
config.add_renderer(name='csv',
factory='mypackage.renderers.CSVRenderer')
then in mypackage/renderers.py:
class CSVRenderer(object):
def __init__(self, info):
pass
def __call__(self, value, system):
fout = StringIO.StringIO()
writer = csv.writer(fout, delimiter=';', quoting=csv.QUOTE_ALL)
writer.writerow(value['header'])
writer.writerows(value['rows'])
resp = system['request'].response
resp.content_type = 'text/csv'
resp.content_disposition = 'attachment;filename="report.csv"'
return fout.getvalue()
After that, you can decorate your view with the renderer:
#view_config(..., renderer='csv')
def myview(self):
header = ['name', 'surname', 'address']
rows = [
(
row['name'],
row['surname'],
row['address'],
)
for row in query_rows(.....)
]
return {
'header': header,
'rows': rows
}
The advantage of this approach is better testable view code (you just check for the dictionary values, no need to parse anything) and you can also add a XLS or whatever renderer to the same view:
#view_config(..., renderer='xls')
#view_config(..., renderer='csv')
def myview(self):
...
Try adding Content-Disposition:
response['Content-Disposition'] = 'attachment; filename="report.csv"'
It's better to set content type as well
response['Content-type'] = 'text/csv'
response['Content-Disposition'] = 'attachment; filename="report.csv"'

Categories

Resources