Узнайте, как в моделях Django можно реализовать soft delete без изменения интерфейсов и контрактов моделей и наборов запросов.
В современной архитектуре или в соответствии с требованиями дизайна у нас часто возникает необходимость сохранять определенные записи даже после того, как они были “удалены” пользователем — вот где на помощь приходит soft delete.
В этой статье мы узнаем, как реализовать soft delete, в то время как наше приложение сохраняет свой Django-ness (за неимением лучшего слова), и мы все еще можем поддерживать тот же интерфейс с объектами нашей модели таким же образом, как если бы у нас было жесткое удаление.
Что такое Soft Delete
Мягкое удаление - это процесс удаления записей таким образом, что они все еще присутствуют в базе данных, хотя и становятся недоступными для пользователя. Это включает в себя использование флага для пометки записи как недоступной, а не удаление записи из базы данных.
Преимущества Soft Delete
- Записи легче восстанавливать.
- Операции мягкого удаления выполняются быстрее, потому что мы запускаем команду ОБНОВЛЕНИЯ в базе данных. Для жесткого удаления мы выполним команду УДАЛЕНИЯ, которая обычно выполняется медленнее, особенно когда в таблицах есть связанные записи.
- Он сохраняет связанные записи на месте.
- Это более безопасный вариант в тех случаях, когда пользователям может потребоваться восстановить удаленную информацию.
Недостатки Soft Delete
- Добавлена сложность фильтрации записей, которые были помечены как удаленные.
- Уникальные ограничения необходимо переработать, чтобы включить столбец, представляющий флаг удаления, чтобы записи, которые были мягко удалены, могли быть воссозданы.
- В некоторых случаях для записей могут потребоваться некоторые уникальные реализации при программном удалении. Например, при программном удалении информации о карте или способах оплаты нам может потребоваться очистить некоторые важные поля, такие как PAN или CVV.
Некоторые из этих запросов на выборку начнут выглядеть как
SELECT * FROM users WHERE is_deleted=false;
Уникальные ограничения также начали бы выглядеть подобным образом при внедрении мягкого удаления:
CREATE UNIQUE INDEX "users_unique" ON users(is_deleted, email) WHERE is_deleted=false;
Внедрение программного удаления в Django
При внедрении soft delete в Django первое, что требуется, - это базовая модель. Это уменьшило бы сложность и помогло устранить вышеупомянутые недостатки, поскольку нам не нужно было бы дублировать логику soft delete во всех наших моделях. Все, что нам нужно было бы сделать, это расширить эту базовую модель.
class BaseModel(models.Model):
class Meta:
abstract = True
# common fields go here
Первое, что мы хотим добавить в нашу базовую модель, - это флаг удаления. Поле is_deleted сообщит нам о доступности записи.
class BaseModel(models.Model):
# ...omitted for brevity...
is_deleted = models.BooleanField(default=False)
Далее мы переопределяем метод delete модели. Итак, мы можем изменить поведение по умолчанию при удалении нашей модели с жесткого на мягкое удаление.
class BaseModel(models.Model):
# ...omitted for brevity...
def delete(self):
"""Mark the record as deleted instead of deleting it"""
self.is_deleted = True
self.save()
Имея этот фрагмент кода на месте, мы могли бы мягко удалять записи, как обычно мы удаляем записи жестко, и поддерживать кодовую базу Django-ic.
class User(BaseModel):
class Meta:
unique_together = ['email', 'is_deleted']
name = models.CharField(max_length=24)
email = models.EmailField()
new_user = User(name="Bolaji", email="user.test@example.com")
new_user.save()
new_user.delete()
print(new_user.is_deleted)
# True
Теперь, когда мы можем легко выполнять мягкое удаление, мы должны убедиться, что результаты нашего запроса на самом деле никогда не содержат никакой удаленной записи. Мы делаем это, создавая пользовательский менеджер, который отфильтровывает удаленное.
from django.db.models import Manager, QuerySet
class AppManager(Manager):
def get_queryset(self):
return QuerySet(self.model, using=self._db).exclude(is_deleted=True)
class BaseModel(model.Model):
# ...omitted for brevity...
objects = AppManager()
Имея это на месте, мы можем заменить поле объектов по умолчанию нашей модели нашим AppManager. Это гарантировало бы, что при запросе с использованием знакомого синтаксиса мы получаем ожидаемые результаты
class User(BaseModel):
# ...omitted for brevity...
user.objects.filter(...)
user.objects.all()
# excludes records flagged as deleted.
Вау!
Можно вызвать delete в наборе запросов вместо того, чтобы вызывать его в объекте.
from django.db.models import Manager, QuerySet
class AppQuerySet(QuerySet):
def delete(self):
self.update(is_deleted=True)
class AppManager(Manager):
def get_queryset(self):
return AppQuerySet(self.model, using=self._db).exclude(is_deleted=True)
Подводя итог, мы имеем:
rom django.db.models import Manager, QuerySet
class AppQuerySet(QuerySet):
def delete(self):
self.update(is_deleted=True)
class AppManager(Manager):
def get_queryset(self):
return AppQuerySet(self.model, using=self._db).exclude(is_deleted=True)
class BaseModel(models.Model):
class Meta:
abstract = True
is_deleted = models.BooleanField(default=False)
def delete(self):
self.status = DELETED
self.save()
# Sample model
class Post(BaseModel):
content = models.CharField(max_length=140)
Наконец, вы внедрили soft delete, и вам не пришлось менять свой контракт. Спасибо за чтение, подписчик!
Поставьте лайк, поделитесь этой статьей и подпишитесь, если вы нашли эту статью полезной. Спасибо!