Union Project - learning - 4주차

김기훈·2026년 1월 30일

부트캠프 프로젝트

목록 보기
36/39
post-thumbnail

[2026.01.26]

[2026.01.27]

[2026.01.28]

[2026.01.29]

[2026.01.30]


2026.01.26 ✅

serializers

비교 항목ModelSerializerSerializer
입력 데이터 형태[{"genre": 1}, {"genre": 2}]{"genre_ids": [1, 2]}
매핑 방식1 JSON 객체 = 1 DB Row1 JSON 필드(List) = N DB Rows
검증 로직개별 객체 단위로 수행 (쿼리 N번 발생 가능)리스트 전체를 한 번에 검증 (id__in 사용 가능)
생성 로직기본적으로 save()를 N번 호출 (느림)bulk_create로 한 번에 저장하기 쉬움
사용 목적일반적인 CRUD (생성, 수정, 조회)특수 목적의 액션 (일괄 등록, 로그인, 이메일 발송 등)

serializers.ModelSerializer

  • ModelSerializer 는 "DB 테이블 모양 그대로 데이터를 받고 싶을 때" 사용
    • 입력받는 데이터의 형태와 저장되는 모델의 형태가 1:1로 매칭되지 않으면 사용하지 않음
# ModelSerializer를 쓴다면 프론트엔드가 보내야 하는 데이터 형태
[
    {"genre": 1},
    {"genre": 2},
    {"genre": 3}
]

——————————————————————————————————————[비교]—————————————————————————————————————————
# 일반 Serializer를 썼을 때 프론트엔드 데이터 형태

{
    "genre_ids": [1, 2, 3]
}

학습

  • serializer.validated_data
    • 딕셔너리 형태({'genre_ids': [1, 2]})
  • constraints = [models.UniqueConstraint(...)]:
    • 한 유저(user)가 동일한 장르(genre)를 중복해서 가질 수 없도록 제약조건 걸기
  • ignore_conflicts=True:
    • Model에 UniqueConstraint를 추가했다면, 중복 데이터 삽입 시 에러가 발생함
    • 이 옵션을 켜면 에러를 내지 않고, 중복된 건은 건너뛰고 새로운 건만 저장

2026/01/27 ✅

번역

googletrans

  • Python에서 Google 번역 API를 무료로 사용할 수 있게 해주는 비공식 라이브러리
  • 별도의 API 키 발급 없이 구글 번역기의 기능을 간편하게 코드에 구현할 수 있어,
    • 가벼운 프로젝트나 프로토타입 개발에 자주 사용됨
  • 특징

    • 사용량 제한이 엄격하지 않고 무료
    • 입력된 텍스트의 언어를 자동으로 식별
    • 구글 번역기가 지원하는 100개 이상의 언어를 사용 가능
  • 안정적인 동작을 위해서는 특정 버전(4.0.0-rc1)을 설치하는 것이 좋음
  • 사용 예시

from googletrans import Translator

# 1. 번역기 객체 생성
translator = Translator()
"""
1. Translator():
- 번역 작업을 수행하는 핵심 클래스 인스턴스를 생성합니다.
- 이 객체를 통해 translate()와 detect() 메서드를 호출합니다.
"""

# 2. 영어를 한국어로 번역
text_to_translate = "Hello, I am studying coding."
result = translator.translate(text_to_translate, dest='ko')
"""
2. translator.translate(text, dest='ko'):
- text: 번역할 원본 문자열입니다.
- dest: 도착 언어(destination language) 코드입니다. 'ko'는 한국어를 의미합니다.
- src: 출발 언어(source language)는 생략 시 자동으로 감지(auto)됩니다.
"""

# 3. 언어 감지
text_to_detect = "Bonjour tout le monde"
detection = translator.detect(text_to_detect)
"""
3. translator.detect(text):
- 입력된 텍스트가 어떤 언어인지 분석하여 반환합니다. 
- 'fr'(프랑스어) 같은 언어 코드가 반환됩니다.
"""

print(f"번역 결과: {result.text}")
print(f"발음(발음이 가능한 경우): {result.pronunciation}")
print(f"감지된 언어: {detection.lang}")
"""
4. 반환 객체 (result):
- result.text: 번역된 최종 텍스트입니다.
- result.src: 원본 텍스트의 감지된 언어입니다.
- result.pronunciation: 번역된 텍스트의 발음 기호(로마자 표기)입니다.
"""

주요언어 코드

