防止在Django模型中删除

dyv*_*yve 14 python django django-signals django-models

我有这样的设置(简化为这个问题):

class Employee(models.Model):
    name = models.CharField(name, unique=True)

class Project(models.Model):
    name = models.CharField(name, unique=True)
    employees = models.ManyToManyField(Employee)
Run Code Online (Sandbox Code Playgroud)

当员工即将被删除时,我想检查他是否已连接到任何项目.如果是这样,删除应该是不可能的.

我知道信号以及如何使用它们.我可以连接到pre_delete信号,并使它像一个例外ValidationError.这可以防止删除但是表单等不能正常处理.

这似乎是其他人会遇到的情况.我希望有人可以指出一个更优雅的解决方案.

Elw*_*win 21

我正在寻找这个问题的答案,无法找到一个好的,这对两个models.Model.delete()和QuerySet.delete()都有效.我一起去实施Steve K的解决方案.我使用此解决方案确保无法以任何方式从数据库中删除对象(此示例中的Employee),但将其设置为非活动状态.

这是一个迟到的答案..只是为了其他人看起来我在这里提出我的解决方案.

这是代码:

class CustomQuerySet(QuerySet):
    def delete(self):
        self.update(active=False)


class ActiveManager(models.Manager):
    def active(self):
        return self.model.objects.filter(active=True)

    def get_queryset(self):
        return CustomQuerySet(self.model, using=self._db)


class Employee(models.Model):
    name = models.CharField(name, unique=True)
    active = models.BooleanField(default=True, editable=False)

    objects = ActiveManager()

    def delete(self):
        self.active = False
        self.save()
Run Code Online (Sandbox Code Playgroud)

用法:

Employee.objects.active() # use it just like you would .all()
Run Code Online (Sandbox Code Playgroud)

或在管理员:

class Employee(admin.ModelAdmin):

    def queryset(self, request):
        return super(Employee, self).queryset(request).filter(active=True)
Run Code Online (Sandbox Code Playgroud)


Col*_*cks 14

对于那些在ForeignKey关系中遇到相同问题的人来说,正确的答案是on_delete=models.PROTECTForeignKey关系上使用 Djago 的字段。这将防止删除任何具有外键链接的对象。这不适用于ManyToManyField关系(如问题中所述),但适用于ForeignKey字段。

因此,如果模型是这样的,这将有助于防止删除任何Employee与一个或多个Project对象相关联的对象:

class Employee(models.Model):
    name = models.CharField(name, unique=True)

class Project(models.Model):
    name = models.CharField(name, unique=True)
    employees = models.ForeignKey(Employee, on_delete=models.PROTECT)
Run Code Online (Sandbox Code Playgroud)

文档可以在这里找到。


Yuj*_*ita 5

如果您知道永远不会有任何大规模员工删除尝试,您可以覆盖delete您的模型,只有super在合法操作时才会调用.

不幸的是,任何可能调用的东西都queryset.delete()将直接用于SQL:http: //docs.djangoproject.com/en/dev/topics/db/queries/#deleting-objects

但我不认为这是一个很大的问题,因为你是编写这个代码的人,并且可以确保从来没有任何queryset.delete()员工.delete()手动呼叫.

我希望删除员工是比较少见的.

def delete(self, *args, **kwargs):
    if not self.related_query.all():
        super(MyModel, self).delete(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

  • 您可以通过编写2个类来处理批量删除:一个继承models.Manager,另一个继承models.query.QuerySet第一个将覆盖get_query_set,返回第二个类的实例.QuerySet派生类将覆盖delete()方法.此delete方法将遍历类实例并在每个项上调用delete().希望这很清楚. (4认同)

小智 5

这将从我的应用程序中的实现中总结解决方案。LWN的答案就是一些代码

在四种情况下,您的数据将被删除:

  • SQL查询
  • 调用delete()Model实例:project.delete()
  • 调用delete()QuerySet实例:Project.objects.all().delete()
  • 在其他型号上由ForeignKey字段删除

尽管第一种情况没有什么可做的,但其他三种可以精细控制。一个建议是,在大多数情况下,您永远不要删除数据本身,因为这些数据反映了我们应用程序的历史和使用情况。active首选在布尔字段上设置。

为了防止出现delete()在Model实例上,请delete()在Model声明中使用子类:

    def delete(self):
        self.active = False
        self.save(update_fields=('active',))
Run Code Online (Sandbox Code Playgroud)

虽然delete()上查询集实例需要一点点的设置与自定义对象管理器在LWN的答案。

将其包装为可重用的实现:

class ActiveQuerySet(models.QuerySet):
    def delete(self):
        self.save(update_fields=('active',))


class ActiveManager(models.Manager):
    def active(self):
        return self.model.objects.filter(active=True)

    def get_queryset(self):
        return ActiveQuerySet(self.model, using=self._db)


class ActiveModel(models.Model):
    """ Use `active` state of model instead of delete it
    """
    active = models.BooleanField(default=True, editable=False)
    class Meta:
        abstract = True

    def delete(self):
        self.active = False
        self.save()

    objects = ActiveManager()
Run Code Online (Sandbox Code Playgroud)

用法,只是子ActiveModel类:

class Project(ActiveModel):
    ...
Run Code Online (Sandbox Code Playgroud)

如果我们的对象的任何ForeignKey字段被删除,仍然可以删除:

class Employee(models.Model):
    name = models.CharField(name, unique=True)

class Project(models.Model):
    name = models.CharField(name, unique=True)
    manager = purchaser = models.ForeignKey(
        Employee, related_name='project_as_manager')

>>> manager.delete() # this would cause `project` deleted as well
Run Code Online (Sandbox Code Playgroud)

这可以通过添加“模型”字段的on_delete参数来防止:

class Project(models.Model):
    name = models.CharField(name, unique=True)
    manager = purchaser = models.ForeignKey(
        Employee, related_name='project_as_manager',
        on_delete=models.PROTECT)
Run Code Online (Sandbox Code Playgroud)

的默认on_delete值为CASCADE,这将导致您的实例被删除,方法是使用PROTECT代替,它将引发ProtectedError(的子类IntegrityError)。这样做的另一个目的是,应保留数据的ForeignKey作为参考。