이전 글
- Docker Compose 생성기
- Django + Nginx + Docker 서버 구축 전체 구조 정리
- 2편 Docker 설치 방법 총정리 (윈도우 / 리눅스 / 시놀로지 NAS)
- 3편 Django Docker Compose 배포 구성 (Nginx + PostgreSQL 완벽 가이드)
- 4편 Django 프로젝트 생성부터 Production 설정까지|PyCharm + Static 파일 처리
- 5편: Django PostgreSQL 연결 방법|Docker Compose 운영 DB 설정 가이드

6편 Django models.py 설계|DB 테이블 만들기와 마이그레이션 흐름 정리
이전 편에서는 Django와 PostgreSQL을 연결해서 실제 운영 데이터베이스를 사용할 수 있는 구조를 만들었습니다.
이번 편에서는 Django 프로젝트에서 가장 중요한 파일 중 하나인 models.py에 대해 정리해보겠습니다.
Django에서 models.py는 단순한 Python 파일이 아니라, 데이터베이스 테이블의 구조를 정의하는 설계도입니다.
models.py에 작성한 Python 클래스가 어떻게 실제 PostgreSQL 테이블로 만들어지는지, 그리고 makemigrations와 migrate가 어떤 역할을 하는지 이해하는 것입니다.
models.py는 언제 작성해야 할까?
Django 프로젝트를 처음 만들면 바로 models.py를 작성할 수도 있습니다. 하지만 실제로는 데이터베이스 연결 구조를 먼저 잡아두고 models.py를 작성하는 것이 좋습니다.
특히 Docker + PostgreSQL 환경에서는 다음 순서가 자연스럽습니다.
- Django 프로젝트 생성
- Docker Compose 구성
- PostgreSQL 연결
- settings.py 데이터베이스 설정
- models.py 작성
- makemigrations 실행
- migrate 실행
PostgreSQL 연결이 끝난 뒤 models.py를 작성하면, 내가 만든 모델 구조가 실제 운영 DB 테이블로 반영되는 흐름을 바로 확인할 수 있습니다.
Django 모델이란?
Django 모델은 데이터베이스 테이블을 Python 코드로 표현한 것입니다.
예를 들어 블로그 게시글을 저장하려면 제목, 내용, 작성일 같은 데이터가 필요합니다. 이 구조를 Django에서는 아래처럼 Python 클래스로 작성합니다.
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
이 코드는 단순한 클래스처럼 보이지만, 마이그레이션을 실행하면 실제 데이터베이스에는 게시글 테이블이 생성됩니다.
모델과 데이터베이스 테이블의 관계
Django에서는 모델 클래스 하나가 보통 데이터베이스 테이블 하나에 대응됩니다.
Post클래스 → 게시글 테이블title필드 → 제목 컬럼content필드 → 내용 컬럼created_at필드 → 작성일 컬럼
즉, models.py는 SQL을 직접 작성하지 않고도 데이터베이스 테이블을 만들 수 있게 해주는 역할을 합니다.
ORM이란?
Django 모델을 이해하려면 ORM이라는 개념을 알아두면 좋습니다.
ORM은 Object Relational Mapping의 약자로, Python 객체와 데이터베이스 테이블을 연결해주는 방식입니다.
SQL을 직접 작성하지 않아도 Python 코드로 데이터를 저장하고 조회할 수 있습니다. Django에서는 models.py에 구조를 만들고, QuerySet을 통해 데이터를 다룰 수 있습니다.
예시 1. 블로그 카테고리 모델 만들기
블로그를 만든다고 가정하면 게시글보다 먼저 카테고리 모델을 만들 수 있습니다.
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=120, unique=True)
order = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["order", "name"]
def __str__(self):
return self.name
이 모델은 블로그 카테고리를 저장하기 위한 구조입니다.
name: 카테고리 이름slug: URL에 사용할 고유 문자열order: 정렬 순서is_active: 사용 여부created_at: 생성일
예시 2. 게시글 모델 만들기
이제 카테고리에 연결되는 게시글 모델을 만들 수 있습니다.
from django.db import models
class Post(models.Model):
STATUS_CHOICES = [
("draft", "임시저장"),
("published", "발행"),
]
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="posts"
)
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=220, unique=True)
summary = models.CharField(max_length=300, blank=True)
content = models.TextField()
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="draft")
views = models.PositiveIntegerField(default=0)
is_pinned = models.BooleanField(default=False)
published_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["-is_pinned", "-published_at", "-created_at"]
def __str__(self):
return self.title
이 모델은 실제 블로그 게시글에 가까운 구조입니다. 제목, 내용, 발행 상태, 조회수, 고정 여부, 작성일, 수정일 등을 저장할 수 있습니다.
ForeignKey는 무엇일까?
게시글 모델에서 가장 중요한 부분은 category 필드입니다.
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="posts"
)
이 코드는 게시글이 하나의 카테고리에 연결될 수 있다는 의미입니다.
- 카테고리 1개 → 여러 게시글 연결 가능
- 게시글 1개 → 하나의 카테고리 선택 가능
이런 관계를 데이터베이스에서는 1:N 관계라고 부릅니다.
on_delete 옵션 이해하기
ForeignKey를 사용할 때는 on_delete 옵션을 반드시 지정해야 합니다.
models.CASCADE: 연결된 카테고리가 삭제되면 게시글도 함께 삭제models.SET_NULL: 카테고리가 삭제되면 게시글의 카테고리 값을 비움models.PROTECT: 연결된 게시글이 있으면 카테고리 삭제 방지
블로그 게시글은 카테고리가 삭제되더라도 글 자체는 남겨두는 것이 좋기 때문에
위 예시에서는 SET_NULL을 사용했습니다.
중요한 데이터가 연결된 모델에는 무조건 CASCADE를 사용하는 것보다, SET_NULL 또는 PROTECT를 고려하는 것이 안전합니다.
자주 사용하는 필드 정리
| 필드 | 용도 |
|---|---|
CharField |
짧은 문자열 저장 |
TextField |
긴 본문 저장 |
IntegerField |
정수 저장 |
PositiveIntegerField |
0 이상의 정수 저장 |
BooleanField |
True / False 값 저장 |
DateTimeField |
날짜와 시간 저장 |
SlugField |
URL용 문자열 저장 |
ForeignKey |
다른 모델과 1:N 관계 연결 |
마이그레이션 파일 만들기
models.py를 작성했다면 Django에게 변경사항을 알려줘야 합니다.
이때 사용하는 명령어가 makemigrations입니다.
docker compose exec web python manage.py makemigrations
이 명령어를 실행하면 Django는 models.py의 변경 내용을 분석해서 마이그레이션 파일을 생성합니다.
예를 들어 아래와 같은 파일이 생성될 수 있습니다.
blog/migrations/0001_initial.py
마이그레이션 적용하기
마이그레이션 파일을 만들었다면 이제 실제 PostgreSQL 데이터베이스에 반영해야 합니다.
docker compose exec web python manage.py migrate
이 명령어를 실행하면 Django가 PostgreSQL에 실제 테이블을 생성합니다.
models.py 작성 → makemigrations 실행 → migration 파일 생성 → migrate 실행 → PostgreSQL 테이블 생성
마이그레이션 상태 확인하기
어떤 마이그레이션이 적용되었는지 확인하려면 아래 명령어를 사용할 수 있습니다.
docker compose exec web python manage.py showmigrations
적용된 마이그레이션은 체크 표시가 되어 있습니다.
Django Admin에 모델 등록하기
모델을 만들고 마이그레이션까지 완료했다면 관리자 페이지에서 데이터를 확인할 수 있도록
admin.py에 등록해야 합니다.
from django.contrib import admin
from .models import Category, Post
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ("id", "name", "slug", "order", "is_active", "created_at")
list_filter = ("is_active",)
search_fields = ("name", "slug")
prepopulated_fields = {"slug": ("name",)}
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("id", "title", "category", "status", "views", "is_pinned", "created_at")
list_filter = ("status", "is_pinned", "category")
search_fields = ("title", "summary", "content")
prepopulated_fields = {"slug": ("title",)}
이렇게 등록하면 Django 관리자 페이지에서 카테고리와 게시글 데이터를 직접 추가하고 수정할 수 있습니다.
관리자 페이지 확인하기
superuser 계정이 없다면 먼저 생성합니다.
docker compose exec web python manage.py createsuperuser
이후 아래 주소로 접속합니다.
https://도메인주소/admin/
관리자 페이지에서 Category와 Post 메뉴가 보인다면 모델 등록이 정상적으로 완료된 것입니다.
모델 수정 시 주의사항
models.py는 한 번 만들고 끝나는 파일이 아닙니다. 서비스를 개발하다 보면 필드를 추가하거나 삭제하는 일이 자주 발생합니다.
모델을 수정했다면 다시 마이그레이션을 만들어야 합니다.
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
단, 운영 중인 서비스에서는 모델 변경이 실제 데이터베이스 구조 변경으로 이어지기 때문에 주의해야 합니다.
이미 데이터가 들어간 테이블에서 필드를 삭제하거나 null을 허용하지 않는 필드를 추가하면 오류가 발생할 수 있습니다. 운영 DB에서는 마이그레이션 전에 반드시 백업을 고려하는 것이 좋습니다.
자주 발생하는 오류
1. no such table 오류
모델은 만들었지만 migrate를 실행하지 않았을 때 발생할 수 있습니다.
docker compose exec web python manage.py migrate
2. app is not installed 오류
앱을 만들고 models.py를 작성했지만 settings.py의 INSTALLED_APPS에 앱을 등록하지 않았을 때 발생할 수 있습니다.
INSTALLED_APPS = [
...
"blog",
]
3. column does not exist 오류
models.py에는 필드가 추가되어 있지만 실제 DB에는 아직 반영되지 않았을 때 발생할 수 있습니다.
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
4. IntegrityError 오류
unique 조건이 중복되거나, null을 허용하지 않는 필드에 값이 없을 때 발생할 수 있습니다.
이 경우 필드 옵션을 확인해야 합니다.
null=True
blank=True
unique=True
null=True와 blank=True 차이
Django 모델을 작성하다 보면 null=True와 blank=True를 자주 사용합니다.
두 옵션은 비슷해 보이지만 의미가 다릅니다.
null=True: 데이터베이스에 NULL 저장 허용blank=True: 폼이나 관리자 화면에서 빈 값 허용
예를 들어 선택 입력값으로 만들고 싶다면 둘 다 사용하는 경우가 많습니다.
summary = models.CharField(max_length=300, null=True, blank=True)
다만 문자열 필드에서는 보통 null=True보다 blank=True만 사용하는 경우도 많습니다.
이번 편 정리
이번 글에서는 Django의 models.py가 어떤 역할을 하는지, 그리고 models.py에 작성한 코드가 실제 PostgreSQL 테이블로 반영되는 흐름을 정리했습니다.
- models.py는 데이터베이스 테이블 설계도
- Django 모델은 Python 클래스로 작성
- ORM을 통해 SQL 없이 데이터 조작 가능
- ForeignKey로 모델 간 관계 설정 가능
- makemigrations는 변경 파일 생성
- migrate는 실제 DB 반영
- admin.py에 등록하면 관리자 페이지에서 데이터 관리 가능
여기까지 진행하면 Django 프로젝트는 단순히 화면을 띄우는 수준을 넘어서 실제 데이터를 저장하고 관리할 수 있는 웹 애플리케이션 구조로 발전하게 됩니다.
다음 편 예고
다음 편에서는 models.py로 만든 데이터를 실제 화면에 출력하기 위해 views.py, urls.py, templates 구조를 연결하는 방법을 정리해보겠습니다.
첫 댓글을 남겨보세요.