summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/documentation/moderator_docs/creating_question.rst28
-rw-r--r--yaksh/file_utils.py2
-rw-r--r--yaksh/fixtures/demo_questions.zipbin3055 -> 3230 bytes
-rw-r--r--yaksh/models.py34
-rw-r--r--yaksh/templates/yaksh/showquestions.html31
-rw-r--r--yaksh/test_models.py4
-rw-r--r--yaksh/test_views.py87
-rw-r--r--yaksh/views.py9
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
index 4e86485..1618341 100644
--- a/yaksh/fixtures/demo_questions.zip
+++ b/yaksh/fixtures/demo_questions.zip
Binary files differ
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