同表Django ORM软删除方法好吗?
我使用以下设置在 Django 中实现软删除。 我对 Django 的底层不太熟悉,所以我很感激任何有关我可能遇到的问题的反馈。 我对对 QuerySet 进行子类化特别不舒服。
基本思想是,对 MyModel
的第一次调用 delete
会将 MyModel
的 date_deleted
更改为当前日期时间。 第二次删除实际上会删除该对象。 (捕获 delete
需要两次重写,一次重写在对象上,另一次重写在 QuerySet
上,这可以绕过对象的 delete
方法。)默认管理器将隐藏已删除的对象,已删除的对象会消失,并且必须通过 deleted_objects
管理器显式请求。
使用此设置需要定义 DeletionQuerySet
和 DeletionManager
并添加 date_deleted
、objects
和 deleted_objects
> 到您的型号。
谢谢,
PS,忘记提及这种从默认管理器中过滤对象的方法是 强烈反对!
class DeletionQuerySet(models.query.QuerySet):
def delete(self):
prev_deleted = self.filter(date_deleted__isnull=False)
prev_deleted.actual_delete()
prev_undeleted = self.filter(date_deleted__isnull=True)
prev_undeleted.update(date_deleted=datetime.datetime.now())
def actual_delete(self):
super(DeletionQuerySet, self).delete()
class DeletionManager(models.manager.Manager):
# setting use_for_related_fields to True for a default manager ensures
# that this manager will be used for chained lookups, a la double underscore,
# and therefore that deleted Entities won't popup unexpectedly.
use_for_related_fields = True
def __init__(self, hide_deleted=False, hide_undeleted=False):
super(DeletionManager, self).__init__()
self.hide_deleted = hide_deleted
self.hide_undeleted = hide_undeleted
def get_query_set(self):
qs = DeletionQuerySet(self.model)
if self.hide_deleted:
qs = qs.filter(date_deleted__isnull=True)
if self.hide_undeleted:
qs = qs.filter(date_deleted__isnull=False)
return qs
class MyModel(models.Model):
# Your fields here...
date_deleted = models.DateTimeField(null=True)
#the first manager defined in a Model will be the Model's default manager
objects = DeletionManager(hide_deleted=True)
deleted_objects = DeletionManager(hide_undeleted=True)
def delete(self):
if self.date_deleted is None:
self.date_deleted = datetime.datetime.now()
self.save()
else:
super(Agreement, self).delete()
I'm using the following setup to implement soft deletes in Django. I'm not very familiar with Django under the hood so I'd appreciate any feedback on gotchas I might encounter. I'm particular uncomfortable subclassing a QuerySet.
The basic idea is that the first call to delete
on a MyModel
changes MyModel
's date_deleted
to the current datetime. A second delete
will actually delete the object. (Catching a delete
requires two overrides, one on the object and one on the QuerySet
, which can bypass an object's delete
method.) Since the default manager will hide deleted objects, deleted objects disappear and must be explicitly requested via the deleted_objects
manager.
Using this setup requires defining DeletionQuerySet
and DeletionManager
and adding date_deleted
, objects
, and deleted_objects
to your model(s).
Thanks,
P.S., forgot to mention that this method of filtering objects out of the default manager is strongly discouraged!
class DeletionQuerySet(models.query.QuerySet):
def delete(self):
prev_deleted = self.filter(date_deleted__isnull=False)
prev_deleted.actual_delete()
prev_undeleted = self.filter(date_deleted__isnull=True)
prev_undeleted.update(date_deleted=datetime.datetime.now())
def actual_delete(self):
super(DeletionQuerySet, self).delete()
class DeletionManager(models.manager.Manager):
# setting use_for_related_fields to True for a default manager ensures
# that this manager will be used for chained lookups, a la double underscore,
# and therefore that deleted Entities won't popup unexpectedly.
use_for_related_fields = True
def __init__(self, hide_deleted=False, hide_undeleted=False):
super(DeletionManager, self).__init__()
self.hide_deleted = hide_deleted
self.hide_undeleted = hide_undeleted
def get_query_set(self):
qs = DeletionQuerySet(self.model)
if self.hide_deleted:
qs = qs.filter(date_deleted__isnull=True)
if self.hide_undeleted:
qs = qs.filter(date_deleted__isnull=False)
return qs
class MyModel(models.Model):
# Your fields here...
date_deleted = models.DateTimeField(null=True)
#the first manager defined in a Model will be the Model's default manager
objects = DeletionManager(hide_deleted=True)
deleted_objects = DeletionManager(hide_undeleted=True)
def delete(self):
if self.date_deleted is None:
self.date_deleted = datetime.datetime.now()
self.save()
else:
super(Agreement, self).delete()
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我认为任何当前使用的流行技术都无法实现与问题域无关的通用软删除。
我认为它更多地与历史/面向历史的数据库系统相关,而不是与我们所使用的数据库系统相关。
我建议你不要规避 django 的删除(这是硬删除)。 保持原样。
在我们的系统中,您最有可能遇到的“删除”在 90% 的情况下是可视化删除...
在这方面,请尝试为您的特定域问题找到与删除相同的同义词,并从一开始就执行此操作该项目。
因为抱怨 IsVisible、IsUnpublished (甚至 IsDeleted)搞乱了你的查询,他们抱怨你必须始终小心地包含它们......
但这显然是对域问题的无知,如果域中有可以使其不可见的对象,或变得未发布 - 当然,当您查询要显示的所有对象的列表时,您应该从一开始就查询所有不可见且未发布的对象,因为这就是以完整形式解决您的域问题的方式。
干杯。
I think anything with current in use, popular, technologies, there is no way to have problem domain agnostic, generic soft deletes.
I think it is more linked to historical/history oriented database systems than to what we are used.
I recommend you to not circumvent django's delete (which is a hard delete). Keep as is.
The "delete" that you most likely will have in our system, is in 90% of the case, a visual delete ...
In this regard, try to find synonyms with delete for your specific domain problem and do this from the start of the project.
Because complain that a IsVisible, IsUnpublished (even IsDeleted) mess up your queries, they complain that you must always be careful to include them...
But this is obviously ignorance of the domain problem, if the domain has objects that can be made invisible, or become unpublished - of course when you query the list of all the objects you want to display, you should FROM THE START, QUERY ALL THE OBJECTS that are not visible and unpublished because this is how your domain problem is solved in a complete form.
Cheers.