diff options
Diffstat (limited to 'testapp/exam/models.py')
-rw-r--r-- | testapp/exam/models.py | 464 |
1 files changed, 0 insertions, 464 deletions
diff --git a/testapp/exam/models.py b/testapp/exam/models.py deleted file mode 100644 index 9e37ef5..0000000 --- a/testapp/exam/models.py +++ /dev/null @@ -1,464 +0,0 @@ -import datetime -import json -from random import sample, shuffle -from itertools import islice, cycle -from django.db import models -from django.contrib.auth.models import User -from taggit.managers import TaggableManager - - -############################################################################### -class Profile(models.Model): - """Profile for a user to store roll number and other details.""" - user = models.OneToOneField(User) - roll_number = models.CharField(max_length=20) - institute = models.CharField(max_length=128) - department = models.CharField(max_length=64) - position = models.CharField(max_length=64) - - -languages = ( - ("python", "Python"), - ("bash", "Bash"), - ("c", "C Language"), - ("cpp", "C++ Language"), - ("java", "Java Language"), - ("scilab", "Scilab"), - ) - - -question_types = ( - ("mcq", "Multiple Choice"), - ("mcc", "Multiple Correct Choices"), - ("code", "Code"), - ("upload", "Assignment Upload"), - ) -attempts = [(i, i) for i in range(1, 6)] -attempts.append((-1, 'Infinite')) -days_between_attempts = ((j, j) for j in range(401)) - -test_status = ( - ('inprogress', 'Inprogress'), - ('completed', 'Completed'), - ) - - -def get_assignment_dir(instance, filename): - return '%s/%s' % (instance.user.roll_number, instance.assignmentQuestion.id) - - -############################################################################### -class Question(models.Model): - """Question for a quiz.""" - - # A one-line summary of the question. - summary = models.CharField(max_length=256) - - # The question text, should be valid HTML. - description = models.TextField() - - # Number of points for the question. - points = models.FloatField(default=1.0) - - # Answer for MCQs. - test = models.TextField(blank=True) - - # Test cases file paths (comma seperated for reference code path and test case code path) - # Applicable for CPP, C, Java and Scilab - ref_code_path = models.TextField(blank=True) - - # Any multiple choice options. Place one option per line. - options = models.TextField(blank=True) - - # The language for question. - language = models.CharField(max_length=24, - choices=languages) - - # The type of question. - type = models.CharField(max_length=24, choices=question_types) - - # Is this question active or not. If it is inactive it will not be used - # when creating a QuestionPaper. - active = models.BooleanField(default=True) - - # Snippet of code provided to the user. - snippet = models.CharField(max_length=256) - - # Tags for the Question. - tags = TaggableManager() - - def consolidate_answer_data(self, test_cases, user_answer): - test_case_data_dict = [] - question_info_dict = {} - - for test_case in test_cases: - kw_args_dict = {} - pos_args_list = [] - - test_case_data = {} - test_case_data['test_id'] = test_case.id - test_case_data['func_name'] = test_case.func_name - test_case_data['expected_answer'] = test_case.expected_answer - - if test_case.kw_args: - for args in test_case.kw_args.split(","): - arg_name, arg_value = args.split("=") - kw_args_dict[arg_name.strip()] = arg_value.strip() - - if test_case.pos_args: - for args in test_case.pos_args.split(","): - pos_args_list.append(args.strip()) - - test_case_data['kw_args'] = kw_args_dict - test_case_data['pos_args'] = pos_args_list - test_case_data_dict.append(test_case_data) - - # question_info_dict['language'] = self.language - question_info_dict['id'] = self.id - question_info_dict['user_answer'] = user_answer - question_info_dict['test_parameter'] = test_case_data_dict - question_info_dict['ref_code_path'] = self.ref_code_path - question_info_dict['test'] = self.test - - return json.dumps(question_info_dict) - - def __unicode__(self): - return self.summary - - -############################################################################### -class Answer(models.Model): - """Answers submitted by the users.""" - - # The question for which user answers. - question = models.ForeignKey(Question) - - # The answer submitted by the user. - answer = models.TextField(null=True, blank=True) - - # Error message when auto-checking the answer. - error = models.TextField() - - # Marks obtained for the answer. This can be changed by the teacher if the - # grading is manual. - marks = models.FloatField(default=0.0) - - # Is the answer correct. - correct = models.BooleanField(default=False) - - # Whether skipped or not. - skipped = models.BooleanField(default=False) - - def __unicode__(self): - return self.answer - - -############################################################################### -class Quiz(models.Model): - """A quiz that students will participate in. One can think of this - as the "examination" event. - """ - - # The start date of the quiz. - start_date = models.DateField("Date of the quiz") - - # This is always in minutes. - duration = models.IntegerField("Duration of quiz in minutes", default=20) - - # Is the quiz active. The admin should deactivate the quiz once it is - # complete. - active = models.BooleanField(default=True) - - # Description of quiz. - description = models.CharField(max_length=256) - - # Mininum passing percentage condition. - pass_criteria = models.FloatField("Passing percentage", default=40) - - # List of prerequisite quizzes to be passed to take this quiz - prerequisite = models.ForeignKey("Quiz", null=True) - - # Programming language for a quiz - language = models.CharField(max_length=20, choices=languages) - - # Number of attempts for the quiz - attempts_allowed = models.IntegerField(default=1, choices=attempts) - - time_between_attempts = models.IntegerField("Number of Days",\ - choices=days_between_attempts) - - class Meta: - verbose_name_plural = "Quizzes" - - def __unicode__(self): - desc = self.description or 'Quiz' - return '%s: on %s for %d minutes' % (desc, self.start_date, - self.duration) - - -############################################################################### -class QuestionPaper(models.Model): - """Question paper stores the detail of the questions.""" - - # Question paper belongs to a particular quiz. - quiz = models.ForeignKey(Quiz) - - # Questions that will be mandatory in the quiz. - fixed_questions = models.ManyToManyField(Question) - - # Questions that will be fetched randomly from the Question Set. - random_questions = models.ManyToManyField("QuestionSet") - - # Option to shuffle questions, each time a new question paper is created. - shuffle_questions = models.BooleanField(default=False) - - # Total marks for the question paper. - total_marks = models.FloatField() - - def update_total_marks(self): - """ Updates the total marks for the Question Paper""" - marks = 0.0 - questions = self.fixed_questions.all() - for question in questions: - marks += question.points - for question_set in self.random_questions.all(): - marks += question_set.marks * question_set.num_questions - self.total_marks = marks - - def _get_questions_for_answerpaper(self): - """ Returns fixed and random questions for the answer paper""" - questions = [] - questions = list(self.fixed_questions.all()) - for question_set in self.random_questions.all(): - questions += question_set.get_random_questions() - return questions - - def make_answerpaper(self, user, ip, attempt_num): - """Creates an answer paper for the user to attempt the quiz""" - ans_paper = AnswerPaper(user=user, user_ip=ip, attempt_number=attempt_num) - ans_paper.start_time = datetime.datetime.now() - ans_paper.end_time = ans_paper.start_time \ - + datetime.timedelta(minutes=self.quiz.duration) - ans_paper.question_paper = self - questions = self._get_questions_for_answerpaper() - question_ids = [str(x.id) for x in questions] - if self.shuffle_questions: - shuffle(question_ids) - ans_paper.questions = "|".join(question_ids) - ans_paper.save() - return ans_paper - - -############################################################################### -class QuestionSet(models.Model): - """Question set contains a set of questions from which random questions - will be selected for the quiz. - """ - - # Marks of each question of a particular Question Set - marks = models.FloatField() - - # Number of questions to be fetched for the quiz. - num_questions = models.IntegerField() - - # Set of questions for sampling randomly. - questions = models.ManyToManyField(Question) - - def get_random_questions(self): - """ Returns random questions from set of questions""" - return sample(self.questions.all(), self.num_questions) - - -############################################################################### -class AnswerPaper(models.Model): - """A answer paper for a student -- one per student typically. - """ - # The user taking this question paper. - user = models.ForeignKey(User) - - # All questions that remain to be attempted for a particular Student - # (a list of ids separated by '|') - questions = models.CharField(max_length=128) - - # The Quiz to which this question paper is attached to. - question_paper = models.ForeignKey(QuestionPaper) - - # The attempt number for the question paper. - attempt_number = models.IntegerField() - - # The time when this paper was started by the user. - start_time = models.DateTimeField() - - # The time when this paper was ended by the user. - end_time = models.DateTimeField() - - # User's IP which is logged. - user_ip = models.CharField(max_length=15) - - # The questions successfully answered (a list of ids separated by '|') - questions_answered = models.CharField(max_length=128) - - # All the submitted answers. - answers = models.ManyToManyField(Answer) - - # Teacher comments on the question paper. - comments = models.TextField() - - # Total marks earned by the student in this paper. - marks_obtained = models.FloatField(null=True, default=None) - - # Marks percent scored by the user - percent = models.FloatField(null=True, default=None) - - # Result of the quiz, True if student passes the exam. - passed = models.NullBooleanField() - - # Status of the quiz attempt - status = models.CharField(max_length=20, choices=test_status,\ - default='inprogress') - - def current_question(self): - """Returns the current active question to display.""" - qu = self.get_unanswered_questions() - if len(qu) > 0: - return qu[0] - else: - return '' - - def questions_left(self): - """Returns the number of questions left.""" - qu = self.get_unanswered_questions() - return len(qu) - - def get_unanswered_questions(self): - """Returns the list of unanswered questions.""" - qa = self.questions_answered.split('|') - qs = self.questions.split('|') - qu = [q for q in qs if q not in qa] - return qu - - def completed_question(self, question_id): - """ - Adds the completed question to the list of answered - questions and returns the next question. - """ - qa = self.questions_answered - if len(qa) > 0: - self.questions_answered = '|'.join([qa, str(question_id)]) - else: - self.questions_answered = str(question_id) - self.save() - - return self.skip(question_id) - - def skip(self, question_id): - """ - Skips the current question and returns the next sequentially - available question. - """ - qu = self.get_unanswered_questions() - qs = self.questions.split('|') - - if len(qu) == 0: - return '' - - try: - q_index = qs.index(unicode(question_id)) - except ValueError: - return qs[0] - - start = q_index + 1 - stop = q_index + 1 + len(qs) - q_list = islice(cycle(qs), start, stop) - for next_q in q_list: - if next_q in qu: - return next_q - - return qs[0] - - def time_left(self): - """Return the time remaining for the user in seconds.""" - dt = datetime.datetime.now() - self.start_time.replace(tzinfo=None) - try: - secs = dt.total_seconds() - except AttributeError: - # total_seconds is new in Python 2.7. :( - secs = dt.seconds + dt.days*24*3600 - total = self.question_paper.quiz.duration*60.0 - remain = max(total - secs, 0) - return int(remain) - - def get_answered_str(self): - """Returns the answered questions, sorted and as a nice string.""" - qa = self.questions_answered.split('|') - answered = ', '.join(sorted(qa)) - return answered if answered else 'None' - - def update_marks_obtained(self): - """Updates the total marks earned by student for this paper.""" - marks = sum([x.marks for x in self.answers.filter(marks__gt=0.0)]) - self.marks_obtained = marks - - def update_percent(self): - """Updates the percent gained by the student for this paper.""" - total_marks = self.question_paper.total_marks - if self.marks_obtained is not None: - percent = self.marks_obtained/self.question_paper.total_marks*100 - self.percent = round(percent, 2) - - def update_passed(self): - """ - Checks whether student passed or failed, as per the quiz - passing criteria. - """ - if self.percent is not None: - if self.percent >= self.question_paper.quiz.pass_criteria: - self.passed = True - else: - self.passed = False - - def update_status(self): - """ Sets status to completed """ - self.status = 'completed' - - def get_question_answers(self): - """ - Return a dictionary with keys as questions and a list of the - corresponding answers. - """ - q_a = {} - for answer in self.answers.all(): - question = answer.question - if question in q_a: - q_a[question].append(answer) - else: - q_a[question] = [answer] - return q_a - - def __unicode__(self): - u = self.user - return u'Question paper for {0} {1}'.format(u.first_name, u.last_name) - - -############################################################################### -class AssignmentUpload(models.Model): - user = models.ForeignKey(Profile) - assignmentQuestion = models.ForeignKey(Question) - assignmentFile = models.FileField(upload_to=get_assignment_dir) - - -################################################################################ -class TestCase(models.Model): - question = models.ForeignKey(Question, blank=True, null = True) - - # Test case function name - func_name = models.CharField(blank=True, null = True, max_length=200) - - # Test case Keyword arguments in dict form - kw_args = models.TextField(blank=True, null = True) - - # Test case Positional arguments in list form - pos_args = models.TextField(blank=True, null = True) - - # Test case Expected answer in list form - expected_answer = models.TextField(blank=True, null = True) |