【问题标题】:save() prohibited to prevent data loss due to unsaved related object禁止 save() 以防止由于未保存的相关对象导致数据丢失
【发布时间】:2016-02-23 15:13:36
【问题描述】:

我需要将新创建的ModelForm 中的主键传递到同一视图中的另一个表单字段,但出现错误。有什么建议可以完成这项工作吗? 看起来过去,这将是答案:

def contact_create(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse(contact_details, args=(form.pk,)))
    else:
        form = ContactForm()

从文档来看,这是在较新的 Django 版本> 1.8.3中发生的事情

p3 = Place(name='Demon Dogs', address='944 W. Fullerton') Restaurant.objects.create(place=p3, serving_hot_dogs=True, serving_pizza=False)
回溯(最近一次通话最后一次):
...
ValueError: save() 被禁止以防止由于未保存的相关对象'place'而导致数据丢失。

这就是我从视图中获取pk 的方式:

my_id = ""
if form.is_valid():
    # deal with form first to get id
    model_instance = form.save(commit=False)
    model_instance.pub_date= timezone.now()
    model_instance.user= current_user.id
    model_instance.save()
    my_id = model_instance.pk

if hourformset.is_valid():
    hourformset.save(commit=False)
    for product in hourformset:
        if product.is_valid():
            product.save(commit=False)
            product.company =  my_id
            product.save()
else:
    print(" modelform not saved")
return HttpResponseRedirect('/bizprofile/success')

【问题讨论】:

  • 有几件事:1)为什么将 model_instance.user 设置为 current_user.id 而不仅仅是 current_user,2)与 product.company = my_id 而不是 product.company = model_instance 相同,以及 3 ) 你得到的错误是什么(是你引用的 ValueError 吗?)以及代码的哪一行?
  • 错误总是在 product.save() 处。
  • save() 禁止以防止由于未保存的相关对象“公司”而导致数据丢失。我已经采纳了你的建议。
  • 我建议在保存产品时使用 pdb 或 print 语句来确定 my_id(或 model_instance)是什么。即使 form.is_valid() 为 False,该代码中也可能尝试保存产品。
  • 模型到底是在什么时候保存的?我实际上已连接到 psql 服务器,我可以用主键确认第一个表单数据存在。

标签: python django


【解决方案1】:

已回答 - 问题源于 django 未保存空或未更改的表单。这导致那些未保存的表单上出现空字段。问题是通过允许外键上的空字段来解决的,事实上——所有字段。这样,空的或未更改的表单在保存时不会返回任何错误。

仅供参考:请参阅@wolendranh 的答案。

【讨论】:

  • 只是为了让任何阅读本文的人知道。拥有 null 值是个坏主意,尤其是对于 ForeignKeys。只使用空值才有意义(通常从不)
  • @llazzaro,为什么会这样,你能提供一些解释或引用的声明。
  • @llazzaro,我忘记了该项目的来龙去脉,但我正在为用户自动生成与一周中的每一天相关的七个表单。然后,用户必须填写每天的营业时间和关闭时间。有些日子自然是空的或不变的。我认为我下面的答案详细解释了这一点。问题是如何处理这些空表单。
【解决方案2】:

这是在 Django 1.8 中引入的。以前,您可以将未保存的实例分配给一对一关系,如果失败,它会被静默跳过。从 Django 1.8 开始,在这种情况下您将收到错误消息。 检查 Django 1.7 -> 1.8 升级的documentation

上面写着:

将未保存的对象分配给 ForeignKey、GenericForeignKey 和 OneToOneField 现在引发 ValueError。

如果您对更多细节感兴趣,可以查看django.db.models.base 中的save 方法:部分内容:

for field in self._meta.concrete_fields:
    if field.is_relation:
        # If the related field isn't cached, then an instance hasn't
        # been assigned and there's no need to worry about this check.
        try:
            getattr(self, field.get_cache_name())
        except AttributeError:
            continue
        obj = getattr(self, field.name, None)
        # A pk may have been assigned manually to a model instance not
        # saved to the database (or auto-generated in a case like
        # UUIDField), but we allow the save to proceed and rely on the
        # database to raise an IntegrityError if applicable. If
        # constraints aren't supported by the database, there's the
        # unavoidable risk of data corruption.
        if obj and obj.pk is None:
            raise ValueError(
                "save() prohibited to prevent data loss due to "
                "unsaved related object '%s'." % field.name
            )

最后 5 行是引发此错误的位置。基本上您未保存的相关obj 将有obj.pk == NoneValueError 将被提出。

【讨论】:

  • 如果我知道该对象在数据库中怎么办?为了说服 Django,我是否必须多跑一趟?
  • 如果我想删除这个对象怎么办?如果我需要它为 None 怎么办?
  • 我在 django-fsm(有限状态机)转换中尝试修改字段并删除相关的 1-1 对象时遇到了这个问题。在提交 FSM 转换后,我使用transaction management 删除相关对象来解决它:transaction.on_commit(delete_related_object)
【解决方案3】:

很简单:

p3 = Place(name='Demon Dogs', address='944 W. Fullerton')   
p3.save() # <--- you need to save the instance first, and then assign
Restaurant.objects.create(
    place=p3, serves_hot_dogs=True, serves_pizza=False
) 

【讨论】:

  • 即使我正在保存模型表单实例,我也会收到相同的值错误。查看我的代码并告诉我为什么会得到这个:save() 禁止以防止由于未保存的相关对象“公司”而导致数据丢失。
  • 如果您只想在模型A的对象创建时从模型A中的模型B创建一个新对象,如果您覆盖模型A上的save()方法,这并不是那么简单。例如,@ 987654323@这个不能用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多