본문으로 건너뛰기

DB 스키마

Penta 백엔드의 전체 데이터베이스 스키마 문서이다. Django ORM 기반이며, 다국어 지원은 django-parler를 사용한다. 소프트 삭제는 django-safedelete로 처리한다.

  • 개발 환경: SQLite3
  • 운영 환경: PostgreSQL
  • ORM: Django ORM + django-parler (다국어) + django-safedelete (소프트 삭제)

1. 사용자 도메인

User (users_user)

사용자 모델. AbstractBaseUser + PermissionsMixin + SafeDeleteModel을 상속한다. USERNAME_FIELDid이다.

필드명타입설명
idBigAutoFieldPK
emailEmailField(255)이메일 (NULL 허용, 소셜 로그인 시 placeholder 생성)
usernameCharField(150)사용자명 (NULL 허용)
full_nameCharField(255)이름
nicknameCharField(100)닉네임 (UNIQUE, 인덱스)
social_providerCharField(50)소셜 로그인 제공자 (google, apple, kakao, line, facebook)
social_idCharField(255)소셜 로그인 고유 ID
languageCharField(10)언어 (기본: en)
countryCharField(2)국가 코드
birth_yearIntegerField출생 연도
has_subscribed_beforeBooleanField과거 구독 이력 여부 (기본: False)
is_activeBooleanField활성 상태 (기본: True)
is_staffBooleanField관리자 여부 (기본: False)
date_joinedDateTimeField가입일
last_loginDateTimeField마지막 로그인
app_languageCharField(10)앱 표시 언어 (기본: en)
viewer_languagesJSONField콘텐츠 언어 설정 (리스트)
push_notification_enabledBooleanField푸시 알림 마스터 토글 (기본: True)
notification_marketingBooleanField마케팅 알림 (기본: False)
notification_bookmarkBooleanField북마크 알림 (기본: True)
marketing_consentBooleanField마케팅 동의 (기본: False)
deletion_reasonCharField(50)탈퇴 사유 (not_used_often, expensive, lack_content, app_error, other)
deletedDateTimeField소프트 삭제 시각 (SafeDelete 자동 관리)

인덱스: email, (social_provider, social_id)

유니크 제약조건: (social_provider, social_id) - deleted가 NULL인 경우에만 (조건부 유니크)

주요 속성/메서드:

  • is_in_trial: 가입 후 3일 무료 체험 중인지 (has_subscribed_before=False일 때)
  • is_subscription_active: 구독 활성 여부 (구독 만료일 또는 무료 체험 확인)
  • subscription_type: 현재 구독 타입
  • subscription_end_date: 구독 종료일

UserDevice (users_device)

사용자 기기 정보. FCM 토큰을 저장하여 푸시 알림에 사용한다.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
device_idCharField(255)기기 고유 ID (UNIQUE)
device_typeCharField(50)기기 타입 (ios, android)
device_modelCharField(100)기기 모델명
app_versionCharField(20)앱 버전
timezoneCharField(50)타임존
fcm_tokenCharField(500)FCM 푸시 토큰 (인덱스)
is_activeBooleanField활성 상태 (기본: True)
push_enabledBooleanField푸시 허용 (기본: True)
last_activeDateTimeField마지막 활동 (auto_now)
created_atDateTimeField생성일 (auto_now_add)

인덱스: (user, last_active)


LoginHistory (users_loginhistory)

로그인 이력 기록.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
login_typeCharField(50)로그인 방식 (email, google, apple, kakao, line, facebook)
ip_addressGenericIPAddressFieldIP 주소
user_agentTextFieldUser-Agent
device_idFK(UserDevice)기기 (SET_NULL, NULL 허용)
created_atDateTimeField로그인 시각 (auto_now_add)

인덱스: (user, -created_at)


ReviewRequestLog (users_reviewrequestlog)

앱 리뷰 요청 노출 기록.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
device_idCharField(255)기기 ID
created_atDateTimeField생성일 (auto_now_add)

인덱스: (user, -created_at)


2. 콘텐츠 도메인

Book (books_book) + BookTranslation

도서 모델. TranslatableModel을 상속하며, 번역 필드와 비번역 필드가 분리된다.

비번역 필드 (books_book):

필드명타입설명
idBigAutoFieldPK
book_codeCharField(10)도서 코드 (UNIQUE, 예: A001)
lexile_labelCharField(50)렉사일 수준 레이블 (예: 400L - 600L)
age_labelCharField(50)연령대 레이블 (예: 2-5, 3-7)
brandCharField(20)브랜드 (disney, pixar)
statusCharField(20)상태 (ongoing, completed, hiatus)
viewsIntegerField조회수 (기본: 0)
is_activeBooleanField활성 상태 (기본: True)
average_ratingDecimalField(3,2)평균 평점 (기본: 0.00)
pv_scoreIntegerFieldPV 점수 (기본: 0)
series_idFK(BookSeries)시리즈 (SET_NULL)
lexile_filter_idFK(HomeFilter)렉사일 필터 (SET_NULL, 1:1)
created_atDateTimeField생성일

