]> git.parisson.com Git - django_quiz.git/commitdiff
more preening of the models and views
authorTom Walker <tomwalker0472@gmail.com>
Sat, 6 Apr 2013 19:00:57 +0000 (20:00 +0100)
committerTom Walker <tomwalker0472@gmail.com>
Sat, 6 Apr 2013 19:00:57 +0000 (20:00 +0100)
quiz/models.py
quiz/urls.py
quiz/views.py

index 938b3a24042c2b5ebb905d4564cc76220e70dbca..d981b9d24a1857fd3db5d40a702421818b679e88 100644 (file)
@@ -1,16 +1,43 @@
+import re  #  uh oh
+
 from django.db import models
-from django.utils.encoding import smart_str 
 from django.conf import settings
+from django.utils.encoding import smart_str 
 from django.contrib.auth.models import User
 
+"""
+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 
+"""
+
+CATEGORY_CHOICES = ( ('Endocrinology', 'Endocrinology'),
+                     ('Dermatology', 'Dermatology'),
+                     ('Cellular Biology', 'Cellular Biology'),
+                     ('Neurology', 'Neurology'),
+                     ('Gastroenterology', 'Gastroenterology'),
+                     ('Statistics', 'Statistics'),
+                     ('Rheumatology', 'Rheumatology'),
+                     ('Tropical medicine', 'Tropical medicine'),
+                     ('Respiratory', 'Respiratory'),
+                     ('Immunology', 'Immunology'),
+                     ('Nephrology', 'Nephrology'),
+                     ('Genetic Medicine', 'Genetic Medicine'),
+                     ('Haematology', 'Haematology'),
+                     ('Pharmacology', 'Pharmacology'),
+                     ('Physiology', 'Physiology'),
+                     ('Ophthalmology', 'Ophthalmology'),
+                     ('Anatomy', 'Anatomy'),
+                     ('Biochemistry', 'Biochemistry'),
+                     ('empty', 'empty'),
+                     ('Psychiatry', 'Psychiatry'),
+                     ('Cardiology', 'Cardiology'),
+                    )
 
 
 """
-Quiz is a container that can be filled with various different question types
-or other content
+Category used to define a category for either a quiz or question
 """
 
-
 class CategoryManager(models.Manager):
     """
     custom manager for Progress class
@@ -26,7 +53,7 @@ class Category(models.Model):
     
     category = models.CharField(max_length=250, 
                                 blank=True, 
-                                choices=CATEGORY_CHOICES,
+                                choices=CATEGORY_CHOICES,
                                 unique=True,
                                 null=True,
                                 )
@@ -39,20 +66,31 @@ 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
+"""
 
 class Quiz(models.Model):
     
-    title = models.CharField(max_length=60)
+    title = models.CharField(max_length=60,
+                             blank=False,
+                             )
     
-    description = models.TextField(blank=True)
+    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, )
+    category = models.ForeignKey(Category, 
+                                 null=True, 
+                                 blank=True,
+                                 )
     
     random_order = models.BooleanField(blank=False,
                                        default=False,
@@ -62,16 +100,17 @@ class Quiz(models.Model):
     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",
-                                       )
+                                     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):  # automatically converts url to lowercase 
-        self.url = self.url.lower()
+    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
         super(Quiz, self).save(force_insert, force_update)
 
 
@@ -84,6 +123,11 @@ class Quiz(models.Model):
         return self.title
     
 
+"""
+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
@@ -102,8 +146,6 @@ class Progress(models.Model):
     
     data stored in csv using the format [category, score, possible, category, score, possible, ...]
     
-    to do:
-            combine the check and update functions into one
     """
     
     user = models.OneToOneField('auth.User')  #  one user per progress class
@@ -112,15 +154,17 @@ class Progress(models.Model):
     
     objects = ProgressManager()
     
+    
     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,
