Django:動態問卷系統的Model設計

daxuesheng發表於2021-09-09

問卷設計的問題

之前的一個網站專案中,對方希望我們實現一個允許使用者自定義的問卷系統。我之前也沒有什麼經驗,經過摸索做出瞭如下的設計,這裡和大家分享一下。按照要求,問卷中包含四種不同的問題:

  • 單選題

  • 多選題

  • 問答題

  • 圖片題

問卷中問題的種類和數量不限。

設計思路

問卷系統首先可以拆解成為兩個部分,一是問卷結構的設計,二是答卷結構的設計。二者十分類似,但是又略有不同。

問卷結構設計

一副問卷應當有一個統一的入口,一個對應於『問卷』這一實體概念的抽象。問卷擁有一些描述整體的屬性,如建立時間,建立問卷的等等(有的人可能會試圖將回答了問卷的使用者和這裡的問卷本身聯絡起來,但事實上和這些使用者關聯的應當是答案)。同時,不同的問題隸屬於一個共同的問卷實體。這些問題,既共享一些通用的性質,但是基於不同的具體問題型別而又有不同的特點(選擇題和問答題就有不同的屬性)。特別的,對於選擇題而言,題目和選項又建立起了一對多的關係。綜合上面的描述,問卷部分的結構應當如下圖所示:

圖片描述

Struction of questionnaire

答卷結構設計

顯然,問卷答卷直接是應該是一對多的。而答卷又是由針對問卷中各個問題的答案組成的。特別的,選擇題的答案又指向所選的選項。故而,答卷部分的結構和問卷部分基本是相同的。

示例程式碼

以開頭我說的問題為例。

問卷

首先我們需要為問卷建立一個model

class Questionnaire(models.Model):
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    modified_at = models.DateTimeField(auto_now=True, editable=False)
    is_active = models.BooleanField(default=True)

    participants = models.ManyToManyField(settings.AUTH_USER_MODEL)
    user = models.ForeignKey(settings.AUTH_USER_MODEL)    def __str__(self):
        return smart_str(self.user.username + "的問卷")    class Meta:
        ordering = ["-created_at"]

由於不同的問題具有一些共享的屬性,我們可以建立一個抽象的基類類表示這些共通的屬性,例子如下:

class Question(models.Model):
    """這個類是單個問題的抽象"""
    question = models.CharField(max_length=200, verbose_name="問題")
    required = models.BooleanField(default=True, help_text="這個問題是否必須回答")

    questionnaire = models.ForeignKey(Questionnaire)
    order_in_list = models.IntegerField(default=1)  # 在問卷列表中的順序,從1開始
    created_at = models.DateTimeField(auto_now_add=True, editable=False)        
    class Meta:
        abstract = True

為不同問題型別我們需要建立對應的model:

class ChoiceQuestion(Question):
    """選擇題"""
    multi_choice = models.BooleanField(default=False, verbose_name="是否為多選")


TEXT_QUESTION_TYPE = 0FILE_QUESTION_TYPE = 1class NonChoiceQuestion(Question):
    """主觀題"""
    type = models.SmallIntegerField(verbose_name="主觀題型別",
                                    choices=(
                                        (TEXT_QUESTION_TYPE, '問答題'),
                                        (FILE_QUESTION_TYPE, '檔案題')
                                    ), default=0)

雖然問題描述中給出了4中不同的問題,但是實際上只需要兩類就足以描述。中單選題和多選題只是能夠同時選擇的選項數量不同,題目本身的描述特點是相同的。而檔案題和問答題只是答案的形式不一樣,題目本身的描述特點是一樣的。(綜上來看,問題主要是表現問題的『描述』性)

最後,我們需要為問答題設計選項:

class Choice(models.Model):
    question = models.ForeignKey(ChoiceQuestion, related_name="choices")
    description = models.CharField(max_length=50)
    multi_choice = models.BooleanField(default=False, verbose_name="是否為多選")
    order_in_list = models.IntegerField(default=1)  # 在選項列表中的順序,從1開始

答案部分

首先是答卷:

class AnswerSheet(models.Model):
    """答卷的抽象"""
    user = models.ForeignKey(settings.AUTH_USER_MODEL)      # 答題者
    questionnaire = models.ForeignKey(Questionnaire)     # 對應問卷

    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    modified_at = models.DateTimeField(auto_now=True, editable=False)
    is_active = models.BooleanField(default=True)

答案則無法像問題只需要兩個類既可以表示,這裡我們需要為四種問題設計四種答案類:

class Answer(models.Model):
    """答案的基類"""
    answer_sheet = models.ForeignKey(AnswerSheet)    class Meta:
        abstract = Trueclass SingleChoiceAnswer(Answer):
    """單選題的答案"""
    choice = models.ForeignKey(Choice, related_name="single_choice_answers")  # 單選題
    question = models.ForeignKey(ChoiceQuestion, related_name="single_choice_answer_set")class MultiChoiceAnswer(Answer):
    """多選題的答案"""
    choices = models.ManyToManyField(Choice, related_name="multi_choice_answers")
    question = models.ForeignKey(ChoiceQuestion, related_name="multi_choice_answers")class TextAnswer(Answer):
    """文字題的答案"""
    text = models.TextField()
    question = models.ForeignKey(NonChoiceQuestion)class FileAnswer(Answer):
    file = models.ImageField(upload_to="your_upload_path")
    question = models.ForeignKey(NonChoiceQuestion)
    is_image = models.BooleanField(default=True)

綜上我們就完成了這個可定製答卷的model部分。


作者:治部少輔
連結:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4692/viewspace-2810050/,如需轉載,請註明出處,否則將追究法律責任。

相關文章