번역 필드 (books_book_translation):

필드명타입설명
titleCharField(500)제목
authorCharField(200)저자
illustratorCharField(200)일러스트레이터
publisherCharField(100)출판사
synopsisTextField시놉시스
cover_urlURLField(500)표지 URL
content_urlURLField(500)콘텐츠 URL
published_dateDateField출판일
is_newBooleanFieldNEW 뱃지 표시 여부

관계:

  • series: FK -> BookSeries (SET_NULL)
  • characters: M2M -> Character
  • illustrators: M2M -> Illustrator
  • age_filters: M2M -> HomeFilter (AGE_ 코드 필터)

인덱스: status, -views


Episode (books_episode) + EpisodeTranslation

에피소드 모델.

비번역 필드 (books_episode):

필드명타입설명
idBigAutoFieldPK
book_idFK(Book)도서 (CASCADE)
episode_numberIntegerField에피소드 번호
published_dateDateField출판일
viewsIntegerField조회수 (기본: 0)

번역 필드 (books_episode_translation):

필드명타입설명
titleCharField(500)제목
pagesJSONField페이지 URL/메타데이터 리스트

유니크 제약조건: (book, episode_number)

인덱스: -views


BookSeries (books_series) + Translation

필드명타입설명
idBigAutoFieldPK
display_orderIntegerField표시 순서 (기본: 0)
created_atDateTimeField생성일

번역 필드: name (CharField(200))

인덱스: display_order


Character (books_character) + Translation

필드명타입설명
idBigAutoFieldPK
character_keyCharField(100)고유 식별자 (UNIQUE, 예: disney_jasmine)
brandCharField(20)브랜드 (disney, pixar)
image_urlURLField(500)이미지 URL
display_orderIntegerField표시 순서 (기본: 0)
created_atDateTimeField생성일

번역 필드: character_name (CharField(200))

인덱스: display_order, character_key


Illustrator (books_illustrator) + Translation

필드명타입설명
idBigAutoFieldPK
profile_image_urlURLField(500)프로필 이미지
display_orderIntegerField표시 순서 (기본: 0)
created_atDateTimeField생성일
updated_atDateTimeField수정일 (auto_now)

번역 필드: name (CharField(200)), bio (TextField)

인덱스: display_order


ReadingHistory (books_readinghistory)

읽기 이력. 에피소드 단위로 진행률을 추적한다.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
book_idFK(Book)도서 (CASCADE)
episode_idFK(Episode)에피소드 (CASCADE)
last_pageIntegerField현재 페이지 번호 (기본: 0)
total_pagesIntegerField총 페이지 수 (기본: 0)
progress_percentageFloatField진행률 0.0~100.0 (기본: 0.0)
reading_timeIntegerField읽기 시간 (초, 기본: 0)
is_completedBooleanField완독 여부 (기본: False)
started_atDateTimeField시작일
last_read_atDateTimeField마지막 읽은 시각
completed_atDateTimeField완독 시각

유니크 제약조건: (user, book, episode)

인덱스: (user, -last_read_at), -last_read_at, (user, book, is_completed), (user, is_completed)


Bookmark (books_bookmark)

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
book_idFK(Book)도서 (CASCADE)
created_atDateTimeField생성일

유니크 제약조건: (user, book)


Recording (books_recording)

사용자 녹음 파일.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
book_idFK(Book)도서 (CASCADE)
episode_idFK(Episode)에피소드 (CASCADE, NULL 허용)
language_codeCharField(10)녹음 언어
file_urlURLField(500)파일 URL
durationIntegerField녹음 길이 (초)
created_atDateTimeField생성일

유니크 제약조건: (user, book, episode, language_code)

인덱스: (user, book), (user, episode)


DailyStat (books_dailystat)

일별 도서 통계.

필드명타입설명
idBigAutoFieldPK
stat_dateDateField통계 날짜
book_idFK(Book)도서 (CASCADE)
viewsIntegerField조회수 (기본: 0)
completionsIntegerField완독 수 (기본: 0)
bookmarksIntegerField북마크 수 (기본: 0)
stickers_earnedIntegerField스티커 획득 수 (기본: 0)

유니크 제약조건: (stat_date, book)

인덱스: (stat_date, -views)


3. 홈/배너/큐레이션

HomeFilter (home_homefilter) + Translation

홈 화면 필터 옵션 (탭, 시리즈, 읽기 수준).

필드명타입설명
idBigAutoFieldPK
filter_typeCharField(20)필터 타입 (tab, series, reading_level)
valueCharField(100)필터 값 (API 쿼리용)
codeCharField(50)고유 코드 (UNIQUE, 예: AGE_0_3, LEX_400_600, TAB_DISNEY)
display_orderIntegerField표시 순서 (기본: 0)
is_activeBooleanField활성 상태 (기본: True)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

번역 필드: name (CharField(100))

정렬: filter_type, display_order, id


RealtimeRanking (home_realtimeranking)

실시간 랭킹 (국가별, 시간별).

필드명타입설명
idBigAutoFieldPK
book_idFK(Book)도서 (CASCADE)
countryCharField(2)국가 코드 (KR, US, JP 등)
rankIntegerField순위 (1~10)
previous_rankIntegerField이전 순위 (NULL 허용)
scoreFloatField점수 (기본: 0)
ranking_dateDateField랭킹 날짜
ranking_hourIntegerField랭킹 시간 (0~23)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

유니크 제약조건: (book, country, ranking_date, ranking_hour)

인덱스: (country, ranking_date, ranking_hour, rank)

주요 속성: rank_change (new/up/down/same), rank_change_value (순위 변동 수치)


배너 모델.

비번역 필드:

필드명타입설명
idBigAutoFieldPK
link_typeCharField(20)링크 타입 (book, external, none, collection)
content_typeCharField(20)콘텐츠 타입 (book, event, collection)
sequenceIntegerField표시 순서 (기본: 0)
is_activeBooleanField활성 상태 (기본: True)
start_dateDateTimeField시작일
end_dateDateTimeField종료일 (NULL 허용)

번역 필드:

필드명타입설명
titleCharField(200)제목
image_urlURLField(500)배너 이미지 URL
link_urlURLField(500)링크 URL
target_typeCharField(20)언어별 콘텐츠 타입
content_idBigIntegerField언어별 Book ID
curation_idBigIntegerField언어별 Curation ID

인덱스: (is_active, start_date, end_date)


Curation (home_curation) + Translation

큐레이션/모아보기 모델.

필드명타입설명
idBigAutoFieldPK
curation_typeCharField(20)큐레이션 타입 (theme, collection)
display_orderIntegerField표시 순서 (기본: 0)
is_activeBooleanField활성 상태 (기본: True)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

번역 필드: title (CharField(200)), description (TextField)

관계: books M2M -> Book (through CurationItem)


CurationItem (home_curationitem)

큐레이션-도서 매핑 (through 테이블).

필드명타입설명
idBigAutoFieldPK
curation_idFK(Curation)큐레이션 (CASCADE)
book_idFK(Book)도서 (CASCADE)
display_orderIntegerField표시 순서 (기본: 999)
created_atDateTimeField생성일 (auto_now_add)

유니크 제약조건: (curation, book)


4. 스티커

Sticker (stickers_sticker) + Translation

에피소드 완독 시 획득하는 스티커. 에피소드와 1:1 관계이다.

필드명타입설명
idBigAutoFieldPK
episode_idOneToOne(Episode)에피소드 (CASCADE, UNIQUE)
image_urlURLField(500)스티커 이미지 URL
base_scoreFloatField사전 계산된 기본 점수 (기본: 0.0, 인덱스)
score_updated_atDateTimeField점수 갱신 시각
created_atDateTimeField생성일

번역 필드: name (CharField(200))

인덱스: (-base_score, -created_at)

점수 계산: 신규(+3) + 캠페인(+2) + 어제수집(+1) + 인기도(+0.1*수집자수)


UserSticker (stickers_usersticker)

사용자가 수집한 스티커.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
sticker_idFK(Sticker)스티커 (CASCADE)
earned_atDateTimeField획득 시각

유니크 제약조건: (user, sticker)

인덱스: (user, -earned_at), earned_at


StickerStats (stickers_stat)

스티커 수집 통계. 스티커와 1:1 관계이다.

필드명타입설명
idBigAutoFieldPK
sticker_idOneToOne(Sticker)스티커 (CASCADE)
total_collectorsIntegerField총 수집자 수 (기본: 0)
daily_collectorsIntegerField일간 수집자 수 (기본: 0)
weekly_collectorsIntegerField주간 수집자 수 (기본: 0)
monthly_collectorsIntegerField월간 수집자 수 (기본: 0)
last_updatedDateTimeField마지막 갱신 시각

인덱스: -total_collectors, -daily_collectors, -weekly_collectors, -monthly_collectors


인기 스티커 목록 (기간별, 국가별).

