diff options
author | ankitjavalkar | 2020-10-07 12:04:12 +0530 |
---|---|---|
committer | GitHub | 2020-10-07 12:04:12 +0530 |
commit | 0e04d98e66b53763926af8027cbd275d890bfab4 (patch) | |
tree | e225826a16daf9a9485607457d5d67b568753553 /yaksh/models.py | |
parent | ed81054c05ff297ea241ce08664e0d2323da115c (diff) | |
parent | c6c57869fe653d2ea0502017a9fb15f2f745491b (diff) | |
download | online_test-0e04d98e66b53763926af8027cbd275d890bfab4.tar.gz online_test-0e04d98e66b53763926af8027cbd275d890bfab4.tar.bz2 online_test-0e04d98e66b53763926af8027cbd275d890bfab4.zip |
Merge pull request #774 from adityacp/upload_toc
Add table of contents for the lessons
Diffstat (limited to 'yaksh/models.py')
-rw-r--r-- | yaksh/models.py | 269 |
1 files changed, 256 insertions, 13 deletions
diff --git a/yaksh/models.py b/yaksh/models.py index 1b76eed..3fa4a04 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1,3 +1,4 @@ +# Python Imports from __future__ import unicode_literals, division from datetime import datetime, timedelta import uuid @@ -8,16 +9,9 @@ from ruamel.yaml.scalarstring import PreservedScalarString from ruamel.yaml.comments import CommentedMap from random import sample from collections import Counter, defaultdict - -from django.db import models -from django.contrib.auth.models import User, Group, Permission -from django.core.exceptions import ValidationError -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 - +import sys +import traceback try: from StringIO import StringIO as string_io except ImportError: @@ -31,14 +25,29 @@ import zipfile import tempfile from textwrap import dedent from ast import literal_eval -from .file_utils import extract_files, delete_files + +# Django Imports +from django.db import models +from django.contrib.auth.models import User, Group, Permission +from django.core.exceptions import ValidationError +from django.contrib.contenttypes.models import ContentType +from taggit.managers import TaggableManager +from django.utils import timezone +from django.core.files import File +from django.contrib.contenttypes.fields import ( + GenericForeignKey, GenericRelation +) +from django.contrib.contenttypes.models import ContentType from django.template import Context, Template +from django.conf import settings +from django.forms.models import model_to_dict +from django.db.models import Count +# Local Imports from yaksh.code_server import ( submit, get_result as get_result_from_code_server ) from yaksh.settings import SERVER_POOL_PORT, SERVER_HOST_NAME -from django.conf import settings -from django.forms.models import model_to_dict +from .file_utils import extract_files, delete_files from grades.models import GradingSystem languages = ( @@ -248,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): @@ -281,11 +299,16 @@ class Lesson(models.Model): # A video file video_file = models.FileField( - upload_to=get_file_dir, default=None, + upload_to=get_file_dir, max_length=255, default=None, null=True, blank=True, help_text="Please upload video files in mp4, ogv, webm format" ) + video_path = models.CharField( + max_length=255, default=None, null=True, blank=True, + help_text="Youtube id, vimeo id, others" + ) + def __str__(self): return "{0}".format(self.name) @@ -1337,6 +1360,8 @@ class Question(models.Model): # Solution for the question. solution = models.TextField(blank=True) + content = GenericRelation("TableOfContents") + tc_code_types = { "python": [ ("standardtestcase", "Standard TestCase"), @@ -2750,6 +2775,224 @@ class Comment(ForumBase): self.post_field.title) +class TOCManager(models.Manager): + + def get_data(self, course_id, lesson_id): + contents = TableOfContents.objects.filter( + course_id=course_id, lesson_id=lesson_id, content__in=[2, 3, 4] + ) + data = {} + for toc in contents: + data[toc] = LessonQuizAnswer.objects.filter( + toc_id=toc.id).values_list("toc_id").distinct().count() + return data + + def get_question_stats(self, toc_id): + answers = LessonQuizAnswer.objects.get_queryset().filter( + toc_id=toc_id).order_by('id') + question = answers.first().toc.content_object + answers = answers.values( + "student__first_name", "student__last_name", "student__email", + "student_id", "toc_id" + ).distinct() + return question, answers + + def get_answer(self, toc_id, user_id): + submission = LessonQuizAnswer.objects.filter( + toc_id=toc_id, student_id=user_id).last() + question = submission.toc.content_object + attempted_answer = submission.answer + if question.type == "mcq": + submitted_answer = literal_eval(attempted_answer.answer) + answers = [ + tc.options + for tc in question.get_test_cases(id=submitted_answer) + ] + answer = ",".join(answers) + elif question.type == "mcc": + submitted_answer = literal_eval(attempted_answer.answer) + answers = [ + tc.options + for tc in question.get_test_cases(id__in=submitted_answer) + ] + answer = ",".join(answers) + else: + 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")) + course = models.ForeignKey(Course, on_delete=models.CASCADE, + related_name='course') + lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE, + related_name='contents') + time = models.CharField(max_length=100, default=0) + content = models.IntegerField(choices=toc_types) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey() + + objects = TOCManager() + + class Meta: + verbose_name_plural = "Table Of Contents" + + def get_toc_text(self): + if self.content == 1: + content_name = self.content_object.name + else: + content_name = self.content_object.summary + return content_name + + def __str__(self): + return f"TOC for {self.lesson.name} with {self.get_content_display()}" + + +class Topic(models.Model): + name = models.CharField(max_length=255) + description = models.TextField(null=True, blank=True) + content = GenericRelation(TableOfContents) + + def __str__(self): + return f"{self.name}" + + +class LessonQuizAnswer(models.Model): + toc = models.ForeignKey(TableOfContents, on_delete=models.CASCADE) + student = models.ForeignKey(User, on_delete=models.CASCADE) + answer = models.ForeignKey(Answer, on_delete=models.CASCADE) + + def check_answer(self, user_answer): + result = {'success': False, 'error': ['Incorrect answer'], + 'weight': 0.0} + question = self.toc.content_object + if question.type == 'mcq': + expected_answer = question.get_test_case(correct=True).id + if user_answer.strip() == str(expected_answer).strip(): + result['success'] = True + result['error'] = ['Correct answer'] + + elif question.type == 'mcc': + expected_answers = [ + str(opt.id) for opt in question.get_test_cases(correct=True) + ] + if set(user_answer) == set(expected_answers): + result['success'] = True + result['error'] = ['Correct answer'] + + elif question.type == 'integer': + expected_answers = [ + int(tc.correct) for tc in question.get_test_cases() + ] + if int(user_answer) in expected_answers: + result['success'] = True + result['error'] = ['Correct answer'] + + elif question.type == 'string': + tc_status = [] + for tc in question.get_test_cases(): + if tc.string_check == "lower": + if tc.correct.lower().splitlines()\ + == user_answer.lower().splitlines(): + tc_status.append(True) + else: + if tc.correct.splitlines()\ + == user_answer.splitlines(): + tc_status.append(True) + if any(tc_status): + result['success'] = True + result['error'] = ['Correct answer'] + + elif question.type == 'float': + user_answer = float(user_answer) + tc_status = [] + for tc in question.get_test_cases(): + if abs(tc.correct - user_answer) <= tc.error_margin: + tc_status.append(True) + if any(tc_status): + result['success'] = True + result['error'] = ['Correct answer'] + + elif question.type == 'arrange': + testcase_ids = sorted( + [tc.id for tc in question.get_test_cases()] + ) + if user_answer == testcase_ids: + result['success'] = True + result['error'] = ['Correct answer'] + self.answer.error = result + ans_status = result.get("success") + self.answer.correct = ans_status + if ans_status: + self.answer.marks = self.answer.question.points + self.answer.save() + return result + + def __str__(self): + return f"Lesson answer of {self.toc} by {self.student.get_full_name()}" + + class MicroManager(models.Model): manager = models.ForeignKey(User, on_delete=models.CASCADE, related_name='micromanaging', null=True) |