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
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()
active_objects = ActiveManager()
- User Model에서 두 개의 Manager를 사용할 수 있도록 구성
- objects 사용 : 데이터베이스의 모든 데이터를 리턴
- active_objects 사용 : active 필드가 True인 데이터만 리턴
- active_objects를 사용하더라도 QuerySet을 리턴하므로 filter 나 exclude, count 등 모든 표준 QuerySet의 기능을 사용할 수 있음
User.objects.all()
User.active_objects.all()
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()
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)