From d2cb6458348f581eba9dccfcf7ea573c8a139fee Mon Sep 17 00:00:00 2001
From: kimth <kimt@mit.edu>
Date: Sun, 19 Aug 2012 09:26:03 -0400
Subject: [PATCH] Multiple file submissions

---
 common/lib/capa/capa/responsetypes.py         | 16 +++++++-------
 common/lib/capa/capa/util.py                  | 18 ++++++++++++++--
 common/lib/capa/capa/xqueue_interface.py      | 15 ++++++-------
 .../xmodule/js/src/capa/display.coffee        | 21 ++++++++++++-------
 lms/djangoapps/courseware/module_render.py    | 17 ++++++++-------
 5 files changed, 54 insertions(+), 33 deletions(-)

diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py
index 3a2e40f896c..cc67389da9f 100644
--- a/common/lib/capa/capa/responsetypes.py
+++ b/common/lib/capa/capa/responsetypes.py
@@ -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,
diff --git a/common/lib/capa/capa/util.py b/common/lib/capa/capa/util.py
index 005494e8c0a..01f5fe4c82a 100644
--- a/common/lib/capa/capa/util.py
+++ b/common/lib/capa/capa/util.py
@@ -39,12 +39,26 @@ 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
+            list_of_filenames = []
+            for inputfile in answer:
+                list_of_filenames.append(inputfile.name)
+            new_answers[answer_id] = list_of_filenames 
         else:
             new_answers[answer_id] = answers[answer_id]
     return new_answers
 
+def is_list_of_files(list_of_files_to_test):
+    if not isinstance(list_of_files_to_test, list):
+        return False
+
+    for li in list_of_files_to_test:
+        if not is_file(li):
+            return False
+
+    return True
+
 def is_file(file_to_test):
     '''
     Duck typing to check if 'file_to_test' is a File object
diff --git a/common/lib/capa/capa/xqueue_interface.py b/common/lib/capa/capa/xqueue_interface.py
index 2847968a89e..81dac229368 100644
--- a/common/lib/capa/capa/xqueue_interface.py
+++ b/common/lib/capa/capa/xqueue_interface.py
@@ -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=[]):
         '''
         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,12 +94,13 @@ 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 }
+        for f in files_to_upload:
+            files = { f.name: f }
+
         return self._http_post(self.url+'/xqueue/submit/', payload, files)
 
 
diff --git a/common/lib/xmodule/xmodule/js/src/capa/display.coffee b/common/lib/xmodule/xmodule/js/src/capa/display.coffee
index 0db000188e7..a242757357f 100644
--- a/common/lib/xmodule/xmodule/js/src/capa/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/capa/display.coffee
@@ -151,28 +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
-    abort_submission = 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'
         for file in element.files
           if file.size > max_filesize
-            abort_submission = true
+            file_too_large = true
             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 
-          abort_submission = true
-          alert 'Submission aborted! You did not select any files to submit'
-          fd.append(element.id, '')
+          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"
@@ -186,7 +191,7 @@ class @Problem
             @updateProgress response
           else
             alert(response.success)
-
+    
     if not abort_submission
       $.ajaxWithPrefix("#{@url}/problem_check", settings)
 
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 558f6deeb2a..53c7e453ddf 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -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)
-- 
GitLab