Commit 9c4b1d70 by David Ormsbee

Merge pull request #456 from MITx/kimth/multiple-fileupload

Kimth/multiple fileupload
parents 9aeec690 20adaa81
......@@ -1119,11 +1119,6 @@ class CodeResponse(LoncapaResponse):
(err, self.answer_id, convert_files_to_filenames(student_answers)))
raise Exception(err)
if is_file(submission):
self.context.update({'submission': submission.name})
else:
self.context.update({'submission': submission})
# Prepare xqueue request
#------------------------------------------------------------
qinterface = self.system.xqueue['interface']
......@@ -1135,14 +1130,19 @@ class CodeResponse(LoncapaResponse):
queue_name=self.queue_name)
# Generate body
if is_list_of_files(submission):
self.context.update({'submission': queuekey}) # For tracking. TODO: May want to record something else here
else:
self.context.update({'submission': submission})
contents = self.payload.copy()
# Submit request. When successful, 'msg' is the prior length of the queue
if is_file(submission):
contents.update({'student_response': submission.name})
if is_list_of_files(submission):
contents.update({'student_response': ''}) # TODO: Is there any information we want to send here?
(error, msg) = qinterface.send_to_queue(header=xheader,
body=json.dumps(contents),
file_to_upload=submission)
files_to_upload=submission)
else:
contents.update({'student_response': submission})
(error, msg) = qinterface.send_to_queue(header=xheader,
......
<section id="filesubmission_${id}" class="filesubmission">
<input type="file" name="input_${id}" id="input_${id}" value="${value}" /><br />
<input type="file" name="input_${id}" id="input_${id}" value="${value}" multiple="multiple" /><br />
% if state == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct':
......
......@@ -39,12 +39,16 @@ def convert_files_to_filenames(answers):
'''
new_answers = dict()
for answer_id in answers.keys():
if is_file(answers[answer_id]):
new_answers[answer_id] = answers[answer_id].name
answer = answers[answer_id]
if is_list_of_files(answer): # Files are stored as a list, even if one file
new_answers[answer_id] = [f.name for f in answer]
else:
new_answers[answer_id] = answers[answer_id]
return new_answers
def is_list_of_files(files):
return isinstance(files, list) and all(is_file(f) for f in files)
def is_file(file_to_test):
'''
Duck typing to check if 'file_to_test' is a File object
......
......@@ -65,7 +65,7 @@ class XQueueInterface(object):
self.auth = django_auth
self.session = requests.session(auth=requests_auth)
def send_to_queue(self, header, body, file_to_upload=None):
def send_to_queue(self, header, body, files_to_upload=None):
'''
Submit a request to xqueue.
......@@ -74,16 +74,16 @@ class XQueueInterface(object):
body: Serialized data for the receipient behind the queueing service. The operation of
xqueue is agnostic to the contents of 'body'
file_to_upload: File object to be uploaded to xqueue along with queue request
files_to_upload: List of file objects to be uploaded to xqueue along with queue request
Returns (error_code, msg) where error_code != 0 indicates an error
'''
# Attempt to send to queue
(error, msg) = self._send_to_queue(header, body, file_to_upload)
(error, msg) = self._send_to_queue(header, body, files_to_upload)
if error and (msg == 'login_required'): # Log in, then try again
self._login()
(error, msg) = self._send_to_queue(header, body, file_to_upload)
(error, msg) = self._send_to_queue(header, body, files_to_upload)
return (error, msg)
......@@ -94,13 +94,15 @@ class XQueueInterface(object):
return self._http_post(self.url+'/xqueue/login/', payload)
def _send_to_queue(self, header, body, file_to_upload=None):
def _send_to_queue(self, header, body, files_to_upload):
payload = {'xqueue_header': header,
'xqueue_body' : body}
files = None
if file_to_upload is not None:
files = { file_to_upload.name: file_to_upload }
return self._http_post(self.url+'/xqueue/submit/', payload, files)
files = {}
if files_to_upload is not None:
for f in files_to_upload:
files.update({ f.name: f })
return self._http_post(self.url+'/xqueue/submit/', payload, files=files)
def _http_post(self, url, data, files=None):
......
......@@ -151,26 +151,33 @@ class @Problem
return
if not window.FormData
alert "Sorry, your browser does not support file uploads. Your submit request could not be fulfilled. If you can, please use Chrome or Safari which have been verified to support file uploads."
alert "Submission aborted! Sorry, your browser does not support file uploads. If you can, please use Chrome or Safari which have been verified to support file uploads."
return
fd = new FormData()
# Sanity check of file size
file_too_large = false
# Sanity checks on submission
max_filesize = 4*1000*1000 # 4 MB
file_too_large = false
file_not_selected = false
@inputs.each (index, element) ->
if element.type is 'file'
if element.files[0] instanceof File
if element.files[0].size > max_filesize
for file in element.files
if file.size > max_filesize
file_too_large = true
alert 'Submission aborted! Your file "' + element.files[0].name + '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
fd.append(element.id, element.files[0])
else
fd.append(element.id, '')
alert 'Submission aborted! Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
fd.append(element.id, file)
if element.files.length == 0
file_not_selected = true
fd.append(element.id, '') # In case we want to allow submissions with no file
else
fd.append(element.id, element.value)
if file_not_selected
alert 'Submission aborted! You did not select any files to submit'
abort_submission = file_too_large or file_not_selected
settings =
type: "POST"
......@@ -184,8 +191,8 @@ class @Problem
@updateProgress response
else
alert(response.success)
if not file_too_large
if not abort_submission
$.ajaxWithPrefix("#{@url}/problem_check", settings)
check: =>
......
......@@ -341,11 +341,11 @@ class CodeResponseTest(unittest.TestCase):
fp = open(problem_file)
answers_with_file = {'1_2_1': 'String-based answer',
'1_3_1': ['answer1', 'answer2', 'answer3'],
'1_4_1': fp}
'1_4_1': [fp, fp]}
answers_converted = convert_files_to_filenames(answers_with_file)
self.assertEquals(answers_converted['1_2_1'], 'String-based answer')
self.assertEquals(answers_converted['1_3_1'], ['answer1', 'answer2', 'answer3'])
self.assertEquals(answers_converted['1_4_1'], fp.name)
self.assertEquals(answers_converted['1_4_1'], [fp.name, fp.name])
class ChoiceResponseTest(unittest.TestCase):
......
......@@ -375,15 +375,16 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
# ''' (fix emacs broken parsing)
# Check for submitted files and basic file size checks
p = request.POST.copy()
p = request.POST.dict()
if request.FILES:
for inputfile_id in request.FILES.keys():
inputfile = request.FILES[inputfile_id]
if inputfile.size > settings.STUDENT_FILEUPLOAD_MAX_SIZE: # Bytes
file_too_big_msg = 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\
(inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2))
return HttpResponse(json.dumps({'success': file_too_big_msg}))
p[inputfile_id] = inputfile
for fileinput_id in request.FILES.keys():
inputfiles = request.FILES.getlist(fileinput_id)
for inputfile in inputfiles:
if inputfile.size > settings.STUDENT_FILEUPLOAD_MAX_SIZE: # Bytes
file_too_big_msg = 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\
(inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2))
return HttpResponse(json.dumps({'success': file_too_big_msg}))
p[fileinput_id] = inputfiles
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, modulestore().get_item(id))
instance = get_module(request.user, request, id, student_module_cache, course_id=course_id)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment