
장고 모델을 정의할 때 필드에 choices 옵션을 주면 해당 필드에 저장될 값을 제한할 수 있다.
예를 들어 area라는 필드에 서울시 행정구역 25개의 값만 저장 되어야할 경우
행정구역 선택값들을 모아둔 서브클래스 AREA를 만들어 아래와 같이 정의할 수 있다.
class RESTAURANT(models.Model):
class AREA(models.IntegerChoices):
Gangnam = 0, '강남구'
Gangdong = 1, '강동구'
Gangbuk = 2, '강북구'
# (생략)
area = models.PositiveBigIntegerField(choices=AREA.choices)
서브클래스 AREA는 열거형(Enumerated Type) 클래스이며 구성 요소는 다음과 같다:
실제 예시는 다음과 같다:


열거형(Enumerated Type)은 사용자 언어의 상수 역할을 하는 식별자이다.
대표적인 예로 1과 0을 True와 False로 표현하는 예시가 있다.
위 예시에서 area 필드를 PositiveBigIntegerField()로 정의 했다.
즉, 0, 1, 2, .. 등의 값을 데이터베이스에 저장하는 언어로 사용하기로 했다.
하지만 실제 사용자 페이지에서도 숫자를 보여주면 곤란하다. 이 값을 꺼내 보여줄 때에는 '강남구', '강동구', '강북구' .. 등의 문자열로 보여줘야 한다.
그리고 이를 위해 열거형 타입 서브클래스 AREA를 정의해 줬다.
class RESTAURANT(models.Model):
class AREA(models.IntegerChoices):
Gangnam = 0, '강남구'
Gangdong = 1, '강동구'
Gangbuk = 2, '강북구'
위 예시에서 Enum 타입의 AREA는 다음과 같은 구조로 변환된다 📌 :
AREA = {
'Gangnam': <AREA.Gangnam: 0> # name: Gangnam, value: 0, label: '강남구'
'Gangdong': <AREA.Gangdong: 1> # name: Gangdong, value: 1, label: '강동구'
'Gangbuk': <AREA.Gangbuk: 2> # name: Gangbuk, value: 2, label: '강북구'
}
⚠️ 유의할 점은 AREA가 파이썬의 dict와 유사하게 생겼지만 둘은 다른 개념이라는 것이다.
딕셔너리는 "키 - 값" 구성이지만
AREA는 "name - (value: label)"로 구성되어 있다.

따라서 다음과 같은 방식으로 실제 인스턴스에 접근할 수 있다:
1. Enum 멤버의 식별자를 통한 접근
2. Enum 멤버의 식별자를 dict처럼 사용한 접근
3. Enum 멤버의 value를 통한 접근

예를들어 서울 동네별로 식당을 추천해주는 서비스가 있다고 하자.
사용자가 처음으로 페이지 진입했을 시에는 전체 추천 식당 목록을 보여주고
특정 지역을 클릭 했을 때에는 해당 지역의 추천 식당 목록을 보여주려 한다.
이때 쿼리셋은 다음과 같다:
restaurants = RESTAURANT.objects.all() # 디폴트 페이지
restaurants = RESTAURANT.objects.filter(area=0) # 강남구 필터 적용
두 가지 경우를 하나의 코드에 담으면 다음과 같다:
def generate_restaurant_list(str_area=None):
area_mapping = {
"Gangnam": 0,
"Gangdong": 1,
"Gangbuk": 2,
}
filters = {}
if str_area:
filters = {"area": area_mapping[str_area]}
restaurants = RESTAURANT.objects.filter(**filters)
return restaurants
문자열 'Gangnam'을 값으로 갖는 str_area 변수를 위 함수에 넘기면
area_mapping 딕셔너리에서 'Gangnam' 해당하는 정수값과 감께
미리 초기화 해둔 filters 딕셔너리에 추가된다.
filters를 언패킹해서 사용하도록 **filters 형태로 쿼리셋에 넣어주면 다음과 같은 쿼리셋이 만들어 진다:
restaurants = RESTAURANT.objects.filter({'area': RESTAURANT.AREA.Gangnam})
하지만 서울에는 동네가 25개나 있다.
이를 mapping해주는 딕셔너리를 모두 적기 귀찮다.
RESTAURANT 모델의 서브클래스 AREA에 이미 해당 정보가 있으니 그걸 가져다 쓰자.
def generate_restaurant_list(str_area=None):
filters = {"area": RESTAURANT.AREA[f"{str_area}"]} if str_area is not None else {}
restaurants = RESTAURANT.objects.filter(**filters)
return restaurants
위에서 설명한 Enum 서브클래스 접근 방식 세번째를 활용(RESTAURANT.AREA[f"{str_area}"])하여 동적 필터를 만들어 줬다.
리팩토링 하는 김에 코드도 짧게 줄여줬다.
⚠️ 오류
RESTAURANT.AREA.str_area와 같은 방식으로 Enum 서브클래스의 멤버에 접근하면 오류가 난다.
RESTAURANT.AREA.str_area <-- 이건 변수값을 키로 사용하는 딕셔너리의 접근 방식이다.
하지만 AREA는 Enum타입이고, 장고는 문자열 변수 str_area를 문자 그대로 받아들여 'str_area'라는 식별자를 찾으려 하고 오류가 발생한다.
# html
<img src="{% static 'FOOD_HUNTING/images/seoul_map.png' %}" alt="서울 지도" usemap="#seoul_map" class="seoul_img">
<map name="seoul_map">
<area target="_self" alt="서대문구" title="서대문구 검색" href="{% url 'index_with_area' 'Seodaemun' %}" coords="(생략)" shape="poly">
<area target="_self" alt="종로구" title="종로구 검색" href="{% url 'index_with_area' 'Jongno' %}" coords="(생략)" shape="poly">
</map>
</map>
지도에서 검색하고싶은 구를 선택하게되면
Enum 타입의 서브클래스 AREA에서 적절한 식별자를 찾아
동적 필터를 통해 원하는 식당이 검색된다.
도커로 CI/CD 환경 구축하는걸 연습하기 위해
더미 장고 프로젝트를 만들다가 여기까지 흘러와 버렸다
근데 더 흘러갈 것 같다..
5월 내로 도커를 사용하기는 할까? ㅎㅎ..
끝!