📮 3. Django ORM을 활용하여 Model 생성하기

2025. 6. 9. 16:53·프로젝트
목차
  1. 🔸 프로젝트 생성
  2. ✏️ 처음 작성한 models.py
  3. ✅ 지금은
  4. ✅ 배포할 때
  5. 🔸 admin.py 작성

📚 2. Django ORM을 활용하여 Model 생성하기 


1️⃣ 프로젝트 초기 세팅

🔸 프로젝트 생성

django-admin startproject account-book .
python manage.py startapp accountbook

2️⃣ 모델 작성 (models.py)

✏️ 처음 작성한 models.py

처음 models.py를 작성할 때, 우리는 다음처럼 만들었어:

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.utils import timezone
from .constants import (
    BANK_CODES, ACCOUNT_TYPE, TRANSACTION_TYPE,
    TRANSACTION_METHOD, ANALYSIS_TYPES, ANALYSIS_ABOUT
)

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('이메일은 필수입니다')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_admin', True)
        extra_fields.setdefault('is_active', True)
        return self.create_user(email, password, **extra_fields)

class CustomUser(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(max_length=255, unique=True)
    nickname = models.CharField(max_length=50, blank=True)
    name = models.CharField(max_length=50, blank=True)
    phone_number = models.CharField(max_length=20, blank=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    last_login = models.DateTimeField(auto_now=True)
    date_joined = models.DateTimeField(default=timezone.now)

    objects = CustomUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        db_table = 'users'

class Account(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='accounts')
    account_number = models.CharField(max_length=30)
    bank_code = models.CharField(max_length=3, choices=BANK_CODES, default='000')
    account_type = models.CharField(max_length=20, choices=ACCOUNT_TYPE, default='CHECKING')
    balance = models.DecimalField(max_digits=15, decimal_places=2, default=0)

    class Meta:
        db_table = 'accounts'

class TransactionHistory(models.Model):
    account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='transactions')
    transaction_amount = models.DecimalField(max_digits=15, decimal_places=2)
    post_transaction_amount = models.DecimalField(max_digits=15, decimal_places=2)
    transaction_details = models.CharField(max_length=255)
    transaction_type = models.CharField(max_length=20, choices=TRANSACTION_TYPE)
    transaction_method = models.CharField(max_length=20, choices=TRANSACTION_METHOD)
    transaction_timestamp = models.DateTimeField()

    class Meta:
        db_table = 'transaction_history'

class Analysis(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='analyses')
    analysis_target = models.CharField(max_length=20, choices=ANALYSIS_ABOUT)
    analysis_period = models.CharField(max_length=20, choices=ANALYSIS_TYPES)
    start_date = models.DateField()
    end_date = models.DateField()
    description = models.TextField(blank=True)
    result_image = models.ImageField(upload_to='analysis_results/', blank=True, null=True)

    class Meta:
        db_table = 'analysis'

class Notification(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='notifications')
    message = models.TextField()
    is_read = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'notifications'

3️⃣ constants.py 작성

모델에서 사용하기 위해 각종 상수 데이터 (choices) 파일도 만들었어.

BANK_CODES = [
    ("000", "알수없음"),
    ("001", "한국은행"),
    ("002", "산업은행"),
    # ...(생략)...
    ("090", "카카오뱅크"),
    ("092", "토스뱅크"),
]

ACCOUNT_TYPE = [
    ("CHECKING", "입출금"),
    ("SAVING", "적금"),
    ("LOAN", "대출"),
]

TRANSACTION_TYPE = [
    ("DEPOSIT", "입금"),
    ("WITHDRAW", "출금"),
]

TRANSACTION_METHOD = [
    ("ATM", "ATM 거래"),
    ("TRANSFER", "계좌이체"),
    ("CARD", "카드결제"),
]

ANALYSIS_TYPES = [
    ("DAILY", "일간"),
    ("WEEKLY", "주간"),
    ("MONTHLY", "월간"),
]

ANALYSIS_ABOUT = [
    ("TOTAL_SPENDING", "총 지출"),
    ("TOTAL_INCOME", "총 수입"),
]

Media 파일 설정 settings.py

result_image 필드 썼으니까 settings.py에 아래 추가 필요:

 
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
 

(배포할 때는 AWS S3나 별도 스토리지 쓰면 됨.)

 

# config/urls.py

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    # your urls
]

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

 