언어 (Language)코드 (Code)예시 (Example)
한국어ko안녕하세요
영어enHello
일본어jaこんにちは
중국어 (간체)zh-cn你好
중국어 (번체)zh-tw你好
프랑스어frBonjour
스페인어esHola

2026/01/29 ✅

Django Debug Toolbar(DDT)

  • 현재 페이지를 렌더링하는 데 SQL 쿼리가 몇 개나 실행되었는지
    • 시간은 얼마나 걸렸는지 시각적으로 보여주는 강력한 도구
  • 설치

    • pip install django-debug-toolbar

settings

  • INSTALLED_APPS, MIDDLEWARE, INTERNAL_IPS 세 가지를 설정
# settings.py

INSTALLED_APPS = [
    # ... 기존 앱들 ...
    'django.contrib.staticfiles',
    
    'debug_toolbar', 
    # [설명]: 서드파티 앱이기 때문에 INSTALLED_APPS 리스트에 추가해야 합니다.
    # 보통 'django.contrib.staticfiles'보다 아래에 위치시키는 것을 권장합니다.
]

MIDDLEWARE = [
    # ... 맨 위나 앞부분에 있는 미들웨어들 ...
    'debug_toolbar.middleware.DebugToolbarMiddleware',
    # [설명]: 미들웨어는 순서가 매우 중요합니다. 
    # 응답(Response)을 처리할 때 툴바가 데이터를 가로채서 보여줘야 하므로, 
    # 가능한 리스트의 상단(GZipMiddleware 등이 있다면 그 뒤)에 배치하는 것이 좋습니다.
    
    # ... 나머지 미들웨어들 ...
]

INTERNAL_IPS = [
    '127.0.0.1',
]
# [설명]: Debug Toolbar는 개발자에게만 보여야 합니다.
# 로컬 개발 환경(localhost)을 의미하는 '127.0.0.1'을 반드시 추가해야 툴바가 나타납니다.
# 만약 Docker를 사용 중이라면, Docker 내부 IP 대역 설정이 추가로 필요할 수 있습니다.

urls.py

  • 프로젝트의 메인 urls.py 파일에 툴바 전용 URL을 연결
# 프로젝트의 메인 urls.py (app의 urls.py 아님)

from django.contrib import admin
from django.urls import path, include
from django.conf import settings 
# [설명]: settings.py의 DEBUG 값을 확인하기 위해 import 합니다.

urlpatterns = [
    path('admin/', admin.site.urls),
    # ... 기존 url 패턴들 ...
]

if settings.DEBUG:
    import debug_toolbar
    urlpatterns += [
        path('__debug__/', include(debug_toolbar.urls)),
    ]
    # [설명]: 개발 모드(DEBUG=True)일 때만 툴바가 작동하도록 설정합니다.
    # '__debug__/' 라는 경로로 들어오는 요청을 툴바가 가로채서 처리하게 됩니다.

패널 이름 (Panel)주요 기능쿼리 낭비 체크 포인트
SQL실행된 모든 SQL 쿼리 조회Duplicated(중복) 횟수가 높은지 확인
비슷한 쿼리가 수십 개 반복된다면 N+1 문제 의심
TimeCPU, 브라우저 처리 시간 분석SQL 처리 시간이 전체 응답 시간 중 얼마나 차지하는지 확인
Templates템플릿 렌더링 과정 및 Context 확인뷰(View)에서 넘겨준 데이터가 템플릿에서 올바르게 쓰이는지 확인
Headers요청/응답 헤더 정보캐싱(Cache) 설정이 제대로 동작하는지 헤더 값을 통해 확인

미들웨어 위치

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    
    "debug_toolbar.middleware.DebugToolbarMiddleware",
    # [설명]: 툴바 미들웨어를 여기에 배치하세요.
    # 1. GZipMiddleware가 없으므로 가능한 상단에 배치하는 것이 좋습니다.
    # 2. 다른 미들웨어(Session, Common 등)가 처리한 결과도 툴바가 감지할 수 있도록 앞쪽에 둡니다.
    # 3. 만약 나중에 'GZipMiddleware'를 추가한다면, 툴바는 그보다 '아래'에 있어야 합니다.

    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

도커 사용

  • 도커 컨테이너 내부에서 실행될 때,
    • 장고는 요청을 보낸 IP를 127.0.0.1이 아닌 도커 게이트웨이 IP(예: 172.17.0.1)로 인식함
    • 그래서 단순히 127.0.0.1만 추가하면 툴바가 뜨지 않음
  • 그래서 파이썬의 socket 라이브러리를 사용해
    • 도커의 게이트웨이 IP를 동적으로 찾아 추가해주는 코드를 추가해야함
