Django:删除SET_DEFAULT行为时的外键

wn9m85ua  于 2023-02-14  发布在  Go
关注(0)|答案(2)|浏览(172)

请让我考虑以下两种模式:

class Policy(models.Model):
        name = models.SlugField(max_length=256, blank = False, unique = True)
def default_policy():
        return Policy.objects.get(name='default').pk
class Item(models.Model):
        policy = models.ForeignKey('Policy', on_delete=models.SET_DEFAULT, default=default_policy)

除了一个操作之外,一切都按预期工作。我仍然可以毫无例外地做以下事情:

p = Policy.objects.get(name='default')
p.delete()

这会产生引用“默认”Policy对象的孤立Item。不幸的是,此操作会导致数据库中的完整性问题,因为存在Item,其policy_id列现在引用Policy表中丢失的记录。
如何防止这种情况发生?当没有Item引用时删除“default”Policy对我来说是可以接受的。

8yparm6h

8yparm6h1#

这取决于您的业务需求。每个Item是否都指向一个有效的Policy?删除Policy时,从业务Angular 看有什么要求?指向它的Item是否也要删除?我们不知道您的需求,因此很难回答您的问题。从技术Angular 看,您可以选择:

  • 如果希望在删除Policy时删除Item,请设置on_delete=CASCADE
  • 如果你不想删除任何Policy,并且仍然有Item指向它,那么设置on_delete=PROTECT。在这种情况下,你必须在代码中使用try: policy.delete(); except ProtectedError: ...来处理这种情况。
  • 如果您知道默认策略不会被删除,则设置on_delete=SET_DEFAULT(您可以覆盖Policy上的delete方法以避免删除默认策略)。
  • 如果Item不能有Policy,则设置on_delete=SET_NULL。这在某些业务方案中可能有效。但在这种情况下,您还必须有null=True
jk9hmnmh

jk9hmnmh2#

我用Django 4.1sqlite重现了您的场景,并且得到了您等待的异常!
但是,为了提供更多的可定制性,特别是在处理异常时,下面的代码可以与上述环境一起工作:

from django.db import models
import django.db

def SET_DEFAULT_AND_PREVENT_DELETE_DEFAULT(collector, field, sub_objs, using):
    """
    ``sub_objs`` is the Queryset of Items that are affected by this delete.
    Hence, none of them should have referenced the default Policy (Otherwise,
    the default Policy is going to be deleted!).
    Also, if we are deleting an Item, there should be a default Policy to
    be set as the new Policy of Item.
    """
    try:
        default_policy = Policy.objects.get(name='default')
    except Policy.DoesNotExist:
        raise django.db.InternalError("You should have default policy before "
                                      "deleting a referenced policy")
    for item in sub_objs:
        if item.policy == default_policy:
            raise django.db.InternalError("You cannot delete default policy "
                                          "when there are items referencing it")

    collector.add_field_update(field, default_policy, sub_objs)

class Policy(models.Model):
    name = models.SlugField(max_length=256, blank=False, unique=True)

class Item(models.Model):
    policy = models.ForeignKey('Policy', on_delete=SET_DEFAULT_AND_PREVENT_DELETE_DEFAULT)

相关问题