【问题标题】:Understanding normalization tables in Django's ORM了解 Django ORM 中的规范化表
【发布时间】:2016-06-20 07:54:25
【问题描述】:

我正在尝试自己直接从对数据库模式进行编码的背景中学习 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:问两个不同的天气问题和一个关于他的健康的问题
  • 与 Alice 的对话 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])

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

【问题讨论】:

  • “规范化表”是众所周知的东西吗?我显然知道数据库规范化,但这并不能帮助我理解“规范化表”是什么。谷歌没有帮助(似乎没有用“规范化表”作为术语的实际结果)。你能定义什么是“规范化表”吗?
  • 不要使用没有意义的“人为的例子”。想出一个可以理解的更好的例子,但即使这样,答案也可能是“编写你的 models.py,这样代码才有意义,不要担心规范化。”
  • @Ludwik Trammer 我只是使用该术语来描述具有标准化数据的表。人们通常给它们起对它们中包含的数据有意义的名称。我可能也应该这样做。
  • @LegoStormtroopr,这个例子实际上比你想象的更真实。请参阅我对凯文克里斯托弗亨利的回答的评论。

标签: python django orm normalization


【解决方案1】:

撇开“规范化表”(这个词我不熟悉),我认为这是解决问题的“djangish”方式。请注意,我同意您的陈述“每个问题都足够复杂,足以保证自己的课程”。对我来说,这意味着每种类型的问题都需要有自己独特的领域和方法。否则我会创建一个Question 模型,通过ForeignKey 连接到Category 模型。

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 类型,但可以通过访问特殊属性(例如HealtQuestions 的healthquestion 属性)转换为特定问题类型。这在"Multi-table model inheritance" section of Django documentation中有详细描述。

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

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}
    )

检测问题类型的代码非常丑陋和复杂。您可以使用来自django-model-utils 包的InheritanceManager 使其更简单、更通用。您需要安装该软件包并将该行添加到 Question 模型:

objects = InheritanceManager()

然后视图将如下所示:

from myapp.models import Question

def converse(request, partner_id):
    question = Question.objects.filter(partner=partner_id).select_subclasses().first()
    question_type = question._meta.object_name.lower()

    return render(
        "questions/{}.html".format(question_type),
        {'question': question}
    )

两个视图都只选择一个问题 - 第一个问题。这就是您示例中的视图的行为方式,所以我选择了它。您可以轻松地转换这些示例以返回(不同类型的)问题列表。

【讨论】:

  • 非常感谢您介绍InheritanceManager。这看起来像我需要的。我会去阅读文档。
【解决方案2】:

我不熟悉术语规范化表,但我明白你在做什么。

在我看来,您所描述的并不是一种非常令人满意的数据库建模方式。最简单的方法是使所有问题成为同一个表的一部分,带有“类型”字段,可能还有一些其他可选字段,这些字段因类型而异。在这种情况下,这在 Django 中变得非常简单。

但是,好吧,你说“假设......每个问题都足够复杂,足以保证有自己的课程。” 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 的优势。

【讨论】:

  • 感谢您接受“每个问题都足够复杂,足以保证自己的课程”。我实际上正在编写一个定量市场研究应用程序,每个问题的结构都与不相关的领域非常不同(“按偏好顺序对这 10 件商品进行排序?”、“这次购物体验缺少什么?”、“猜猜这个价格产品?”等)。关于 GenericForeignKey 的注释很有帮助 - 它让我思考我可以在 ORM 中利用多少常见内容。
猜你喜欢
  • 1970-01-01
  • 2018-08-11
  • 2020-04-18
  • 1970-01-01
  • 2016-10-31
  • 2014-09-22
  • 2018-12-12
  • 1970-01-01
  • 2015-12-13
相关资源
最近更新 更多