diff options
Diffstat (limited to 'yaksh/models.py')
-rw-r--r-- | yaksh/models.py | 355 |
1 files changed, 289 insertions, 66 deletions
diff --git a/yaksh/models.py b/yaksh/models.py index e3212fd..949b87e 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -6,7 +6,8 @@ import ruamel.yaml from ruamel.yaml.scalarstring import PreservedScalarString from ruamel.yaml.comments import CommentedMap from random import sample -from collections import Counter +from collections import Counter, defaultdict + from django.db import models from django.contrib.auth.models import User, Group, Permission from django.contrib.contenttypes.models import ContentType @@ -45,6 +46,7 @@ languages = ( ("cpp", "C++ Language"), ("java", "Java Language"), ("scilab", "Scilab"), + ("r", "R"), ) question_types = ( @@ -80,6 +82,17 @@ string_check_type = ( ("exact", "Case Sensitive"), ) +legend_display_types = { + "mcq": {"label": "Objective Type"}, + "mcc": {"label": "Objective Type"}, + "code": {"label": "Programming"}, + "upload": {"label": "Upload"}, + "integer": {"label": "Objective Type"}, + "string": {"label": "Objective Type"}, + "float": {"label": "Objective Type"}, + "arrange": {"label": "Objective Type"}, + } + attempts = [(i, i) for i in range(1, 6)] attempts.append((-1, 'Infinite')) @@ -94,8 +107,10 @@ MOD_GROUP_NAME = 'moderator' def get_assignment_dir(instance, filename): - upload_dir = instance.question_paper.quiz.description.replace(" ", "_") - return os.sep.join((upload_dir, instance.user.username, + folder_name = instance.course.name.replace(" ", "_") + sub_folder_name = instance.question_paper.quiz.description.replace( + " ", "_") + return os.sep.join((folder_name, sub_folder_name, instance.user.username, str(instance.assignmentQuestion.id), filename )) @@ -208,7 +223,7 @@ def write_templates_to_zip(zipfile, template_path, data, filename, filepath): str(rendered_template)) -def render_template(template_path, data): +def render_template(template_path, data=None): with open(template_path) as f: template_data = f.read() template = Template(template_data) @@ -243,7 +258,7 @@ class Lesson(models.Model): html_data = models.TextField(null=True, blank=True) # Creator of the lesson - creator = models.ForeignKey(User) + creator = models.ForeignKey(User, on_delete=models.CASCADE) # Activate/Deactivate Lesson active = models.BooleanField(default=True) @@ -284,7 +299,7 @@ class Lesson(models.Model): if os.path.exists(file_path): os.remove(file_path) - def _add_lesson_to_zip(self, module, course, zip_file, path): + def _add_lesson_to_zip(self, next_unit, module, course, zip_file, path): lesson_name = self.name.replace(" ", "_") course_name = course.name.replace(" ", "_") module_name = module.name.replace(" ", "_") @@ -302,17 +317,20 @@ class Lesson(models.Model): lesson_file.file.name))) zip_file.writestr(filename, lesson_file.file.read()) unit_file_path = os.sep.join(( - path, "templates", "yaksh", "unit.html" + path, "templates", "yaksh", "download_course_templates", + "unit.html" )) lesson_data = {"course": course, "module": module, - "lesson": self, "lesson_files": lesson_files} + "lesson": self, "next_unit": next_unit, + "lesson_files": lesson_files} write_templates_to_zip(zip_file, unit_file_path, lesson_data, lesson_name, sub_folder_name) ############################################################################# class LessonFile(models.Model): - lesson = models.ForeignKey(Lesson, related_name="lesson") + lesson = models.ForeignKey(Lesson, related_name="lesson", + on_delete=models.CASCADE) file = models.FileField(upload_to=get_file_dir, default=None) def remove(self): @@ -460,7 +478,7 @@ class Quiz(models.Model): is_exercise = models.BooleanField(default=False) - creator = models.ForeignKey(User, null=True) + creator = models.ForeignKey(User, null=True, on_delete=models.CASCADE) objects = QuizManager() @@ -517,7 +535,7 @@ class Quiz(models.Model): except QuestionPaper.DoesNotExist: qp = None ans_ppr = AnswerPaper.objects.filter( - user=user, course=course, question_paper=qp + user_id=user.id, course_id=course.id, question_paper_id=qp ).order_by("-attempt_number") if ans_ppr.exists(): status = ans_ppr.first().status @@ -525,6 +543,18 @@ class Quiz(models.Model): status = "not attempted" return status + def get_answerpaper_passing_status(self, user, course): + try: + qp = self.questionpaper_set.get().id + except QuestionPaper.DoesNotExist: + qp = None + ans_ppr = AnswerPaper.objects.filter( + user_id=user.id, course_id=course.id, question_paper_id=qp + ).order_by("-attempt_number") + if ans_ppr.exists(): + return any([paper.passed for paper in ans_ppr]) + return False + def _create_quiz_copy(self, user): question_papers = self.questionpaper_set.all() new_quiz = self @@ -541,16 +571,43 @@ class Quiz(models.Model): return '%s: on %s for %d minutes' % (desc, self.start_date_time, self.duration) + def _add_quiz_to_zip(self, next_unit, module, course, zip_file, path): + quiz_name = self.description.replace(" ", "_") + course_name = course.name.replace(" ", "_") + module_name = module.name.replace(" ", "_") + sub_folder_name = os.sep.join(( + course_name, module_name, quiz_name + )) + unit_file_path = os.sep.join(( + path, "templates", "yaksh", "download_course_templates", + "quiz.html" + )) + quiz_data = {"course": course, "module": module, + "quiz": self, "next_unit": next_unit} + + write_templates_to_zip(zip_file, unit_file_path, quiz_data, + quiz_name, sub_folder_name) + ########################################################################## class LearningUnit(models.Model): """ Maintain order of lesson and quiz added in the course """ order = models.IntegerField() type = models.CharField(max_length=16) - lesson = models.ForeignKey(Lesson, null=True, blank=True) - quiz = models.ForeignKey(Quiz, null=True, blank=True) + lesson = models.ForeignKey(Lesson, null=True, blank=True, + on_delete=models.CASCADE) + quiz = models.ForeignKey(Quiz, null=True, blank=True, + on_delete=models.CASCADE) check_prerequisite = models.BooleanField(default=True) + def get_lesson_or_quiz(self): + unit = None + if self.type == 'lesson': + unit = self.lesson + else: + unit = self.quiz + return unit + def toggle_check_prerequisite(self): if self.check_prerequisite: self.check_prerequisite = False @@ -558,10 +615,12 @@ class LearningUnit(models.Model): self.check_prerequisite = True def get_completion_status(self, user, course): - course_status = CourseStatus.objects.filter(user=user, course=course) + course_status = CourseStatus.objects.filter( + user_id=user.id, course_id=course.id + ) state = "not attempted" if course_status.exists(): - if self in course_status.first().completed_units.all(): + if course_status.first().completed_units.filter(id=self.id): state = "completed" elif self.type == "quiz": state = self.quiz.get_answerpaper_status(user, course) @@ -572,7 +631,7 @@ class LearningUnit(models.Model): def has_prerequisite(self): return self.check_prerequisite - def is_prerequisite_passed(self, user, learning_module, course): + def is_prerequisite_complete(self, user, learning_module, course): ordered_units = learning_module.learning_unit.order_by("order") ordered_units_ids = list(ordered_units.values_list("id", flat=True)) current_unit_index = ordered_units_ids.index(self.id) @@ -599,6 +658,14 @@ class LearningUnit(models.Model): order=self.order, type="lesson", lesson=new_lesson) return new_unit + def __str__(self): + name = None + if self.type == 'lesson': + name = self.lesson.name + else: + name = self.quiz.description + return name + ############################################################################### class LearningModule(models.Model): @@ -608,8 +675,10 @@ class LearningModule(models.Model): name = models.CharField(max_length=255) description = models.TextField(default=None, null=True, blank=True) order = models.IntegerField(default=0) - creator = models.ForeignKey(User, related_name="module_creator") + creator = models.ForeignKey(User, related_name="module_creator", + on_delete=models.CASCADE) check_prerequisite = models.BooleanField(default=True) + check_prerequisite_passes = models.BooleanField(default=False) html_data = models.TextField(null=True, blank=True) active = models.BooleanField(default=True) is_trial = models.BooleanField(default=False) @@ -639,6 +708,9 @@ class LearningModule(models.Model): def toggle_check_prerequisite(self): self.check_prerequisite = not self.check_prerequisite + def toggle_check_prerequisite_passes(self): + self.check_prerequisite_passes = not self.check_prerequisite_passes + def get_next_unit(self, current_unit_id): ordered_units = self.learning_unit.order_by("order") ordered_units_ids = list(ordered_units.values_list("id", flat=True)) @@ -666,7 +738,7 @@ class LearningModule(models.Model): default_status = "inprogress" return default_status - def is_prerequisite_passed(self, user, course): + def is_prerequisite_complete(self, user, course): """ Check if prerequisite module is completed """ ordered_modules = course.learning_module.order_by("order") ordered_modules_ids = list(ordered_modules.values_list( @@ -684,6 +756,35 @@ class LearningModule(models.Model): success = False return success + def get_passing_status(self, user, course): + course_status = CourseStatus.objects.filter(user=user, course=course) + if course_status.exists(): + learning_units_with_quiz = self.learning_unit.filter(type='quiz') + ordered_units = learning_units_with_quiz.order_by("order") + + statuses = [ + unit.quiz.get_answerpaper_passing_status(user, course) + for unit in ordered_units + ] + + if not statuses: + status = False + else: + status = all(statuses) + return status + + def is_prerequisite_passed(self, user, course): + """ Check if prerequisite module is passed """ + ordered_modules = course.learning_module.order_by("order") + if ordered_modules.first() == self: + return True + else: + if self.order == 0: + return True + prev_module = ordered_modules.get(order=self.order-1) + status = prev_module.get_passing_status(user, course) + return status + def has_prerequisite(self): return self.check_prerequisite @@ -715,12 +816,28 @@ class LearningModule(models.Model): course_name = course.name.replace(" ", "_") folder_name = os.sep.join((course_name, module_name)) lessons = self.get_lesson_units() - for lesson in lessons: - lesson._add_lesson_to_zip(self, course, zip_file, path) + + units = self.get_learning_units() + for idx, unit in enumerate(units): + next_unit = units[(idx + 1) % len(units)] + if unit.type == 'lesson': + unit.lesson._add_lesson_to_zip(next_unit, + self, + course, + zip_file, + path) + else: + unit.quiz._add_quiz_to_zip(next_unit, + self, + course, + zip_file, + path) + module_file_path = os.sep.join(( - path, "templates", "yaksh", "module.html" + path, "templates", "yaksh", "download_course_templates", + "module.html" )) - module_data = {"course": course, "module": self, "lessons": lessons} + module_data = {"course": course, "module": self, "units": units} write_templates_to_zip(zip_file, module_file_path, module_data, module_name, folder_name) @@ -736,7 +853,8 @@ class Course(models.Model): active = models.BooleanField(default=True) code = models.CharField(max_length=128, null=True, blank=True) hidden = models.BooleanField(default=False) - creator = models.ForeignKey(User, related_name='creator') + creator = models.ForeignKey(User, related_name='creator', + on_delete=models.CASCADE) students = models.ManyToManyField(User, related_name='students') requests = models.ManyToManyField(User, related_name='requests') rejected = models.ManyToManyField(User, related_name='rejected') @@ -744,6 +862,7 @@ class Course(models.Model): teachers = models.ManyToManyField(User, related_name='teachers') is_trial = models.BooleanField(default=False) instructions = models.TextField(default=None, null=True, blank=True) + view_grade = models.BooleanField(default=False) learning_module = models.ManyToManyField(LearningModule, related_name='learning_module') @@ -764,7 +883,8 @@ class Course(models.Model): null=True ) - grading_system = models.ForeignKey(GradingSystem, null=True, blank=True) + grading_system = models.ForeignKey(GradingSystem, null=True, blank=True, + on_delete=models.CASCADE) objects = CourseManager() @@ -784,6 +904,7 @@ class Course(models.Model): copy_module_name = "Copy of {0}".format(module.name) new_module = module._create_module_copy(user, copy_module_name) new_course.learning_module.add(new_module) + return new_course def request(self, *users): self.requests.add(*users) @@ -855,16 +976,28 @@ class Course(models.Model): demo_que_ppr = QuestionPaper() demo_que_ppr.create_demo_quiz_ppr(demo_quiz, user) success = True + file_path = os.sep.join( + (os.path.dirname(__file__), "templates", "yaksh", + "demo_video.html") + ) + rendered_text = render_template(file_path) + lesson_data = "Demo Lesson\n{0}".format(rendered_text) demo_lesson = Lesson.objects.create( - name="Demo lesson", description="demo lesson", - html_data="demo lesson", creator=user) + name="Demo Lesson", description=lesson_data, + html_data=lesson_data, creator=user + ) quiz_unit = LearningUnit.objects.create( - order=1, type="quiz", quiz=demo_quiz) + order=1, type="quiz", quiz=demo_quiz, check_prerequisite=False + ) lesson_unit = LearningUnit.objects.create( - order=2, type="lesson", lesson=demo_lesson) + order=2, type="lesson", lesson=demo_lesson, + check_prerequisite=False + ) learning_module = LearningModule.objects.create( - name="demo module", description="demo module", creator=user, - html_data="demo module") + name="Demo Module", description="<center>Demo Module</center>", + creator=user, html_data="<center>Demo Module</center>", + check_prerequisite=False + ) learning_module.learning_unit.add(quiz_unit) learning_module.learning_unit.add(lesson_unit) course.learning_module.add(learning_module) @@ -972,7 +1105,12 @@ class Course(models.Model): with zipfile.ZipFile(zip_file_name, "a") as zip_file: course_name = self.name.replace(" ", "_") modules = self.get_learning_modules() - file_path = os.sep.join((path, "templates", "yaksh", "index.html")) + file_path = os.sep.join( + ( + path, "templates", "yaksh", + "download_course_templates", "index.html" + ) + ) write_static_files_to_zip(zip_file, course_name, path, static_files) course_data = {"course": self, "modules": modules} @@ -1000,9 +1138,10 @@ class CourseStatus(models.Model): completed_units = models.ManyToManyField(LearningUnit, related_name="completed_units") current_unit = models.ForeignKey(LearningUnit, related_name="current_unit", - null=True, blank=True) - course = models.ForeignKey(Course) - user = models.ForeignKey(User) + null=True, blank=True, + on_delete=models.CASCADE) + course = models.ForeignKey(Course, on_delete=models.CASCADE) + user = models.ForeignKey(User, on_delete=models.CASCADE) grade = models.CharField(max_length=255, null=True, blank=True) percentage = models.FloatField(default=0.0) percent_completed = models.IntegerField(default=0) @@ -1014,7 +1153,7 @@ class CourseStatus(models.Model): if self.is_course_complete(): self.calculate_percentage() if self.course.grading_system is None: - grading_system = GradingSystem.objects.get(name='default') + grading_system = GradingSystem.objects.get(name__contains='default') else: grading_system = self.course.grading_system grade = grading_system.get_grade(self.percentage) @@ -1048,17 +1187,22 @@ class CourseStatus(models.Model): self.current_unit = unit self.save() + def __str__(self): + return "{0} status for {1}".format( + self.course.name, self.user.username + ) + ############################################################################### class ConcurrentUser(models.Model): - concurrent_user = models.OneToOneField(User) + concurrent_user = models.OneToOneField(User, on_delete=models.CASCADE) session_key = models.CharField(max_length=40) ############################################################################### class Profile(models.Model): """Profile for a user to store roll number and other details.""" - user = models.OneToOneField(User) + user = models.OneToOneField(User, on_delete=models.CASCADE) roll_number = models.CharField(max_length=20) institute = models.CharField(max_length=128) department = models.CharField(max_length=64) @@ -1076,18 +1220,23 @@ class Profile(models.Model): def get_user_dir(self): """Return the output directory for the user.""" - user_dir = join(settings.OUTPUT_DIR, str(self.user.username)) + user_dir = join(settings.OUTPUT_DIR, str(self.user.id)) if not exists(user_dir): os.makedirs(user_dir) os.chmod(user_dir, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) return user_dir + def get_moderated_courses(self): + return Course.objects.filter(teachers=self.user) + def _toggle_moderator_group(self, group_name): group = Group.objects.get(name=group_name) if self.is_moderator: self.user.groups.add(group) else: self.user.groups.remove(group) + for course in self.get_moderated_courses(): + course.remove_teachers(self.user) def save(self, *args, **kwargs): if self.pk is not None: @@ -1131,7 +1280,8 @@ class Question(models.Model): snippet = models.TextField(blank=True) # user for particular question - user = models.ForeignKey(User, related_name="user") + user = models.ForeignKey(User, related_name="user", + on_delete=models.CASCADE) # Does this question allow partial grading partial_grading = models.BooleanField(default=False) @@ -1144,6 +1294,42 @@ class Question(models.Model): # Solution for the question. solution = models.TextField(blank=True) + tc_code_types = { + "python": [ + ("standardtestcase", "Standard TestCase"), + ("stdiobasedtestcase", "StdIO TestCase"), + ("hooktestcase", "Hook TestCase") + ], + "c": [ + ("standardtestcase", "Standard TestCase"), + ("stdiobasedtestcase", "StdIO TestCase"), + ("hooktestcase", "Hook TestCase") + ], + "cpp": [ + ("standardtestcase", "Standard TestCase"), + ("stdiobasedtestcase", "StdIO TestCase"), + ("hooktestcase", "Hook TestCase") + ], + "java": [ + ("standardtestcase", "Standard TestCase"), + ("stdiobasedtestcase", "StdIO TestCase"), + ("hooktestcase", "Hook TestCase") + ], + "r": [ + ("standardtestcase", "Standard TestCase"), + ("hooktestcase", "Hook TestCase") + ], + "bash": [ + ("standardtestcase", "Standard TestCase"), + ("stdiobasedtestcase", "StdIO TestCase"), + ("hooktestcase", "Hook TestCase") + ], + "scilab": [ + ("standardtestcase", "Standard TestCase"), + ("hooktestcase", "Hook TestCase") + ] + } + def consolidate_answer_data(self, user_answer, user=None): question_data = {} metadata = {} @@ -1345,6 +1531,24 @@ class Question(models.Model): files, extract_path = extract_files(zip_file_path) self.read_yaml(extract_path, user, files) + def get_test_case_options(self): + options = None + if self.type == "code": + options = self.tc_code_types.get(self.language) + elif self.type == "mcq" or self.type == "mcc": + options = [("mcqtestcase", "Mcq TestCase")] + elif self.type == "integer": + options = [("integertestcase", "Integer TestCase")] + elif self.type == "float": + options = [("floattestcase", "Float TestCase")] + elif self.type == "string": + options = [("stringtestcase", "String TestCase")] + elif self.type == "arrange": + options = [("arrangetestcase", "Arrange TestCase")] + elif self.type == "upload": + options = [("hooktestcase", "Hook TestCase")] + return options + def __str__(self): return self.summary @@ -1352,7 +1556,8 @@ class Question(models.Model): ############################################################################### class FileUpload(models.Model): file = models.FileField(upload_to=get_upload_dir, blank=True) - question = models.ForeignKey(Question, related_name="question") + question = models.ForeignKey(Question, related_name="question", + on_delete=models.CASCADE) extract = models.BooleanField(default=False) hide = models.BooleanField(default=False) @@ -1377,13 +1582,16 @@ class FileUpload(models.Model): self.hide = True self.save() + def get_filename(self): + return os.path.basename(self.file.name) + ############################################################################### class Answer(models.Model): """Answers submitted by the users.""" # The question for which user answers. - question = models.ForeignKey(Question) + question = models.ForeignKey(Question, on_delete=models.CASCADE) # The answer submitted by the user. answer = models.TextField(null=True, blank=True) @@ -1408,7 +1616,7 @@ class Answer(models.Model): self.marks = marks def __str__(self): - return self.answer + return "Answer for question {0}".format(self.question.summary) ############################################################################### @@ -1458,7 +1666,7 @@ class QuestionPaper(models.Model): """Question paper stores the detail of the questions.""" # Question paper belongs to a particular quiz. - quiz = models.ForeignKey(Quiz) + quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE) # Questions that will be mandatory in the quiz. fixed_questions = models.ManyToManyField(Question) @@ -1570,7 +1778,9 @@ class QuestionPaper(models.Model): attempts = AnswerPaper.objects.get_total_attempt(questionpaper=self, user=user, course_id=course_id) - return attempts != self.quiz.attempts_allowed + attempts_allowed = attempts < self.quiz.attempts_allowed + infinite_attempts = self.quiz.attempts_allowed == -1 + return attempts_allowed or infinite_attempts def can_attempt_now(self, user, course_id): if self._is_attempt_allowed(user, course_id): @@ -1743,10 +1953,10 @@ class AnswerPaperManager(models.Manager): def _get_answerpapers_for_quiz(self, questionpaper_id, course_id, status=False): if not status: - return self.filter(question_paper_id=questionpaper_id, + return self.filter(question_paper_id__in=questionpaper_id, course_id=course_id) else: - return self.filter(question_paper_id=questionpaper_id, + return self.filter(question_paper_id__in=questionpaper_id, course_id=course_id, status="completed") @@ -1788,14 +1998,15 @@ class AnswerPaperManager(models.Manager): .distinct() def get_user_all_attempts(self, questionpaper, user, course_id): - return self.filter(question_paper=questionpaper, user=user, + return self.filter(question_paper_id__in=questionpaper, user_id=user, course_id=course_id)\ .order_by('-attempt_number') def get_user_data(self, user, questionpaper_id, course_id, attempt_number=None): if attempt_number is not None: - papers = self.filter(user=user, question_paper_id=questionpaper_id, + papers = self.filter(user_id=user.id, + question_paper_id__in=questionpaper_id, course_id=course_id, attempt_number=attempt_number) else: @@ -1813,7 +2024,8 @@ class AnswerPaperManager(models.Manager): def get_user_best_of_attempts_marks(self, quiz, user_id, course_id): best_attempt = 0.0 - papers = self.filter(question_paper__quiz=quiz, course_id=course_id, + papers = self.filter(question_paper__quiz_id=quiz.id, + course_id=course_id, user=user_id).values("marks_obtained") if papers: best_attempt = max([marks["marks_obtained"] for marks in papers]) @@ -1825,15 +2037,15 @@ class AnswerPaper(models.Model): """A answer paper for a student -- one per student typically. """ # The user taking this question paper. - user = models.ForeignKey(User) + user = models.ForeignKey(User, on_delete=models.CASCADE) questions = models.ManyToManyField(Question, related_name='questions') # The Quiz to which this question paper is attached to. - question_paper = models.ForeignKey(QuestionPaper) + question_paper = models.ForeignKey(QuestionPaper, on_delete=models.CASCADE) # Answepaper will be unique to the course - course = models.ForeignKey(Course, null=True) + course = models.ForeignKey(Course, null=True, on_delete=models.CASCADE) # The attempt number for the question paper. attempt_number = models.IntegerField() @@ -2073,6 +2285,16 @@ class AnswerPaper(models.Model): def get_previous_answers(self, question): return self.answers.filter(question=question).order_by('-id') + def get_categorized_question_indices(self): + category_question_map = defaultdict(list) + for index, question in enumerate(self.get_all_ordered_questions(), 1): + question_category = legend_display_types.get(question.type) + if question_category: + category_question_map[ + question_category["label"] + ].append(index) + return dict(category_question_map) + def validate_answer(self, user_answer, question, json_data=None, uid=None, server_port=SERVER_POOL_PORT): """ @@ -2223,20 +2445,19 @@ class AnswerPaper(models.Model): ############################################################################## class AssignmentUploadManager(models.Manager): - def get_assignments(self, qp, que_id=None, user_id=None): + def get_assignments(self, qp, que_id=None, user_id=None, course_id=None): if que_id and user_id: assignment_files = AssignmentUpload.objects.filter( assignmentQuestion_id=que_id, user_id=user_id, - question_paper=qp + question_paper=qp, course_id=course_id ) file_name = User.objects.get(id=user_id).get_full_name() else: assignment_files = AssignmentUpload.objects.filter( - question_paper=qp + question_paper=qp, course_id=course_id ) - file_name = "{0}_Assignment_files".format( - assignment_files[0].question_paper.quiz.description + assignment_files[0].course.name ) return assignment_files, file_name @@ -2244,16 +2465,20 @@ class AssignmentUploadManager(models.Manager): ############################################################################## class AssignmentUpload(models.Model): - user = models.ForeignKey(User) - assignmentQuestion = models.ForeignKey(Question) + user = models.ForeignKey(User, on_delete=models.CASCADE) + assignmentQuestion = models.ForeignKey(Question, on_delete=models.CASCADE) assignmentFile = models.FileField(upload_to=get_assignment_dir) - question_paper = models.ForeignKey(QuestionPaper, blank=True, null=True) + question_paper = models.ForeignKey(QuestionPaper, blank=True, null=True, + on_delete=models.CASCADE) + course = models.ForeignKey(Course, null=True, blank=True, + on_delete=models.CASCADE) objects = AssignmentUploadManager() ############################################################################## class TestCase(models.Model): - question = models.ForeignKey(Question, blank=True, null=True) + question = models.ForeignKey(Question, blank=True, null=True, + on_delete=models.CASCADE) type = models.CharField(max_length=24, choices=test_case_types, null=True) @@ -2390,15 +2615,13 @@ class TestCaseOrder(models.Model): """ # Answerpaper of the user. - answer_paper = models.ForeignKey(AnswerPaper, related_name="answer_paper") + answer_paper = models.ForeignKey(AnswerPaper, related_name="answer_paper", + on_delete=models.CASCADE) # Question in an answerpaper. - question = models.ForeignKey(Question) + question = models.ForeignKey(Question, on_delete=models.CASCADE) # Order of the test case for a question. order = models.TextField() - class Meta: - unique_together = ("answer_paper", "question", "order") - ############################################################################## |