Django模型中的一对一关系是什么?

7
假设我正在使用默认的 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”,但这不包括“Profile”和“Address”的字段(这些是必需的)。那么构建此表单的最佳方法是什么?我应该根本不使用“ModelForm”吗? 此外,如何使用相同的表单编辑复杂对象?我可以轻松地将“Profile”的实例传递回去,它持有对必要的“Address”和“Profile”对象的引用,但如何让它为我填写字段?

2
@Mark 在你的例子中,在创建Address之前你引用了它。如果你不想使用引号,那么你需要把它包裹起来,或者把Profile移到Address下面。 - orokusaki
@orokusaki:我想我是从两个不同的位置粘贴的,否则我就会得到一个错误。不过我不知道我可以只使用引号。谢谢。 - mpen
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
8
使用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>。 - mpen
1
@Clément,现在正确的做法(1.2或dev 1.2)是在clean()中进行,而不是覆盖save() - orokusaki
@orokusaki:我认为他并不是建议我覆盖保存方法,而是使用commit=False来调用它。@clement:我的做法是if a.is_valid() and b.is_vald() and c...,所以我希望在开始保存它们之前,它们永远都是有效的。 - mpen
太棒了!我本来打算深入研究自定义inlineformsets,但这听起来更加简洁。 - Michael Bylstra

3
你应该首先查看官方推荐的扩展用户模型的方法,可以在文档中找到,如此,我相信这些内容直接来自项目经理的个人博客关于这个主题的文章。(实际博客文章已经很老了) 至于你在表单方面的实际问题,请查看项目经理自己的可重用django-profiles应用程序,看看浏览代码是否解决了你的问题。具体地,查看这些函数使用它们的视图编辑添加: 我稍微调查了一下(因为我自己也需要这样做)。似乎像这样的东西就足够了:
# 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设置为UserProfile而不是反过来,就像你的问题中那样。我相信内联表单集需要这样才能正常工作。 当然,在您的视图和模板中,您将单独处理UserForm、UserProfileForm和AddressFormSet,但它们都可以插入到同一个表单中。

我已经在使用官方推荐的扩展用户模型的方式。这个 Django-profiles 应用程序似乎也没有什么用处。它是用于在用户对象已经存在之后创建配置文件,而不是使用 一个 表单同时创建它们。 - mpen
编辑:好的,我并没有直接使用“推荐的方法”。 我使用的是OneToOne而不是ForeignKey,因为这对我更有意义,但这是唯一的区别。 - mpen
我相信在编程中使用ForeignKey并设置unique=True是推荐的,原因有很多,其中之一是它只需要进行一次查询,而使用OneToOneField则需要两次查询。至于我所阅读到的原因是否适用于1.1或1.2版本,我不能确定。 - user136565
表单集仍然具有is_valid()方法,所以我不知道你在说什么。我并没有打算提供一个充满表单验证示例的完整示例。 - user136565
是的...他们有 is_valid(),但据我理解,如果所有表单字段都留空,它不会保存该模型,因为表单集是用于输入多个对象,而不是混合和匹配模型。 - mpen
显示剩余2条评论

1

这看起来似乎有潜力,但是我如何使用两个或更多的外键进行内联格式切换? - mpen
如果你谈论的是两个或更多外键指向不同的模型,那么这并不重要,因为你只需要指定模型即可。如果是两个或更多外键指向同一模型,你可以使用fk_name来指定哪个字段。无论如何,我已经在昨天更新了我的答案,并且你应该去看看。 - user136565
哎?inlineformset_factory(Author,Book)Book有一个到Author的外键。但是我不知道该函数需要什么参数,我能在那里指定更多的外键吗?就像inlineformset_factory(User, Address, Profile)那样,其中Profile具有对User和Address的外键?然后我可以呈现此表单集,并且它将显示所有表单,并将它们全部保存到Profile中? - mpen
“什么?”没错..我不知道你在说什么。你在模板中开始一个<form>,然后输入(例如)user_formprofile_form以及任何内联表单集,最后再输入一个</form>标签! - user136565
重点是将用户、地址和个人资料合并到一个表单中。如果我要分别处理它们,那么我就看不出这些“内联表单集”给我的优势了。 - mpen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,