-        the third is the percentage correct 
+        the third is the percentage correct.
+        The dict will have one key for every category that you have defined.
         """
-        import re  #  uh oh
         
         categories = Category.objects.all()  #  all the categories possible
+        score_before = self.score  #  copy the original score csv to use later....
         output = {}        
         
         for cat in categories:  # for each of the categories
@@ -136,11 +180,14 @@ class Progress(models.Model):
             
             
             else:  #  Is possible to remove/comment this section out
-                temp = self.score
-                temp = temp + cat.category + ",0,0,"  #  always end with a comma
+                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
                 self.score = temp
                 output[cat.category] = [0, 0]
-                self.save()  #  add this new output to disk
+        
+        
+        if len(self.score) > len(score_before):  #  if changes have been made
+            self.save()  #  save only at the end to minimise disc writes
             
         return output
             
@@ -155,9 +202,8 @@ class Progress(models.Model):
         category_test = Category.objects.filter(category=category_queried).exists()
         
         if category_test == False:
-            return "error",  "category does not exist"  #  to do: update
+            return "error",  "category does not exist"  #  to do: make this useful!
         
-        import re  #  :'(  always a bad start
         my_regex = re.escape(category_queried) + r",(\d+),(\d+),"  #  group 1 is score, group 2 is possible
         
         match = re.search(my_regex, self.score, re.IGNORECASE)
@@ -189,9 +235,8 @@ class Progress(models.Model):
         category_test = Category.objects.filter(category=category_queried).exists()
         
         if category_test == False:
-            return "error",  "category does not exist"  #  to do: update
+            return "error",  "category does not exist"  #  to do: make useful
         
-        import re  #  :'(  always a bad start
         my_regex = re.escape(str(category_queried)) + r",(\d+),(\d+),"  #  group 1 is score, group 2 is possible
         
         match = re.search(my_regex, self.score, re.IGNORECASE)
@@ -200,8 +245,8 @@ class Progress(models.Model):
             current_score = int(match.group(1))
             current_possible = int(match.group(2))
                         
-            updated_current_score = current_score + score_to_add
-            updated_current_possible = current_possible + possible_to_add
+            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
             
             new_score = str(category_queried) + "," + str(updated_current_score) + "," + str(updated_current_possible) + ","
             
@@ -220,10 +265,10 @@ class Progress(models.Model):
             temp = self.score
             temp = temp + str(category_queried) + "," + str(score_to_add) + "," + str(possible_to_add) + ","
             self.score = temp
-            self.save()  
-
+            self.save()
+    
     
-    def show_exams(self,):
+    def show_exams(self):
         """
         finds the previous exams marked as 'exam papers'
         
@@ -231,9 +276,9 @@ class Progress(models.Model):
         """
         
         exams = Sitting.objects.filter(user=self.user).filter(complete=True)  #  list of exam objects from user that are complete
-        
         return exams
-    
+
+
 
 class SittingManager(models.Manager):
     """
@@ -268,11 +313,15 @@ 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
-    quiz
-    question_list is a list of id's of the unanswered questions. Stored as a textfield to allow >255 chars. CSV
+  
+    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.
+
     incorrect_questions is a list of id's of the questions answered wrongly
-    current_Score is total of 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 trued, or DB will become huge 
+    
+    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 
     """
     
     user = models.ForeignKey('auth.User')  #  one user per exam class
@@ -285,18 +334,20 @@ class Sitting(models.Model):
     
     current_score = models.TextField()  #  a string of the score ie 19  convert to int for use
     
-    complete = models.BooleanField(default=False,)
+    complete = models.BooleanField(default=False, blank=False)
     
     objects = SittingManager()
     
     def get_next_question(self):
         """
         Returns the next question ID (as an integer).
+        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
             return False
+        
         qID = self.question_list[:first_comma]  #  up to but not including the first comma
         
         return qID
@@ -314,12 +365,13 @@ class Sitting(models.Model):
             
     def add_to_score(self, points):
         """
-        Adds the input (points) to the running total.
+        Adds the points to the running total.
         Does not return anything
         """
         present_score = self.get_current_score()
-        present_score = present_score + points
-        self.current_score = str(present_score)
+        updated_score = present_score + int(points)
+        self.current_score = str(updated_score)
+        self.save()
         
     def get_current_score(self):
         """
@@ -327,6 +379,12 @@ class Sitting(models.Model):
         """
         return int(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))
