高效代码的 Django 模型继承

Django Model inheritance for efficient code

我有一个 Django 应用程序,它使用抽象基础 Class ('Answer') 并根据问题对象所需的 answer_type 创建不同的答案。 (这个项目开始于民意调查教程)。现在的问题是:

class Question(models.Model):
    ANSWER_TYPE_CHOICES = (
    ('CH', 'Choice'),
    ('SA', 'Short Answer'),
    ('LA', 'Long Answer'),
    ('E3', 'Expert Judgement of Probabilities'),
    ('E4', 'Expert Judgment of Values'),
    ('BS', 'Brainstorms'),
    ('FB', 'Feedback'),
    )
    answer_type = models.CharField(max_length=2,
                               choices=ANSWER_TYPE_CHOICES,
                               default='SA')
    question_text = models.CharField(max_length=200, default="enter a question here")

答案是:

class Answer(models.Model):
"""
Answer is an abstract base class which ensures that question and user are
always defined for every answer
"""
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
class Meta:
    abstract = True
    ordering = ['user']

目前,我在 Answer 中只有一个方法(覆盖 get_or_update_answer()),其中包含特定于类型的说明,以查看正确的 table 并收集或创建正确类型的对象.

    @classmethod
def get_or_update_answer(self, user, question, submitted_value={}, pk_ans=None):
    """
    this replaces get_or_update_answer with appropriate handling for all
    different Answer types. This allows the views answer and page_view to get
    or create answer objects for every question type calling this function.
    """
    if question.answer_type == 'CH':
        if not submitted_value:
            # by default, select the top of a set of radio buttons
            selected_choice = question.choice_set.first()
            answer, _created = Vote.objects.get_or_create(
                user=user,
                question=question,
                defaults={'choice': selected_choice})
        else:
            selected_choice = question.choice_set.get(pk=submitted_value)
            answer = Vote.objects.get(user=user, question=question)
            answer.choice = selected_choice

    elif question.answer_type == 'SA':
        if not submitted_value:
            submitted_value = ""
            answer, _created = Short_Answer.objects.get_or_create(
                user=user,
                question=question,
                defaults={'short_answer': submitted_value})
        else:
            answer = Short_Answer.objects.get(
                user=user,
                question=question)
            answer.short_answer = hashtag_cleaner(submitted_value['short_answer'])
 etc... etc... (similar handling for five more types)

通过将所有这些逻辑放在 'models.py' 中,我可以为 page_view 的任意数量的问题加载用户答案:

    for question in page_question_list:
        answers[question] = Answer.get_or_update_answer(user, question, submitted_value, pk_ans)

我相信有一种更 Pythonic 的方法来设计此代码 - 我还没有学会使用,但我不确定是什么。类似于接口的东西,这样每个对象类型都可以实现自己的 Answer.get_or_update_answer() 版本,而 Python 将使用适合该对象的版本。这将使扩展 'models.py' 更整洁。

根据您所展示的内容,您已经完成了重新实现 Visitor pattern 的大部分工作,这是处理此类情况的一种非常标准的方法(您有一堆相关的子类,每个都需要自己的处理逻辑,并希望遍历它们的实例并对每个实例做一些事情)。

我建议看一下该模式的工作原理,并可能更明确地实施它。

我最近重新审视了这个问题,将一两百行代码替换为五行或十行,并认为有一天可能对某人有用,以找到我在这里所做的事情。

我遇到的问题有几个要素 - 首先,需要创建、保存和检索许多答案类型;其次,GET 与 POST 二分法(以及我总是创建答案并将其发送到表单的特殊解决方案);第三,一些类型有不同的逻辑(Brainstorm 可以为每个用户提供多个答案,FeedBack 甚至不需要响应——如果它是为用户创建的,它已经被呈现。)这些元素可能掩盖了一些删除的机会重复,这使得访问者模式非常合适。

元素 1 和 2 的解决方案

映射到相关答案子 class 的 question.answer_type 代码的字典在 views.py 中创建(因为很难将它放在 models.py 中并且解决依赖关系):

# views.py: 
ANSWER_CLASS_DICT = {
'CH': Vote,
'SA': Short_Answer,
'LA': Long_Answer,
'E3': EJ_three_field,
'E4': EJ_four_field,
'BS': Brainstorm,
'FB': FB,}

然后我可以获得 class 我想要 'get_or_created' 的任何问题的答案:

ANSWER_CLASS_DICT[question.answer_type]

我将它作为参数传递给 class 方法:

# models.py:
def get_or_update_answer(self, user, question, Cls, submitted_value=None, pk_ans=None):            
    if not submitted_value:
            answer, _created = Cls.objects.get_or_create(user=user, question=question)
    elif isinstance(submitted_value, dict):
            answer, _created = Cls.objects.get_or_create(user=user, question=question)
        for key, value in submitted_value.items():
                setattr(answer, key, value)
    else:
        pass

所以相同的六行代码处理 get_or_creating 当 submitted_value=None (GET) 或不 (submitted_value) 时的任何答案。

元素 3 的解决方案

要素三的解决方案是扩展模型,以便为再次访问同一问题的用户分离至少三种处理类型: 'S' - 单一,允许他们只记录一个答案,重新访问和修改答案,但永远不会给出两个不同的答案。 'T' - 跟踪,这允许他们每次都更新他们的答案,但会记录他们的答案可用的历史(例如研究人员)。 'M' - 多个,允许对一个问题提交多个答案。

所有这些更改后仍在修复错误,所以我不会 post 代码。 下一个功能:复合问题和问题模板,因此人们可以使用管理员筛选来制作自己的答案类型。