diff options
-rw-r--r-- | yaksh/documentation/moderator_docs/creating_question.rst | 28 | ||||
-rw-r--r-- | yaksh/file_utils.py | 2 | ||||
-rw-r--r-- | yaksh/fixtures/demo_questions.zip | bin | 3055 -> 3230 bytes | |||
-rw-r--r-- | yaksh/models.py | 34 | ||||
-rw-r--r-- | yaksh/templates/yaksh/showquestions.html | 31 | ||||
-rw-r--r-- | yaksh/test_models.py | 4 | ||||
-rw-r--r-- | yaksh/test_views.py | 87 | ||||
-rw-r--r-- | yaksh/views.py | 9 |
8 files changed, 169 insertions, 26 deletions
diff --git a/yaksh/documentation/moderator_docs/creating_question.rst b/yaksh/documentation/moderator_docs/creating_question.rst index 82bb6e5..3e878ea 100644 --- a/yaksh/documentation/moderator_docs/creating_question.rst +++ b/yaksh/documentation/moderator_docs/creating_question.rst @@ -330,14 +330,34 @@ Features in Question * **Download Questions** - Select questions from the list of questions displayed on the Questions page. Click on the Download Selected button to download the questions. This will create a zip file of the Questions selected. + Select questions from a list of questions displayed on the + Questions page. Click on the Download Selected button to download + the questions. This will create a zip file of the Questions + selected. The zip will contain yaml file and an folder called + **additional_files** which will contain files required by questions + downloaded. Finally one can also download a template yaml file + and modify it to add his/her questions. * **Upload Questions** - Click on the browse button. This will open up a window. Select the zip file of questions and click Ok and then click on Upload file button, questions will be uploaded and displayed on the Questions page. + Click on the **Upload and Download questions** tab in the + **Question Page**. + One can upload Yaml file with extensions .yaml or .yml. + Please note that you cannot upload files associated to a question. + Yaml file can have any name. + + One can also upload zip with the following zip structure - + + .. code:: + + .zip + |-- .yaml or .yml + |-- .yaml or .yml + |-- folder1 + | |-- Files required by questions + |-- folder2 + | |-- Files required by questions - Zip file should contain **questions_dump.yaml** from which questions will be loaded. - Zip file can contain files related to questions. * **Test Questions** diff --git a/yaksh/file_utils.py b/yaksh/file_utils.py index 88fc46d..1dc6006 100644 --- a/yaksh/file_utils.py +++ b/yaksh/file_utils.py @@ -47,7 +47,7 @@ def extract_files(zip_file, path=None): if path: extract_path = path else: - extract_path = tempfile.gettempdir() + extract_path = tempfile.mkdtemp() zip_file.extractall(extract_path) zip_file.close() return zfiles, extract_path diff --git a/yaksh/fixtures/demo_questions.zip b/yaksh/fixtures/demo_questions.zip Binary files differindex 4e86485..1618341 100644 --- a/yaksh/fixtures/demo_questions.zip +++ b/yaksh/fixtures/demo_questions.zip diff --git a/yaksh/models.py b/yaksh/models.py index 464eeb5..e25fd81 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -13,6 +13,8 @@ from django.contrib.contenttypes.models import ContentType from taggit.managers import TaggableManager from django.utils import timezone from django.core.files import File +import glob + try: from StringIO import StringIO as string_io except ImportError: @@ -1109,7 +1111,7 @@ class Question(models.Model): tags = question.pop('tags') if 'tags' in question else None test_cases = question.pop('testcase') que, result = Question.objects.get_or_create(**question) - if file_names: + if file_names and file_path: que._add_files_to_db(file_names, file_path) if tags: que.tags.add(*tags) @@ -1178,13 +1180,18 @@ class Question(models.Model): files = FileUpload.objects.filter(question=self) files_list = [] for f in files: - zip_file.write(f.file.path, (os.path.basename(f.file.path))) + zip_file.write(f.file.path, os.path.join("additional_files", + os.path.basename( + f.file.path + ) + ) + ) files_list.append(((os.path.basename(f.file.path)), f.extract)) return files_list def _add_files_to_db(self, file_names, path): for file_name, extract in file_names: - q_file = os.path.join(path, file_name) + q_file = glob.glob(os.path.join(path, "**", file_name))[0] if os.path.exists(q_file): que_file = open(q_file, 'rb') # Converting to Python file object with @@ -1219,16 +1226,17 @@ class Question(models.Model): shutil.rmtree(tmp_file_path) def read_yaml(self, file_path, user, files=None): - yaml_file = os.path.join(file_path, "questions_dump.yaml") - msg = "" - if os.path.exists(yaml_file): - with open(yaml_file, 'r') as q_file: - questions_list = q_file.read() - msg = self.load_questions(questions_list, user, - file_path, files - ) - else: - msg = "Please upload zip file with questions_dump.yaml in it." + msg = "Failed to upload Questions" + for ext in ["yaml", "yml"]: + for yaml_file in glob.glob(os.path.join(file_path, + "*.{0}".format(ext) + )): + if os.path.exists(yaml_file): + with open(yaml_file, 'r') as q_file: + questions_list = q_file.read() + msg = self.load_questions(questions_list, user, + file_path, files + ) if files: delete_files(files, file_path) diff --git a/yaksh/templates/yaksh/showquestions.html b/yaksh/templates/yaksh/showquestions.html index 4240b2e..ff7a627 100644 --- a/yaksh/templates/yaksh/showquestions.html +++ b/yaksh/templates/yaksh/showquestions.html @@ -15,13 +15,40 @@ <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar nav-stacked"> <li class="active"><a href="#show" data-toggle="pill" > Show all Questions</a></li> - <li><a href="#updown" data-toggle="pill" > Upload and Download Questions</a></li> + <li><a href="#updown" data-toggle="pill" > Upload Questions</a></li> </ul> </div> <div class="tab-content col-md-9 col-md-offset-2 main"> <!-- Upload Questions --> + <div id="updown" class="tab-pane fade"> -<a class="btn btn-primary" href="{{URL_ROOT}}/exam/manage/courses/download_yaml_template/"> Download Template</a> +<div class="alert alert-info" role="alert"> + <p>You can upload question files the following ways - + <li><b><u>Yaml File</u></b> + <p>One can upload Yaml file with extensions .yaml or .yml. Please note + that you cannot upload files associated to a question. Yaml file can + have any name. + </p> + </li> + <li><b><u>Zip File</u></b> + <p> One can also upload zip with the following zip structure - + <pre> + .zip + |-- .yaml or .yml + |-- .yaml or .yml + |-- folder1 + | |-- Files required by questions + |-- folder2 + | |-- Files required by questions + </pre> + </li> + </p> + + <p> + <b> Click <a class="btn btn-success" href="{{URL_ROOT}}/exam/manage/courses/download_yaml_template/" +>here</a> to download a sample YAML, edit and upload it</b> + </p> + </div> <br/> <h4> Or </h4> <form action="" method="post" enctype="multipart/form-data"> diff --git a/yaksh/test_models.py b/yaksh/test_models.py index eaf5bbc..e2795bb 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -434,7 +434,9 @@ class QuestionTestCases(unittest.TestCase): def test_load_questions_with_all_fields(self): """ Test load questions into database from Yaml """ question = Question() - question.load_questions(self.yaml_questions_data, self.user1) + question.load_questions(self.yaml_questions_data, self.user1, + self.load_tmp_path + ) question_data = Question.objects.get(summary="Yaml Demo") file = FileUpload.objects.get(question=question_data) test_case = question_data.get_test_cases() diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 150a624..06a4fa3 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -25,7 +25,7 @@ from yaksh.models import ( User, Profile, Question, Quiz, QuestionPaper, AnswerPaper, Answer, Course, AssignmentUpload, McqTestCase, IntegerTestCase, StringTestCase, FloatTestCase, FIXTURES_DIR_PATH, LearningModule, LearningUnit, Lesson, - LessonFile, CourseStatus + LessonFile, CourseStatus, dict_to_yaml ) from yaksh.decorators import user_has_profile @@ -4049,6 +4049,36 @@ class TestShowQuestions(TestCase): points=1.0, language="python", type="mcq", user=self.user, active=True ) + test_case_upload_data = [{"test_case": "assert fact(3)==6", + "test_case_type": "standardtestcase", + "test_case_args": "", + "weight": 1.0 + }] + question_data_1 = {"snippet": "def fact()", "active": True, + "points": 1.0, + "description": "factorial of a no", + "language": "Python", "type": "Code", + "testcase": test_case_upload_data, + "summary": "Yaml Demo 2", + "tags": ['yaml_demo'] + } + + question_data_2 = {"snippet": "def fact()", "active": True, + "points": 1.0, + "description": "factorial of a no", + "language": "Python", "type": "Code", + "testcase": test_case_upload_data, + "summary": "Yaml Demo 3", + "tags": ['yaml_demo'] + } + yaml_question_1 = dict_to_yaml(question_data_1) + yaml_question_2 = dict_to_yaml(question_data_2) + self.yaml_file_1 = SimpleUploadedFile("test1.yaml", + yaml_question_1.encode("utf-8") + ) + self.yaml_file_2 = SimpleUploadedFile("test2.yaml", + yaml_question_2.encode("utf-8") + ) def test_show_questions_denies_student(self): """ @@ -4111,7 +4141,7 @@ class TestShowQuestions(TestCase): self.assertTemplateUsed(response, 'yaksh/showquestions.html') self.assertIn("download", response.context['msg']) - def test_upload_questions(self): + def test_upload_zip_questions(self): """ Check for uploading questions zip file """ @@ -4151,6 +4181,59 @@ class TestShowQuestions(TestCase): self.assertTemplateUsed(response, 'yaksh/showquestions.html') self.assertIn("ZIP file", response.context['message']) + def test_upload_yaml_questions(self): + """ + Check for uploading questions yaml file + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + + response = self.client.post( + reverse('yaksh:show_questions'), + data={'file': self.yaml_file_1, + 'upload': 'upload'} + ) + uploaded_ques = Question.objects.filter( + active=True, summary="Yaml Demo 2", + user=self.user) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/showquestions.html') + self.assertEqual(uploaded_ques.count(), 1) + uploaded_ques.delete() + + def test_upload_multiple_yaml_zip_questions(self): + """ + Check for uploading questions zip file with + multiple yaml files + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + zipfile_name = string_io() + zip_file = zipfile.ZipFile(zipfile_name, "w") + zip_file.writestr("test1.yaml", self.yaml_file_1.read()) + zip_file.writestr("test2.yaml", self.yaml_file_2.read()) + zip_file.close() + zipfile_name.seek(0) + questions_file = SimpleUploadedFile("questions.zip", + zipfile_name.read(), + content_type="application/zip" + ) + response = self.client.post( + reverse('yaksh:show_questions'), + data={'file': questions_file, + 'upload': 'upload'} + ) + uploaded_ques = Question.objects.filter( + active=True, summary="Yaml Demo 2", + user=self.user).count() + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/showquestions.html') + self.assertEqual(uploaded_ques, 1) + def test_attempt_questions(self): """ Check for testing questions diff --git a/yaksh/views.py b/yaksh/views.py index 0aa73b8..c6b1184 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1434,12 +1434,15 @@ def show_all_questions(request): form = UploadFileForm(request.POST, request.FILES) if form.is_valid(): questions_file = request.FILES['file'] - file_name = questions_file.name.split('.') - if file_name[-1] == "zip": - ques = Question() + file_extension = questions_file.name.split('.')[-1] + ques = Question() + if file_extension == "zip": files, extract_path = extract_files(questions_file) context['message'] = ques.read_yaml(extract_path, user, files) + elif file_extension in ["yaml", "yml"]: + questions = questions_file.read() + context['message'] = ques.load_questions(questions, user) else: message = "Please Upload a ZIP file" context['message'] = message |