+    
     def mark_quiz_complete(self):
         """
         Changes the quiz to complete.
@@ -338,14 +396,15 @@ class Sitting(models.Model):
     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
         """
-        current = self.incorrect_questions
+        current_incorrect = self.incorrect_questions
         question_id = question.id
-        if current == "":
+        if current_incorrect == "":
             updated = str(question_id) + ","
         else:
-            updated = current + str(question_id) + ","
+            updated = current_incorrect + str(question_id) + ","
         self.incorrect_questions = updated
         self.save()
         
@@ -354,5 +413,5 @@ class Sitting(models.Model):
         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 numbers [32,19,22,3,75]
+        split_questions = question_list.split(',')  # list of strings ie [32,19,22,3,75]
         return split_questions
\ No newline at end of file
index 7716fb2fbc392ad358fac5009e0f4e14b43049a9..d120d842f78e367e0b3792b000fdafe1e4811e27 100644 (file)
@@ -10,6 +10,9 @@ urlpatterns = patterns('',
     #  passes variable 'quiz_name' to quiz_take view
     url(r'^(?P<quiz_name>[\w-]+)/$', 'quiz.views.quiz_take'),  #  quiz/
     url(r'^(?P<quiz_name>[\w-]+)$', 'quiz.views.quiz_take'),  #  quiz
+    url(r'^(?P<quiz_name>[\w-]+)/take/$', 'quiz.views.quiz_take')  #  quiz/take/
+    url(r'^(?P<quiz_name>[\w-]+)take$', 'quiz.views.quiz_take')  #  quiz/take
+
 
 
 )
index b5f79e733d7f43e2396ac8d6da23fa267cf06309..d45c78007eb56f33beae19517199a16722ad3f4d 100644 (file)
@@ -1,13 +1,14 @@
-from django.http import HttpResponse
-from django.shortcuts import render_to_response
+import random
+
 from django.core.exceptions import ObjectDoesNotExist
-from django.contrib import auth
 from django.core.context_processors import csrf
+from django.contrib import auth
+from django.http import HttpResponse
 from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
 from django.template import RequestContext
 
 
-
 from quiz.models import Quiz, Category, Progress, Sitting
 from multichoice.models import Question, Answer
 
@@ -18,24 +19,24 @@ Views related directly to the quiz
 
 ***********************************
 
-
-used by anon only:
+used by anonymous (non-logged in) users only:
 
     request.session[q_list] is a list of the remaining question IDs in order. "q_list" = quiz_id + "q_list"
     request.session[quiz_id + "_score"] is current score. Best possible score is number of questions. 
     
 used by both user and anon:
 
-    request.session['page_count'] is a counter used for displaying adverts every X number of pages
+    request.session['page_count'] is a counter used for displaying message every X number of pages
+
+useful query sets:
 
-question.answer_set.all() is all the answers for question
-quiz.question_set.all() is all the questions in a quiz
+    question.answer_set.all() is all the answers for question
+    quiz.question_set.all() is all the questions in a quiz
 
 To do: 
         variable scores per question
-        seperate the login portion so that other django apps are compatible
         if a user does some questions as anon, then logs in, remove these questions from remaining q list for logged in user
-        allow the page count to be set in admin
+        allow the page count before a message is shown to be set in admin
 """
 
 
@@ -64,6 +65,7 @@ def quiz_take(request, quiz_name):
                                                       quiz=quiz,
                                                       complete=False,
                                                       )[0]  #  use the first one
+            
             return user_load_next_question(request, previous_sitting, quiz)
             
         else:
@@ -83,32 +85,29 @@ def quiz_take(request, quiz_name):
 
 def new_anon_quiz_session(request, quiz):
     """
