본문 바로가기
소프트웨어 마에스트로/BackEnd(Django)

[백엔드] manager 넣어보기!

by alpakaka 2024. 8. 1.

Fat Models 를 잘 써보자!

일단 https://www.softkraft.co/django-best-practises/ 이 링크를 참고했을 때 느낀점은 다음과 같다.

-> 우선 manager 를 통해 view 에 있는 코드를 잘 관리하도록 바꿔보자!, view 에 너무 많은 로직이 들어갔다...

그래서 후보군 model methods, classmethods, properties, manager methods 인데..

 

view 가 아닌 manager 나 models 에 로직을 넣는 방식으로 가도록 코드를 개편해보겠다!!

일단 내가 작성한 코드는 전부 이렇게 views.py 에 모든 로직이 들어가있다.

바꿔보자!!

 

https://docs.djangoproject.com/en/5.0/topics/db/managers/

 

Managers | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

일단 개편을 위한 공식문서..

와 적당한 오픈 소스를 발견했다!

https://github.com/adlnet/ADL_LRS/blob/c441821b9d0f92e0ca263cf446b2223b67e84b19/lrs/models.py#L58

 

ADL_LRS/lrs/models.py at c441821b9d0f92e0ca263cf446b2223b67e84b19 · adlnet/ADL_LRS

ADL's Open Source Learning Record Store (LRS) is used to store learning data collected with the Experience API. - adlnet/ADL_LRS

github.com

일단 이 코드를 보고 대략 느낌을 파악했다!

 

일단 위의 오픈소스를 확인했을 때 모든 로직이 model에 들어가 있었다.

사실상 views.py 에서는 요청을 받으면 거의 Return 만 하는 정도였다.

 

일단 짜봤다. (with copilot)

이 코드를 보니 정말 내가 있는 모든 코드를 여기에 넣어서 함께 진행할 수 있다는 자신감이 생겼다! (중복 코드 삭제!!)

그런데 도입하려고 여러가지 자료를 보면서 생각해 봤는데 이런 문제가 있었다.

 

문제사항 

- subtodo, todo, category 에서 delete와 read의 로직은 완벽히 겹친다. (Todo의 get 제외 <- 조금 다르다)

- create, update는 파라미터만 제외하면 겹친다.

 

그래서 고민하고 있었던 점 

1. 투두, 서브투두, 카테고리를 각각 매니저를 만들어야하나?

2. read, delete 는 추상 카테고리로 만들고 각각의 매니저에 상속받도록 해서 create, update 를 구현해야하나?

 

그래서 룰루루팀장님께 물어본 결과는 이렇다!

일단 코드를 슥스슥 보시더니 validation 이 필요한 친구는 serializer 에 빼고, 쿼리문을 통해 가져오는 친구들은 manager 로 넣어보시는 건 어떠신가요?

 

그래서 일단 결정난대로 적용해볼 예정이다!

 

문제 상황 1

일단 우리 todo 가 start_date, end_date를 받는 상황인데

하나가 null 이면 아래의 쿼리에서 안받아지는 문제를 확인했다.

그래서 받아지도록 수정했다.

 

지금 보니 end_date 보다 작아야 된다고 되어있어서 제대로 불러와지지 않는 문제가 있었다.

나는 항상 date 없는 걸로 테스트 해서 그런가보다. 이젠 정확히 테스트 해야지

fat model 쓰기 전
fat model 사용 후

엄청나게 깔끔해진 것을 확인할 수 있다!!

 

일단 Model manager 상태

class TodosManager(models.Manager):
    def delete(self, instance):
        instance.deleted_at = timezone.now()
        instance.save()
        return instance
    def delete_many(self, instances):
        for instance in instances:
            instance.deleted_at = timezone.now()
            instance.save()
        return instances
    def get_queryset(self):
        return super().get_queryset().filter(deleted_at__isnull=True)
    def get_with_id(self, id):
        return self.get_queryset().get(id=id)
    def get_with_user_id(self, user_id):
        return self.get_queryset().filter(user_id=user_id)
    def get_with_date(self, user_id, start_date, end_date):
        return self.get_queryset().filter(user_id=user_id, start_date__gte=start_date, end_date__lte=end_date)
    def get_subtodo(self, todo_id):
        return self.get_queryset().filter(todo=todo_id)
    def get_inbox(self, user_id):
        return Todo.objects.filter(
            user_id=user_id,
            deleted_at__isnull=True
        ).annotate(
            children_count=Count('children', filter=Q(children__deleted_at__isnull=True, children__date__isnull=True))
        ).filter(
            Q(end_date__isnull=True, start_date__isnull=True) |  Q(children_count__gt=0)
        ).prefetch_related(
            Prefetch('children', queryset=SubTodo.objects.filter(deleted_at__isnull=True, date__isnull=True).order_by('order'))
        )
    def get_today_with_date(self, user_id, start_date, end_date):
        return Todo.objects.filter(
                user_id = user_id,
                deleted_at__isnull=True
            ).filter(
                Q(Q(end_date__gte=end_date) | Q(end_date__isnull = True)) 
                & Q(Q(start_date__lte=start_date) | Q(start_date__isnull = True)) 
                & Q(Q(end_date__isnull = False) | Q(start_date__isnull = False))
            ).filter(
                Q(end_date__isnull = False) | Q(start_date__isnull = False)
            ).order_by('order').prefetch_related(
                Prefetch('children', queryset=SubTodo.objects.filter(deleted_at__isnull=True, date__isnull=False).order_by('order'))
            )
    def get_today(self, user_id):
        return Todo.objects.filter(
                user_id = user_id, deleted_at__isnull=True
                ).filter(
                    Q(end_date__isnull = False) | Q(start_date__isnull = False)
                ).order_by('order').prefetch_related(
                Prefetch('children', queryset=SubTodo.objects.filter(deleted_at__isnull=True, date__isnull=False).order_by('order'))
            )

view가 진짜 간단해져서 일단 계속 해볼 예정이다!

get 이 manager 에서 많은 비중을 차지하니 더이상 별로 크게 안 늘어날 것 같다.

 

일단 update, post, delete 를 내일 적당히 계속 변경해봐야겠다. 생각보다 리팩토링 시간이 많이 걸리는 구나.. + 테스트 코드 작성

 

매니저와 시리얼라이저 차이가 갑자기 헷갈려서.. 내일 한번 지대루 찾아서 다시 정리해봐야 겠다.