【问题标题】:Django cascade save?Django级联保存?
【发布时间】:2011-01-16 09:37:56
【问题描述】:

我的用户注册表上有一个方法,如下所示:

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

问题是当我调用form.save() 时,它会按预期创建user 对象,但不会保存他的个人资料或地址。为什么不级联保存所有子模型?我怀疑我可以手动调用user.profile.save()user.profile.address.save(),但我希望整个事情一起成功或失败。最好的方法是什么?


目前的解决方案:

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()

我必须将profile 设为“中心”对象。需要设置 profile.user = user 而不是 user.profile = profile 才能使其工作(我猜是因为密钥在配置文件模型上,而不是在用户模型上)。


较新的解决方案:

我从this article 中得到了提示,在this answer 中建议。

现在我已经分离了我的模型表单并将逻辑移到了视图中:

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)
    )

我不太喜欢将负担转移到视图上,但我想这样我会获得更多的灵活性?

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    问题是您正在尝试创建或更新用户对象中甚至还不存在的字段。所以其他字段并没有真正更新,因为它们没有与子字段的任何主键相关联。

    每次实例化新的模型字段时,都必须确保正在保存,以便子模型字段具有要关联的 id(主键)。

    你需要更多这样的东西:

    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
    

    你在这里进行的这种级联save() 的事情感觉不对。那里很容易出现太多错误,如果任何字段不能保存,您最终会得到一个部分完整的用户实例,并且如果用户必须返回并重试,则可能会导致重复。不好玩!

    编辑:删除了第二部分,因为它不准确。

    【讨论】:

    • 它看起来更好,因为您没有发布 UserFormSet 的内部,而是发布了 view 方法,它看起来或多或少与我现在拥有的相同。我将不得不更深入地研究表单集,看看它们是否是正确的选择。我一直认为它们是用于一次创建多个对象,而不是用于创建一个对象的多种形式。我的级联想法旨在通过将它们全部保存或一起失败来防止重复,因此只需调用一次save()
    • 是的,你说得对。我不知道我在想什么!我将从答案中删除后半部分。
    【解决方案2】:

    它不会级联保存,因为它实际上并不知道是否需要保存其他对象

    一气呵成,先start a transaction

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

    然后按顺序保存子对象:

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

    【讨论】:

    • 为什么无法判断是否需要保存?他们甚至还没有ids……这很容易检查。这么简单的事情真的有必要使用交易吗?我在掩盖其他错误的事务方面遇到了各种各样的问题。
    • 拥有一组 PK 不足以确定不应保存对象。 PK 可能是强制的,或者对象可能需要更新,这两者都需要调用save()
    • 哦...更大的问题是profile.user_id 不能为空。 profile.user_id 从未设置,即使 profileuser 的属性...
    • 这确实会造成问题。
    • 你不应该这样做。一个是场,另一个是反向关系。只有实际字段必须绑定到具有 PK 的模型。
    猜你喜欢
    • 2011-01-13
    • 2014-04-26
    • 1970-01-01
    • 2016-01-23
    • 2015-08-01
    • 2019-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多