✅ 지금은

  • 그냥 BASE_DIR / media에 저장해도 돼. (개발이니까)

✅ 배포할 때

  • AWS S3 같은 외부 스토리지에 저장해야 해.
  • Django storages 라이브러리 설치하고, S3 설정 연결.
pip install django-storages[boto3]
 

그리고 settings에 이렇게 S3 세팅:

# settings.py

# S3 설정
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

AWS_ACCESS_KEY_ID = 'your-aws-access-key'
AWS_SECRET_ACCESS_KEY = 'your-aws-secret-key'
AWS_STORAGE_BUCKET_NAME = 'your-bucket-name'
AWS_QUERYSTRING_AUTH = False  # public access

📝 pyproject.toml 파일 설정 루트폴더

[tool.black]
line-length = 88
target-version = ['py311']  # 너가 쓰는 Python 버전 맞춰 (ex. 3.12이면 py312)
skip-string-normalization = true
include = '\.pyi?$'

[tool.isort]
profile = "black"
line_length = 88
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
combine_as_imports = true
known_first_party = ["accountbook"]  # 너 프로젝트 app 이름
skip_gitignore = true

 

4️⃣ Pillow 설치 문제

🛑 ImageField 때문에 Pillow가 필요했는데 처음엔 설치가 안 되어 있었어.

  • 로컬에서:
pip install Pillow
  • Docker에서는 requirements.txt에 추가했어:
Django==5.2.2
Pillow
psycopg2

그리고 도커 재빌드:

 
docker-compose up -d --build

5️⃣ settings.py 분리

개발환경/배포환경 분리해서 세팅했지.

  • settings/base.py: 공통
  • settings/dev.py: 로컬용 (localhost, 직접 깔린 Postgres 연결)
  • settings/prod.py: Docker용 (my-db 서비스 연결)

⚙️ manage.py 수정
→ 환경변수 기반으로 dev/prod 자동선택:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", os.getenv("DJANGO_SETTINGS_MODULE", "config.settings.dev"))

 

⚙️ docker-compose.yml 수정
→ 환경변수 기반으로 prod 자동선택:

    environment:
      - DJANGO_SETTINGS_MODULE=config.settings.prod

 

6️⃣ 로컬 마이그레이션

✅ makemigrations

python manage.py makemigrations accountbook --settings=config.settings.dev

✅ migrate

python manage.py migrate --settings=config.settings.dev

7️⃣ 마이그레이션 충돌 발생

🛑 에러

InconsistentMigrationHistory:
Migration admin.0001_initial is applied before its dependency accountbook.0001_initial

📚 원인: 기존 admin 테이블은 migrate 됐고 accountbook은 안 된 상태.

🛠️ 해결:

  • 로컬 Postgres DB Drop
  • 새로 생성
DROP DATABASE "django-postgres";
CREATE DATABASE "django-postgres" WITH OWNER "postgres";

다시 migrate:

python manage.py migrate --settings=config.settings.dev

 


8️⃣ Django Admin 설정

Django Admin 설정

🔸 admin.py 작성

from django.contrib import admin
from .models import CustomUser, Account, TransactionHistory, Analysis, Notification

@admin.register(CustomUser)
class CustomUserAdmin(admin.ModelAdmin):
    list_display = ('email', 'nickname', 'is_admin', 'is_active')
    search_fields = ('email', 'nickname')
    list_filter = ('is_admin', 'is_active')
    ordering = ('email',)

@admin.register(Account)
class AccountAdmin(admin.ModelAdmin):
    list_display = ('user', 'account_number', 'bank_code', 'account_type', 'balance')

@admin.register(TransactionHistory)
class TransactionHistoryAdmin(admin.ModelAdmin):
    list_display = ('account', 'transaction_amount', 'transaction_type', 'transaction_timestamp')

@admin.register(Analysis)
class AnalysisAdmin(admin.ModelAdmin):
    list_display = ('user', 'analysis_target', 'analysis_period', 'start_date', 'end_date')

@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
    list_display = ('user', 'message', 'is_read', 'created_at')
  • admin.py에 등록 완료
  • 슈퍼유저 생성:
python manage.py createsuperuser --settings=config.settings.dev

9️⃣ Docker 환경 마이그레이션

✅ 도커컴포즈 올림

docker-compose up -d --build

✅ 컨테이너 접속

docker exec -it django bash

✅ migrate

python manage.py migrate

1️⃣0️⃣ Docker 마이그레이션 충돌

🛑 또 에러 발생:

InconsistentMigrationHistory:
Migration admin.0001_initial is applied before its dependency accountbook.0001_initial

🛠️ 해결:

  • docker-compose down -v 로 볼륨 초기화
  • docker-compose up -d --build 다시 빌드/실행
  • 다시 migrate 성공

📝 정리

항목상태
models.py 작성 ✅
constants.py 작성 ✅
Pillow 설치 ✅
settings 분리 (base/dev/prod) ✅
manage.py 환경변수 분기 처리 ✅
로컬 마이그레이션 ✅
Migration 충돌 해결 (DB Drop) ✅
Admin 설정 및 슈퍼유저 생성 ✅
Docker 환경 마이그레이션 ✅
Docker 볼륨 초기화 후 migrate 성공 ✅

'프로젝트' 카테고리의 다른 글

📌 [3단계] DRF(Django REST Framework) 설치 및 Docker 적용  (0) 2025.06.09
📮4. Django DB 연결 대기 커스텀 커맨드 제작 및 GitHub Actions 적용기  (0) 2025.06.09
📮 3. ERD를 구성 Mermid 활용  (0) 2025.06.09
📮2. 데이터베이스 스키마 설계  (1) 2025.06.09
📮 1. ERD(Entity Relationship Diagram) 작성하기  (0) 2025.06.09
  1. 🔸 프로젝트 생성
  2. ✏️ 처음 작성한 models.py
  3. ✅ 지금은
  4. ✅ 배포할 때
  5. 🔸 admin.py 작성
'프로젝트' 카테고리의 다른 글
  • 📌 [3단계] DRF(Django REST Framework) 설치 및 Docker 적용
  • 📮4. Django DB 연결 대기 커스텀 커맨드 제작 및 GitHub Actions 적용기
  • 📮 3. ERD를 구성 Mermid 활용
  • 📮2. 데이터베이스 스키마 설계
Chansman
Chansman
안녕하세요! 코딩을 시작한 지 얼마 되지 않은 초보 개발자 찬스맨입니다. 이 블로그는 제 학습 기록을 남기고, 다양한 코딩 실습을 통해 성장하는 과정을 공유하려고 합니다. 초보자의 눈높이에 맞춘 실습과 팁, 그리고 개발하면서 겪은 어려움과 해결 과정을 솔직하게 풀어내려 합니다. 함께 성장하는 개발자 커뮤니티가 되기를 바랍니다.
  • Chansman
    찬스맨의 프로그래밍 스토리
    Chansman
  • 전체
    오늘
    어제
    • 분류 전체보기 (792)
      • Python (32)
      • 프로젝트 (112)
      • 과제 (25)
      • Database (40)
      • 멘토링 (11)
      • 특강 (37)
      • 기술블로그 (41)
      • 기술블로그-Fastapi편 (33)
      • 기술블로그-Django편 (153)
      • 기술블로그-Flask편 (36)
      • AI 분석 (5)
      • HTML & CSS (31)
      • JavaScript (17)
      • AWS_Cloud (21)
      • 웹스크래핑과 데이터 수집 (14)
      • Flask (42)
      • Django (77)
      • Fastapi (16)
      • 연예 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    livebroadcast
    americanlaw
    urbantrends
    titaniumcase
    btscomeback
    lawsuitculture
    btsreunion
    youngprofessionals
    global politics
    trumpmuskclash
    remittance
    life reflection
    btsdischarge
    travel ban
    RM
    chinanightlife
    basalcellcarcinoma
    americaparty
    hotcoffeecase
    smartphonedurability
    newpoliticalparty
    college reunions
    chatgpterror
    뷔
    self-growth
    homebartrend
    bts
    gpterror
    classaction
    btsjungkook
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Chansman
📮 3. Django ORM을 활용하여 Model 생성하기

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.