# settings.py 맨 하단에 추가

# ... 기존 설정들 ...

INTERNAL_IPS = [
    "127.0.0.1",
]

# [도커 설정을 위한 코드 추가]
import socket
# [설명]: 소켓 라이브러리를 임포트합니다.

hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
# [설명]: 현재 컨테이너의 호스트 이름과 IP 주소들을 가져옵니다.

INTERNAL_IPS += [ip[:-1] + "1" for ip in ips]
# [설명]: 도커 게이트웨이 IP를 동적으로 계산하여 INTERNAL_IPS에 추가합니다.
# 보통 도커 내부 IP의 마지막 자리를 1로 바꾼 것이 게이트웨이 주소인 경우가 많습니다.
# 이 코드가 있으면 로컬 개발환경과 도커 환경 모두에서 툴바가 잘 작동합니다.
# settings.py

# ... 기존 코드들 ...

# 1. 미들웨어 설정 (아까 확인한 위치에 잘 넣으셨죠?)
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "debug_toolbar.middleware.DebugToolbarMiddleware", # [설명]: Security 바로 아래 배치
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    # ...
]

# 2. 기본 INTERNAL_IPS 설정
INTERNAL_IPS = [
    "127.0.0.1",
]

# 3. 도커 호스트 IP 자동 감지 및 추가 (Socket 사용)
import socket

try:
    hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
    INTERNAL_IPS += [ip[:-1] + "1" for ip in ips]
except Exception:
    pass

# [코드 설명]
# 1. import socket: 
#    - 파이썬 내장 네트워크 라이브러리인 소켓을 불러옵니다.
#
# 2. socket.gethostname(): 
#    - 현재 코드가 실행 중인 환경(도커 컨테이너)의 호스트 이름을 가져옵니다.
#
# 3. socket.gethostbyname_ex(...): 
#    - 위에서 얻은 호스트 이름에 할당된 모든 IP 주소들을 리스트로 반환합니다.
#    - 도커 컨테이너는 보통 '172.x.x.x' 대역의 사설 IP를 가지고 있습니다.
#
# 4. [ip[:-1] + "1" for ip in ips]:
#    - 여기가 핵심입니다. 도커 네트워크에서 컨테이너 IP의 마지막 자리를 '1'로 바꾸면,
#      그게 바로 호스트 컴퓨터(내 컴퓨터)와 통신하는 '게이트웨이 IP'가 됩니다.
#    - 예: 컨테이너가 172.18.0.3 이라면 -> 게이트웨이는 172.18.0.1 입니다.
#    - 이 주소를 INTERNAL_IPS에 추가해줌으로써, 도커가 중개해준 요청을 신뢰하게 만듭니다.
#
# 5. try-except:
#    - 혹시 모를 네트워크 오류로 서버가 꺼지는 것을 방지하기 위한 안전장치입니다.
  • SHOW_TOOLBAR_CALLBACK 설정

    • IP 검사를 아예 무시하고 "그냥 보여줘!"라고 강제하는 방식
# settings.py

DEBUG_TOOLBAR_CONFIG = {
    "SHOW_TOOLBAR_CALLBACK": lambda request: True,
}
# [설명]: 이 설정은 Debug Toolbar가 "툴바를 보여줄지 말지" 결정하는 로직을 덮어씁니다.
# 'lambda request: True'는 어떤 요청이 오든 무조건 True를 반환하라는 뜻입니다.
# 즉, IP가 127.0.0.1이든 도커 IP든 상관없이 무조건 툴바를 띄웁니다.

# [주의사항]: 이 설정이 있으면 배포 환경(Production)에서도 툴바가 보일 수 있는 위험이 있습니다.
# 따라서 반드시 아래처럼 DEBUG 모드일 때만 적용되도록 감싸주는 것이 안전합니다.

if DEBUG:
    import mimetypes
    mimetypes.add_type("application/javascript", ".js", True)
    # [설명]: 도커에서 가끔 자바스크립트 파일의 MIME 타입을 인식 못해서 
    # 툴바 스타일이 깨지는 경우가 있는데, 그걸 방지하는 코드입니다. (선택사항이지만 추천)
    
    DEBUG_TOOLBAR_CONFIG = {
        "SHOW_TOOLBAR_CALLBACK": lambda request: True,
    }
구분강제 설정 (아까 거절한 방식)Socket 방식 (현재 방식)
원리IP 검사를 아예 무시IP를 정확히 찾아서 등록
보안성낮음 (실수로 켜두면 위험)높음 (지정된 네트워크만 허용)
유연성도커 IP가 바뀌어도 상관없음도커 IP가 바뀌면 자동으로 감지해서 적용함
코드량1줄약 5~6줄

