diff options
-rw-r--r-- | yaksh/fixtures/invalid_yaml.yaml | 8 | ||||
-rw-r--r-- | yaksh/fixtures/sample_lesson_toc.yaml | 66 | ||||
-rw-r--r-- | yaksh/models.py | 72 | ||||
-rw-r--r-- | yaksh/templates/base.html | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_lesson_quiz.html | 2 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_toc.html | 16 | ||||
-rw-r--r-- | yaksh/test_views.py | 83 | ||||
-rw-r--r-- | yaksh/views.py | 21 |
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> 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") |