Django模型加入一对多关系以在模板中显示

Posted

技术标签:

【中文标题】Django模型加入一对多关系以在模板中显示【英文标题】:Django model joining a one to many relationship for displaying in a template 【发布时间】:2011-07-03 22:52:02 【问题描述】:

不确定描述问题的最佳方式是什么。我有 2 个表联系人和属性。联系人表每人有 1 个条目,属性表每人有 0、1 或多个条目。它们目前与一个“假”外键连接,该外键并不是真正的外键。如果我需要添加外键,我会处理旧数据并没有什么大不了的,原来没有外键。所以表格的布局如下:

联系人:

class contact(models.Model):
    contactId = models.AutoField(primary_key=True, db_column='contactId')
    firstName = models.CharField(max_length=255, null=True, db_column='firstName')
    middleName = models.CharField(max_length=255, null=True, db_column='middleName')
    lastName = models.CharField(max_length=255, null=True, db_column='lastName')

属性:

class attribute(models.Model):
    attributeId = models.AutoField(primary_key=True, db_column='attributeId')
    contactId = models.IntegerField(db_index=True, null=True, db_column='contactId')
    attributeValue = models.TextField(null=True, db_column='attributeValue')

所以我已经正确设置了 Django 模型来表示这些表。现在我需要完成的是一个视图和模板来循环这些表,以便它生成以下格式的 xml 文档:

<contacts>
    <contact>
        <contactId></contactId>
        <firstName></firstName>
        <lastName></lastName>
        <attributes>
            <attribute>
                <attributeId></attributeId>
                <attributeValue></attributeValue>
            </attribute>
        </attributes>
    </contact>
</contacts>

所以会有一个所有联系人的列表以及与每个联系人关联的所有属性。

我确信有一种简单的方法可以实现这一点。在其他语言中,我会简单地编写两个循环查询来遍历联系人,然后遍历每个联系人的属性。然而,我工作的公司正在迁移到一个新平台,并且想要用 django/python 编写的新应用程序,我仍在努力学习这两者。

感谢任何人提供的任何帮助。

【问题讨论】:

你的模型是什么样的?有什么理由不将attribute.contactId 定义为ForeignKey 它没有外键集的唯一原因是因为它是一个较旧的数据库模型。公司没有任何针对外键的东西。我在原始帖子中添加了实际模型。 【参考方案1】:

假设您已将 django 模型设置为使用当前的数据库设置,如果我没有设置外键,我会执行以下操作。

contacts = Contact.objects.all()

for contact in contacts:
    contact.attributes = Attribute.objects.filter(contactId=contact.pk) 


return render_to_response("mytemplate.html", 'contacts': contacts )

或者,保存一些查询;

attributes_map = dict( 
    [(attribute.contactId, attribute) for attribute in \
        Attribute.objects.filter(contactId__in=[contact.pk for contact in contacts])]
    )

for contact in contacts:
    contact.attributes = attributes_map.get(contact.pk)

return render_to_response("mytemplate.html", 'contacts': contacts )

模板

<contacts>
% for contact in contacts %
    <contact>
        <contactId> contact.pk </contactId>
        <firstName> contact.firstName </firstName>
        <lastName> contact.lastName </lastName>

        % if contact.attributes %
            <attributes>
            % for attribute in contact.attributes %
                <attribute>
                    <attributeId> attribute.pk </attributeId>
                    <attributeValue> attribute.attributeValue </attributeValue>
                </attribute>
            % endfor %
            </attributes>
        % endif %

    </contact>
% endfor %
</contacts>

【讨论】:

这看起来正是我需要的。我没有意识到我可以将属性添加到最初没有在模型类中定义的查询集中。如果我将外键添加到表中,我应该采取另一种方法吗? 是的!一切都只是一个 python 对象——这是一个很好的概念。您可以在您的请求对象上放置随机的东西,将表单传递到您无法控制的视图中,等等。有趣的是,即使您有外键,我仍然会使用我的第二种方法 - 原因是“向后" 不会自动查询关系,因此为了保存数据库查询,我将使用 2 步过程。 如果是我们只有一个联系人的情况,有一种方便的反向外键方法是fieldname_set。每个具有指向它的外键的模型都会自动获得一个属性fieldname_set,您可以使用与filter 相同的方式查询该属性。例如contact.attribute_set.all() 如果您想进一步探索,您应该查看文档以了解一些怪癖【参考方案2】:

听起来你就像是在遗留数据库地狱。

因此,您可能会发现很难使用诸如 ForeignKey 之类的标准 django 模型,但您仍应努力将此类代码排除在视图代码之外。

所以最好在你的联系人类上定义一些方法,比如...

def attributes(self):
    return Attribute.objects.filter(contactId=self.contactId)

然后,您可以在模板中使用它。

<attributes>
    % for attribute in contact.attributes.all %
        <attribute>
            ...
        </attribute>
    % endfor %
</attribute>

不过,模板中的contact.attributes.all 可能并不理想(在模板中确定关系)。但是添加到您的视图中以将某些内容添加到上下文中会很简单。

【讨论】:

【参考方案3】:

Yuji 的回答很像 Nick Johnson 的 App Engine 的 prefetch_refprops: http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine

我找到了优化这段代码的方法:

attributes_map = dict( 
    [(attribute.contactId, attribute) for attribute in \
        Attribute.objects.filter(contactId__in=[contact.pk for contact in contacts])]
    )

替换为:

attributes_map = dict(Attribute.objects
    .filter(contactId__in=set([contact.pk for contact in contacts]))
    .values_list('contactId', 'attribute'))

这种替换通过以下方式节省了查询时间:

将键列表压缩成一个 set(),然后 仅检索 dict() 使用的两个字段。

它还利用 .values_list() 返回 dict() 预期的元组列表这一事实,但这仅在第一个字段(即 'contactId')是唯一的情况下才有效。

要将此解决方案扩展到多个外键,请使用上面的代码为每个键字段创建一个映射。然后,在这个循环内部:

for contact in contacts:
    contact.attributes = attributes_map.get(contact.pk)

为每个外键添加一行。例如,

attributes_map = dict(Attribute.objects
    .filter(contactId__in=set([contact.pk for contact in contacts]))
    .values_list('contactId', 'attribute'))
names_map = dict(Name.objects
    .filter(nameId__in=set([contact.nameId for contact in contacts]))
    .values_list('nameId', 'name'))

for contact in contacts:
    contact.attributes = attributes_map.get(contact.pk)
    contact.name = names_map.get(contact.nameId)

如果您曾经有一个模板为结果集的每一行生成大量额外的 SQL 调用(以便查找每个外键的值),这种方法将通过预加载所有数据来节省大量成本在将其发送到模板之前。

【讨论】:

以上是关于Django模型加入一对多关系以在模板中显示的主要内容,如果未能解决你的问题,请参考以下文章

Django数据模型--表关系(一对多)

Django数据模型--表关系(一对多)

Django 无法确定使用一对一关系链接一对多的查询集

如何在 Django 中表达一对多关系?

如何使用模型序列化器在 django-rest 中序列化一对多关系?

Django:是否可以通过模型的PK将模型的FK链接到一对多关系?