]> git.parisson.com Git - django_quiz.git/commitdiff
Working mainly on quiz.models to allow for any question type to be used by rest of...
authorTom Walker <tomwalker0472@gmail.com>
Thu, 12 Jun 2014 22:03:56 +0000 (23:03 +0100)
committerTom Walker <tomwalker0472@gmail.com>
Thu, 12 Jun 2014 22:03:56 +0000 (23:03 +0100)
quiz/models.py
quiz/views.py
templates/quiz/question.html

index 9b2aa42cff77e1e6af1290c35965e8d812977cc3..6c743b906294282bcfc0cf56eda08511560a36a8 100644 (file)
@@ -7,12 +7,12 @@ from django.contrib.auth.models import User
 
 from model_utils.managers import InheritanceManager
 # the above taken from:
-# https://django-model-utils.readthedocs.org/en/latest/managers.html#inheritancemanager
+# https://django-model-utils.readthedocs.org/en/latest/managers.html
 
 
 """
-If you want to prepopulate the category choices then use the following and uncomment 'choices' in the category model
-I have left in my original set as an example
+If you want to prepopulate the category choices then here is an example.
+Uncomment 'choices' in the category model.
 """
 
 CATEGORY_CHOICES = ( ('Endocrinology', 'Endocrinology'),
@@ -58,12 +58,11 @@ class CategoryManager(models.Manager):
 
 class Category(models.Model):
 
-    category = models.CharField(max_length=250,
-                                blank=True,
-                                # choices=CATEGORY_CHOICES,
-                                unique=True,
-                                null=True,
-                                )
+    category = models.CharField(max_length = 250,
+                                blank = True,
+                                # choices = CATEGORY_CHOICES,
+                                unique = True,
+                                null = True,)
 
 
     class Meta:
@@ -73,54 +72,47 @@ class Category(models.Model):
     def __unicode__(self):
         return self.category
 
+
 """
-Quiz is a container that can be filled with various different question types
-or other content
+Quiz is a container that can be filled with various different question types.
 """
 
 class Quiz(models.Model):
 
-    title = models.CharField(max_length=60,
-                             blank=False,
-                             )
+    title = models.CharField(max_length = 60,
+                             blank = False,)
 
-    description = models.TextField(blank=True,
-                                   help_text="a description of the quiz",
-                                   )
+    description = models.TextField(blank = True,
+                                   help_text = "a description of the quiz",)
 
-    url = models.CharField(max_length=60,
-                           blank=False,
-                           help_text="an SEO friendly url",
-                           verbose_name='SEO friendly url',
-                           )
+    url = models.CharField(max_length = 60,
+                           blank = False,
+                           help_text = "an SEO friendly url",
+                           verbose_name = 'SEO friendly url',)
 
     category = models.ForeignKey(Category,
-                                 null=True,
-                                 blank=True,
-                                 )
-
-    random_order = models.BooleanField(blank=False,
-                                       default=False,
-                                       help_text="Display the questions in a\
-                                                  random order or as they are set?",
-                                       )
-
-    answers_at_end = models.BooleanField(blank=False,
-                                         default=False,
-                                         help_text="Correct answer is NOT shown\
-                                                    after question. Answers displayed at end",
-                                        )
-
-    exam_paper = models.BooleanField(blank=False,
-                                     default=False,
-                                     help_text="If yes, the result of each\
-                                                attempt by a user will be stored",
-                                     )
-
-
-    def save(self, force_insert=False, force_update=False):
+                                 null = True,
+                                 blank = True,)
+
+    random_order = models.BooleanField(blank = False,
+                                       default = False,
+                                       help_text = "Display the questions in a \
+                                       random order or as they are set?",)
+
+    answers_at_end = models.BooleanField(blank = False,
+                                         default = False,
+                                         help_text = "Correct answer is NOT \
+                                         shown after question. Answers \
+                                         displayed at end",)
+
+    exam_paper = models.BooleanField(blank = False,
+                                     default = False,
+                                     help_text = "If yes, the result of each \
+                                     attempt by a user will be stored",)
+
+
+    def save(self, force_insert = False, force_update = False):
         self.url = self.url.replace(' ', '-').lower()
