summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/fixtures/invalid_yaml.yaml8
-rw-r--r--yaksh/fixtures/sample_lesson_toc.yaml66
-rw-r--r--yaksh/models.py72
-rw-r--r--yaksh/templates/base.html5
-rw-r--r--yaksh/templates/yaksh/show_lesson_quiz.html2
-rw-r--r--yaksh/templates/yaksh/show_toc.html16
-rw-r--r--yaksh/test_views.py83
-rw-r--r--yaksh/views.py21
8 files changed, 268 insertions, 5 deletions
diff --git a/yaksh/fixtures/invalid_yaml.yaml b/yaksh/fixtures/invalid_yaml.yaml
new file mode 100644
index 0000000..bcc153c
--- /dev/null
+++ b/yaksh/fixtures/invalid_yaml.yaml
@@ -0,0 +1,8 @@
+---
+name: 'Sample lesson topic 1'
+description: 'Topic 1 description'
+---
+name: 'Sample lesson topic 1'
+description: 'Topic 1 description'
+content_type: 1
+time: '000000' \ No newline at end of file
diff --git a/yaksh/fixtures/sample_lesson_toc.yaml b/yaksh/fixtures/sample_lesson_toc.yaml
new file mode 100644
index 0000000..8030d5e
--- /dev/null
+++ b/yaksh/fixtures/sample_lesson_toc.yaml
@@ -0,0 +1,66 @@
+# content_type 1: Topic, 2: Grading quiz, 3: Exercise, 4: Poll
+---
+summary: |-
+ Sample lesson quiz 1
+type: |-
+ mcq
+language: |-
+ other
+description: |-
+ Choose the letter from the following
+points: 1.0
+testcase:
+- test_case_type: |-
+ mcqtestcase
+ options: |-
+ A
+ correct: false
+- test_case_type: |-
+ mcqtestcase
+ options: |-
+ B
+ correct: true
+- test_case_type: |-
+ mcqtestcase
+ options: |-
+ C
+ correct: false
+- test_case_type: |-
+ mcqtestcase
+ options: |-
+ D
+ correct: false
+active: true
+topic: 'Dummy1'
+content_type: 2
+time: '00:02:00'
+---
+summary: |-
+ Sample lesson quiz 2
+type: |-
+ mcq
+language: |-
+ python
+description: |-
+ What will be the output of the statement
+ <br>
+ print(1+2)
+points: 1.0
+testcase:
+- test_case_type: |-
+ integertestcase
+ correct: '3'
+active: true
+topic: 'Dummy2'
+content_type: 2
+time: '00:05:00'
+---
+name: 'Sample lesson topic 1'
+description: 'Topic 1 description'
+content_type: 1
+time: '00:00:00'
+---
+name: 'Sample lesson topic 2'
+description: 'Topic 2 description'
+content_type: 1
+time: '00:01:00'
diff --git a/yaksh/models.py b/yaksh/models.py
index 0c5a6f5..3fa4a04 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -10,7 +10,8 @@ from ruamel.yaml.comments import CommentedMap
from random import sample
from collections import Counter, defaultdict
import glob
-
+import sys
+import traceback
try:
from StringIO import StringIO as string_io
except ImportError:
@@ -256,6 +257,15 @@ def get_image_dir(instance, filename):
))
+def is_valid_time_format(time):
+ try:
+ hh, mm, ss = time.split(":")
+ status = True
+ except ValueError:
+ status = False
+ return status
+
+
###############################################################################
class CourseManager(models.Manager):
@@ -2810,6 +2820,66 @@ class TOCManager(models.Manager):
answer = attempted_answer.answer
return answer, attempted_answer.correct
+ def add_contents(self, course_id, lesson_id, user, contents):
+ toc = []
+ messages = []
+ for content in contents:
+ name = content.get('name') or content.get('summary')
+ if "content_type" not in content or "time" not in content:
+ messages.append(
+ (False,
+ f"content_type or time key is missing in {name}")
+ )
+ else:
+ content_type = content.pop('content_type')
+ time = content.pop('time')
+ if not is_valid_time_format(time):
+ messages.append(
+ (False,
+ f"Invalid time format in {name}. "
+ "Format should be 00:00:00")
+ )
+ else:
+ if content_type == 1:
+ topic = Topic.objects.create(**content)
+ toc.append(TableOfContents(
+ course_id=course_id, lesson_id=lesson_id, time=time,
+ content_object=topic, content=content_type
+ ))
+ messages.append((True, f"{topic.name} added successfully"))
+ else:
+ content['user'] = user
+ test_cases = content.pop("testcase")
+ que_type = content.get('type')
+ if "files" in content:
+ content.pop("files")
+ if "tags" in content:
+ content.pop("tags")
+ if (que_type in ['code', 'upload']):
+ messages.append(
+ (False, f"{que_type} question is not allowed. "
+ f"{content.get('summary')} is not added")
+ )
+ else:
+ que = Question.objects.create(**content)
+ for test_case in test_cases:
+ test_case_type = test_case.pop('test_case_type')
+ model_class = get_model_class(test_case_type)
+ model_class.objects.get_or_create(
+ question=que, **test_case, type=test_case_type
+ )
+ toc.append(TableOfContents(
+ course_id=course_id, lesson_id=lesson_id,
+ time=time, content_object=que,
+ content=content_type
+ ))
+ messages.append(
+ (True, f"{que.summary} added successfully")
+ )
+ if toc:
+ TableOfContents.objects.bulk_create(toc)
+ return messages
+
class TableOfContents(models.Model):
toc_types = ((1, "Topic"), (2, "Graded Quiz"), (3, "Exercise"), (4, "Poll"))
diff --git a/yaksh/templates/base.html b/yaksh/templates/base.html
index 093ccf3..7bf70fb 100644
--- a/yaksh/templates/base.html
+++ b/yaksh/templates/base.html
@@ -57,6 +57,11 @@
<script>
new WOW().init();
+ $(document).ready(function() {
+ $(".alert").delay(2000).slideUp(200, function() {
+ $(this).alert('close');
+ });
+ });
</script>
{% block script %}
{% endblock %}
diff --git a/yaksh/templates/yaksh/show_lesson_quiz.html b/yaksh/templates/yaksh/show_lesson_quiz.html
index 2d5184e..fb5ae6c 100644
--- a/yaksh/templates/yaksh/show_lesson_quiz.html
+++ b/yaksh/templates/yaksh/show_lesson_quiz.html
@@ -43,7 +43,7 @@
<small class="text text-muted"><strong>Type:</strong> <span class="badge badge-primary">ARRANGE THE OPTIONS IN CORRECT ORDER</span></small>
{% endif %}
<span class="badge badge-info pull-right">
- <small><strong>Marks: {{ question.points }}</strong></small>
+ <small><strong>Points: {{ question.points }}</strong></small>
</span>
</div>
</div>
diff --git a/yaksh/templates/yaksh/show_toc.html b/yaksh/templates/yaksh/show_toc.html
index b263652..ddaad74 100644
--- a/yaksh/templates/yaksh/show_toc.html
+++ b/yaksh/templates/yaksh/show_toc.html
@@ -1,8 +1,18 @@
+<div>
+ <form action="" method="POST" enctype="multipart/form-data">
+ {% csrf_token %}
+ <input type="file" name="toc" required="">
+ <button class="btn btn-outline-success" id="upload_toc" name="upload_toc">
+ <i class="fa fa-upload"></i>&nbsp;Upload TOC
+ </button>
+ </form>
+</div>
+<hr>
<table class="table table-responsive-sm">
{% for toc in contents %}
{% with toc.get_toc_text as toc_name %}
<tr>
- <td>
+ <td width="30%">
{{ toc_name }}
</td>
<td>
@@ -36,6 +46,7 @@
<span class="badge badge-warning">No Table of contents added</span>
</center>
{% endfor %}
+</table>
<script type="text/javascript">
$(document).ready(function() {
var divs = document.getElementsByClassName("hidden");
@@ -50,5 +61,4 @@
lock_screen();
ajax_call(url, "GET");
}
-</script>
-</table> \ No newline at end of file
+</script> \ No newline at end of file
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index 7383c6c..9ce3e8b 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -8484,3 +8484,86 @@ class TestLessonContents(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(student_info.get("student_id"), self.student.id)
+ def test_upload_lesson_contents(self):
+ self.client.login(
+ username=self.user1.username,
+ password=self.user1_plaintext_pass
+ )
+ dummy_file = SimpleUploadedFile("test.txt", b"test")
+ # Invalid file type
+ response = self.client.post(
+ reverse('yaksh:edit_lesson',
+ kwargs={"lesson_id": self.lesson1.id,
+ "course_id": self.user1_course1.id,
+ "module_id": self.learning_module1.id}),
+ data={"toc": dummy_file,
+ "upload_toc": "upload_toc"}
+ )
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(response.status_code, 200)
+ self.assertIn('Please upload yaml or yml type file', messages)
+
+ # Valid yaml file for TOC
+ yaml_path = os.sep.join((FIXTURES_DIR_PATH, 'sample_lesson_toc.yaml'))
+ with open(yaml_path, 'rb') as fp:
+ yaml_file = SimpleUploadedFile("test.yml", fp.read())
+ response = self.client.post(
+ reverse('yaksh:edit_lesson',
+ kwargs={"lesson_id": self.lesson1.id,
+ "course_id": self.user1_course1.id,
+ "module_id": self.learning_module1.id}),
+ data={"toc": yaml_file,
+ "upload_toc": "upload_toc"}
+ )
+ contents = [
+ 'Sample lesson quiz 1', 'Sample lesson quiz 2',
+ 'Sample lesson topic 1', 'Sample lesson topic 2'
+ ]
+ self.assertEqual(response.status_code, 200)
+ has_que = Question.objects.filter(
+ summary__in=contents[:2]
+ ).exists()
+ has_topics = Topic.objects.filter(
+ name__in=contents[2:]
+ ).exists()
+ self.assertTrue(has_que)
+ self.assertTrue(has_topics)
+
+ # Invalid YAML file data
+ yaml_content = b"""
+ ---
+ name: 'Sample lesson topic 2'
+ description: 'Topic 2 description'
+ """
+ yaml_file = SimpleUploadedFile("test.yml", yaml_content)
+ response = self.client.post(
+ reverse('yaksh:edit_lesson',
+ kwargs={"lesson_id": self.lesson1.id,
+ "course_id": self.user1_course1.id,
+ "module_id": self.learning_module1.id}),
+ data={"toc": yaml_file,
+ "upload_toc": "upload_toc"}
+ )
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(response.status_code, 200)
+ self.assertIn("Error parsing yaml", messages[0])
+
+ # Invalid YAML with no content_type and invalid time format
+ yaml_path = os.sep.join((FIXTURES_DIR_PATH, 'invalid_yaml.yaml'))
+ with open(yaml_path, 'rb') as fp:
+ yaml_file = SimpleUploadedFile("test.yml", fp.read())
+ response = self.client.post(
+ reverse('yaksh:edit_lesson',
+ kwargs={"lesson_id": self.lesson1.id,
+ "course_id": self.user1_course1.id,
+ "module_id": self.learning_module1.id}),
+ data={"toc": yaml_file,
+ "upload_toc": "upload_toc"}
+ )
+ messages = [m.message for m in get_messages(response.wsgi_request)]
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(
+ "content_type or time key is missing", messages[0]
+ )
+ self.assertIn("Invalid time format", messages[1])
+
diff --git a/yaksh/views.py b/yaksh/views.py
index a3d7def..9cca425 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -24,6 +24,7 @@ import json
from textwrap import dedent
import zipfile
import markdown
+import ruamel
try:
from StringIO import StringIO as string_io
except ImportError:
@@ -2710,6 +2711,26 @@ def edit_lesson(request, course_id=None, module_id=None, lesson_id=None):
request, "Please select atleast one file to delete"
)
+ if 'upload_toc' in request.POST:
+ toc_file = request.FILES.get('toc')
+ file_extension = os.path.splitext(toc_file.name)[1][1:]
+ if file_extension not in ['yaml', 'yml']:
+ messages.warning(
+ request, "Please upload yaml or yml type file"
+ )
+ else:
+ try:
+ toc_data = ruamel.yaml.safe_load_all(toc_file.read())
+ results = TableOfContents.objects.add_contents(
+ course_id, lesson_id, user, toc_data)
+ for status, msg in results:
+ if status == True:
+ messages.success(request, msg)
+ else:
+ messages.warning(request, msg)
+ except Exception as e:
+ messages.warning(request, f"Error parsing yaml: {e}")
+
contents = TableOfContents.objects.filter(
course_id=course_id, lesson_id=lesson_id
).order_by("time")