Commit cf20839b by David Ormsbee

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

Kimth/multiple fileupload
parents be1607b9 739e5845
...@@ -1119,11 +1119,6 @@ class CodeResponse(LoncapaResponse): ...@@ -1119,11 +1119,6 @@ class CodeResponse(LoncapaResponse):
(err, self.answer_id, convert_files_to_filenames(student_answers))) (err, self.answer_id, convert_files_to_filenames(student_answers)))
raise Exception(err) raise Exception(err)
if is_file(submission):
self.context.update({'submission': submission.name})
else:
self.context.update({'submission': submission})
# Prepare xqueue request # Prepare xqueue request
#------------------------------------------------------------ #------------------------------------------------------------
qinterface = self.system.xqueue['interface'] qinterface = self.system.xqueue['interface']
...@@ -1135,14 +1130,19 @@ class CodeResponse(LoncapaResponse): ...@@ -1135,14 +1130,19 @@ class CodeResponse(LoncapaResponse):
queue_name=self.queue_name) queue_name=self.queue_name)
# Generate body # 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() contents = self.payload.copy()
# Submit request. When successful, 'msg' is the prior length of the queue # Submit request. When successful, 'msg' is the prior length of the queue
if is_file(submission): if is_list_of_files(submission):
contents.update({'student_response': submission.name}) contents.update({'student_response': ''}) # TODO: Is there any information we want to send here?
(error, msg) = qinterface.send_to_queue(header=xheader, (error, msg) = qinterface.send_to_queue(header=xheader,
body=json.dumps(contents), body=json.dumps(contents),
file_to_upload=submission) files_to_upload=submission)
else: else:
contents.update({'student_response': submission}) contents.update({'student_response': submission})
(error, msg) = qinterface.send_to_queue(header=xheader, (error, msg) = qinterface.send_to_queue(header=xheader,
......
<section id="filesubmission_${id}" class="filesubmission"> <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': % if state == 'unsubmitted':
<span class="unanswered" style="display:inline-block;" id="status_${id}"></span> <span class="unanswered" style="display:inline-block;" id="status_${id}"></span>
% elif state == 'correct': % elif state == 'correct':
......
...@@ -39,12 +39,16 @@ def convert_files_to_filenames(answers): ...@@ -39,12 +39,16 @@ def convert_files_to_filenames(answers):
''' '''
new_answers = dict() new_answers = dict()
for answer_id in answers.keys(): for answer_id in answers.keys():
if is_file(answers[answer_id]): answer = answers[answer_id]
new_answers[answer_id] = answers[answer_id].name 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: else:
new_answers[answer_id] = answers[answer_id] new_answers[answer_id] = answers[answer_id]
return new_answers 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): def is_file(file_to_test):
''' '''
Duck typing to check if 'file_to_test' is a File object Duck typing to check if 'file_to_test' is a File object
......
...@@ -65,7 +65,7 @@ class XQueueInterface(object): ...@@ -65,7 +65,7 @@ class XQueueInterface(object):
self.auth = django_auth self.auth = django_auth
self.session = requests.session(auth=requests_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. Submit a request to xqueue.
...@@ -74,16 +74,16 @@ class XQueueInterface(object): ...@@ -74,16 +74,16 @@ class XQueueInterface(object):
body: Serialized data for the receipient behind the queueing service. The operation of body: Serialized data for the receipient behind the queueing service. The operation of
xqueue is agnostic to the contents of 'body' 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 Returns (error_code, msg) where error_code != 0 indicates an error
''' '''
# Attempt to send to queue # 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 if error and (msg == 'login_required'): # Log in, then try again
self._login() 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) return (error, msg)
...@@ -94,13 +94,15 @@ class XQueueInterface(object): ...@@ -94,13 +94,15 @@ class XQueueInterface(object):
return self._http_post(self.url+'/xqueue/login/', payload) 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, payload = {'xqueue_header': header,
'xqueue_body' : body} 'xqueue_body' : body}
files = None files = {}
if file_to_upload is not None: if files_to_upload is not None:
files = { file_to_upload.name: file_to_upload } for f in files_to_upload:
return self._http_post(self.url+'/xqueue/submit/', payload, files) files.update({ f.name: f })
return self._http_post(self.url+'/xqueue/submit/', payload, files=files)
def _http_post(self, url, data, files=None): def _http_post(self, url, data, files=None):
......
...@@ -151,26 +151,33 @@ class @Problem ...@@ -151,26 +151,33 @@ class @Problem
return return
if not window.FormData 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 return
fd = new FormData() fd = new FormData()
# Sanity check of file size # Sanity checks on submission
file_too_large = false
max_filesize = 4*1000*1000 # 4 MB max_filesize = 4*1000*1000 # 4 MB
file_too_large = false
file_not_selected = false
@inputs.each (index, element) -> @inputs.each (index, element) ->
if element.type is 'file' if element.type is 'file'
if element.files[0] instanceof File for file in element.files
if element.files[0].size > max_filesize if file.size > max_filesize
file_too_large = true file_too_large = true
alert 'Submission aborted! Your file "' + element.files[0].name + '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)' alert 'Submission aborted! Your file "' + file.name '" is too large (max size: ' + max_filesize/(1000*1000) + ' MB)'
fd.append(element.id, element.files[0]) fd.append(element.id, file)
else if element.files.length == 0
fd.append(element.id, '') file_not_selected = true
fd.append(element.id, '') # In case we want to allow submissions with no file
else else
fd.append(element.id, element.value) 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 = settings =
type: "POST" type: "POST"
...@@ -184,8 +191,8 @@ class @Problem ...@@ -184,8 +191,8 @@ class @Problem
@updateProgress response @updateProgress response
else else
alert(response.success) alert(response.success)
if not file_too_large if not abort_submission
$.ajaxWithPrefix("#{@url}/problem_check", settings) $.ajaxWithPrefix("#{@url}/problem_check", settings)
check: => check: =>
......
...@@ -341,11 +341,11 @@ class CodeResponseTest(unittest.TestCase): ...@@ -341,11 +341,11 @@ class CodeResponseTest(unittest.TestCase):
fp = open(problem_file) fp = open(problem_file)
answers_with_file = {'1_2_1': 'String-based answer', answers_with_file = {'1_2_1': 'String-based answer',
'1_3_1': ['answer1', 'answer2', 'answer3'], '1_3_1': ['answer1', 'answer2', 'answer3'],
'1_4_1': fp} '1_4_1': [fp, fp]}
answers_converted = convert_files_to_filenames(answers_with_file) answers_converted = convert_files_to_filenames(answers_with_file)
self.assertEquals(answers_converted['1_2_1'], 'String-based answer') 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_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): class ChoiceResponseTest(unittest.TestCase):
......
...@@ -375,15 +375,16 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None): ...@@ -375,15 +375,16 @@ def modx_dispatch(request, dispatch=None, id=None, course_id=None):
# ''' (fix emacs broken parsing) # ''' (fix emacs broken parsing)
# Check for submitted files and basic file size checks # Check for submitted files and basic file size checks
p = request.POST.copy() p = request.POST.dict()
if request.FILES: if request.FILES:
for inputfile_id in request.FILES.keys(): for fileinput_id in request.FILES.keys():
inputfile = request.FILES[inputfile_id] inputfiles = request.FILES.getlist(fileinput_id)
if inputfile.size > settings.STUDENT_FILEUPLOAD_MAX_SIZE: # Bytes for inputfile in inputfiles:
file_too_big_msg = 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\ if inputfile.size > settings.STUDENT_FILEUPLOAD_MAX_SIZE: # Bytes
(inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2)) file_too_big_msg = 'Submission aborted! Your file "%s" is too large (max size: %d MB)' %\
return HttpResponse(json.dumps({'success': file_too_big_msg})) (inputfile.name, settings.STUDENT_FILEUPLOAD_MAX_SIZE/(1000**2))
p[inputfile_id] = inputfile 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)) 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) 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