高效代码的 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 代码。
下一个功能:复合问题和问题模板,因此人们可以使用管理员筛选来制作自己的答案类型。
我有一个 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 代码。 下一个功能:复合问题和问题模板,因此人们可以使用管理员筛选来制作自己的答案类型。