Параметры мягкого удаления Django | Кодементор
Понимание нюансов мягкого удаления в Django
Сохраняйте спокойствие. Источник:
В этом посте я попытаюсь изучить различные способы мягкого удаления в Django, используя библиотеку или напрямую из моделей.
Причина, по которой я пробую все эти варианты, заключается в том, чтобы убедиться, что последствия любой структуры или подхода хорошо известны, прежде чем делать выбор. И случайное удаление производственных данных не проблема.
Подходы:
Для всех подходов мы проверим, как обрабатываются следующие.
- ПОЛУЧИТЬ
- УДАЛИТЬ
- Набор запросов GET и DELETE
- связи
Модель паранойи:
Я нашел этот код в часовой
Идея здесь состоит в том, чтобы создать собственный менеджер моделей, который включает в себя собственный набор запросов.
Мы создаем ParanoiaModel
который будет служить базовой моделью.
class ParanoidModel(models.Model):
class Meta:
abstract = True
deleted_on = models.DateTimeField(null=True, blank=True)
def delete(self):
self.deleted_on=timezone.now()
self.save()
Любая модель, требующая безопасного удаления, может наследовать эту базовую модель. Это хорошо работает только тогда, когда необходимо удалить отдельный объект. Но потерпит неудачу, если для набора запросов выдается удаление.
Итак, мы добавляем пользовательский набор запросов ParanoidQuerySet
.
class ParanoidQuerySet(QuerySet):
""" Prevents objects from being hard-deleted. Instead, sets the
``date_deleted``, effectively soft-deleting the object. """
def delete(self):
for obj in self:
obj.deleted_on=timezone.now()
obj.save()
class ParanoidManager(models.Manager):
""" Only exposes objects that have NOT been soft-deleted. """
def get_queryset(self):
return ParanoidQuerySet(self.model, using=self._db).filter(
deleted_on__isnull=True)
class ParanoidModel(models.Model):
class Meta:
abstract = True
deleted_on = models.DateTimeField(null=True, blank=True)
objects = ParanoidManager()
original_objects = models.Manager()
def delete(self):
self.deleted_on=timezone.now()
self.save()
Мы также добавляем собственный менеджер. Это помогает нам двумя способами. Мы можем получить доступ к исходному менеджеру, который будет возвращать обратимо удаленные объекты, а во-вторых, запросы, которые возвращают набор запросов, будут фильтровать обратимо удаленные объекты без необходимости указывать их в каждом запросе.
Теперь оба следующих запроса работают
class Post(ParanoidModel):
title = models.CharField(max_length=100)
content = models.TextField()
post = Post(title="soft delete strategies", content="Trying out various soft delete strategies")
post.delete() # Will soft delete the post
Post.objects.all().delete() # Will also soft delete all the posts.
Post.objects.get() # Will not return any post and will raise an exception.
Post.original_objects.get() # Will return the soft deleted post.
Post.original_objects.all() # Returns soft deleted objects as well, along with
# the undeleted ones.
Эта стратегия очень хорошо работает для первых 3-х критериев. Но как это работает в отношениях?
Давайте добавим еще одну модель к приведенному выше примеру.
post = Post(
title="soft delete strategies",
content="Trying out various soft delete strategies"
)
class Comment(ParanoidModel):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
message = models.TextField()
comment = Comment(post, message="Well written blog post") # post is the object we
# created earlier.
post.delete() # Soft delete the post.
print(Comment.objects.count()) # The comment of the post still exists.
Из приведенного выше примера видно, что обратимое удаление не распространяется на отношения. Удаление поста не удаляет связанные с ним комментарии. К ним по-прежнему можно получить доступ независимо, но нельзя получить к ним доступ из сообщения, так как сообщение удалено.
Итак, резюмируя этот подход, все работает хорошо, кроме обработки отношений. Эта реализация достаточно хороша, если модели отношений не запрашиваются напрямую. Например, как только мы удаляем пост, комментарии, связанные с постом, становятся неактуальными. Комментарии ничего не значат без их родителя post
.
Еще одна вещь, которую следует отметить, это то, что если мы решим восстановить обратимо удаленный объект, нам не нужно беспокоиться о его связях, поскольку они не были удалены (ни мягко, ни жестко).
Если вы хотите восстановить вариант, вы можете добавить undelete
метод базовой модели ParanoiaModel
.
class ParanoidModel(models.Model):
class Meta:
abstract = True
deleted_on = models.DateTimeField(null=True, blank=True)
objects = ParanoidManager()
original_objects = models.Manager()
def delete(self):
self.deleted_on=timezone.now()
self.save()
def undelete(self):
self.deleted_on=None
self.save()
Вы также можете добавить это в пользовательский набор запросов.
class ParanoidQuerySet(QuerySet):
""" Prevents objects from being hard-deleted. Instead, sets the
``date_deleted``, effectively soft-deleting the object. """
def delete(self):
for obj in self:
obj.deleted_on=timezone.now()
obj.save()
def undelete(self):
for obj in self:
obj.deleted_on=None
obj.save()
Примечание: Я внес некоторые изменения в код, найденный в sentry, например, изменил имя поля. deleted_on
Джанго безопасно удалить
Этот фреймворк предоставляет множество возможностей для мягкого удаления. У них есть следующая политика
- HARD_DELETE
- SOFT_DELETE
- SOFT_DELETE_CASCADE
- HARD_DELETE_NOCASCADE
- NO_DELETE
Политики применяются к тому, как удаление обрабатывается и сохраняется в базе данных.
Они имеют следующие параметры видимости
- DELETED_INVISIBLE (по умолчанию)
- DELETED_VISIBLE_BY_FIELD
Параметры видимости применяются для получения данных.
Я остановлюсь только на политиках обратимого удаления, так как другие варианты не актуальны. Вы можете ознакомиться с документацией, если вам это интересно. документ
HARD_DELETE
Это похоже на поведение Django по умолчанию, но с некоторыми дополнительными параметрами. Я не собираюсь обсуждать их здесь. Вы можете проверить их документация.
SOFT_DELETE
Эта политика просто мягко удаляет удаляемый объект. Связанные объекты остаются нетронутыми.
Давайте начнем с создания некоторых моделей
from django.db import models
from safedelete.models import SafeDeleteModelfrom safedelete.models import SOFT_DELETE
class Article(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Comment(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name="article_comments")
text = models.TextField()
Попробуем удалить статью
# First we create an article
article = Article.objects.create( title="article 1 title", content="article 1 content")
article.delete() # Will soft delete the article.
Article.objects.all().delete() # Will soft delete all the articles.
Article.objects.all() # Will return objects which are not deleted (Either soft/hard)
Article.objects.all_with_deleted() # Will fetch all the objects including the deleted one's
Article.original_objects.all() # Will fetch all the objects including the deleted one's using our custom manager.
Мы можем восстановить мягко удаленный объект
article.undelete()
В этом подходе первые 3 критерия работают хорошо, но не для отношений. Таким образом, мягкое удаление объекта не удаляет его отношения.
Эта стратегия почти аналогична схеме паранойи, которую мы обсуждали выше.
SOFT_DELETE_CASCADE
Это почти похоже на описанное выше, за исключением того, что оно также мягко удаляет связанные объекты.
Начнем с создания некоторых моделей
from django.db import models
from safedelete.models import SafeDeleteModelfrom safedelete.models import SOFT_DELETE_CASCADE
class User(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE_CASCADE
full_name = models.CharField(max_length=100)
email = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class UserLogin(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE_CASCADE
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user_logins")
login_time = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Попробуем удалить пользователя
user = User.objects.create( full_name="sam kin", email="sam@gm.com")
UserLogin.objects.create( user=user)UserLogin.objects.create( user=user)
user.delete()
User.objects.count() # User count will be 0
UserLogin.objects.count() # UserLogin count will also be 0. (Since this is cascade soft delete)
# Both user and user login are soft deleted.
Мы видим, что мягкое удаление распространяется и на отношения.
Здесь восстановление объекта пользователя восстановит все его логины. Таким образом, все связанные объекты восстанавливаются.
user.undelete()
Этот подход соответствует всем нашим критериям.
NO_DELETE
Эта политика предотвращает любое мягкое/жесткое удаление. Единственный способ удалить — через необработанный sql-запрос. Это может быть полезно в местах, где любое удаление из приложения запрещено.
Резюме
Все подходы подпадают под 2 категории
- Поддерживает отношения
- Не поддерживает отношения
Если вы хотите, чтобы ваше мягкое удаление распространялось на отношения, используйте soft-delete-cascade. Если этого не требуется, то можно выбрать любой из вышеперечисленных подходов.
Вы можете найти примеры кода с тестами в моем репозитории Github.
Примечание: я опубликовал тот же пост в среде.