Django 中的软删除
AWS AI 直播!
什么是软删除?
有时,您可能想从数据库中删除某些内容,但实际上并不想删除它们。您希望保留这些数据,以便将来可能需要恢复或用于其他用途,例如数据分析……但这些数据应该看起来像是已被删除,而不是出现在应用程序中意想不到的地方。这被称为“软删除”。
与其从数据库中真正删除记录(这是不可逆的),不如将其“标记”为已删除,通常是在表的另一列中进行标记。现在的挑战是如何防止软删除的记录“泄露”到应用程序中。
在本文中,我们将以一个简单的笔记应用程序后端为例,学习如何在 Django 中实现软删除。
软删除模式
我们将首先创建一个SoftDeleteModel可供其他模型继承的基础模型。
class SoftDeleteModel(models.Model):
is_deleted = models.BooleanField(default=False)
def soft_delete(self):
self.is_deleted = True
self.save()
def restore(self):
self.is_deleted = False
self.save()
class Meta:
abstract = True
请注意,我们已将此模型标记为“未定义” abstract = True。这意味着 Django 不会为其创建数据库表。
现在,我们可以将模型创建为子类,SoftDeleteModel从而赋予它们软删除的能力。让我们以一个模型为例Note。
class Note(SoftDeleteModel):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notes")
title = models.CharField(max_length=32)
content = models.CharField(max_length=255)
我们可以通过筛选查询数据库,is_deleted以排除已被软删除的记录。
Note.objects.filter(is_deleted=False)
试一试
让我们来尝试运行一下目前为止编写的代码。首先,python manage.py shell在终端中输入命令打开 Django shell。
导入所需模型:
from django.contrib.auth.models import User
from tutorialapp.models import Note
由于每条笔记都与某个用户存在外键关联,因此我们的第一步是创建一个User对象:
john = User.objects.create_user('john', 'lennon@thebeatles.com', 'johnpassword')
现在我们可以创建几个笔记:
my_note = Note.objects.create(user=john, title="Strawberry Fields", content="Strawberry Fields Forever")
another_note = Note.objects.create(user=john, title="Here Comes The Sun", content="It's All Right")
您现在可以对笔记进行软删除和恢复操作了:
my_note.soft_delete()
my_note.restore()
您可以查询所有笔记,无论它们是否已被软删除:
Note.objects.all()
您还可以筛选出未被软删除的笔记:
Note.objects.filter(is_deleted=False)
软删除管理器
is_deleted=False虽然我们的代码在功能上是正确的,但缺点是每次编写查询时都必须记住进行筛选。
我们可以通过创建一个自定义模型管理器来改进这种行为,以便在后台自动应用过滤器。如果您以前使用过 Django,您可能熟悉类似这样的语句:MyModel.objects.all()。.objects语句中的部分就是管理器。管理器充当 Django 代码和数据库之间的“桥梁”。它们控制对其“管理”的表执行的数据库操作。
我们新的自定义管理器可以定义为:
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
我们需要将新的管理器添加到我们的SoftDeleteModel基础类中:
class SoftDeleteModel(models.Model):
is_deleted = models.BooleanField(default=False)
objects = models.Manager()
undeleted_objects = SoftDeleteManager()
def soft_delete(self):
self.is_deleted = True
self.save()
def restore(self):
self.is_deleted = False
self.save()
class Meta:
abstract = True
objects请注意,由于我们已向类中添加了自定义管理器,因此我们也需要显式地添加默认管理器。
然后,我们可以简单地将查询重写为:
Note.undeleted_objects.all()
获取QuerySet未删除的笔记列表。
我们仍然可以使用
Note.objects.all()
获取完整的笔记列表,包括已被软删除的笔记。
处理外键关系
那么,如果有多个用户,而你想获取某个特定用户的所有笔记,该怎么办呢?最简单的办法就是编写一个查询语句,根据用户进行筛选:
Note.objects.filter(user=john, is_deleted=False)
然而,更优雅、更易读的解决方案是利用Django 为此提供的反向关系。
john.notes.all()
尝试软删除一些笔记,然后运行此查询。您是否注意到结果有任何异常?
我们发现结果中QuerySet包含了我们已软删除的记录。这是因为 Django 使用的是默认的objects记录管理器来执行反向查找,而您可能还记得,该管理器不会过滤掉已软删除的记录。
如何强制 Django 使用我们自定义的SoftDeleteManager反向查找表?我们可以简单地替换默认的objects查找表管理器SoftDeleteModel:
class SoftDeleteModel(models.Model):
is_deleted = models.BooleanField(default=False)
objects = SoftDeleteManager()
all_objects = models.Manager()
def soft_delete(self):
self.is_deleted = True
self.save()
def restore(self):
self.is_deleted = False
self.save()
class Meta:
abstract = True
现在,objects管理器会在查询数据库时自动过滤掉软删除的对象,确保它们在任何情况下都不会泄露到我们的应用程序中!如果需要,我们仍然可以利用all_objects管理器将软删除的对象包含在查询中。
Note.all_objects.all()
存储更多信息
我们的 Django 应用中已经有一个相当完善的软删除框架,但还可以进行最后的改进。知道一条记录是否被软删除固然有用,但如果能知道记录被软删除的时间deleted_at就更好了。为此,我们可以向我们的框架添加一个新的属性SoftDeleteModel:
deleted_at = models.DateTimeField(null=True, default=None)
我们还可以按如下方式更新我们的soft_delete方法restore:
def soft_delete(self):
self.deleted_at = timezone.now()
self.save()
def restore(self):
self.deleted_at = None
self.save()
对于未删除的记录,该值为deleted_at空;而对于软删除的记录,该值为删除的日期和时间。
由于新增的deleted_at属性使得我们之前创建的is_deleted属性变得多余,因为我们可以简单地对其进行空值检查,deleted_at以确定记录是否已被软删除。
我们重写SoftDeleteModel后的代码如下:
class SoftDeleteModel(models.Model):
deleted_at = models.DateTimeField(null=True, default=None)
objects = SoftDeleteManager()
all_objects = models.Manager()
def soft_delete(self):
self.deleted_at = timezone.now()
self.save()
def restore(self):
self.deleted_at = None
self.save()
class Meta:
abstract = True
我们重写后的SoftDeleteManager内容如下:
class SoftDeleteManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(deleted_at__isnull=True)
除了之前的功能外,我们现在还可以看到记录被软删除的确切日期和时间:
my_note.deleted_at
综上所述
软删除是一种强大的软件模式,具有诸多优点,包括更好的数据保存和恢复、历史记录跟踪以及从故障中更快恢复。
同时,也应谨慎使用。敏感和个人数据,包括支付相关信息,应始终彻底删除。用户应始终有权选择永久删除其数据。世界各地多个司法管辖区都制定了包含“被遗忘权”的信息隐私和数据保护法律,例如欧盟的《通用数据保护条例》(GDPR)。定期删除或存档非常陈旧的数据也可能是明智之举,以避免占用过多的数据库存储空间。
如果您想查看本教程中使用的示例的完整源代码,可以在 GitHub 上找到。
参考文献及延伸阅读
如何在 Django 中使用管理器?
什么是软删除,以及如何实现软删除?
Django 中的软删除