목록으로

Django 5에 새롭게 도입된 GeneratedField

낱글, 입문
2024. 5. 14. PM 8:01:50
#generatedfield, #django, #computed, #장고

GeneratedField

GeneratedField는 Django 5.0에 새로 추가된 모델 필드입니다. 예시로 나온 코드를 보면 변을 제곱하여 사각형의 면적을 area 필드로 다루는데요. 다른 필드를 참고하여 미리 연산된(computed) 값을 담은 필드지요. Django ORM처럼 많이 사용되는 ORM인 SQLAlchemy의 column_propertyhybrid_property와 비슷하죠.

사용법은 Django답게 간단합니다. 기존 모델 필드처럼 사용하면 되거든요. 한 가지 사소한 난관을 제외하면요. 바로 Expression 이지요.

게시물 본문에서 제목 따오기

푸딩캠프 웹사이트는 곳곳에 마크다운, 정확히는 깃헙식 마크다운(Github Flavour Markdown)처럼 마크다운을 확장한 문법을 사용해요. 컨텐츠는 PFM(Puuding Flavour Markdown)이라는 푸딩캠프 자체 확장 마크다운을 사용해서 퀴즈 등 상호작용 요소를 마크다운에 기술했고, 게시판은 GFM을 사용하고 있어요.

마크다운 문법 중에는 Header 요소가 있죠. # 기호를 이용하거나 =, - 기호로 줄을 그어 Header라는 의미를 드러내는데, 저희는 Header 1(# 기호 하나)을 주로 문서의 제목으로 사용해요.

그렇다면 게시판에서 굳이 제목 필드가 있지 않아도 괜찮지 않을까요? 그래서 게시물 본문 맨 위에 있는 Header 1을 제목으로 따오는 것을 GeneratedField로 구현해보겠습니다!

게시물 모델

크게 세 개 인자가 있어요. 간단한 것부터 살펴볼게요.

먼저 db_persist는 데이터베이스에 실제로 존재하는 값으로 유지할지 여부예요. 간단히 말해 데이터베이스 열(column)로 만들어 연산된(computed) 값을 자동으로 넣을지 아니면 가상의 열로 다룰지를 결정하죠. 이 옵션은 RDBMS 종류에 따라 다르게 동작해요.

  • PostgreSQL : db_persist=True 로 동작.

  • Oracle : db_persist=False로 동작.

  • SQLite : db_persist=True로 동작.

output_field는 이름 그대로 결과물로 나올 모델 필드를 정의하는 거예요. db_persist=True 설정으로 동작하는 경우, 데이터베이스에 실제로 만들어질 열 유형(column type)이 되는 것이기도 해서, 모델 필드 정의하듯이 코드를 작성하면 돼요.

expression이 좀 까다로운데요. Django를 사용하면서 미리 잘 선언된 Expression을 갖다 쓰는 경우가 대부분이지 Expression의 재료로 맞춤 Expression을 만들 일이 흔하진 않거든요. 그런 상황이 오더라도 Raw SQL을 작성하는 경우가 많지 Expression을 구현하는 경우는 드물어요. 게다가 공식 문서나 여타 Django 관련 책에서도 Expression을 깊게 다루진 않아요. 사실상 동작 원리와 예제를 Django 내부 코드를 까보며 익혀야 하는 거죠.

Expression

Expression 클래스는 Query 표현을 담당하는 BaseExpression 클래스와 Combinable 간 조합으로 새로운 Query 표현을 해주는 Combinable 클래스를 상속 받는, 각종 표현식 객체의 조상에 해당하는 클래스예요. Raw SQL을 작성할 때에 동원되는 객체가 RawSQL 클래스의 객체인데, 이 클래스도 Expression의 파생 클래스이고, Switch문처럼 사용하는 WhenCaseExpression의 파생 클래스지요. Expression 위에 Query 계층이 있고, Query 계층 위에 Model Manager/QuerySet 계층이 있으니 상당히 아래 계층(low level layer)에 있는 거죠.

예제 코드에 나온 Expression 파생 클래스를 하나씩 살펴볼게요. 실은 이름에서 어떤 역할을 하는지 잘 드러나긴 해요. 😁 Expression이 선언형으로 동작한다는 것만 머리 속에 상기하며 보면 이해하기 수월하죠.

StrIndex 는 지정한 문자열의 위치를 반환해요. 첫 번째 인자는 대상이 되는 모델 필드명을 전달하고, 두 번째 인자로는 으로써 Query에서 사용될 값으로 # 문자열을 Value 클래스의 인스턴스 객체로 전달했습니다. 선언형이라는 점을 상기하라고 했잖아요?! 이 Expression이 동작하는 상황은 Query를 작성해서 SQL로 동작시킬 때예요. 그러므로 Python의 문자열 객체인 #이 아니라 Query의 값으로써 #를 전달하기 위해 Value를 사용한 거예요.

StrIndex가 두 번 사용됐는데, 첫 번째는 # 문자 위치로부터 +1 위치이고, 두 번째는 개행(\n) 문자의 위치예요.

이러한 마크다운 문서에서 # 문자 바로 뒤인 공백 부분부터 GeneratedField 바로 뒤까지 위치를 잡은 거죠. 이 두 위치값을 SubStr에 인자로 사용합니다. content 모델 필드를 대상으로 말이죠. SubStr 객체는 이름 그대로 문자열의 부분을 잘라내는 역할을 해요. 위 마크다운 내용이 본문이라면 0002 - Django 5에 새롭게 도입된 GeneratedField를 잘라낸 셈이죠.

이렇게 잘라낸 문자열의 앞뒤 공백을 Trim으로 쳐냅니다. 정리하면 이 Expression은 다음 SQL을 선언한 거예요.

의사 코드로 작성하자면 이런 코드를 짠 셈입니다.

여기에 사용된 Trim, SubStr, StrIndex, Value 모두 Expression의 파생 클래스입니다.

GeneratedField가 Python property와 다른 점

이러한 동작은 이미 Python에 property로 수행할 수 있어요. 코드가 더 간결하고 직관적이죠. 데이터베이스에서 데이터를 가져와 Python 비즈니스 영역 코드단에서 이런 기능이 필요하다면 property로도 충분해요. GeneratedField가 다른 점은 데이터베이스단에서 수행된다는 점이예요. 그래서 데이터 탐색(lookup)에 사용할 수 있어요.

바로 Post.objects.filter(subject__contains="검색어")와 같이 사용하는 거죠. Python으로 property로는 이러한 기능을 수행할 수 없어요.

활용처

게시물 본문에서 제목을 따오는 것 말고도 활용할 곳은 많아 보여요. 바로 떠오르는 예는 단위 변환이죠. 예를 들어 생일을 저장하고, 생일을 기준으로 국제 나이(만 나이)를 계산하는 age 모델 필드를 만들기에도 좋고요. 길이나 면적처럼 단위 변환을 하기에도 좋아보여요. 가령, 저장할 때엔 평방미터로 넓이를 저장하고, 검색할 때엔 평으로 검색하는 식으로요.

학습이 이뤄지고 있는지 확신이 안 서나요?
사수가 없나요?
효과적이고 효율적으로 학습하며 성장하는
자기만의 학습과 성장 체계를 만들 수 있습니다.
연사자들의 각양각색 학습과 성장 스토리로
여러분의 학습과 성장을 키워보세요.
푸딩캠프 뉴스레터를 구독하면 학습과 성장, 기술에 관해 요약된 컨텐츠를 매주 편하게 받아보실 수 있습니다.