필드명타입설명
idBigAutoFieldPK
sticker_idFK(Sticker)스티커 (CASCADE)
positionIntegerField위치 (기본: 0)
popularity_scoreFloatField인기 점수 (기본: 0.0)
periodCharField(20)기간 (daily, weekly, monthly, all-time)
countryCharField(2)국가 코드 (NULL이면 글로벌)
created_atDateTimeField생성일
updated_atDateTimeField수정일 (auto_now)

유니크 제약조건: (sticker, period, country)

인덱스: (period, country, position), updated_at, -popularity_score


StickersMissing (stickers_missing)

사용자가 보유하지 않은 스티커 목록.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
sticker_idFK(Sticker)스티커 (CASCADE)
positionIntegerField위치 (기본: 0)
created_atDateTimeField생성일
updated_atDateTimeField수정일 (auto_now)

유니크 제약조건: (user, sticker)

인덱스: (user, position), updated_at


StickersUpcoming (stickers_upcoming)

공개 예정 스티커 목록.

필드명타입설명
idBigAutoFieldPK
sticker_idFK(Sticker)스티커 (CASCADE)
positionIntegerField위치 (기본: 0)
release_dateDateTimeField공개 예정일
is_featuredBooleanField추천 여부 (기본: False)
created_atDateTimeField생성일
updated_atDateTimeField수정일 (auto_now)

인덱스: (release_date, position), (is_featured, release_date), updated_at


StickerWishlist (stickers_wishlist)

사용자의 관심 스티커(찜) 목록.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
sticker_idFK(Sticker)스티커 (CASCADE)
created_atDateTimeField생성일

유니크 제약조건: (user, sticker)

인덱스: (user, created_at), (sticker, created_at)


StickerExposure (stickers_exposure)

유저에게 Popular/Missing 영역에서 노출된 스티커 기록. 최근 7일 내 노출 스티커에 -2점 적용.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
sticker_idFK(Sticker)스티커 (CASCADE)
exposure_typeCharField(20)노출 유형 (popular, missing)
exposed_atDateTimeField노출 시각

유니크 제약조건: (user, sticker, exposure_type) - 같은 조합은 하나만 유지, exposed_at만 갱신

인덱스: (user, exposed_at), exposed_at


StickerCampaign (stickers_campaign)

이벤트/캠페인과 스티커 연결. 캠페인 대상 스티커에 기본 +2점 부스트 적용.

필드명타입설명
idBigAutoFieldPK
sticker_idFK(Sticker)스티커 (CASCADE)
event_idFK(Event)이벤트 (CASCADE, NULL 허용)
campaign_nameCharField(200)캠페인 이름
is_activeBooleanField활성 상태 (기본: True)
priority_boostIntegerField우선순위 부스트 점수 (기본: 2)
start_dateDateTimeField시작일
end_dateDateTimeField종료일 (NULL 허용)
created_atDateTimeField생성일
updated_atDateTimeField수정일 (auto_now)

인덱스: (sticker, is_active), (start_date, end_date), (event, is_active)

주요 속성: is_ongoing - 현재 진행중인 캠페인인지 확인


5. 결제

Subscription (payments_subscription)

구독 모델. User와 1:1 관계이다.

필드명타입설명
idBigAutoFieldPK
user_idOneToOne(User)사용자 (CASCADE)
typeCharField(20)구독 타입 (1_month, 6_month, 12_month)
start_dateDateTimeField시작일
end_dateDateTimeField종료일 (구독 유효성의 주요 기준)
cancelled_atDateTimeField취소일 (NULL 허용)
is_cancelledBooleanField취소 여부 (기본: False, 자동갱신만 중지)
pause_untilDateTimeField일시정지 종료일 (NULL 허용)
grace_untilDateTimeField유예 기간 종료일 (NULL 허용)
auto_renewBooleanField자동갱신 (기본: False)
next_billing_dateDateTimeField다음 결제일
promo_codeCharField(50)적용된 프로모 코드
cancellation_reasonCharField(50)취소 사유
has_referral_bonusBooleanField래퍼럴 혜택 여부
referral_bonus_weeksIntegerField래퍼럴 보너스 주수 (기본: 0)
referral_minimum_period_daysIntegerField래퍼럴 최소 유지 기간 일수 (기본: 14)
referral_benefits_revokedBooleanField래퍼럴 혜택 회수 여부 (기본: False)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

인덱스: (user, is_cancelled, end_date), end_date

주요 속성/메서드:

  • effective_end: grace_until 고려한 실제 종료일
  • is_active: 현재 활성 여부 (일시정지, 만료 확인)
  • status: active / cancelled / paused / expired
  • cancel(reason): 취소 처리 (래퍼럴 혜택 회수 포함)
  • reduce_by_weeks(weeks): 구독 기간 단축

PaymentTransaction (payments_transaction)

결제 거래 기록.