-        #  automatically converts url to lowercase, replace space with dash
 
         self.url = ''.join(letter for letter in self.url if letter.isalnum()
                            or letter == '-')  #  removes non-alphanumerics
@@ -134,37 +126,35 @@ class Quiz(models.Model):
 
 
     def __unicode__(self):
-        return self.title
+        return self.title[:25]
 
 
 """
-Progress is used to track an individual signed in users score on different quiz's and categories
+Progress is used to track an individual signed in users score on different
+quiz's and categories
 """
 
-
 class ProgressManager(models.Manager):
-    """
-    custom manager for Progress class
-    """
+
     def new_progress(self, user):
-        """
-        method to call when a user is accessing the progress section for the first time
-        """
-        new_progress = self.create(user=user, score='')
+        new_progress = self.create(user = user, score = '')
         new_progress.save()
         return new_progress
 
+
 class Progress(models.Model):
     """
-    Stores the score for each category, max possible and previous exam paper scores
-
-    data stored in csv using the format [category, score, possible, category, score, possible, ...]
+    Currently stores the score for each category, max possible they could
+    have got, and previous exam paper scores.
 
+    Data stored in csv using the format:
+        category, score, possible, category, score, possible, ...
     """
 
     user = models.OneToOneField('auth.User')  #  one user per progress class
 
-    score = models.TextField()  #  the god awful csv. guido forgive me. Always end this with a comma
+    score = models.TextField()  #  The god awful csv.
+                                #  Always end this with a comma,
 
     objects = ProgressManager()
 
@@ -174,264 +164,253 @@ class Progress(models.Model):
 
     def list_all_cat_scores(self):
         """
-        Returns a dict in which the key is the category name and the item is a list of three integers.
-        The first is the number of questions correct, the second is the possible best score,
+        Returns a dict in which the key is the category name and the item is
+        a list of three integers.
+
+        The first is the number of questions correct,
+        the second is the possible best score,
         the third is the percentage correct.
-        The dict will have one key for every category that you have defined.
+
+        The dict will have one key for every category that you have defined
+        BUT
         """
 
-        categories = Category.objects.all()  #  all the categories possible
-        score_before = self.score  #  copy the original score csv to use later....
+        categories = Category.objects.all()
+        score_before = self.score
         output = {}
 
-        for cat in categories:  # for each of the categories
-            my_regex = re.escape(cat.category) + r",(\d+),(\d+),"  #  group 1 is score, group 2 is possible
-            match = re.search(my_regex, self.score, re.IGNORECASE)
+        for cat in categories:
+            to_find = re.escape(cat.category) + r",(\d+),(\d+),"
+            #  group 1 is score, group 2 is possible
+
+            match = re.search(to_find, self.score, re.IGNORECASE)
 
             if match:
                 score = int(match.group(1))
                 possible = int(match.group(2))
+
                 try:
                     percent = int(round((float(score) / float(possible)) * 100))
                 except:
                     percent = 0
+
                 score_list = [score, possible, percent]
                 output[cat.category] = score_list
 
 
-            else:  #  Is possible to remove/comment this section out
-                temp = self.score  #  temporarily store the current csv that lists all the scores
-                temp = temp + cat.category + ",0,0,"  #  Add the class that is not listed at the end. Always end with a comma
+            else:  #  if category has not been added yet, add it.
+                temp = self.score + cat.category + ",0,0,"
                 self.score = temp
                 output[cat.category] = [0, 0]
 
 
-        if len(self.score) > len(score_before):  #  if changes have been made
-            self.save()  #  save only at the end to minimise disc writes
+        if len(self.score) > len(score_before):
+            """
+            If a new category has been added, save changes. Otherwise nothing
+            will be saved.
+            """
+            self.save()
 
         return output
 
 
     def check_cat_score(self, category_queried):
         """
-        pass in a category, get the users score and possible score as x,y respectively
-
-        note: score returned as integers
+        Pass in a category, get the users score and possible maximum score
+        as the integers x,y respectively
         """
 
-        category_test = Category.objects.filter(category=category_queried).exists()
+        category_test = Category.objects.filter(category = category_queried) \
+                                        .exists()
 
         if category_test == False:
