[Django] ORM QuerySet 중복 코드와 Model Manager

EUN JY·2022년 2월 15일
2

Python

목록 보기
3/3
post-thumbnail

1. Django 설계 철학

1-1. 일반

1-1-1. 느슨한 결합

  • Django 스택의 근본적인 목표는 느슨한 결합, 탄탄한 응집
  • 편의성을 위해 풀스택으로 제공되지만 스택의 각 부분은 독립성을 띔

1-1-2. 적은 코드

  • 가능한 한 최소한의 코드를 사용
  • 인트로스펙션(introspection)과 같은 Python의 동적인 기능을 최대한 활용

1-1-3. DRY

  • 고유한 개념 및 데이터는 단 한 번, 단 한 곳에 존재하도록 함
  • 중복성은 최소화하고 정규화를 지향
  • 최소한의 것들을 가지고 최대한의 것을 만들어내도록 함

1-1-4. 기타

  • 신속한 개발
  • 명시적 개발 지향
  • 모든 수준에서 일관적이어야 함

1-2. 모델

1-2-1. 명시적 키워드

  • 필드의 성질은 키워드 인자에 근거해야 함

1-2-2. 관련 도메인 로직 포함

  • 마틴 파울러의 활성 레코드(Active Record) 디자인 패턴을 따라, 모델은 객체의 모든 관점(aspect)을 캡슐화해야 함
  • 모델을 이해하는 데 요구되는 모든 정보가 모델 내에 있어야 함
    • 사람이 읽을 수 있는 이름, 기본 순서 같은 선택 사항 등

1-3. 데이터베이스 API

1-3-1. SQL 효율성

  • SQL 문을 최대한 적은 횟수로 실행해야 함
  • SQL 문을 내부적으로 최적화해야 함
  • select_related() QuerySet 메서드
    • 관련된 모든 객체를 선택하는 공통적인 경우에 성능을 향상시키는 선택 사항

1-3-2. 간결하고 강력한 구문

  • 가능한 한 적은 구문을 가지고 표현력이 뛰어난 문장을 생성할 수 있어야 함
  • 다른 모듈이나 도움 객체를 임포트 하는 것에 의존해서는 안됨
  • 조인(join)이 요구될 때에는 겉으로 드러나지 않게 자동으로 수행되어야 함
  • 모든 객체는 시스템의 모든 관련 객체에 접근할 수 있어야 하며, 접근은 양방향이어야 함

1-3-3. SQL 문을 직접 작성하기 쉬워야 함

  • 데이터베이스 API는 개발의 편의를 위한 것
  • 프레임워크는 맞춤 SQL(전체 문장 또는 API 호출 시 맞춤 파라미터로서의 맞춤 WHERE 절)도 쉽게 작성할 수 있어야 함

1-4. URL 설계

1-4-1. 느슨한 결합

  • Django 앱의 URL은 하부 Python 코드와 결합되어서는 안됨
  • Python 함수 이름과 URL을 엮는 것은 좋지 않음
  • Django URL 시스템은 같은 앱을 다른 맥락에서 사용할 수 있도록 함
    • 한 사이트에서는 /stories/에, 다른 사이트에서는 /news/에 넣을 수 있음

1-4-2. 무한한 유연성

  • URL은 가능한 한 유연해야 함
  • 생각할 수 있는 모든 URL 설계가 가능

1-4-3. 모범 사례를 장려

  • 간결하고 보기 좋은 URL을 더 쉽게 설계할 수 있도록 해야 함
  • File extensions in web-page URLs should be avoided. URL에 Vignette 스타일의 콤마는 큰 벌을 받아 마땅합니다. ㅋㅋㅋㅋㅋ

1-4-4. 명확한 URL

  • 기술적으로 foo.com/bar와 foo.com/bar/는 서로 다른 두 개의 URL
  • 검색 엔진 로봇(및 일부 웹 트래픽 분석 도구)은 이를 별도의 페이지로 취급
  • Django는 검색 엔진 로봇이 혼동하지 않도록 URL을 정규화 하도록 노력해야 함
  • 이것이 바로 APPEND_SLASH 설정이 있는 이유

1-5. 템플릿 시스템

1-5-1. 표현과 로직을 분리

  • 템플릿 시스템은 표현을 제어하는 도구이자 표현에 관련된 로직일 뿐
  • 템플릿 시스템은 이 기본 목표를 넘어서는 기능을 지원하지 말아야 함