필드명타입설명
idCharField(32)PK (수동 설정)
user_idFK(User)사용자 (CASCADE)
typeCharField(20)거래 타입 (subscription, renewal, refund, cancellation)
statusCharField(20)상태 (pending, completed, failed, refunded)
amountDecimalField(10,2)금액
currencyCharField(3)통화 (기본: USD)
payment_methodCharField(50)결제 수단 (기본: google_play)
gateway_transaction_idCharField(200)게이트웨이 거래 ID
purchase_tokenCharField(500)구매 토큰
metadataJSONField추가 데이터 (기본: )
external_idCharField(200)외부 ID
gatewayCharField(50)게이트웨이
created_atDateTimeField생성일 (auto_now_add)
processed_atDateTimeField처리일 (auto_now)

인덱스: (user, status), gateway_transaction_id, purchase_token


AppStoreTransaction (payments_app_store_transaction)

Apple App Store 거래 캐시. 멱등적 검증을 위한 모델.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
transaction_idCharField(200)거래 ID (UNIQUE)
original_transaction_idCharField(200)원본 거래 ID
product_idCharField(200)상품 ID
subscription_typeCharField(50)구독 타입
environmentCharField(20)환경 (Sandbox/Production)
app_account_tokenCharField(200)앱 계정 토큰
signed_atDateTimeField서명 시각
signed_transaction_jwsTextFieldJWS 서명 거래 데이터
purchase_dateDateTimeField구매일
expires_dateDateTimeField만료일
price_amountDecimalField(10,2)가격 (기본: 0.00)
price_currencyCharField(3)통화 (기본: USD)
auto_renew_statusBooleanField자동갱신 상태 (기본: True)
is_trial_periodBooleanField체험판 여부 (기본: False)
is_in_intro_offer_periodBooleanField소개 오퍼 여부 (기본: False)
ownership_typeCharField(20)소유 유형 (PURCHASED, FAMILY_SHARED)
raw_transactionJSONField원본 거래 JSON
raw_renewalJSONField원본 갱신 JSON
last_verified_atDateTimeField마지막 검증 시각
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

인덱스: transaction_id, original_transaction_id, (user, transaction_id), app_account_token


GooglePlayReceipt (payments_google_play_receipt)

Google Play v2 API 구독 영수증.

필드명타입설명
idBigAutoFieldPK
latest_order_idCharField(200)최신 주문 ID (UNIQUE)
purchase_tokenCharField(500)구매 토큰
linked_purchase_tokenCharField(500)연결된 구매 토큰 (토큰 교체 추적)
subscription_stateCharField(50)구독 상태 (ACTIVE, CANCELED, EXPIRED 등)
acknowledgement_stateCharField(50)확인 상태 (PENDING, ACKNOWLEDGED 등)
start_timeDateTimeField구독 시작일
expiry_timeDateTimeField만료/갱신 시점
price_amount_microsBigIntegerField가격 (micros, 1,000,000 = 1.00)
currency_codeCharField(3)통화 코드
region_codeCharField(2)지역 코드
product_idCharField(200)상품 ID
base_plan_idCharField(200)기본 플랜 ID
offer_idCharField(200)오퍼 ID
offer_tagsJSONField오퍼 태그 (리스트)
auto_renew_enabledBooleanField자동갱신 설정 (기본: True)
canceled_state_contextJSONField취소 사유/시점 (기본: )
paused_state_contextJSONField일시중지 사유/시점 (기본: )
raw_jsonJSONField원본 JSON 스냅샷
user_idFK(User)사용자 (CASCADE, NULL 허용)
transaction_idOneToOne(PaymentTransaction)거래 (CASCADE, NULL 허용)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

인덱스: latest_order_id, purchase_token, subscription_state, acknowledgement_state, start_time, expiry_time, product_id


RefundRequest (payments_refundrequest)

환불 요청 및 추적.

필드명타입설명
idBigAutoFieldPK
transaction_idFK(PaymentTransaction)거래 (CASCADE)
user_idFK(User)사용자 (CASCADE)
statusCharField(20)상태 (pending, approved, rejected, completed, failed)
refund_typeCharField(30)환불 유형 (full, partial, subscription_cancel)
reasonCharField(30)사유 (user_request, technical_issue, billing_error 등)
reason_descriptionTextField상세 사유
google_refund_idCharField(200)Google 환불 ID (UNIQUE)
google_order_idCharField(200)Google 주문 ID
purchase_tokenCharField(500)구매 토큰
revoke_entitlementBooleanField즉시 접근 취소 여부 (기본: True)
initiated_by_idFK(User)환불 처리 관리자 (SET_NULL)
metadataJSONField추가 데이터 (기본: )
error_messageTextField에러 메시지
created_atDateTimeField생성일 (auto_now_add)
processed_atDateTimeField처리 시작일
completed_atDateTimeField완료일