-            return "error",  "category does not exist"  #  to do: make this useful!
+            return "error",  "category does not exist"
 
-        my_regex = re.escape(category_queried) + r",(\d+),(\d+),"  #  group 1 is score, group 2 is possible
+        to_find = re.escape(category_queried) + r",(\d+),(\d+),"
 
-        match = re.search(my_regex, self.score, re.IGNORECASE)
+        match = re.search(to_find, self.score, re.IGNORECASE)
 
         if match:
             score = int(match.group(1))
             possible = int(match.group(2))
             return score, possible
 
-        else:  #  if not found, and since category exists, add category to the csv with 0 points
-            """
-            #  removed to lower disk writes
-            temp = self.score
-            temp = temp + category_queried + ",0,0,"  #  always end with a comma
+        else:  #  if not found but category exists, add category  with 0 points
+            temp = self.score + category_queried + ",0,0,"
             self.score = temp
             self.save()
-            """
+
             return 0, 0
 
+
     def update_score(self, category_queried, score_to_add, possible_to_add):
         """
-        pass in category, amount to increase score and max possible increase if all were correct
-
-        does not return anything
+        Pass in category, amount to increase score and max possible
+        increase if all were correct.
 
-        data stored in csv using the format [category, score, possible, category, score, possible, ...]
+        Does not return anything.
         """
 
-        category_test = Category.objects.filter(category=category_queried).exists()
+        category_test = Category.objects.filter(category = category_queried) \
+                                        .exists()
 
         if category_test == False:
-            return "error",  "category does not exist"  #  to do: make useful
+            return "error",  "category does not exist"
 
-        my_regex = re.escape(str(category_queried)) + r",(\d+),(\d+),"  #  group 1 is score, group 2 is possible
+        to_find = re.escape(str(category_queried)) + r",(\d+),(\d+),"
 
-        match = re.search(my_regex, self.score, re.IGNORECASE)
+        match = re.search(to_find, self.score, re.IGNORECASE)
 
         if match:
             current_score = int(match.group(1))
             current_possible = int(match.group(2))
 
-            updated_current_score = current_score + score_to_add  #  add on the score
-            updated_current_possible = current_possible + possible_to_add  #  add the possible maximum score
+            updated_current_score = current_score + score_to_add
+            updated_current_possible = current_possible + possible_to_add
 
-            new_score = str(category_queried) + "," + str(updated_current_score) + "," + str(updated_current_possible) + ","
+            new_score = (str(category_queried) + "," +
+                         str(updated_current_score) + "," +
+                         str(updated_current_possible) + ",")
 
             temp = self.score
             found_instance = match.group()
-            temp = temp.replace(found_instance, new_score)  #  swap the old score for the new one
 
-            self.score = temp
+            # swap old score for the new one
+            self.score = temp.replace(found_instance, new_score)
             self.save()
 
-
         else:
             """
-            if not present but a verified category, add with the points passed in
+            if not present but existing category, add with the points passed in
             """
-            temp = self.score
-            temp = temp + str(category_queried) + "," + str(score_to_add) + "," + str(possible_to_add) + ","
+            temp = (self.score +
+                    str(category_queried) + "," +
+                    str(score_to_add) + "," +
+                    str(possible_to_add) + ",")
             self.score = temp
             self.save()
 
 
     def show_exams(self):
         """
-        finds the previous exams marked as 'exam papers'
-
-        returns a queryset of complete exams
+        Finds the previous quizzes marked as 'exam papers'.
+        Returns a queryset of complete exams.
         """
-        #  list of exam objects from user that are complete
-        return Sitting.objects.filter(user=self.user).filter(complete=True)
+        return Sitting.objects.filter(user = self.user) \
+                              .filter(complete = True)
 
 
 class SittingManager(models.Manager):
-    """
-    custom manager for Sitting class
-    """
+
     def new_sitting(self, user, quiz):
-        """
-        method to call at the start of each new attempt at a quiz
-        """
         if quiz.random_order == True:
-            question_set = quiz.question_set.all().order_by('?')
+            question_set = quiz.question_set.all() \
+                                            .select_subclasses() \
+                                            .order_by('?')
         else:
