DRF에서 soft deletion 구현하기

이승연·2021년 11월 2일
0

DJango

목록 보기
6/11

개요

  • soft deletion 개념을 백오피스에 적용하고 복구 및 영구삭제를 자유롭게 할 수 있는 기능을 개발해야 한다.
  • 다음과 같이 요구사항을 받았다:

    삭제된 데이터만 따로 모으는 섹션이 있으면 좋을 것 같아요.
    말이 삭제지만 실제로는 섹션 이동만 하는거죠.
    그리고 그 섹션에서는 영구삭제 or 원 위치로 복구 두가지 옵션만 있도록 하고요.

  • 소요시간 4시간

조사 및 방향 설정

  • 사실 이미 프로젝트를 시작했을 때부터 모델에 soft deletion 기능을 다음과 같이 구현해두긴 했다.
class SoftDeleteManager(models.Manager):
    use_for_related_fields = True  # 옵션은 기본 매니저로 이 매니저를 정의한 모델이 있을 때 이 모델을 가리키는 모든 관계 참조에서 모델 매니저를 사용할 수 있도록 한다.

    def get_queryset(self):
        return super().get_queryset().filter(deleted_at__isnull=True)


class TimeStampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    deleted_at = models.DateTimeField(null=True, default=None)

    class Meta:
       abstract = True  # 상속 할수 있게
       ordering = ["created_at"]

    objects = SoftDeleteManager()  # 커스텀 매니저

    # def delete(self, using=None, keep_parents=False):
    #    self.deleted_at = now()
    #    self.save(update_fields=['deleted_at'])
    #
    # def restore(self):  # 삭제된 레코드를 복구한다.
    #    self.deleted_at = None
    #    self.save(update_fields=['deleted_at'])
  • timestamped 모델과 softdeletion manager 함수를 만들고 각 모델에 상속했다. 이것의 단점은 related model일 때 cascading deletion이 되지 않는다는 점이다. 처음 프로젝트 아웃라인을 받았을 때 삭제는 필수구현 사항이 아니라 잠시 잊고 있었는데 이제는 해야 한다...!
  • django에 soft deletion 관련 패키지가 무엇이 있는지 찾아보았다:
  • safedelete과 softdelete 패키지 둘 다 가지고 놀아보았는데 safedelete은 확실히 related field cascade delete이 되고 문서도 더 잘 정리되어 있어서 safedelete으로 결정했다.

django-safedelete

settings

  • installed_apps에 safedelete를 추가해준다.

models

from safedelete import DELETED_VISIBLE_BY_PK
from safedelete.managers import SafeDeleteManager
from safedelete.models import SafeDeleteModel, SOFT_DELETE_CASCADE

class CustomSafeDeleteManager(SafeDeleteManager):
    _safedelete_visibility = DELETED_VISIBLE_BY_PK

class TimeStampedModel(SafeDeleteModel):
    _safedelete_policy = SOFT_DELETE_CASCADE

    created_at = models.DateTimeField(auto_now_add=True)

    objects = CustomSafeDeleteManager()
    class Meta:
        abstract = True
        ordering = ["created_at"]
  • safedelete 패키지에는 safedeletemanager와 safedeletemodel이라는 빌트인이 존재하는데 나는 여러 커스터마이징이 필요해서 오버라이딩했다. 이게 필요없는 분들은 import해서 그대로 쓰면 된다.
  • 우리 프로젝트에서는 soft delete된 객체의 정보를 다시 볼 수 있어야 하기 때문에 DELETED_VISIBLE_BY_PK라는 visibility 옵션을 추가했다.
  • _safedelete_policy가 이 패키지의 정수인데 네가지 옵션 중 원하는 policy를 골라서 디폴트로 명시해주어도 되고 다른 policy가 임시로 필요한 경우 일회용으로 쓸 수도 있다. 이건 views에서 설명!
  • 커스터마이징한 클래스를 다른 모델에 그대로 상속해주고 makemigrations, migrate 했다.

views

  • soft deletion 자체는 delete api를 사용했을 때 문제없이 되었다.
  • 추가 구현 기능:
    • 삭제된 데이터 보기
    • 삭제된 데이터 복구
    • 삭제한 데이터 영구삭제

추가기능구현0: 베이스 클래스 만들기

  • 위 기능은 모든 viewset에 적용되어야 했기 때문에 custom action decorator가 있는 함수를 정의한 클래스를 만들고 다른 viewset에 상속함으로서 재사용성을 높이고 반복을 피하기로 했다.
  • 그런데 모든 viewset에서 사용되는 모델의 이름이 다르다. Dynamic variables를 사용해 모델을 가져와야 하는 것이다. 이 링크를 참고해 apps.get_model('staffs', self.basename)이라는 장고 빌트인 함수를 사용했다. 이 맛에 개발한다.
from safedelete.models import HARD_DELETE

class SoftDeleteViewSet(viewsets.ViewSet):

    @action(detail=False)
    def deletes(self, request):
        queryset_id_list = apps.get_model('staffs', self.basename).objects.deleted_only().values_list('id', flat=True)
        return Response(queryset_id_list)

    @action(detail=True, methods = ['patch', 'delete'])
    def dodelete(self, request, pk):
        queryset = apps.get_model('staffs', self.basename).objects.deleted_only().get(id=pk)
        if self.request.method == "PATCH":
            queryset.undelete()
            queryset.save()
        elif self.request.method == "DELETE":
            queryset.delete(force_policy=HARD_DELETE)
            queryset.save()
        return Response(f'{self.request.method} successful')

추가기능구현1: 삭제된 데이터 보기

  • soft delete 패키지에는 deleted_only attribute이 있는데 말 그대로 삭제처리가 된 객체를 모두 필터링해서 보여주고 참조할 수 있도록 해준다. 이를 활용해서 queryset을 오버라이딩했다.
  • 각 viewset의 serializer class를 참조하여 list와 detail action 모두 기존의 serializer를 사용할 수 있도록 하고 싶었으나 이미 '삭제'처리 된 객체는 아무리 queryset을 오버라이딩해서 준다고 해도 serializer 레이어에서 한번 더 쿼리가 들어가기 때문에 접근이 어려웠다.
  • 이런 경우, soft delete 패키지에는 visibility 옵션을 주는데 이때 특정 pk값으로는 해당 객체의 데이터를 조회할 수 있는 권한을 준다. 그래서 위에서 말했듯이 DELETED_VISIBLE_BY_PK라는 visibility 옵션을 추가한 것이다.
  • 따라서, deleted_only attribute을 통해서 현재 삭제처리가 된 데이터의 id리스트를 반환하고, 해당 id값을 통해 객체를 조회하면 정보가 보이게 된다.

추가기능구현2: 삭제된 데이터 복구, 삭제된 데이터 영구삭제

  • 이 부분은 너무 쉬웠다. 이미 softdelete에 undelete()delete(force_policy=none)이라는 빌트인 함수가 있었기 때문에 이를 활용했고, 영구삭제 같은 경우는 HARD_DELETE policy를 활용하여 구현했다.

마무리

  • 뭔가 빠르게 잘 마무리했다. drf를 사용하면서 상속의 개념을 알아두었고 action decorator 활용법을 익혀두었기 때문에 쉽게 되었던 것 같다.
  • 다만 action 사용시에 detail = True인 경우 pk를 인자로 넣는 것을 자꾸 깜빡하는데 이런 것으로 시간소요하기 싫으니까 다음엔 잘 기억하도록 하자.
  • 이렇게 글로 쓰니까 고군분투한 것 처럼 느껴지지가 않는데 그래도 나름 고생했다!!

0개의 댓글