인덱스: status, google_refund_id, google_order_id, purchase_token, created_at


6. 프로모코드/레퍼럴

PromoCode (promocodes_promocode) + Translation

프로모션 코드 모델.

필드명타입설명
idBigAutoFieldPK
codeCharField(50)프로모 코드 (UNIQUE)
typeCharField(20)타입 (corporate, influencer, referral)
nameCharField(100)프로모션 이름
descriptionTextField프로모션 설명
bonus_weeksIntegerField보너스 주수 (기본: 0)
max_usesIntegerField최대 사용 횟수 (NULL이면 무제한)
current_usesIntegerField현재 사용 횟수 (기본: 0)
one_time_onlyBooleanField1회 사용 제한 (기본: False)
new_users_onlyBooleanField신규 사용자 전용 (기본: False)
valid_fromDateTimeField유효 시작일
valid_untilDateTimeField유효 종료일 (NULL 허용)
is_activeBooleanField활성 여부 (기본: True)
subscription_typesJSONField적용 가능 구독 타입 (리스트)
is_referral_stackableBooleanField래퍼럴 중복 가능 (기본: False)
referrer_bonus_weeksIntegerField추천인 보너스 주수
offer_mappingJSONField플랜별 offer ID 매핑 (기본: )
discount_mappingJSONField플랜별 할인율(%) 매핑 (기본: )
ios_offer_codeCharField(100)iOS App Store offer 코드
qr_code_urlURLFieldQR 코드 URL
partner_nameCharField(100)협력사 이름
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

번역 필드: banner_text (CharField(200)) - 프로모 배너 텍스트

인덱스: (code, is_active), type, (valid_from, valid_until)

주요 속성/메서드: remaining_uses, is_valid, can_be_used_by_user(user), apply_to_subscription(type, price)


PromoCodeUsage (promocodes_usage)

프로모 코드 사용 내역.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
promo_code_idFK(PromoCode)프로모 코드 (CASCADE)
subscription_idFK(Subscription)구독 (CASCADE, NULL 허용)
used_atDateTimeField사용 시각 (auto_now_add)
ip_addressGenericIPAddressFieldIP 주소
user_agentTextFieldUser-Agent
applied_bonus_weeksIntegerField적용된 보너스 주수 (기본: 0)

유니크 제약조건: (user, promo_code) - 사용자당 프로모 코드 1회 사용

인덱스: (promo_code, -used_at), (user, -used_at)


ReferralCode (promocodes_referral)

래퍼럴 코드 (추천인 시스템). PromoCode와 1:1 관계이다.

필드명타입설명
idBigAutoFieldPK
referrer_idFK(User)추천인 (CASCADE)
promo_code_idOneToOne(PromoCode)연결된 프로모 코드 (CASCADE)
total_referralsIntegerField총 추천 수 (기본: 0)
successful_referralsIntegerField성공한 추천 수 (기본: 0)
created_atDateTimeField생성일 (auto_now_add)

AppliedPromoCode (promocodes_applied)

임시 적용된 프로모 코드 (IAP 결제 전 임시 저장). User와 1:1 관계이다.

필드명타입설명
idBigAutoFieldPK
user_idOneToOne(User)사용자 (CASCADE)
promo_code_idFK(PromoCode)프로모 코드 (CASCADE)
created_atDateTimeField생성일 (auto_now_add)
expires_atDateTimeField만료 시각

ReferralReward (promocodes_referral_reward)

래퍼럴 리워드 내역.

필드명타입설명
idBigAutoFieldPK
referral_code_idFK(ReferralCode)래퍼럴 코드 (CASCADE)
user_idFK(User)수혜자 (CASCADE)
reward_typeCharField(20)리워드 유형 (referrer: 추천인, referee: 피추천인)
bonus_weeksIntegerField추가 구독 주수
subscription_idFK(Subscription)구독 (CASCADE)
statusCharField(20)상태 (pending, applied, revoked)
awarded_atDateTimeField지급 시각 (auto_now_add)
applied_atDateTimeField실제 적용 시각
revoked_atDateTimeField회수 시각
revoke_reasonTextField회수 사유

주요 메서드: revoke(reason) - 리워드 회수, 구독 기간 차감 포함


PartnerPromotion (promocodes_partner_promotion)

파트너 프로모션 링크 관리.

필드명타입설명
idBigAutoFieldPK
codeCharField(20)짧은 URL 코드 (UNIQUE)
partner_nameCharField(100)파트너 이름
promo_code_idFK(PromoCode)프로모 코드 (CASCADE, NULL 허용)
landing_pageURLField파트너 전용 랜딩 페이지
custom_messageTextField파트너 전용 메시지
custom_image_urlURLField파트너 이미지 URL
is_activeBooleanField활성 상태 (기본: True)
total_clicksIntegerField총 클릭 수 (기본: 0)
total_conversionsIntegerField총 전환 수 (기본: 0)
total_viewsIntegerField총 조회 수 (기본: 0)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

