]> git.parisson.com Git - django_quiz.git/commitdiff
changes to allow easier creation of new question types, updated admin to match
authorTom Walker <tomwalker0472@gmail.com>
Tue, 10 Jun 2014 21:59:40 +0000 (22:59 +0100)
committerTom Walker <tomwalker0472@gmail.com>
Tue, 10 Jun 2014 21:59:40 +0000 (22:59 +0100)
multichoice/models.py
quiz/admin.py
quiz/models.py
true_false/models.py

index 738a851a8756b0db1fca3f196064e9b6c4dc3421..8ed98d2f21ad0273a09fb151731d4c84e6a747f4 100644 (file)
@@ -1,42 +1,23 @@
 from django.db import models
-from quiz.models import Quiz, Category
+from quiz.models import Quiz, Category, Question
 
 """
 Multiple choice style question for quiz
 
 """
 
-class Question(models.Model):
-
-    quiz = models.ManyToManyField(Quiz, blank=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',
-                               )
-
-    explanation = models.TextField(max_length=2000,
-                                   blank=True,
-                                   help_text="Explanation to be shown after the question has been answered.",
-                                   verbose_name='Explanation',
-                               )
-
-
+class MCQuestion(Question):
+    """
+    Everything inherited from base question class.
+    """
     class Meta:
-        verbose_name = "Question"
-        verbose_name_plural = "Questions"
-        ordering = ['category']
+        verbose_name = "Multiple Choice Question"
+        verbose_name_plural = "Multiple Choice Questions"
 
 
-    def __unicode__(self):
-        return self.content
-
 
 class Answer(models.Model):
-    question = models.ForeignKey(Question)
+    question = models.ForeignKey(MCQuestion)
 
     content = models.CharField(max_length=1000,
                                blank=False,
index 6fa7afdce27b347dd25bfe9ca4fa00408481b388..97ebe7ba7bfa2c444231b711b0047fa2f3fc709c 100644 (file)
@@ -1,8 +1,9 @@
 from django import forms
 from django.contrib import admin
 from django.contrib.admin.widgets import FilteredSelectMultiple
-from quiz.models import Quiz, Category, Progress
-from multichoice.models import Question, Answer
+
+from quiz.models import Quiz, Category, Progress, Question
+from multichoice.models import MCQuestion, Answer
 from true_false.models import TF_Question
 
 class QuestionInline(admin.TabularInline):
@@ -24,10 +25,10 @@ class QuizAdminForm(forms.ModelForm):
         model = Quiz
 
     questions = forms.ModelMultipleChoiceField(
-                          queryset=Question.objects.all(),
-                          required=False,
-                          widget=FilteredSelectMultiple(verbose_name=('Questions'),
-                                                        is_stacked=False))
+                          queryset = Question.objects.all().select_subclasses(),
+                          required = False,
+                          widget = FilteredSelectMultiple(verbose_name = ('Questions'),
+                                                          is_stacked = False))
 
     def __init__(self, *args, **kwargs):
         super(QuizAdminForm, self).__init__(*args, **kwargs)
@@ -55,7 +56,7 @@ class QuizAdmin(admin.ModelAdmin):
 class CategoryAdmin(admin.ModelAdmin):
     search_fields = ('category', )
 
-class QuestionAdmin(admin.ModelAdmin):
+class MCQuestionAdmin(admin.ModelAdmin):
     list_display = ('content', 'category', )
     list_filter = ('category',)
     fields = ('content', 'category', 'quiz', 'explanation' )
@@ -77,13 +78,13 @@ class ProgressAdmin(admin.ModelAdmin):
 class TFQuestionAdmin(admin.ModelAdmin):
     list_display = ('content', 'category', )
     list_filter = ('category',)
-    fields = ('content', 'category', 'quiz', 'explanation' )
+    fields = ('content', 'category', 'quiz', 'explanation', 'correct',)
 
     search_fields = ('content', 'explanation')
     filter_horizontal = ('quiz',)
 
 admin.site.register(Quiz, QuizAdmin)
 admin.site.register(Category, CategoryAdmin)
-admin.site.register(Question, QuestionAdmin)
+admin.site.register(MCQuestion, MCQuestionAdmin)
 admin.site.register(Progress, ProgressAdmin)
 admin.site.register(TF_Question, TFQuestionAdmin)
index 528223062767e7b4989b8940946c5922e987ffd9..8cb6364ca70a0ea753736714f3fea40b75e8ef8a 100644 (file)
@@ -2,12 +2,17 @@ import re  #  uh oh
 
 from django.db import models
 from django.conf import settings
-from django.utils.encoding import smart_str 
+from django.utils.encoding import smart_str
 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
+
+
 """
 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 
+I have left in my original set as an example
 """
 
 CATEGORY_CHOICES = ( ('Endocrinology', 'Endocrinology'),
@@ -41,28 +46,28 @@ Category used to define a category for either a quiz or question
 class CategoryManager(models.Manager):
     """
     custom manager for Progress class
-    """ 
+    """
     def new_category(self, category):
         """
         add a new category
         """
         new_category = self.create(category=category)
-        new_category.save()        
+        new_category.save()
 
 class Category(models.Model):
-    
-    category = models.CharField(max_length=250, 
-                                blank=True, 
+
+    category = models.CharField(max_length=250,
+                                blank=True,
                                 # choices=CATEGORY_CHOICES,
                                 unique=True,
                                 null=True,
                                 )
-    
+
 
     class Meta:
         verbose_name = "Category"
         verbose_name_plural = "Categories"
-    
+
     def __unicode__(self):
         return self.category
 
@@ -72,38 +77,38 @@ or other content
 """
 
 class Quiz(models.Model):
-    
+
     title = models.CharField(max_length=60,
                              blank=False,
                              )
-    
+
     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',
                            )