테스트 오류

  • "SHOW_TOOLBAR_CALLBACK": lambda request: "test" not in sys.argv,
    • 아래의 무조건 보여줘는 설정이 너무 강해서 위의 테스트시에는 꺼달라 필요
      • 테스트(manage.py test)를 돌릴 때는 Django가 내부적으로 DEBUG = False로 설정을 잠시 바꿈
        • 이때 urls.py에서 debug_toolbar의 URL들이 빠지게 되는데
        • 툴바 미들웨어는 "무조건 보여줘" 설정 때문에 계속 작동하려고 하다가
        • "어? 내 URL(djdt)들이 어디 갔지?" 하고 길을 잃어버려서 에러가 남
if DEBUG:
    import mimetypes
    mimetypes.add_type("application/javascript", ".js", True)

    DEBUG_TOOLBAR_CONFIG = {
        # 기존 설정 (무조건 보여주기)
        "SHOW_TOOLBAR_CALLBACK": lambda request: True,
        
        # [추가] 테스트 실행 중일 때 툴바 에러 무시하기
        "IS_RUNNING_TESTS": False,
    }

최종 필요 코드

  • settings.py

DJANGO_APPS = [
	...
]

THIRD_PARTY_APPS = [
    "rest_framework",
    "drf_spectacular",
    "debug_toolbar",
]

CUSTOM_APPS: list[str] = [
	...
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + CUSTOM_APPS

MIDDLEWARE = [
	...
    "debug_toolbar.middleware.DebugToolbarMiddleware",
	...
]

.
.
.

INTERNAL_IPS = [
    "127.0.0.1",
]

if DEBUG:
    import mimetypes

    mimetypes.add_type("application/javascript", ".js", True)

    DEBUG_TOOLBAR_CONFIG = {
        "SHOW_TOOLBAR_CALLBACK": lambda request: "test" not in sys.argv,
        # 테스트 실행 중일 때 툴바 에러 무시하기
        "IS_RUNNING_TESTS": False,
    }
  • config/urls.py

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

    if "debug_toolbar" in settings.INSTALLED_APPS:
        # import를 이 블록 안에서 함으로써, 배포 환경에서는 불필요한 import를 막음
        import debug_toolbar  # type: ignore

        urlpatterns += [
            path("__debug__/", include(debug_toolbar.urls)),
        ]

깃헙 올린 후 공지

쿼리 최적화를 위해 Django Debug Toolbar를 추가했습니다.

패키지 설정이 변경되었으니, 코드 pull 받으신 후 아래 두 가지를 꼭 진행해 주세요!

1. 로컬 환경 갱신 (빨간 줄 방지)
poetry install

2. 도커 서버 재빌드 (서버 에러 방지)
docker-compose up --build

도커 진행 방식

도커

  • 왜 도커로 서버를 켜면 굳이 소켓이나 강제 설정을 해야할 까
    • 서버를 도커로 켜게되면 Django는 사용자를 127.0.0.1로 인식하지 못함
    • 그래서 도커 전용 설정(IP 추가 or 강제 표시)이 필수
  • 예시

    • 상황
      • 사용자가 Django에게 택배를 보냄
    • 배송과정
      • 사용자(브라우저)가 아파트 경비실(Docker)에 택배를 맡깁니다. (주소: 127.0.0.1:8000)
      • 경비 아저씨가 택배를 들고 친구의 집(Container)으로 가서 문을 두드립니다.
      • 친구(Django)가 문을 열고 "누가 보냈어요?"라고 묻습니다.
      • 이때, 문 앞에 서 있는 건 님이 아니라 경비 아저씨(Docker Gateway IP)입니다.
    • 친구 입장에서 택배를 건네준 사람은 사용자(127.0.0.1)가 아니라 경비 아저씨(172.xx.xx.1)

구분브라우저 (나)의 시각Django (서버)의 시각
접속 주소http://127.0.0.1:8000(요청을 받음)
누가 요청했나?내가 (127.0.0.1) 보냄도커(172.17.0.1)가 전달해 줌
사이트 접속성공 (주소는 맞으니까)성공 (누가 줬든 페이지는 보여줌)
툴바 표시 여부당연히 보여야지?안 보여줌 (보낸 사람이 127.0.0.1이 아니네? 모르는 사람이다!)

profile
안녕하세요.

0개의 댓글