Merge remote-tracking branch 'origin/master' into feature/bridger/new_wiki
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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,19 @@ 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)
|
||||
if files_to_upload is not None:
|
||||
for f in files_to_upload: # Need to rewind file pointers
|
||||
f.seek(0)
|
||||
(error, msg) = self._send_to_queue(header, body, files_to_upload)
|
||||
|
||||
return (error, msg)
|
||||
|
||||
@@ -94,13 +97,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: =>
|
||||
|
||||
@@ -172,11 +172,13 @@ schematic = (function() {
|
||||
this.tools = new Array();
|
||||
this.toolbar = [];
|
||||
|
||||
/* DISABLE HELP BUTTON (target URL not consistent with multicourse hierarchy) -- SJSU
|
||||
if (!this.diagram_only) {
|
||||
this.tools['help'] = this.add_tool(help_icon,'Help: display help page',this.help);
|
||||
this.enable_tool('help',true);
|
||||
this.toolbar.push(null); // spacer
|
||||
}
|
||||
END DISABLE HELP BUTTON -- SJSU */
|
||||
|
||||
if (this.edits_allowed) {
|
||||
this.tools['grid'] = this.add_tool(grid_icon,'Grid: toggle grid display',this.toggle_grid);
|
||||
|
||||
@@ -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,22 @@ 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)
|
||||
|
||||
if len(inputfiles) > settings.MAX_FILEUPLOADS_PER_INPUT:
|
||||
too_many_files_msg = 'Submission aborted! Maximum %d files may be submitted at once' %\
|
||||
settings.MAX_FILEUPLOADS_PER_INPUT
|
||||
return HttpResponse(json.dumps({'success': too_many_files_msg}))
|
||||
|
||||
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)
|
||||
|
||||
@@ -144,6 +144,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
)
|
||||
|
||||
STUDENT_FILEUPLOAD_MAX_SIZE = 4*1000*1000 # 4 MB
|
||||
MAX_FILEUPLOADS_PER_INPUT = 10
|
||||
|
||||
# FIXME:
|
||||
# We should have separate S3 staged URLs in case we need to make changes to
|
||||
|
||||
Reference in New Issue
Block a user