-    
-    category = models.ForeignKey(Category, 
-                                 null=True, 
+
+    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\
@@ -111,13 +116,13 @@ class Quiz(models.Model):
                                      )
 
 
-    def save(self, force_insert=False, force_update=False):   
+    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)
 
 
@@ -128,7 +133,7 @@ class Quiz(models.Model):
 
     def __unicode__(self):
         return self.title
-    
+
 
 """
 Progress is used to track an individual signed in users score on different quiz's and categories
@@ -138,7 +143,7 @@ Progress is used to track an individual signed in users score on different quiz'
 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
@@ -150,34 +155,37 @@ class ProgressManager(models.Manager):
 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, ...]
-    
+
     """
-    
+
     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
-    
+
     objects = ProgressManager()
-    
-    
+
+    class Meta:
+        verbose_name = "User Progress"
+        verbose_name_plural = "User progress records"
+
     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. 
+        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.
         """
-        
+
         categories = Category.objects.all()  #  all the categories possible
         score_before = self.score  #  copy the original score csv to use later....
-        output = {}        
-        
+        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)
-            
+
             if match:
                 score = int(match.group(1))
                 possible = int(match.group(2))
@@ -187,42 +195,42 @@ class Progress(models.Model):
                     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
                 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
-            
+
         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
         """
-        
+
         category_test = Category.objects.filter(category=category_queried).exists()
-        
+
         if category_test == False:
             return "error",  "category does not exist"  #  to do: make this useful!
-        
+
         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)
-        
+
         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
@@ -230,44 +238,44 @@ class Progress(models.Model):
             temp = temp + category_queried + ",0,0,"  #  always end with a comma
             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
-        
-        data stored in csv using the format [category, score, possible, category, score, possible, ...] 
-        """                                          
-        
+
+        data stored in csv using the format [category, score, possible, category, score, possible, ...]
+        """
+
         category_test = Category.objects.filter(category=category_queried).exists()
-        
+
         if category_test == False:
             return "error",  "category does not exist"  #  to do: make useful
-        
+
         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)
-        
+
         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
-            
+
             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
             self.save()
-        
-        
+
+
         else:
             """
             if not present but a verified category, add with the points passed in
@@ -276,12 +284,12 @@ class Progress(models.Model):
             temp = temp + 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
         """
         #  list of exam objects from user that are complete
@@ -291,7 +299,7 @@ class Progress(models.Model):
 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
@@ -300,17 +308,17 @@ class SittingManager(models.Manager):
             question_set = quiz.question_set.all().order_by('?')
         else:
             question_set = quiz.question_set.all()
-        
+
         questions = ""
         for question in question_set:
             questions = questions + str(question.id) + ","  #  string of IDs seperated by commas
-        
+
         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
@@ -319,33 +327,33 @@ class SittingManager(models.Manager):
 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
-  
+
     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 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 
+
+    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
-    
+
     quiz = models.ForeignKey(Quiz)
-    
+
     question_list = models.TextField()  #  another awful csv. Always end with a comma
-    
+
     incorrect_questions = models.TextField(blank=True)  #  more awful csv. Always end with a comma
-    
+
     current_score = models.TextField()  #  a string of the score ie 19  convert to int for use
-    
+
     complete = models.BooleanField(default=False, blank=False)
-    
+
     objects = SittingManager()
-    
+
     def get_next_question(self):
         """
         Returns the next question ID (as an integer).
@@ -355,11 +363,11 @@ class Sitting(models.Model):
         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
-    
+
     def remove_first_question(self):
         """
         Removes the first question on the list.
@@ -370,7 +378,7 @@ class Sitting(models.Model):
             temp = self.question_list[first_comma+1:]  #  saves from the first number after the first comma
             self.question_list = temp
         self.save()
-            
+
     def add_to_score(self, points):
         """
         Adds the points to the running total.
@@ -380,19 +388,19 @@ class Sitting(models.Model):
         updated_score = present_score + int(points)
         self.current_score = str(updated_score)
         self.save()
-        
+
     def get_current_score(self):
         """
         returns the current score as an integer
         """
         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.
@@ -400,7 +408,7 @@ class Sitting(models.Model):
         """
         self.complete = True
         self.save()
-        
+
     def add_incorrect_question(self, question):
         """
         Adds the uid of an incorrect question to the list of incorrect questions
@@ -415,7 +423,7 @@ class Sitting(models.Model):
             updated = current_incorrect + str(question_id) + ","
         self.incorrect_questions = updated
         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
@@ -423,3 +431,38 @@ class Sitting(models.Model):
         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]
         return split_questions
+
+
+class Question(models.Model):
+    """
+    Base class for all question types.
+    Shared properties placed here.
+    Was originally going to be an abstract class but limits the use
+    of managers as one isn't created.
+    """
+
+    quiz = models.ManyToManyField(Quiz, blank=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',
+                               )
+
+    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()
+
+    class Meta:
+        ordering = ['category']
+
+
+
+    def __unicode__(self):
+        return self.content
index d35d059ca2c0157b1b489754c87e7d0e37556cda..a55a6f8674a1d563d5537d32d60f2b7f5fb197d0 100644 (file)
@@ -18,10 +18,10 @@ class TF_Question(Question):
                                   )
 
     class Meta:
-        verbose_name = "Question"
-        verbose_name_plural = "Questions"
+        verbose_name = "True/False Question"
+        verbose_name_plural = "True/False Questions"
         ordering = ['category']
 
 
     def __unicode__(self):
-        return self.content
+        return self.content[:50]