了解 Django 的 ORM 中的规范化表
Understanding normalization tables in Django's ORM
我正在尝试自己直接对数据库模式进行编码的背景来学习 Django。我想了解我应该如何有效地使用数据库抽象工具进行规范化。
作为一个人为的例子,假设我有一个对话可以就 3 个主题提出问题,并且每个问题都足够复杂以保证其本身 Class。
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:询问天气和她的家人
通常,我会为此编写规范化 table:
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
我是否需要明确创建此规范化 table 还是不鼓励这样做?
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 或其他工具吗?
我不熟悉术语规范化table,但我知道你在做什么。
在我看来,您所描述的并不是一种非常令人满意的数据库建模方法。最简单的方法是使所有问题成为同一个 table 的一部分,带有一个 "type" 字段,可能还有一些其他可选字段因类型而异。在那种情况下,这在 Django 中变得非常简单。
但是,好吧,你说 "let's say... each question is complicated enough to warrant its own class." 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 的优势。
撇开"normalization tables"(我不熟悉这个词),这就是我认为"djangish"解决问题的方法。请注意,我同意你的声明 "each question is complicated enough to warrant its own Class"。对我来说,这意味着每种类型的问题都需要其独特的领域和方法。否则,我会创建一个 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
类型,但可以通过访问特殊属性(例如 HealtQuestion
的 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}
)
检测问题类型的代码非常难看和复杂。您可以使用 InheritanceManager from django-model-utils 包使其更简单、更通用。您需要安装软件包并将行添加到 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}
)
两种观点 select 只有一个问题 - 第一个。这就是您示例中的视图的行为方式,所以我同意了。您可以轻松地将这些示例转换为 return 问题列表(不同类型)。
我正在尝试自己直接对数据库模式进行编码的背景来学习 Django。我想了解我应该如何有效地使用数据库抽象工具进行规范化。
作为一个人为的例子,假设我有一个对话可以就 3 个主题提出问题,并且每个问题都足够复杂以保证其本身 Class。
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:询问天气和她的家人
通常,我会为此编写规范化 table:
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
我是否需要明确创建此规范化 table 还是不鼓励这样做?
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 或其他工具吗?
我不熟悉术语规范化table,但我知道你在做什么。
在我看来,您所描述的并不是一种非常令人满意的数据库建模方法。最简单的方法是使所有问题成为同一个 table 的一部分,带有一个 "type" 字段,可能还有一些其他可选字段因类型而异。在那种情况下,这在 Django 中变得非常简单。
但是,好吧,你说 "let's say... each question is complicated enough to warrant its own class." 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 的优势。
撇开"normalization tables"(我不熟悉这个词),这就是我认为"djangish"解决问题的方法。请注意,我同意你的声明 "each question is complicated enough to warrant its own Class"。对我来说,这意味着每种类型的问题都需要其独特的领域和方法。否则,我会创建一个 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
类型,但可以通过访问特殊属性(例如 HealtQuestion
的 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}
)
检测问题类型的代码非常难看和复杂。您可以使用 InheritanceManager from django-model-utils 包使其更简单、更通用。您需要安装软件包并将行添加到 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}
)
两种观点 select 只有一个问题 - 第一个。这就是您示例中的视图的行为方式,所以我同意了。您可以轻松地将这些示例转换为 return 问题列表(不同类型)。