Skip to content

Commit 1860439

Browse files
committed
Add multiple file upload suppoert
1 parent 054533a commit 1860439

File tree

9 files changed

+91
-38
lines changed

9 files changed

+91
-38
lines changed

s3file/middleware.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import logging
23
import os
34

@@ -14,9 +15,9 @@ def __init__(self, get_response):
1415
self.get_response = get_response
1516

1617
def __call__(self, request):
17-
file_fields = request.POST.getlist('s3file', [])
18+
file_fields = json.loads(request.POST.get('s3file', '[]'))
1819
for field_name in file_fields:
19-
paths = request.POST.getlist(field_name, [])
20+
paths = json.loads(request.POST.get(field_name, '[]'))
2021
request.FILES.setlist(field_name, list(self.get_files_from_storage(paths)))
2122

2223
if local_dev and request.path == '/__s3_mock__/':

s3file/static/s3file/js/s3file.js

+16-21
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,6 @@
77
return decodeURI(tag.childNodes[0].nodeValue)
88
}
99

10-
function addHiddenInput (body, name, form) {
11-
var key = parseURL(body)
12-
var input = document.createElement('input')
13-
input.type = 'hidden'
14-
input.value = key
15-
input.name = name
16-
form.appendChild(input)
17-
}
18-
1910
function waitForAllFiles (form) {
2011
if (window.uploading !== 0) {
2112
setTimeout(function () {
@@ -45,22 +36,19 @@
4536
form.loaded += diff
4637
fileInput.loaded += diff
4738
file.loaded = e.loaded
48-
4939
var defaultEventData = {
5040
currentFile: file,
5141
currentFileName: file.name,
5242
currentFileProgress: Math.min(e.loaded / e.total, 1),
5343
originalEvent: e
5444
}
55-
5645
form.dispatchEvent(new window.CustomEvent('progress', {
5746
detail: Object.assign({
5847
progress: Math.min(form.loaded / form.total, 1),
5948
loaded: form.loaded,
6049
total: form.total
6150
}, defaultEventData)
6251
}))
63-
6452
fileInput.dispatchEvent(new window.CustomEvent('progress', {
6553
detail: Object.assign({
6654
progress: Math.min(fileInput.loaded / fileInput.total, 1),
@@ -100,15 +88,15 @@
10088
return request('POST', url, s3Form, fileInput, file, form)
10189
})
10290
Promise.all(promises).then(function (results) {
103-
results.forEach(function (result) {
104-
addHiddenInput(result, name, form)
91+
var keys = results.map(function (result) {
92+
return parseURL(result)
10593
})
106-
var input = document.createElement('input')
107-
input.type = 'hidden'
108-
input.name = 's3file'
109-
input.value = fileInput.name
94+
var hiddenFileInput = document.createElement('input')
95+
hiddenFileInput.type = 'hidden'
96+
hiddenFileInput.name = name
97+
hiddenFileInput.value = JSON.stringify(keys)
98+
form.appendChild(hiddenFileInput)
11099
fileInput.name = ''
111-
form.appendChild(input)
112100
window.uploading -= 1
113101
}, function (err) {
114102
console.log(err)
@@ -131,8 +119,15 @@
131119
window.uploading = 0
132120
form.loaded = 0
133121
form.total = 0
134-
var inputs = form.querySelectorAll('.s3file')
135-
Array.from(inputs).forEach(function (input) {
122+
var inputs = Array.from(form.querySelectorAll('.s3file'))
123+
var hiddenS3Input = document.createElement('input')
124+
hiddenS3Input.type = 'hidden'
125+
hiddenS3Input.name = 's3file'
126+
form.appendChild(hiddenS3Input)
127+
hiddenS3Input.value = JSON.stringify(inputs.map(function (input) {
128+
return input.name
129+
}))
130+
inputs.forEach(function (input) {
136131
window.uploading += 1
137132
uploadFiles(form, input, input.name)
138133
})

tests/conftest.py

+18
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,24 @@ def upload_file(request):
3030
return file_name
3131

3232

33+
@pytest.fixture
34+
def another_upload_file(request):
35+
path = tempfile.mkdtemp()
36+
file_name = os.path.join(path, 'another_%s.txt' % request.node.name)
37+
with open(file_name, 'w') as f:
38+
f.write(request.node.name)
39+
return file_name
40+
41+
42+
@pytest.fixture
43+
def yet_another_upload_file(request):
44+
path = tempfile.mkdtemp()
45+
file_name = os.path.join(path, 'yet_another_%s.txt' % request.node.name)
46+
with open(file_name, 'w') as f:
47+
f.write(request.node.name)
48+
return file_name
49+
50+
3351
@pytest.fixture
3452
def filemodel(request, db):
3553
from tests.testapp.models import FileModel

tests/test_checks.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ def test_storage_check():
1010
call_command('check', '--deploy')
1111

1212
assert (
13-
'FileSystemStorage should not be used in a production environment.'
14-
) in str(e.value)
13+
'FileSystemStorage should not be used in a production environment.'
14+
) in str(e.value)

tests/test_forms.py

+28-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import os
23
from contextlib import contextmanager
34

45
import pytest
@@ -40,8 +41,8 @@ def test_value_from_datadict(self, client, upload_file):
4041
with open(upload_file) as f:
4142
uploaded_file = default_storage.save('test.jpg', f)
4243
response = client.post(reverse('upload'), {
43-
'file': uploaded_file,
44-
's3file': 'file'
44+
'file': json.dumps([uploaded_file]),
45+
's3file': '["file"]',
4546
})
4647

4748
assert response.status_code == 201
@@ -133,7 +134,7 @@ def test_no_js_error(self, driver, live_server):
133134

134135
def test_file_insert(self, request, driver, live_server, upload_file, freeze):
135136
driver.get(live_server + self.url)
136-
file_input = driver.find_element_by_xpath('//input[@type=\'file\']')
137+
file_input = driver.find_element_by_xpath('//input[@name=\'file\']')
137138
file_input.send_keys(upload_file)
138139
assert file_input.get_attribute('name') == 'file'
139140
with wait_for_page_load(driver, timeout=10):
@@ -146,7 +147,7 @@ def test_file_insert(self, request, driver, live_server, upload_file, freeze):
146147

147148
def test_file_insert_submit_value(self, driver, live_server, upload_file, freeze):
148149
driver.get(live_server + self.url)
149-
file_input = driver.find_element_by_xpath('//input[@type=\'file\']')
150+
file_input = driver.find_element_by_xpath('//input[@name=\'file\']')
150151
file_input.send_keys(upload_file)
151152
assert file_input.get_attribute('name') == 'file'
152153
save_button = driver.find_element_by_xpath('//input[@name=\'save\']')
@@ -155,7 +156,7 @@ def test_file_insert_submit_value(self, driver, live_server, upload_file, freeze
155156
assert 'save' in driver.page_source
156157

157158
driver.get(live_server + self.url)
158-
file_input = driver.find_element_by_xpath('//input[@type=\'file\']')
159+
file_input = driver.find_element_by_xpath('//input[@name=\'file\']')
159160
file_input.send_keys(upload_file)
160161
assert file_input.get_attribute('name') == 'file'
161162
save_button = driver.find_element_by_xpath('//button[@name=\'save_continue\']')
@@ -166,7 +167,7 @@ def test_file_insert_submit_value(self, driver, live_server, upload_file, freeze
166167

167168
def test_progress(self, driver, live_server, upload_file, freeze):
168169
driver.get(live_server + self.url)
169-
file_input = driver.find_element_by_xpath('//input[@type=\'file\']')
170+
file_input = driver.find_element_by_xpath('//input[@name=\'file\']')
170171
file_input.send_keys(upload_file)
171172
assert file_input.get_attribute('name') == 'file'
172173
save_button = driver.find_element_by_xpath('//input[@name=\'save\']')
@@ -175,14 +176,33 @@ def test_progress(self, driver, live_server, upload_file, freeze):
175176
assert 'save' in driver.page_source
176177

177178
driver.get(live_server + self.url)
178-
file_input = driver.find_element_by_xpath('//input[@type=\'file\']')
179+
file_input = driver.find_element_by_xpath('//input[@name=\'file\']')
179180
file_input.send_keys(upload_file)
180181
assert file_input.get_attribute('name') == 'file'
181182
save_button = driver.find_element_by_xpath('//button[@name=\'save_continue\']')
182183
with wait_for_page_load(driver, timeout=10):
183184
save_button.click()
184185
response = json.loads(driver.find_elements_by_css_selector('pre')[0].text)
185-
assert response['progress'] == '1'
186+
assert response['POST']['progress'] == '1'
187+
188+
def test_multi_file(self, driver, live_server, freeze,
189+
upload_file, another_upload_file, yet_another_upload_file):
190+
driver.get(live_server + self.url)
191+
file_input = driver.find_element_by_xpath('//input[@name=\'file\']')
192+
file_input.send_keys(' \n '.join([upload_file, another_upload_file]))
193+
file_input = driver.find_element_by_xpath('//input[@name=\'other_file\']')
194+
file_input.send_keys(yet_another_upload_file)
195+
save_button = driver.find_element_by_xpath('//input[@name=\'save\']')
196+
with wait_for_page_load(driver, timeout=10):
197+
save_button.click()
198+
response = json.loads(driver.find_elements_by_css_selector('pre')[0].text)
199+
assert response['FILES'] == {
200+
'file': [
201+
os.path.basename(upload_file),
202+
os.path.basename(another_upload_file),
203+
],
204+
'other_file': [os.path.basename(yet_another_upload_file)]
205+
}
186206

187207
def test_media(self):
188208
assert ClearableFileInput().media._js == ['s3file/js/s3file.js']

tests/test_middleware.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,16 @@ def test_process_request(self, rf):
2222
assert request.FILES.get('file').read() == b'uploaded'
2323

2424
default_storage.save('s3_file.txt', ContentFile(b's3file'))
25-
request = rf.post('/', data={'file': 's3_file.txt', 's3file': 'file'})
25+
request = rf.post('/', data={'file': '["s3_file.txt"]', 's3file': '["file"]'})
2626
S3FileMiddleware(lambda x: None)(request)
2727
assert request.FILES.getlist('file')
2828
assert request.FILES.get('file').read() == b's3file'
2929

3030
def test_process_request__no_file(self, rf, caplog):
31-
request = rf.post('/', data={'file': 'does_not_exist.txt', 's3file': 'file'})
31+
request = rf.post(
32+
'/',
33+
data={'file': '["does_not_exist.txt"]', 's3file': '["file"]'}
34+
)
3235
S3FileMiddleware(lambda x: None)(request)
3336
assert not request.FILES.getlist('file')
3437
assert 'File not found: does_not_exist.txt' in caplog.text

tests/testapp/forms.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
class UploadForm(forms.ModelForm):
1313
class Meta:
1414
model = FileModel
15-
fields = ('file',)
15+
fields = ('file', 'other_file')
1616
widgets = {
17-
'file': forms.ClearableFileInput(attrs={'multiple': True})
17+
'file': forms.ClearableFileInput(attrs={'multiple': True}),
1818
}

tests/testapp/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33

44
class FileModel(models.Model):
55
file = models.FileField(upload_to='path/to/files', blank=True)
6+
other_file = models.FileField(upload_to='path/to/files', blank=True)

tests/testapp/views.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
1+
from django.core.files import File
2+
from django.core.serializers.json import DjangoJSONEncoder
13
from django.http import JsonResponse
24
from django.views import generic
35

46
from tests.testapp import forms
57

68

9+
class FileEncoder(DjangoJSONEncoder):
10+
def default(self, o):
11+
if isinstance(o, File):
12+
return o.name
13+
super().default(o)
14+
15+
716
class ExampleFormView(generic.FormView):
817
form_class = forms.UploadForm
918
template_name = 'form.html'
1019

1120
def form_valid(self, form):
12-
return JsonResponse(self.request.POST, status=201)
21+
return JsonResponse({
22+
'POST': self.request.POST,
23+
'FILES': {
24+
'file': self.request.FILES.getlist('file'),
25+
'other_file': self.request.FILES.getlist('other_file'),
26+
}
27+
}, status=201, encoder=FileEncoder)

0 commit comments

Comments
 (0)