인덱스: code, partner_name

주요 속성: conversion_rate - 전환율 (%)


PromoCodeClick (promocodes_click_tracking)

프로모션 클릭 추적.

필드명타입설명
idBigAutoFieldPK
promo_code_idFK(PromoCode)프로모 코드 (CASCADE, NULL 허용)
partner_promotion_idFK(PartnerPromotion)파트너 프로모션 (SET_NULL, NULL 허용)
ip_addressGenericIPAddressFieldIP 주소
user_agentTextFieldUser-Agent
refererURLField리퍼러 URL
utm_sourceCharField(100)UTM 소스
utm_mediumCharField(100)UTM 매체
utm_campaignCharField(100)UTM 캠페인
utm_termCharField(100)UTM 키워드
utm_contentCharField(100)UTM 콘텐츠
device_typeCharField(20)기기 유형 (mobile, tablet, desktop)
osCharField(50)운영체제
browserCharField(50)브라우저
clicked_atDateTimeField클릭 시각 (auto_now_add)
convertedBooleanField전환 여부 (기본: False)
converted_atDateTimeField전환 시각

인덱스: (promo_code, -clicked_at), (partner_promotion, -clicked_at), (converted, -clicked_at)


7. 이벤트

Event (events_news) + Translation

이벤트/뉴스 모델.

비번역 필드:

필드명타입설명
idBigAutoFieldPK
typeCharField(20)유형 (event, news)
image_urlURLField대표 이미지 URL
start_dateDateTimeField시작일
end_dateDateTimeField종료일 (NULL 허용)
button_actionCharField(20)버튼 동작 (subscribe, external_link, deeplink, none)
button_action_valueCharField(500)버튼 동작 값
button_colorCharField(7)버튼 색상 (기본: #660FD1)
target_audienceCharField(20)대상 (all, guests, new_users, non_subscribers, subscribers)
sequenceIntegerField표시 순서 (기본: 0)
is_activeBooleanField활성 상태 (기본: True)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

번역 필드:

필드명타입설명
titleCharField(200)제목
contentTextField내용
subtitleCharField(200)부제목
bodyTextField본문
notesJSONField참고사항 (리스트)
hero_image_urlURLField(500)히어로 이미지 URL
button_textCharField(100)버튼 텍스트

정렬: -start_date

주요 속성: is_ongoing - 현재 진행중인지 확인


EventParticipation (events_news_participations)

이벤트 참여 기록.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
event_idFK(Event)이벤트 (CASCADE)
participated_atDateTimeField참여 시각 (auto_now_add)
completedBooleanField완료 여부 (기본: False)
completed_atDateTimeField완료 시각

유니크 제약조건: (user, event)


8. 알림

Notification (notifications_notification) + Translation

알림 모델.

필드명타입설명
idBigAutoFieldPK
typeCharField(50)유형 (user_content: 유저소식, penta_news: 팬타소식)
subtypeCharField(50)서브타입 (sticker_release, inquiry_response, feature_update, brand_news, event_promotion)
action_typeCharField(50)동작 유형 (book_detail, event_news, event_event, inquiry_detail)
target_idBigIntegerField대상 ID (book_id 또는 event_id)
created_atDateTimeField생성일

번역 필드: title (CharField(500)), message (TextField)

인덱스: -created_at, (type, subtype)


UserNotification (notifications_usernotification)

사용자별 알림 수신 상태.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
notification_idFK(Notification)알림 (CASCADE)
is_readBooleanField읽음 여부 (기본: False)
received_atDateTimeField수신 시각

유니크 제약조건: (user, notification)

인덱스: (user, is_read, -received_at)


PushMessageTemplate (notifications_push_message_template) + Translation

푸시 메시지 템플릿.

필드명타입설명
idBigAutoFieldPK
push_typeCharField(30)푸시 유형 (trial_reminder, sticker_release, weekly_update, inactive_routine, brand_news, event_promotion, feature_update)
is_activeBooleanField활성 상태 (기본: True)
created_atDateTimeField생성일 (auto_now_add)

번역 필드: title (CharField(200)), body (TextField)

인덱스: (push_type, is_active)


PushLog (notifications_push_log)

푸시 발송 기록.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
push_typeCharField(30)푸시 유형
template_idFK(PushMessageTemplate)템플릿 (SET_NULL, NULL 허용)
sent_atDateTimeField발송 시각 (auto_now_add)
statusCharField(10)상태 (success, failed)

인덱스: (user, push_type, -sent_at), (user, -sent_at)


9. 지원

FAQ (support_faq) + Translation

자주 묻는 질문.

필드명타입설명
idBigAutoFieldPK
categoryCharField(20)카테고리 (account, payment, content, technical, other)
orderIntegerField표시 순서 (기본: 0)
is_activeBooleanField활성 상태 (기본: True)
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

번역 필드: question (CharField(500)), answer (TextField)

정렬: category, order, -created_at


Inquiry (support_inquiry)

1:1 문의.

필드명타입설명
idBigAutoFieldPK
user_idFK(User)사용자 (CASCADE)
typeCharField(20)유형 (bug, payment, content, account, suggestion, other)
subjectCharField(200)제목
messageTextField내용
statusCharField(20)상태 (pending, in_progress, resolved, closed)
admin_responseTextField관리자 응답
responded_atDateTimeField응답 시각
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

정렬: -created_at


Announcement (support_announcement) + Translation

공지사항.

필드명타입설명
idBigAutoFieldPK
is_activeBooleanField활성 상태 (기본: True)
published_atDateTimeField게시일
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

번역 필드: title (CharField(200)), content (TextField)

정렬: -published_at


10. 블로그

Post (blog_post) + Translation

블로그 포스트.

비번역 필드:

필드명타입설명
idBigAutoFieldPK
slugSlugField(200)URL 슬러그 (UNIQUE)
author_idFK(User)작성자 (SET_NULL)
category_idFK(Category)카테고리 (SET_NULL)
statusCharField(20)상태 (draft, published, archived)
is_featuredBooleanField메인 노출 여부 (기본: False)
thumbnailImageField썸네일 이미지
viewsPositiveIntegerField조회수 (기본: 0)
published_atDateTimeField게시일
created_atDateTimeField생성일 (auto_now_add)
updated_atDateTimeField수정일 (auto_now)

번역 필드:

필드명타입설명
titleCharField(200)제목
excerptTextField요약 (목록 표시용)
contentTextField본문 (HTML 또는 Markdown)
meta_titleCharField(70)SEO 제목
meta_descriptionCharField(160)SEO 설명

관계: tags M2M -> Tag

인덱스: (status, -published_at), (is_featured, status), -views


Category (blog_category) + Translation

블로그 카테고리.

필드명타입설명
idBigAutoFieldPK
slugSlugField(100)URL 슬러그 (UNIQUE)
display_orderPositiveIntegerField표시 순서 (기본: 0)
is_activeBooleanField활성 상태 (기본: True)
created_atDateTimeField생성일 (auto_now_add)

번역 필드: name (CharField(100)), description (TextField)


Tag (blog_tag) + Translation

블로그 태그 (해시태그).

필드명타입설명
idBigAutoFieldPK
slugSlugField(50)URL 슬러그 (UNIQUE, 자동 생성)
post_countPositiveIntegerField사용된 포스트 수 (캐시, 기본: 0)
created_atDateTimeField생성일 (auto_now_add)

번역 필드: name (CharField(50))

정렬: -post_count, slug


BlogImage (blog_image)

블로그 콘텐츠 내 이미지.

필드명타입설명
idBigAutoFieldPK
post_idFK(Post)포스트 (CASCADE, NULL 허용)
imageImageField이미지 파일 (WebP 변환 저장)
original_filenameCharField(255)원본 파일명
file_sizePositiveIntegerField파일 크기 (bytes, 기본: 0)
created_atDateTimeField생성일 (auto_now_add)

PostView (blog_post_view)

포스트 조회 기록 (어뷰징 방지). 동일 IP에서 1시간 내 중복 조회 시 views 카운트 미증가.

필드명타입설명
idBigAutoFieldPK
post_idFK(Post)포스트 (CASCADE)
ip_addressGenericIPAddressFieldIP 주소
session_keyCharField(40)세션 키
user_idFK(User)사용자 (SET_NULL, NULL 허용)
user_agentCharField(500)User-Agent
viewed_atDateTimeField조회 시각 (auto_now_add)

인덱스: (post, ip_address, viewed_at), (post, session_key, viewed_at), -viewed_at

주요 클래스 메서드: can_count_view(), record_view(), cleanup_old_records(days=7)


기타

AppConfig (app_config)

앱 동적 설정 (key-value).

필드명타입설명
idBigAutoFieldPK
keyCharField(50)설정 키 (UNIQUE, 인덱스, 예: min_required_version)
valueCharField(100)설정 값 (NULL이면 비활성)
descriptionTextField설명
updated_atDateTimeField수정일 (auto_now)
created_atDateTimeField생성일 (auto_now_add)

캐싱: 5분 캐시 (get_value(key) 메서드)