-            question_set = quiz.question_set.all()
+            question_set = quiz.question_set.all() \
+                                            .select_subclasses()
 
         questions = ""
         for question in question_set:
-            questions = questions + str(question.id) + ","  #  string of IDs seperated by commas
+            questions = (questions + str(question.id) + ",")
 
-        new_sitting = self.create(user=user,
-                                  quiz=quiz,
+        new_sitting = self.create(user = user,
+                                  quiz = quiz,
                                   question_list = questions,
                                   incorrect_questions = "",
-                                  current_score="0",
-                                  complete=False,
-                                  )
+                                  current_score = 0,
+                                  complete = False,)
         new_sitting.save()
         return new_sitting
 
 
 class Sitting(models.Model):
     """
-    Used to store the progress of logged in users sitting an exam. Replaces the session system used by anon users.
-
-    user is the logged in user. Anon users use sessions to track progress
+    Used to store the progress of logged in users sitting a quiz.
+    Replaces the session system used by anon users.
 
-    question_list is a list of id's of the unanswered questions. Stored as a textfield to allow >255 chars. quesion_list
-    is in csv format.
+    Question_list is a list of integers which represent id's of
+    the unanswered questions in csv format.
 
-    incorrect_questions is a list of id's of the questions answered wrongly
+    Incorrect_questions is a list in the same format.
 
-    current_Score is a total of the answered questions value. Needs to be converted to int when used.
-
-    complete - True when exam complete. Should only be stored if quiz.exam_paper is true, or DB will swell quickly in size
+    Sitting deleted when quiz finished unless quiz.exam_paper is true.
     """
 
-    user = models.ForeignKey('auth.User')  #  one user per exam class
+    user = models.ForeignKey('auth.User')
 
     quiz = models.ForeignKey(Quiz)
 
-    question_list = models.TextField()  #  another awful csv. Always end with a comma
+    question_list = models.TextField()
 
-    incorrect_questions = models.TextField(blank=True)  #  more awful csv. Always end with a comma
+    incorrect_questions = models.TextField(blank = True)
 
-    current_score = models.TextField()  #  a string of the score ie 19  convert to int for use
+    current_score = models.IntegerField()
 
-    complete = models.BooleanField(default=False, blank=False)
+    complete = models.BooleanField(default = False, blank = False)
 
     objects = SittingManager()
 
     def get_next_question(self):
         """
-        Returns the next question ID (as an integer).
+        Returns integer of the next question ID.
         If no question is found, returns False
         Does NOT remove the question from the front of the list.
         """
-        first_comma = self.question_list.find(',')  #  finds the index of the first comma in the string
-        if first_comma == -1 or first_comma == 0:  #  if no question number is found
+        first_comma = self.question_list.find(',')
+        if first_comma == -1 or first_comma == 0:
             return False
 
-        qID = self.question_list[:first_comma]  #  up to but not including the first comma
+        return self.question_list[:first_comma]
 
-        return qID
 
     def remove_first_question(self):
-        """
-        Removes the first question on the list.
-        Does not return a value.
-        """
-        first_comma = self.question_list.find(',')  #  finds the index of the first comma in the string
-        if first_comma != -1 or first_comma != 0:  #  if question number IS found
-            temp = self.question_list[first_comma+1:]  #  saves from the first number after the first comma
-            self.question_list = temp
-        self.save()
+        first_comma = self.question_list.find(',')
+        if first_comma != -1 or first_comma != 0:
+            self.question_list = self.question_list[first_comma+1:]
+            self.save()
 
     def add_to_score(self, points):
-        """
-        Adds the points to the running total.
-        Does not return anything
-        """
         present_score = self.get_current_score()
         updated_score = present_score + int(points)
-        self.current_score = str(updated_score)
+        self.current_score = updated_score
         self.save()
 
     def get_current_score(self):
-        """
-        returns the current score as an integer
-        """
-        return int(self.current_score)
+        return self.current_score
 
     def get_percent_correct(self):
