我有这样的设置(针对此问题进行了简化):
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
一样抛出异常。这样可以防止删除,但表单等不能正常处理。
这似乎是其他人会遇到的情况。我希望有人可以指出一个更优雅的解决方案。
答案 0 :(得分:20)
我一直在寻找这个问题的答案,无法找到一个好的,这对两个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)
答案 1 :(得分: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)
答案 2 :(得分:5)
这将从我的应用程序中的实现中结束解决方案。有些代码形式为LWN's answer.
有4种情况会删除您的数据:
delete()
:project.delete()
delete()
:Project.objects.all().delete()
虽然第一种情况没有什么可以做的,但其他三种情况可以进行细粒度控制。
一种建议是,在大多数情况下,您永远不应删除数据本身,因为这些数据反映了我们的应用程序的历史和用法。最好在active
布尔字段上进行设置。
要防止模型实例上的delete()
,请在模型声明中创建子类delete()
:
def delete(self):
self.active = False
self.save(update_fields=('active',))
虽然QuerySet实例上的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()
用法,只是子类ActiveModel
class:
class Project(ActiveModel):
...
如果任何一个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
可以通过添加on_delete argument模型字段来防止这种情况:
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
而不是会引发ProtectedError
(IntegrityError
的子类) 。另一个目的是保留数据的ForeignKey作为参考。
答案 3 :(得分:3)
我想就LWN和anhdat'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的回答所述,请务必在模型上设置外键的on_delete
property以避免级联行为,例如
class Employee(SoftDeleteModel):
latest_project = models.ForeignKey(Project, on_delete=models.PROTECT)
注意:
正如我刚刚发现的那样,django-model-utils
的SoftDeletableModel
中包含了类似的功能。值得一试。附带一些其他方便的东西。
答案 4 :(得分:2)
我有一个建议,但我不确定它比你现在的想法更好。看看答案here是否有一个遥远但不相关的问题,你可以在django admin中覆盖各种操作,基本上删除它们并使用你自己的操作。所以,例如,他们在哪里:
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 admin,那么只需在允许用户删除对象之前将该检查构建到您的UI逻辑中。
答案 5 :(得分:1)
对于那些使用ForeignKey
关系的相同问题来引用此问题的人,正确的答案是在on_delete=models.PROTECT
关系中使用Djago的ForeignKey
字段。这将防止删除任何具有外键链接的对象。这不适用于ManyToManyField
关系(如this问题中所述),但适用于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)
可以找到文档HERE。