diff options
Diffstat (limited to 'testapp/exam/models.py')
-rw-r--r-- | testapp/exam/models.py | 142 |
1 files changed, 109 insertions, 33 deletions
diff --git a/testapp/exam/models.py b/testapp/exam/models.py index 758091f..42c5d5a 100644 --- a/testapp/exam/models.py +++ b/testapp/exam/models.py @@ -1,12 +1,12 @@ import datetime +from random import sample, shuffle from django.db import models from django.contrib.auth.models import User from taggit_autocomplete_modified.managers import TaggableManagerAutocomplete\ as TaggableManager -from django.http import HttpResponse -################################################################################ +############################################################################### class Profile(models.Model): """Profile for a user to store roll number and other details.""" user = models.OneToOneField(User) @@ -16,20 +16,25 @@ class Profile(models.Model): position = models.CharField(max_length=64) -QUESTION_TYPE_CHOICES = ( +LANGUAGES = ( ("python", "Python"), ("bash", "Bash"), - ("mcq", "MultipleChoice"), ("C", "C Language"), ("C++", "C++ Language"), ("java", "Java Language"), ("scilab", "Scilab"), + ) + + +QUESTION_TYPES = ( + ("mcq", "Multiple Choice"), + ("code", "Code"), ) -################################################################################ +############################################################################### class Question(models.Model): - """A question in the database.""" + """Question for a quiz.""" # A one-line summary of the question. summary = models.CharField(max_length=256) @@ -43,31 +48,35 @@ class Question(models.Model): # Test cases for the question in the form of code that is run. test = models.TextField(blank=True) - # Any multiple choice options. Place one option per line. + # 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_TYPE_CHOICES) + type = models.CharField(max_length=24, choices=QUESTION_TYPES) - # Is this question active or not. If it is inactive it will not be used + # Is this question active or not. If it is inactive it will not be used # when creating a QuestionPaper. active = models.BooleanField(default=True) - #Code Snippet + # Snippet of code provided to the user. snippet = models.CharField(max_length=256) - #Tags for the Question. + # Tags for the Question. tags = TaggableManager() def __unicode__(self): return self.summary -################################################################################ +############################################################################### class Answer(models.Model): - """Answers submitted by users. - """ - # The question for which we are an answer. + """Answers submitted by the users.""" + + # The question for which user answers. question = models.ForeignKey(Question) # The answer submitted by the user. @@ -76,7 +85,7 @@ class Answer(models.Model): # Error message when auto-checking the answer. error = models.TextField() - # Marks obtained for the answer. This can be changed by the teacher if the + # Marks obtained for the answer. This can be changed by the teacher if the # grading is manual. marks = models.FloatField(default=0.0) @@ -87,44 +96,105 @@ class Answer(models.Model): return self.answer -################################################################################ +############################################################################### class Quiz(models.Model): """A quiz that students will participate in. One can think of this as the "examination" event. """ - # The starting/ending date of the quiz. + # 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 + # 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) - #Tags for the Quiz. - tags = TaggableManager() - 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) + 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 = models.ManyToManyField(Question) + + # 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") + + # Total marks for the question paper. total_marks = models.FloatField() + def update_total_marks(self): + """ Returns 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 + return None + + 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): + """Creates an answer paper for the user to attempt the quiz""" + ans_paper = AnswerPaper(user=user, profile=user.profile, user_ip=ip) + 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] + shuffle(questions) + 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. """ @@ -135,7 +205,8 @@ class AnswerPaper(models.Model): # data. profile = models.ForeignKey(Profile) - # All questions that remains to attempt to perticular Student + # 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. @@ -176,8 +247,10 @@ class AnswerPaper(models.Model): return qs.count('|') + 1 def completed_question(self, question_id): - """Removes the question from the list of questions and returns -the next.""" + """ + Removes the completed question from the list of questions and + returns the next question. + """ qa = self.questions_answered if len(qa) > 0: self.questions_answered = '|'.join([qa, str(question_id)]) @@ -193,7 +266,9 @@ the next.""" return qs[0] def skip(self): - """Skip the current question and return the next available question.""" + """ + Skips the current question and returns the next available question. + """ qs = self.questions.split('|') if len(qs) == 0: return '' @@ -223,13 +298,14 @@ the next.""" answered = ', '.join(sorted(qa)) return answered if answered else 'None' - def get_total_marks(self): + def get_marks_obtained(self): """Returns the total marks earned by student for this paper.""" return sum([x.marks for x in self.answers.filter(marks__gt=0.0)]) def get_question_answers(self): - """Return a dictionary with keys as questions and a list of the corresponding - answers. + """ + Return a dictionary with keys as questions and a list of the + corresponding answers. """ q_a = {} for answer in self.answers.all(): |