Параметры мягкого удаления Django | Кодементор

Понимание нюансов мягкого удаления в Django


Сохраняйте спокойствие. Источник:

В этом посте я попытаюсь изучить различные способы мягкого удаления в Django, используя библиотеку или напрямую из моделей.

Причина, по которой я пробую все эти варианты, заключается в том, чтобы убедиться, что последствия любой структуры или подхода хорошо известны, прежде чем делать выбор. И случайное удаление производственных данных не проблема.

Подходы:

  1. Паранойя модель
  2. Джанго безопасно удалить

Для всех подходов мы проверим, как обрабатываются следующие.

  1. ПОЛУЧИТЬ
  2. УДАЛИТЬ
  3. Набор запросов GET и DELETE
  4. связи

Модель паранойи:

Я нашел этот код в часовой

Идея здесь состоит в том, чтобы создать собственный менеджер моделей, который включает в себя собственный набор запросов.

Мы создаем 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

Джанго безопасно удалить

Этот фреймворк предоставляет множество возможностей для мягкого удаления. У них есть следующая политика

  1. HARD_DELETE
  2. SOFT_DELETE
  3. SOFT_DELETE_CASCADE
  4. HARD_DELETE_NOCASCADE
  5. NO_DELETE

Политики применяются к тому, как удаление обрабатывается и сохраняется в базе данных.

Они имеют следующие параметры видимости

  1. DELETED_INVISIBLE (по умолчанию)
  2. 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 категории

  1. Поддерживает отношения
  2. Не поддерживает отношения

Если вы хотите, чтобы ваше мягкое удаление распространялось на отношения, используйте soft-delete-cascade. Если этого не требуется, то можно выбрать любой из вышеперечисленных подходов.

Вы можете найти примеры кода с тестами в моем репозитории Github.

Примечание: я опубликовал тот же пост в среде.


Следуй за мной на Твиттер & Гитхаб

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *