ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [개념 정리] Custom Manager vs Custom Queryset
    django 2022. 2. 19. 18:17

    # Manager란?

     : Manager는 장고 모델에 데이터베이스 쿼리 작업을 제공해주는 인터페이스다. Manager는 파이썬을 이용해서, sql을 쓰지 않고 DB로부터 데이터를 가져올 수 있게 해준다. 기본적으로 장고는 모든 클래스에 objects라는 이름의 Manager를 추가한다. 장고 모델마다 기본적으로 사용되는 기본 관리자는 django.db.models.Manager이다.

    class User(models.Model):
    	...
        
    >> User.objects.all()
    
    
    class CustomUser(models.Model):
    
    	...
        
        people = models.Manager()
        
        
    >> CustomUser.people.all() # O
    >> CustomUser.objects.all() # X (Attribute Error)

     

    # Manager를 왜 Custom 할까?

      1. 기존 models.Manager가 제공하는 기능 외에 추가 기능이 필요할 때

         : 예를 들어 active=True인 User의 수를 구하고 싶다고 하자

    User.objects.filter(active=True).count()

        보통은 위 코드로 active=True인 유저의 수를 구하지만, 만약 위 상황이 반복되어 해당 코드를 자주 써야 하는 경우엔

        Manager 메소드로 지정할 수 있다.

     

    class CustomUserManager(models.Manager):
    
        def active_counts(self):
        
        	return self.get_queryset().filter(active=True).count()
            
      
    >> User.objects.active_counts()

     

      2. 리턴되는 기본 Queryset을 바꾸고 싶을 때

        : Manager의 기본 QuerySet은 해당 Model의 모든 데이터를 리턴하도록 작성되어있다. Manager의 get_queryset 메서드를 override 하면 기본 QuerySet을 수정할 수 있다.

    class ActiveManager(models.Manager):
    	def get_queryset(self):
    		return super().get_queryset().filter(active=True)
            
    class User(models.Model):
    	...
        
        objects = models.Manager()	# default manager
        active_objects = ActiveManager()
        
        
    >> User.objects.all()  # 유저 전체
    >> User.active_objects.all()  # active=True인 유저 전체


    주의할 점은 위 Extra Manager 메소드와는 다르게 get_queryset 메서드는 반드시 QuerySet을 리턴해야 한다.


    # Default Manager?

     : 다만 위 경우와 같이 복수의 Manager를 사용하는 경우 default manager는 무엇이 될까? 이를 고려해야 되는 이유는 Django의 dumpdata 같은 기능에서 혹은 Django Third Party 애플리케이션의 경우 Model의 Manager 재정의 여부를 알 수 없다. 위와 같이 Manager name을 알 수 없는 경우 Model._default_manager를 사용하게 된다. 만약 모든 데이터를 백업하는 용도로 사용되는 dumpdata가 active_objects를 default manager로 정의해 백업을 진행하면, 유저 전체가 아닌 active=True인 유저만 백업되므로 큰 혼란이 발생한다.

     

    보통 첫 번째 정의되는 Manager가 Model의 default manager가 되기 때문에 위 코드에서는 objects를 default로 여겨 문제가 발생하진 않겠지만, 안전하게 Model의 Meta.default_manager_name을 사용하여 manager name을 직접 정의하자.

     

    class User(models.Model):
    	...
        active_objects = ActiveManager()
        objects = models.Manager()
        
        class Meta:
        	default_manager_name = 'objects'

    그 이유는 default manager가 결정되는 우선순위는 다음과 같기 때문이다.

     

      1. Meta.default_manager_name

      2. 부모의 Meta.default_manager_name

      3. 해당 모델에 manager 이름이 정의된 순서 (가장 위)


    # Custom Manager의 단점

     : Manager에서 정의한 커스텀 메소드들끼리는 체인 해서 사용할 수 없다. 아래 예시 코드를 보자

     

    from django.db import models
    
    class DocumentManager(models.Manager):
    
        def pdfs(self):
            return self.filter(file_type='pdf')
    
        def smaller_than(self, size):
            return self.filter(size__lt=size)
    
    class Document(models.Model):
    
        name = models.CharField(max_length=30)
        size = models.PositiveIntegerField(default=0)
        file_type = models.CharField(max_length=10, blank=True)
    
        objects = DocumentManager()
        
    
    
    >> Document.objects.pdfs()  # OK
    
    >> Document.objects.pdfs().filter(name='test')  # OK
    
    >> Document.objects.pdfs().order_by('name')  # OK
    
    >> Document.objects.pdfs().smaller_than(1000)  # AttributeError: 'QuerySet' object has no attribute 'smaller_than'

    그 이유는 위 .pdfs()는 쿼리셋을 리턴하는데, .smaller_than()은 Manager 함수지 QuerySet 함수가 아니기 때문이다.

     

     

    # 해결책 = Custom QuerySet

     : 2가지 방법이 있다.

     

      1. Custom QuerySet을 추가로 선언해주기

    class DocumentQuerySet(models.QuerySet):
    
        def pdfs(self):
            return self.filter(file_type='pdf')
    
        def smaller_than(self, size):
            return self.filter(size__lt=size)
    
    class DocumentManager(models.Manager):
    
        def get_queryset(self):
            return DocumentQuerySet(self.model, using=self._db)  # 중요
    
        def pdfs(self):
            return self.get_queryset().pdfs()
    
        def smaller_than(self, size):
            return self.get_queryset().smaller_than(size)
    
    class Document(models.Model):
    
        name = models.CharField(max_length=30)
        size = models.PositiveIntegerField(default=0)
        file_type = models.CharField(max_length=10, blank=True)
    
        objects = DocumentManager()
    
    
    Document.objects.pdfs().smaller_than(1000) # OK

    위 코드를 보면 Custom QuerySet을 선언해준 다음 Custom Manager에 get_queryset()에 연결시켜주었다.

    또 objects는 그대로 Custom Manager을 둔 것을 확인할 수 있다.

     

      2. Custom QuerySet만 사용하기

        : 사실 모든 QuerySet 메서드는 복사되어 Manager의 메서드로서 사용될 수 있기 때문에, Custom QuerySet만 사용해도 된다.

    class DocumentQuerySet(models.QuerySet):
    
        def pdfs(self):
            return self.filter(file_type='pdf')
    
        def smaller_than(self, size):
            return self.filter(size__lt=size)
    
    class Document(models.Model):
    
        name = models.CharField(max_length=30)
        size = models.PositiveIntegerField(default=0)
        file_type = models.CharField(max_length=10, blank=True)
    
        objects = DocumentQuerySet.as_manager()
        
        
    >> Document.objects.pdfs() # OK
    >> Document.objects.pdfs().filter(name='test') # OK
    >> Document.objects.pdfs().order_by('name') # OK
    >> Document.objects.pdfs().smaller_than(1000) # OK

    코드를 보면 Custom Manager는 구현하지 않고 Custom QuerySet만 구현한 다음 objects에 CustomQuerySet.as_manager()를 둔 것을 확인할 수 있다.

     


    참고

    https://tech.ashe.kr/10

    https://brownbears.tistory.com/434

    http://blog.hwahae.co.kr/all/tech/tech-tech/4108/

     

Designed by Tistory.