1-5-2. 중복을 배제

  • 대다수의 동적 웹사이트는 공통 헤더, 푸터, 네이게이션 바 같은 사이트 공통 디자인을 가짐
  • Django 템플릿 시스템은 이러한 요소를 한 곳에 저장하기 쉽게 하여 중복 코드를 없애야 함
    • 템플릿 상속의 기초

1-5-3. HTML과의 분리

  • 템플릿 시스템은 HTML만을 출력하도록 설계하지 말아야 함
  • 텍스트 기반 포맷 또는 일반 텍스트도 마찬가지로 잘 생성할 수 있어야 함

1-5-4. XML을 템플릿 언어로 사용하지 말 것

  • 템플릿 해석을 위해 XML 엔진을 사용하지 말 것
  • 템플릿 편집에 있어서 실수가 발생할 가능성이 매우 커짐
  • 템플릿 처리에 너무 큰 과부하가 걸림

1-5-5. 디자이너가 코딩 능력이 있는 것으로 가정

  • 템플릿 시스템은 템플릿이 반드시 위지윅(WYSIWYG) 편집기에서 제대로 보이도록 설계하지 않음
    • 제약이 많아지며 지금과 같은 훌륭한 구문을 유지하기 힘듦
  • Django는 템플릿 작성자가 HTML을 직접 편집하는 것을 어려워 하지 않을 것을 전제로 함

1-5-6. 공백에 특별한 의미 없음

  • 템플릿 시스템은 공백을 가지고 특수한 동작을 일으켜서는 안됨
  • 템플릿에 공백이 포함되어 있으면 그것을 그저 텍스트의 공백으로서 표출(display)해야 함
  • 템플릿 태그 내에 있지 않은 모든 공백은 표출해야 함

1-5-7. 프로그래밍 언어를 발명하지 말 것

  • 프로그래밍 언어의 발명을 목적으로 하는 것이 아님
  • 분기와 반복 같이 표현 계층에 꼭 필요한 프로그래밍 기능을 제공하는 것이 목표
  • Django 템플릿 언어(DTL)는 고급 로직을 제공하지 않음
  • Django 템플릿 시스템은 템플릿이 디자이너에 의해 작성되는 것을 전제로 함

1-5-8. 안전과 보안

  • 템플릿 시스템은 악의적 코드를 포함할 수 없게 되어 있어야 함
    • 데이터베이스의 레코드를 삭제하는 명령 등
    • 템플릿 시스템이 임의의 Python 코드를 실행할 수 없는 이유

1-5-9. 확장성

  • 높은 수준의 기술을 가진 템플릿 작성자는 템플릿 시스템을 확장할 수 있음
    • 맞춤 템플릿 태그와 필터

1-6. 뷰

1-6-1. 단순성

  • 뷰를 작성하는 것은 Python 함수를 작성하는 것만큼 단순해야 함
  • 개발자는 함수로 처리할 수 있는 일을 하기 위해 클래스의 인스턴스를 굳이 생성하지 않아도 됨

1-6-2. 요청 객체의 사용

  • 뷰는 요청 객체(현재 요청에 대한 메타데이터를 갖는 객체)에 접근할 수 있어야 함
  • 뷰 함수가 글로벌 변수의 요청 데이터에 접근하도록 하지 말고, 요청 객체를 뷰 함수에 직접 전달
  • 가짜 요청 객체를 전달함으로써 가볍고 깔끔하며 쉬운 테스트 뷰를 만들 수 있음

1-6-3. 느슨한 결합

  • 뷰는 개발자가 어느 템플릿 시스템을 사용하는지, 혹은 템플릿 시스템을 사용하는지에 무관해야 함

1-6-4. GET과 POST를 구분

  • GET과 POST는 고유한 것
    • 개발자는 명시적으로 둘 중 하나를 사용해야 함
    • 본 프레임워크는 GET과 POST 데이터를 쉽게 구분할 수 있도록 해야 함

1-7. 캐시 프레임워크

1-7-1. 적은 코드

  • 캐시는 가능한 한 빨라야 함
  • 캐시 백엔드와 관련된 모든 프레임워크 코드, 특히 get() 연산은 빨라야 함

1-7-2. 일관성

  • 캐시 API는 여러 캐시 백엔드에 대해 일관적 인터페이스를 제공해야 함