-        """
-        returns the percentage correct as an integer
-        """
-        return int(round((float(self.current_score) / float(self.quiz.question_set.all().count())) * 100))
+        dividend = float(self.current_score)
+        divisor = self.quiz.question_set.all().select_subclasses().count()
+        if divisor < 1:
+            return 0            # prevent divide by zero error
+
+        correct = int(round((dividend / divisor) * 100))
+
+        if correct >= 1:
+            return correct
+        else:
+            return 0
 
     def mark_quiz_complete(self):
-        """
-        Changes the quiz to complete.
-        Does not return anything
-        """
         self.complete = True
         self.save()
 
     def add_incorrect_question(self, question):
         """
-        Adds the uid of an incorrect question to the list of incorrect questions
-        The question object must be passed in
-        Does not return anything
+        Adds uid of incorrect question to the list.
+        The question object must be passed in.
         """
         current_incorrect = self.incorrect_questions
         question_id = question.id
-        if current_incorrect == "":
-            updated = str(question_id) + ","
-        else:
-            updated = current_incorrect + str(question_id) + ","
-        self.incorrect_questions = updated
+
+        self.incorrect_questions = current_incorrect + str(question_id) + ","
         self.save()
 
     def get_incorrect_questions(self):
-        """
-        Returns a list of IDs that indicate all the questions that have been answered incorrectly in this sitting
-        """
-        question_list = self.incorrect_questions  #  string of question IDs as CSV  ie 32,19,22,3,75
-        split_questions = question_list.split(',')  # list of strings ie [32,19,22,3,75]
+        question_list = self.incorrect_questions
+        split_questions = question_list.split(',')
         return split_questions
 
 
@@ -443,20 +422,20 @@ class Question(models.Model):
     of managers as one isn't created.
     """
 
-    quiz = models.ManyToManyField(Quiz, blank=True, )
+    quiz = models.ManyToManyField(Quiz, blank = True, )
 
-    category = models.ForeignKey(Category, blank=True, null=True, )
+    category = models.ForeignKey(Category, blank = True, null = True, )
 
-    content = models.CharField(max_length=1000,
-                               blank=False,
-                               help_text="Enter the question text that you want displayed",
-                               verbose_name='Question',
+    content = models.CharField(max_length = 1000,
+                               blank = False,
+                               help_text = "Enter the question text that you want displayed",
+                               verbose_name = 'Question',
                                )
 
-    explanation = models.TextField(max_length=2000,
-                                   blank=True,
-                                   help_text="Explanation to be shown after the question has been answered.",
-                                   verbose_name='Explanation',
+    explanation = models.TextField(max_length = 2000,
+                                   blank = True,
+                                   help_text = "Explanation to be shown after the question has been answered.",
+                                   verbose_name = 'Explanation',
                                )
 
     objects = InheritanceManager()
index fed4282ad02f8437b4853ae4c3f0305b3045fb98..be829d1d7e491e9ce6ef22c187174cb0c08fc2b8 100644 (file)
@@ -186,12 +186,12 @@ def load_anon_next_question(request, quiz):
     #     request.session['page_count'] = int(0)  #  since one hasnt been started, make it now
 
     next_question_id = question_list[0]
-    question = Question.objects.get(id=next_question_id)
+    next_question = Question.objects.get_subclass(id = next_question_id)
     question_type = next_question.__class__.__name__
 
     return render_to_response('question.html',
                               {'quiz': quiz,
-                               'question': question,
+                               'question': next_question,
                                'question_type': question_type,
                                'previous': previous,
                                'show_advert': show_advert,
@@ -233,7 +233,7 @@ def user_load_next_question(request, sitting, quiz):
     #     request.session['page_count'] = int(0)  #  since one hasnt been started, make it now
 
 
-    next_question = Question.objects.get(id=question_ID)
+    next_question = Question.objects.get_subclass(id = next_question_id)
     question_type = next_question.__class__.__name__
 
     return render_to_response('question.html',
index ea07a326245244b693b83f8a6a403e76a4e09288..d9ee9b8972162c92ae282b1663efe9b506f8a523 100644 (file)
@@ -41,6 +41,8 @@
 {% if question %}
 
        <p><small class="muted">Question category:</small> <strong>{{ question.category }}</strong></p>
+               <p>{{ question.id }}</p>
+               <p>{{ question_type }}</p>
         <p class="lead">{{ question.content }}</p>
                {% answers_for_question question quiz %}