具有 OneToOne 关系的 Django 模型?

Posted

技术标签:

【中文标题】具有 OneToOne 关系的 Django 模型?【英文标题】:Django models with OneToOne relationships? 【发布时间】:2011-01-16 09:39:58 【问题描述】:

假设我使用默认的auth.models.User 加上我的自定义ProfileAddress 模型,如下所示:

class Profile(models.Model):
    user = models.OneToOneField(User)
    primary_phone = models.CharField(max_length=20)
    address = models.ForeignKey("Address")

class Address(models.Model):
    country = CountryField(default='CA')
    province = CAProvinceField(default='BC')
    city = models.CharField(max_length=80)
    postal_code = models.CharField(max_length=6)
    street1 = models.CharField(max_length=80)
    street2 = models.CharField(max_length=80, blank=True, null=True)
    street3 = models.CharField(max_length=80, blank=True, null=True)

现在我想创建一个注册表单。我可以基于User 创建一个ModelForm,但这不包括ProfileAddress 的字段(这是必需的)。那么构建此表单的最佳方法是什么?我到底应该使用ModelForm 吗?

此外,我将如何使用相同的表单来编辑复杂对象?我可以轻松地将 Profile 实例传回给它,它包含对必要 Address 和 Profile 对象的引用,但我如何让它为我填写字段?

【问题讨论】:

@Mark 在您的示例中,您在创建之前引用了Address。您需要将其括在引号中才能执行此操作,或者如果您不想使用引号,请将 Profile 移动到 Address 下方。 @orokusaki:我想我从两个不同的位置粘贴了这些,否则我会出错。不知道我可以只使用引号。谢谢。 【参考方案1】:

你应该先看看官方推荐的扩展 User 模型的方法,as seen in the docs,我相信它直接来自项目经理的个人博客about the subject。 (现在实际的博客文章已经相当老了)

至于您对表单的实际问题,请查看项目经理自己的可重用 django-profiles 应用程序,看看仔细阅读代码是否可以解决您的问题。特别是 these functions 和 the views 在其中使用它们。

编辑添加:

我已经对其进行了一些研究(因为我自己需要这样做)。看起来像这样就足够了:

# apps.profiles.models

from django.db import models
from django.contrib.auth.models import User

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    ...
    birth_date = models.DateField(blank=True, null=True)
    joined = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = 'user profile'
        verbose_name_plural = 'user profiles'
        db_table = 'user_profiles'

class Address(models.Model):
    user = models.ForeignKey(UserProfile)
    ...

# apps.profiles.forms

from django import forms
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from django.contrib.auth.models import User
from apps.profiles.models import UserProfile, Address

class UserForm(ModelForm):
    class Meta:
        model = User
        ...

class UserProfileForm(ModelForm):
    class Meta:
        model = UserProfile
        ...

AddressFormSet = inlineformset_factory(UserProfile, Address)

我在上面的代码中使用“...”来截取内容。我尚未对此进行测试,但通过查看示例和表单文档,我认为这是正确的。

请注意,我将 FK 从 Address 模型放到 UserProfile 中,而不是像您的问题那样反过来。我相信内联表单集需要它才能正常工作。

当然,在您的视图和模板中,您最终将分别处理 UserForm、UserProfileForm 和 AddressFormSet,但它们都可以插入到同一个表单中。

【讨论】:

我已经在使用官方推荐的扩展用户模型的方式。这个 django-profiles 应用程序似乎也没有用。它是在用户对象已经存在之后创建配置文件,而不是与 one 表单同时创建它们。 编辑: 好的,我没有逐字使用“推荐方式”。我使用 OneToOne 而不是 ForeignKey 因为这对我来说更有意义,但这是唯一的区别。 我相信推荐使用ForeignKeyunique=True 有几个原因,其中之一是它只会是一个查询,而 OneToOneField 会导致它是两个。我不能确定我读到的原因是否仍然适用于 1.1 或 1.2。 在地址上使用 FK 完全违背了地址对象的目的。其他东西也有地址。无论如何,您可以在inlineformset_factory 中交换顺序,但这仍然不能解决问题,因为我有多个 FK,而不仅仅是一个。而且我仍然不知道该表单集应该如何呈现。 哦,此外,听起来好像使用表单集,您可以将特定表单中的数据完全留空,这不是我想要的(这是必需的)。【参考方案2】:

我认为您正在寻找带有模型表格的inline formsets。这可以帮助您在一个页面上处理多个表单,还可以处理外键关系。

更新:

也许这个问题对你也有帮助:Django: multiple models in one template using forms

【讨论】:

这看起来可能有潜力,但是我如何使用 两个 或更多外键进行内联形式交换? 如果您说的是不同模型的两个或多个外键,那没关系,因为您只需要指定模型。如果是同一模型的两个或多个外键,则使用 fk_name 指定哪个字段。无论如何,我已经用一个例子更新了我自己的答案(昨天),你应该检查一下。 嗯? inlineformset_factory(Author, Book)Book 有一个 FK ao Author。但是我不知道该函数需要什么参数,我可以在那里指定更多的 FK 吗?就像inlineformset_factory(User, Address, Profile) 一样,Profile 对用户和地址都有 FK?然后我可以渲染这个表单集,它将显示所有表单,并将它们全部保存到配置文件中? “嗯?”是对的..我不知道你在说什么。您在模板中开始一个<form>,然后输入(例如)user_formprofile_form,以及您可能拥有的任何内联表单集,然后输入一个 `' 标签! 重点是将用户、地址和个人资料组合成一种形式。如果我要单独执行它们,那么我看不到这些“内联表单集”给我带来了什么优势。【参考方案3】:

如何使用 3 个单独的 ModelForm。一种用于Address,一种用于User,另一种用于Profile,但带有:

class ProfileForm(ModelForm):
  class Meta:
    model = Profile
    exclude = ('user', 'address',)

然后,在您的视图中分别处理这 3 个表单。具体来说,对于ProfileForm 使用savecommit=False 来更新实例上的useraddress 字段:

# ...
profile_form = ProfileForm(request.POST)
if profile_form.is_valid():
  profile = profile_form.save(commit=False)
  # `user` and `address` have been created previously
  # by saving the other forms
  profile.user = user
  profile.address = address

不要犹豫,在这里使用事务以确保仅当 3 个表单有效时才插入行。

【讨论】:

看起来这是要走的路。我对此并不满意,但这是唯一有效的方法。我会喜欢一些“DeepModelForm”,其中 ForeignKeys/OneToOnes 被转换为他们自己的形式,而不是带有现有对象的 <select> @Clément 新的正确方法(1.2 或 dev 1.2)在 clean() 中,而不是覆盖 save() @orokusaki:我不认为他建议我重写 save 方法,只是用commit=False 调用它。 @clement:我的做法是if a.is_valid() and b.is_vald() and c...,所以我希望在我开始保存它们之前永远不会出现它们不是都有效的情况。 好主意!我本来打算去定制 inlineformsets 的兔子洞,但这听起来像是一个更整洁的选择。

以上是关于具有 OneToOne 关系的 Django 模型?的主要内容,如果未能解决你的问题,请参考以下文章

无法在 graphene_django 中获取 OneToOne 关系查询的值

使用 Auth、用户模型、视图、序列化程序的 Django API OneToOne 关系

具有 OneToOne 关系的两个模型之间的错误(违反完整性约束)

Django OneToOne 字段到自我

检查 Django 中是不是存在 OneToOne 关系

Django Admin:如何在 oneToOne 关系中的两个模型中显示带有 list_display 的字段值?