ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Django ORM Manager, QuerySet
    Django 2022. 9. 26. 23:06

    Django Manager? QuerySet?

    Django의 manager란 DB와 상호작용하는 인터페이스 즉 테이블 단위의 작업이다.
    장고의 Model에 objects라는 이름으로 Manager가 구현되어있다.
    기본적으로 사용하는 get(), filter(), create(), delete(), update(), exists(), aggregate(), annotate() 등등이 _QuerySet안에 구현되어있다.

    Custom manager, Custom QuerySet

    왜 사용하고 필요한지 간단한 예시를 들어보자

    from django.db import models
    
    class TeamManager(models.Manager):
    	pass
            
     
    class Team(models.Model):
        name = models.CharField(
            verbose_name=_("팀 이름"),
            help_text=_("팀의 이름입니다."),
            max_length=30,
            blank=True,
        )
    
        objects: TeamManager = TeamManager()
    
        class Meta:
            verbose_name = verbose_name_plural = _("팀")
    
        def __str__(self) -> str:
            return f"{self.name}팀"

    이런 모델이 있다고 생각해보자
    TeamManager에는 models.Manager를 상속받아 평소에 사용하는 Manager들을 사용할 수 있다.
    그리고 유저 모델은 이렇게 생겼다고 가정해보자

    from django.db import models
    
    
    class UserManager(models.Manager):
    	pass
    
    
    class User(models.Model):
        name = models.CharField(
            verbose_name=_("유저 이름"),
            help_text=_("유저의 이름입니다."),
            max_length=10,
        )
        team = models.ForeignKey(
            "team.team",
            verbose_name=_("팀"),
            help_text=_("소속한 팀 입니다."),
            related_name="users",
            on_delete=models.CASCADE,
    	)
        power = models.PositiveIntegerField(
        	verbose_name=_("전투력"),
            help_text=_("유저의 전투력입니다."),
            default = 100,
    	)
        is_active = models.BooleanField(
        	verbose_name=_("활동 상태"),
            help_text=_("유저의 활동 상태입니다."),
            default = False,
    	)
    
        objects: UserManager = UserManager()
    
        class Meta:
            verbose_name = verbose_name_plural = _("유저")
    
        def __str__(self) -> str:
            return f"{self.name}"

    유저 모델부터 살펴보자

    만약 현재 활동하고 있는 유저를 찾는다라면

    User.objects.filter(is_active=True)

    이런 식으로 찾을 수 있을 것이다.

    전투력이 100이 넘는 유저를 찾고 싶다면

    User.objects.filter(power__gt=100)

    이런 식으로 찾을 수 있을 것이다.

    이 둘을 모두 만족하는 유저들을 찾는다면

    User.objects.filter(power__gt=100, is_active=True)

    이런 식으로 찾을 수 있을 것이다

    현재까지 아무런 필요성을 못 느끼게 된다.
    하지만 활동 중이고 전투력이 100이 넘는 유저를 찾는 것이 플레이하고 있는 유저라는 기준으로 되어 이 쿼리 셋이 아주 자주 사용된다면?
    그 부분마다 위의 쿼리 셋이 중복이 되고 유지보수 시 모든 부분을 변경해주어야 하는 문제가 발생한다.
    그런 부분을 해결하기 위한 것이 custom manager이다.

    class UserManager(models.Manager):
        def is_playuser(self):
        	# 여기서의 self란 User.objects
        	return self.filter(is_active=True, power__gt=100)

    그렇다면 models.QuerySet의 역할은 무엇일까?
    위의 쿼리 셋이 is_active=True인 유저, 전투력이 100 넘는 유저도 따로따로 자주 사용된다고 가정해보자

    class UserManager(models.Manager):
        def is_active(self):
        	return self.filter(is_active=True)
    
        def exclude_default_power(self):
        	return self.filter(power__gt=100)

    이런 식으로 따로 두고 사용하게 될 것이고 둘 다 만족하는 경우를 구할 시

    User.objects.is_active().exculde_default_power()

    하나 이런 식으로 사용할 시 에러가 발생한다.
    이런 custom manager 끼리의 chain과 manager에서도 중복을 방지하기 위해 custom queryset을 사용한다.
    내부적으로 정확히는 self.filter 이런 식으로 구현이 되어있는 것이 아니라 self.get_queryset() 메서드를 이용하여 구현이 되어있다.
    get_queryset()이란 타깃으로 할 모델의 쿼리 셋 default는 모델의 모든 데이터이다.

    class UserQuerySet(models.QuerySet):
        def is_active(self):
        	return self.filter(is_active=True)
    
        def exclude_default_power(self):
        	return self.filter(power__gt=100)
    
    
    class UserManager(models.Manager):
        def get_queryset(self):
        	return UserQuerySet(self.model, using=self._db)
    
        def is_active(self):
        	return self.get_queryset().is_active()
    
        def exclude_default_power(self):
        	return self.get_queryset().exclude_default_power()

    이런 식으로 완성해 중복을 피하고 유지보수 또한 용이해진 manager와 queryset이 완성되었다.

    마지막으로 예시를 위해 각 팀에 속한 유저들의 전투력을 모두 합한 값을 구하는 manager, queryset을 구현해보자.

    from django.db import models
    from django.db.models import Sum
    
    class TeamQuerySet(models.QuerySet):
        def team_power(self):
            return self.aggregate(team_power=Sum("users__power"))["team_power"]
    
    
    class TeamManager(models.Manager):
        def get_queryset(self):
        	return TeamQuerySet(self.model, using=self._db)
        def team_power(self):
        	return self.get_queryset().team_power()
            
     
    class Team(models.Model):
        name = models.CharField(
            verbose_name=_("팀 이름"),
            help_text=_("팀의 이름입니다."),
            max_length=30,
            blank=True,
        )
    
        objects: TeamManager = TeamManager()
    
        class Meta:
            verbose_name = verbose_name_plural = _("팀")
    
        def __str__(self) -> str:
            return f"{self.name}팀"

    'Django' 카테고리의 다른 글

    Django Redis cache  (1) 2023.01.03
    Django timezone  (0) 2022.10.14
    Django UniqueConstraint  (0) 2022.10.07
    Django Signal  (1) 2022.09.29
    Django unittest pytest  (0) 2022.09.28

    댓글

Designed by Tistory.