简体   繁体   English

Django级联保存?

[英]Django cascade save?

I have a method on my user registration form that looks like this: 我的用户注册表单上有一个方法,如下所示:

def save(self):
    user = User(
        username = self.cleaned_data['username'],
        email = self.cleaned_data['email1'],
        first_name = self.cleaned_data['first_name'],
        last_name = self.cleaned_data['last_name'],
    )
    user.set_password(self.cleaned_data['password1'])
    user.profile = Profile(
        primary_phone = self.cleaned_data['phone'],
    )
    user.profile.address = Address(
        country = self.cleaned_data['country'],
        province = self.cleaned_data['province'],
        city = self.cleaned_data['city'],
        postal_code = self.cleaned_data['postal_code'],
        street1 = self.cleaned_data['street1'],
        street2 = self.cleaned_data['street2'],
        street3 = self.cleaned_data['street3'],
    )
    user.save()
    return user

Problem is when I call form.save() it creates the user object as expected, but doesn't save his profile or address. 问题是当我调用form.save()它按预期创建user对象,但不保存他的配置文件或地址。 Why doesn't it cascade and save all the sub-models? 为什么不级联并保存所有子模型? I suspect I could call user.profile.save() and user.profile.address.save() manually, but I want the whole thing to succeed or fail together. 我怀疑我可以手动调用user.profile.save()user.profile.address.save() ,但我希望整个事情能够成功或失败。 What's the best way to do this? 最好的方法是什么?


Current solution: 当前解决方案

def save(self):
    address = Address(
        country = self.cleaned_data['country'],
        province = self.cleaned_data['province'],
        city = self.cleaned_data['city'],
        postal_code = self.cleaned_data['postal_code'],
        street1 = self.cleaned_data['street1'],
        street2 = self.cleaned_data['street2'],
        street3 = self.cleaned_data['street3'],
    )
    address.save()

    user = User(
        username = self.cleaned_data['username'],
        email = self.cleaned_data['email1'],
        first_name = self.cleaned_data['first_name'],
        last_name = self.cleaned_data['last_name'],
    )
    user.set_password(self.cleaned_data['password1'])
    user.save()

    profile = Profile(
        primary_phone = self.cleaned_data['phone'],
    )
    profile.address = address
    profile.user = user
    profile.save()

I had to make profile the "central" object. 我不得不将profile作为“中心”对象。 Needed to set profile.user = user rather than user.profile = profile to make it work (I guess because the key is on the profile model, not on the user model). 需要设置profile.user = user而不是user.profile = profile才能使其工作(我想因为密钥在配置文件模型上,而不是在用户模型上)。


Newer solution: 更新的解决方案:

I took a hint from this article suggested in this answer . 我从这篇文章中提出了这个答案的暗示。

Now I have separated my model forms and moved the logic into the view: 现在我将模型表单分开并将逻辑移到视图中:

def register(request):
    if request.POST:
        account_type_form = forms.AccountTypeForm(request.POST)
        user_form = forms.UserForm(request.POST)
        profile_form = forms.ProfileForm(request.POST)
        address_form = forms.AddressForm(request.POST)

        if user_form.is_valid() and profile_form.is_valid() and address_form.is_valid():
            user = user_form.save()
            address = address_form.save()
            profile = profile_form.save(commit=False)
            profile.user = user
            profile.address = address
            profile.save()
            return HttpResponseRedirect('/thanks/')
    else:
        account_type_form = forms.AccountTypeForm()
        user_form = forms.UserForm()
        profile_form = forms.ProfileForm()
        address_form = forms.AddressForm()

    return render_to_response(
        'register.html',
        {'account_type_form': account_type_form, 'user_form': user_form, 'address_form': address_form, 'profile_form': profile_form},
        context_instance=RequestContext(request)
    )

I'm not too fond of shifting the burden to the view, but I guess I get a bit more flexibility this way? 我不太喜欢将负担转移到视图上,但我想我会通过这种方式获得更多灵活性?

It doesn't cascade-save because it doesn't actually know whether or not the other objects need to be saved. 它不会级联保存,因为它实际上并不知道是否需要保存其他对象。

To do it in one go, first start a transaction : 要一次性完成,首先要开始一个事务

@transaction.commit_on_success
def save(self):
  ....

Then save the subobjects in order: 然后按顺序保存子对象:

  user.profile.address.save()
  user.profile.save()
  user.save()

The problem is that you're trying to create or update fields in a User object that doesn't even exist yet. 问题是您正在尝试创建或更新尚未存在的User对象中的字段。 So the other fields aren't really updated because they aren't associated to any primary keys of the child fields. 因此,其他字段并未真正更新,因为它们与子字段的任何主键都没有关联。

Every time you're instantiating a new model field, you have to make sure you're saving so that a child model field has an id (primary key) to associate with. 每次实例化一个新的模型字段时,都必须确保保存,以便子模型字段具有要关联的id(主键)。

You need something more like this: 你需要更像这样的东西:

def save(self):
    user = User(
        username = self.cleaned_data['username'],
        email = self.cleaned_data['email1'],
        first_name = self.cleaned_data['first_name'],
        last_name = self.cleaned_data['last_name'],
    )
    ## save user so we get an id
    user.save()

    ## make sure we have a user.id
    if user.id:
        ## this doesn't save the password, just updates the working instance
        user.set_password(self.cleaned_data['password1'])
        user.profile = Profile(
            primary_phone = self.cleaned_data['phone'],
        )
        ## save the profile so we get an id
        user.profile.save()

    ## make sure we have a profile.id
    if user.profile.id:
        user.profile.address = Address(
            country = self.cleaned_data['country'],
            province = self.cleaned_data['province'],
            city = self.cleaned_data['city'],
            postal_code = self.cleaned_data['postal_code'],
            street1 = self.cleaned_data['street1'],
            street2 = self.cleaned_data['street2'],
            street3 = self.cleaned_data['street3'],
        )
        ## save the profile address
        user.profile.address.save()

    ## final save to commit password and profile changes
    user.save()
    return user

This cascading save() thing you have going on here just doesn't feel right. 你在这里级联的save()事情感觉不对。 You're prone to way too many errors there, if any of the fields doesn't save you'll end up with a partially complete user instance and posisbly end up with duplicates if the user has to go back and try again. 你可能会遇到太多的错误,如果任何字段没有保存,你最终会得到一个部分完整的用户实例,并且如果用户必须返回并再试一次,则最终会产生重复。 Not fun! 不好玩!

Edit: Removed the 2nd half of this because it was not accurate. 编辑:删除了下半部分,因为它不准确。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM