防止删除

防止删除

问题描述:

我有这样的设置(简化了这个问题):防止删除

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

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

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

我了解信号以及如何工作。我可以连接到pre_delete信号,并使其产生异常,如ValidationError。这可以防止删除,但不能通过表格等来优雅地处理。

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

+1

这仅仅使用Python代码是不可行的;数据库本身也需要修改。 – 2011-01-28 07:32:41

+0

感谢您的评论。我首先查找Python/Django部分,然后看看在我的应用程序中有多远。 – dyve 2011-01-28 07:35:18

我有一个建议,但我不知道它是否比你目前的想法更好。看看回答here一个遥远的问题,但不是无关的问题,你可以重写在Django管理员的各种行动,基本上删除它们,并使用自己的。因此,举例来说,他们有:

def really_delete_selected(self, request, queryset): 
    deleted = 0 
    notdeleted = 0 
    for obj in queryset: 
     if obj.project_set.all().count() > 0: 
      # set status to fail 
      notdeleted = notdeleted + 1 
      pass 
     else: 
      obj.delete() 
      deleted = deleted + 1 
    # ... 

如果你不使用Django管理像我一样,后来干脆你允许用户删除对象之前构建支票存入你的UI逻辑。

+0

谢谢。我没有为此使用Django管理员,尽管包含Django管理员和自定义UI代码的解决方案将非常棒。如果它只是Django管理员,您的解决方案和参考将非常好。 +1。 – dyve 2011-01-28 09:40:08

如果您知道永远不会有员工大规模删除企图,您可以在您的模型上覆盖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) 
+0

谢谢。我知道这一点,如果pre_delete信号不能解决,它可能会是我的解决方案。 +1用于利用优点和缺点来描述这一点。 – dyve 2011-01-28 09:38:05

+0

+1,感受良好。房子上的+1! – 2011-01-29 04:04:47

我一直在寻找的答案,这个问题,是不是能找到一个好的,这两个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() 

用法:

Employee.objects.active() # use it just like you would .all() 

或管理员:

class Employee(admin.ModelAdmin): 

    def queryset(self, request): 
     return super(Employee, self).queryset(request).filter(active=True) 

这将包裹从我的应用程序的实施方案。一些代码的形式LWN's answer.

有你的数据被删除4种情况:

  • SQL查询
  • 对Model实例调用delete()project.delete()
  • 上查询集innstance调用delete()Project.objects.all().delete()
  • 外地字段删除其他型号

虽然第一种情况没有什么可以做,但其他三种可以进行细粒度控制。 一个建议是,在大多数情况下,您绝对不应该删除数据本身,因为这些数据反映了我们应用程序的历史和使用情况。设置为active而不是布尔字段。

为了防止Model实例,子类delete()delete()在模型声明:

def delete(self): 
     self.active = False 
     self.save(update_fields=('active',)) 

虽然delete()上查询集实例需要用自定义对象管理一个小设置为LWN's answer.

总结这部长达一个可重用的实现:

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

用法,只是su bclass ActiveModel类:

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 

这是可以预防通过增加模型字段on_delete argument

class Project(ActiveModel): 
    ... 

仍然是我们的目标仍然可以如果ForeignKey的领域中的任何一个被删除删除

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

on_delete的缺省值是CASCADE这将导致您的实例被删除,通过使用PROTECT取而代之的将会产生ProtectedErrorIntegrityError的子类)。这样做的另一个目的是数据的ForeignKey应该保留作为参考。

我想提出一个更多的变化上LWNanhdat's答案,其中我们使用deleted场,而不是一个active领域,我们排除从默认查询集“已删除”的对象,这样才能把这些对象为不再存在除非我们特别包含它们。

class SoftDeleteQuerySet(models.QuerySet): 
    def delete(self): 
     self.update(deleted=True) 


class SoftDeleteManager(models.Manager): 
    use_for_related_fields = True 

    def with_deleted(self): 
     return SoftDeleteQuerySet(self.model, using=self._db) 

    def deleted(self): 
     return self.with_deleted().filter(deleted=True) 

    def get_queryset(self): 
     return self.with_deleted().exclude(deleted=True) 


class SoftDeleteModel(models.Model): 
    """ 
    Sets `deleted` state of model instead of deleting it 
    """ 
    deleted = models.NullBooleanField(editable=False) # NullBooleanField for faster migrations with Postgres if changing existing models 
    class Meta: 
     abstract = True 

    def delete(self): 
     self.deleted = True 
     self.save() 

    objects = SoftDeleteManager() 


class Employee(SoftDeleteModel): 
    ... 

用法:

Employee.objects.all()   # will only return objects that haven't been 'deleted' 
Employee.objects.with_deleted() # gives you all, including deleted 
Employee.objects.deleted()  # gives you only deleted objects 

正如anhdat的回答说,一定要设置ForeignKeys的on_delete property你的模型,以避免级联行为,例如

class Employee(SoftDeleteModel): 
    latest_project = models.ForeignKey(Project, on_delete=models.PROTECT) 

注:

类似的功能包含在django-model-utilsSoftDeletableModel,因为我刚刚发现。值得检查。附带一些其他便利的东西。

对于那些提及ForeignKey关系时出现同样问题的问题,正确的答案是在ForeignKey关系中使用Djago的on_delete=models.PROTECT字段。这将防止删除任何具有外键链接的对象。这不适用于ManyToManyField关系(如this问题中所述),但对于ForeignKey字段非常有用。

因此,如果模型是这样的,这样的工作,以防止具有与其关联的一个或多个Project对象(S) 任何Employee对象的删除:

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) 

文档可以发现HERE