1-7-3. 확장성

  • 캐시 API는 개발자의 요구에 따라 애플리케이션 수준에서 확장 가능해야 함

2. Manager

  • 참고 https://tech.ashe.kr/10
  • 참고 http://blog.hwahae.co.kr/all/tech/tech-tech/4108/
  • Django는 Manager를 통해 데이터베이스와 통신
  • ORM QuerySet을 적극적으로 사용하는 것이 특징
    • 애플리케이션의 규모가 커질수록 QuerySet 중복 코드가 늘어나면서 유지 관리가 어려워짐
    • Django Manager를 사용하여 해결 가능

2-1. Django Manager

  • Django Model에 제공되는 데이터베이스 쿼리 인터페이스
  • 모든 Model은 하나 이상의 Manager를 가지게 됨
  • Django는 기본으로 모든 Model에 objects라는 이름으로 Manager를 추가
    • 별도의 Manager 추가 없이 QuerySet 사용 가능
User.objects.all()
  • Manager 이름 변경 시, 해당 Model의 models.Manager() 타입의 클래스 attribute를 정의하면 됨
    • 재정의 후 기본 제공되는 objects를 사용 시 AttributeError 예외 발생
class User(models.Model):
	...
    people = models.Manager()
    
User.people.all()

2-2. Custom Manager 사용

  • 기본 Manager의 확장이 필요한 경우 Custom Manager를 모델에 사용할 수 있음
  • 필요성에 따라 커스텀 Manager를 활용하는 방법은 두 가지
    • extra Manager method를 추가하는 것
    • initial QuerySet을 수정하는 것

2-2-1. Queryset 수정

  • 초기의 Queryset에 Manager를 사용하여 필터링 가능
  • Manager의 기본 QuerySet은 해당 Model의 모든 데이터를 리턴하도록 작성되어 있음
from django.db import models

class PostManager(models.Manager):
  """삭제된 Post 는 제외하고 가져오는 Manager"""
  
  def get_queryset(self):
    return super().get_queryset().filter(deleted__isnull=True)

class AllPostManager(models.Manager):
  """삭제된 Post 도 포함하여 가져오는 Manager"""
  pass
  
class Post(models.Model):
  deleted = models.DateTiemField(blank=True, null=True)

  objects = PostManager()
  all_objects = AllPostManager()
  • Manager의 get_queryset 메소드 override로 기본 QuerySet 수정 가능
    • Extra Manager 메소드와 다르게 get_queryset 메소드는 반드시 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 Model에서 두 개의 Manager를 사용할 수 있도록 구성
    • objects 사용 : 데이터베이스의 모든 데이터를 리턴
    • active_objects 사용 : active 필드가 True인 데이터만 리턴
  • active_objects를 사용하더라도 QuerySet을 리턴하므로 filter 나 exclude, count 등 모든 표준 QuerySet의 기능을 사용할 수 있음
User.objects.all() # 모든 User 데이터
User.active_objects.all() # active 필드가 True인 데이터

User.active_objects.count()
User.active_objects.exclude(name="admin")

2-2-2. 테이블 단위의 기능 추가

  • Extra Manager method 추가하기
  • 행 단위(row-level)의 경우 기능을 작성할 때 Model의 메서드를 사용하는 것이 좋음
  • 모델에 테이블 단위(table-level)로 기능을 추가하고 싶을 때 사용 (집계 쿼리 등)
  • Manager 메서드는 QuerySet, int, string, dict 등의 형태로 리턴 가능
from django.db import models

class PostManager(models.Manager):
  def publish_count(self):
    """Post 중 publish 된 것의 개수를 리턴하는 메서드"""
    return self.get_queryset().filter(publish__isnull=False).count()

# 아래와 같이 사용 가능합니다.
# Post.objects.publish_count()

3. Queryset

  • Manager에서 정의한 메서드는 체인하여 사용할 수 없음
  • 체인하여 QuerySet을 수정해야 할 경우 사용
from django.db import models.

class PostQueryset(models.Queryset):
  def publish(self):
    """Post 중 publish 된 쿼리셋을 리턴하는 메서드"""
    return self.filter(publish__isnull=False)
  
  def important(self):
    """important 가 붙어있는 쿼리셋을 리턴하는 메서드"""
    return self.filter(important=True)

# 아래와 같이 사용 가능합니다.
# Post.objects.publish().important()
profile
개린이

0개의 댓글