了解Django的ORM中的规范化表

时间:2016-03-06 20:45:55

标签: python django orm normalization

我正在尝试直接自己编写数据库模式的背景来学习Django。我想了解如何有效地使用数据库抽象工具进行规范化。

作为一个人为的例子,假设我有一个可以就3个主题提问的对话,每个问题都很复杂,足以保证自己的课程。

Class Conversation(models.Model):
  partner = models.CharField()
Class Weather_q(models.Model):
  #stuff
Class Health_q(models.Model):
  #stuff
Class Family_q(models.Model):
  #stuff

所以我想说我想要进行2次对话:

  • 与Bob的对话1:询问两个不同的天气问题和一个关于他健康的问题
  • 与爱丽丝的对话2:询问天气及其家人

通常,我会为此编写一个规范化表:

INSERT INTO Conversation (partner) values ("Bob", "Alice"); --primary keys = 1 and 2
INSERT INTO NormalizationTable (fk_Conversation, fk_Weather_q, fk_Health_q,  fk_Family_q) VALUES 
  (1,1,0,0), -- Bob weather#1
  (1,2,0,0), -- Bob weather#2
  (1,0,1,0), -- Bob health#1
  (2,1,0,0), -- Alice weather#1
  (2,0,0,1); -- Alice family#1

我是否需要显式创建此规范化表,还是不鼓励这样做?

Class NormalizationTable(models.Model):
  fk_Conversation = models.ForeignKey(Conversation)
  fk_Weather_q = models.ForeignKey(Weather)
  fk_Health_q = models.ForeignKey(Health)
  fk_Family_q = models.ForeignKey(Family)

然后我想执行对话。我写了一个这样的视图(跳过异常捕获和逻辑来迭代每个对话的多个问题):

from myapp.models import Conversation, Weather_q, Health_q, Family_q
def converse(request):
  #get this conversation's pk
  #assuming "mypartner" is provided by the URL dispatcher
  conversation = Conversation.objects.filter(partner=mypartner)[0]
  #get the relevant row of the NormalizationTable
  questions = NormalizationTable.objects.filter(fk_Conversation=conversation)[0]
  for question in questions:
    if question.fk_Weather_q:
      return render("weather.html", Weather_q.objects.filter(pk=fk_Weather_q)[0])
    if question.fk_Health_q:
      return render("health.html", Health_q.objects.filter(pk=fk_Health_q)[0])
    if question.fk_Family_q:
      return render("family.html", Family_q.objects.filter(pk=fk_Family_q)[0])

从整体上考虑,这是解决这种规范化问题的“Django”方法(与容器对象相关的N个对象)吗?我可以更好地使用Django内置的ORM或其他工具吗?

2 个答案:

答案 0 :(得分:2)

暂且退出"规范化表格" (这个词对我来说并不熟悉),这就是我认为的一个" djangish"解决问题的方法。请注意,我附上了你的陈述"每个问题都很复杂,足以保证自己的等级#34;。对我来说,这意味着每种类型的问题都需要自己独特的领域和方法。否则,我会创建一个Question模型,通过Category连接到ForeignKey模型。

class Partner(models.Model):
    name = models.CharField()


class Question(models.Model):
    # Fields and methods common to all kinds of questions
    partner = models.ForeignKey(Partner)
    label = models.CharField()  # example field


class WeatherQuestion(Question):
    # Fields and methods for weather questions only


class HealthQuestion(Question):
    # Fields and methods for health questions only


class FamilyQuestion(Question):
    # Fields and methods for family questions only

通过这种方式,您可以获得所有问题共有的所有字段和方法的基本Question模型,以及用于描述不同类型问题的一系列子模型。基本模型与其子模型之间存在隐含关系,由Django维护。这使您能够创建具有不同问题的单个查询集,无论其类型如何。默认情况下,此查询集中的项目为Question类型,但可以通过访问特殊属性(例如healthquestion的{​​{1}}属性)将其转换为特定问题类型。这在"Multi-table model inheritance" section of Django documentation

中有详细描述

然后在视图中,您可以获得(不同类型)问题的列表,然后检测其特定类型:

HealtQuestion

检测问题类型的代码非常难看和复杂。您可以使用InheritanceManager包中的django-model-utils使其更简单,更通用。您需要安装包并将行添加到from myapp.models import Question def converse(request, partner_id): question = Question.objects.filter(partner=partner_id).first() # Detect question type question_type = "other" question_obj = question # in real life the list of types below would probably live in the settings for current_type in ['weather', 'health', 'family']: if hasattr(question, current_type + 'question'): question_type = current_type question_obj = getattr(question, current_type + 'question') break return render( "questions/{}.html".format(question_type), {'question': question_obj} ) 模型:

Question

然后视图看起来像这样:

objects = InheritanceManager()

两个视图只选择一个问题 - 第一个问题。这就是你的例子中的视图如何表现,所以我选择了它。您可以轻松转换这些示例以返回问题列表(不同类型)。

答案 1 :(得分:1)

我不熟悉规范化表这个术语,但我看到你正在尝试做什么。

在我看来,您所描述的并不是一种非常令人满意的数据库建模方法。最简单的方法是将所有问题都放在同一个表中,使用"类型"字段,可能还有一些其他可选字段,这些字段在不同类型之间在这种情况下,这在Django中变得非常简单。

但是,好吧,你说'#34;让我们说......每个问题都很复杂,足以保证自己的课程。" Django确实有一个解决方案,即generic relations。它看起来像这样:

class ConversationQuestion(models.Model):
    conversation = models.ForeignKey(Conversation)
    content_type = models.ForeignKey(ContentType)
    question_id = models.PositiveIntegerField()
    question = GenericForeignKey('content_type', 'question_id')

# you can use prefetch_related("question") for efficiency
cqs = ConversationQuestion.objects.filter(conversation=conversation)
for cq in cqs:
    # do something with the question
    # you can look at the content_type if, as above, you need to choose
    # a separate template for each type.
    print(cq.question)

因为它是Django的一部分,所以在管理员,表单等方面获得了一些(但不是全部)支持。

或者你可以做你上面所做的事情,但是,正如你所注意到的那样,它很丑陋并且似乎没有捕捉到使用ORM的优势。