-    Sets the session variables when starting a quiz for the first time
-    
-    to do:
-            include a cron job to clear the expired sessions daily
+    Sets the session variables when starting a quiz for the first time when not logged in
     """
-
+    
     request.session.set_expiry(259200)  #  set the session to expire after 3 days
     
     questions = quiz.question_set.all()
     question_list = []
     for question in questions:
         question_list.append(question.id)  #  question_list is a list of question IDs, which are integers
-
+    
     if quiz.random_order == True:
-        import random
         random.shuffle(question_list)
     
     quiz_id = str(quiz.id)
-
+    
     score = quiz_id + "_score" 
     request.session[score] = int(0)  #  session score for anon users
     
     q_list = quiz_id + "_q_list"
     request.session[q_list] = question_list  #  session list of questions
     
-    request.session['page_count'] = int(0)  #  session page count for adverts
+    if 'page_count' not in request.session:
+        request.session['page_count'] = int(0)  #  session page count for adverts
     
     return load_anon_next_question(request, quiz)
 
@@ -118,7 +117,8 @@ def user_new_quiz_session(request, quiz):
     """
     sitting = Sitting.objects.new_sitting(request.user, quiz)
     
-    request.session['page_count'] = int(0)  #  session page count for adverts
+    if 'page_count' not in request.session:
+        request.session['page_count'] = int(0)  #  session page count for adverts
     
     return user_load_next_question(request, sitting, quiz)
     
@@ -141,9 +141,7 @@ def load_anon_next_question(request, quiz):
         request.session[q_list] = question_list
         
         counter = request.session['page_count']
-        request.session['page_count'] = counter + 1  #  add 1 to the page counter
-        
-        
+        request.session['page_count'] = counter + 1  #  add 1 to the page counter     
     
     if not request.session[q_list]:
         #  no questions left!
@@ -163,8 +161,7 @@ def load_anon_next_question(request, quiz):
     
     next_question_id = question_list[0]
     question = Question.objects.get(id=next_question_id)
-
-        
+    
     return render_to_response('question.html', 
                               {'quiz': quiz, 
                                'question': question, 
@@ -188,9 +185,9 @@ def user_load_next_question(request, sitting, quiz):
         counter = request.session['page_count']
         request.session['page_count'] = counter + 1  #  add 1 to the page counter
     
-    qID = sitting.get_next_question()
+    question_ID = sitting.get_next_question()
     
-    if qID == False:
+    if question_ID == False:
         #  no questions left
         return final_result_user(request, sitting, previous)
 
@@ -207,7 +204,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=qID)
+    next_question = Question.objects.get(id=question_ID)
     
     return render_to_response('question.html', 
                               {'quiz': quiz,
@@ -229,14 +226,14 @@ def final_result_anon(request, quiz, previous):
     quiz_id = str(quiz.id)
     score = quiz_id + "_score"
     score = request.session[score]
+    percent = int(round((float(score) / float(max_score)) * 100))
     if score == 0:
         score = "nil points"
     max_score = quiz.question_set.all().count()
-    percent = int(round((float(score) / float(max_score)) * 100))
     
     session_score, session_possible = anon_session_score(request)
     
-    if quiz.answers_at_end != True:  #  answer was shown after each question
+    if quiz.answers_at_end != True:  #  if answer was shown after each question
         return render_to_response('result.html',
                                   {
                                    'score': score, 
@@ -270,7 +267,7 @@ def final_result_user(request, sitting, previous):
     score = sitting.get_current_score()
     incorrect = sitting.get_incorrect_questions()
     max_score = quiz.question_set.all().count()
-    percent = int(round((float(score) / float(max_score)) * 100))
+    percent = sitting.get_percent_correct()
 
     sitting.mark_quiz_complete()  #  mark as complete    
     
@@ -323,8 +320,7 @@ def question_check_anon(request, quiz):
     else:
         outcome = "incorrect"
         anon_session_score(request, 0, 1)
-    
-    #  to do - allow explanations
+        
     if quiz.answers_at_end != True:  #  display answer after each question
         return {'previous_answer': answer, 'previous_outcome': outcome, 'previous_question': question, }
     else:  #  display all answers at end
@@ -348,7 +344,6 @@ def question_check_user(request, quiz, sitting):
         sitting.add_incorrect_question(question)
         user_progress_score_update(request, question.category, 0, 1)
     
-    #  to do - allow explanations
     if quiz.answers_at_end != True:  #  display answer after each question
         return {'previous_answer': answer, 'previous_outcome': outcome, 'previous_question': question, }
     else:  #  display all answers at end