본문으로 건너뛰기
김신건의 로그

[Django] MTV 패턴과 Model 기본

· 수정 · 📖 약 2분 · 628자/단어 #python #django #mvc #orm #model
django-mvc-model-bindingresult, Django MVC, Django MTV, Django Model, Django MTV 패턴, Django 모델

정의

Django 는 MTV (Model, Template, View) 패턴 사용. 다른 프레임워크의 MVC 와 이름만 다르고 개념은 유사.

MVCDjango MTV역할
ModelModel (ORM)데이터 구조 + DB 접근
ViewTemplate표현 (HTML 렌더링)
ControllerView (function/class)요청 처리 + 비즈니스 로직

IMPORTANT

Django 의 “View” 는 MVC 의 “Controller” 에 해당한다. 이름이 역전되어 있어 혼동 주의.

MTV 흐름

flowchart LR
    Browser -->|"HTTP Request"| URLConf["urls.py\n(URL Dispatcher)"]
    URLConf -->|"Route"| View["views.py\n(View / Controller)"]
    View -->|"QuerySet"| Model["models.py\n(Model / ORM)"]
    Model -->|"SQL"| DB[("Database")]
    DB -->|"Rows"| Model
    Model -->|"Objects"| View
    View -->|"Context"| Template["templates/\n(Template / View)"]
    Template -->|"HTML"| Browser

URL Dispatcher 가 정규식/경로 패턴으로 View 를 찾고, View 가 Model 에 쿼리 후 Template 에 렌더링을 위임한다.

MVC vs MTV 비교

전통 MVC 와 Django MTV 는 동일 아키텍처지만 용어가 다르다.

flowchart TB
    subgraph "전통 MVC"
        C["Controller"] --> M1["Model"]
        C --> V1["View"]
        M1 --> V1
    end
    subgraph "Django MTV"
        DV["View\n(views.py)"] --> DM["Model\n(models.py)"]
        DV --> DT["Template\n(templates/)"]
        DM --> DT
    end

Model 정의

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='posts')
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    published = models.BooleanField(default=False)
    slug = models.SlugField(unique=True)

    class Meta:
        ordering = ['-created']
        verbose_name = 'post'
        verbose_name_plural = 'posts'
        indexes = [
            models.Index(fields=['slug']),
            models.Index(fields=['author', '-created']),
        ]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        from django.urls import reverse
        return reverse('post_detail', kwargs={'slug': self.slug})

관계 필드

ForeignKey (N:1)

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True)
    body = models.TextField()

on_delete 옵션:

동작
CASCADE부모 삭제 시 자식도 삭제
PROTECT자식 존재 시 부모 삭제 차단 (ProtectedError)
SET_NULLNULL 로 설정 (null=True 필요)
SET_DEFAULT기본값으로 설정
DO_NOTHINGDB 에 맡김 (외래 키 제약 위반 가능)

OneToOneField (1:1)

class Profile(models.Model):
    user = models.OneToOneField('auth.User', on_delete=models.CASCADE, related_name='profile')
    bio = models.TextField(blank=True)
    avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)

user.profile 으로 역참조. 상속 대신 프로필 확장에 자주 쓰임.

ManyToManyField (N:M)

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

class Post(models.Model):
    ...
    tags = models.ManyToManyField(Tag, blank=True, related_name='posts')

중간 테이블 자동 생성. 추가 필드가 필요하면 through= 사용:

class PostTag(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
    added_by = models.ForeignKey('auth.User', on_delete=models.SET_NULL, null=True)
    added_at = models.DateTimeField(auto_now_add=True)

class Post(models.Model):
    tags = models.ManyToManyField(Tag, through=PostTag, related_name='posts')

주요 필드 타입

필드설명예시
CharField짧은 문자열max_length=200
TextField긴 텍스트블로그 본문
IntegerField정수조회수
FloatField / DecimalField실수 / 고정소수점가격, 평점
BooleanFieldTrue/Falsedefault=False
DateField / DateTimeField날짜/시각auto_now_add, auto_now
SlugFieldURL-friendly 문자열unique=True
EmailField이메일 검증 포함
URLFieldURL 검증 포함
FileField / ImageField파일/이미지upload_to='photos/'
JSONFieldJSON 저장 (PostgreSQL 최적화)
UUIDFieldUUIDdefault=uuid.uuid4

마이그레이션

python manage.py makemigrations
python manage.py migrate

자세히: django-migrations

CRUD

# Create
post = Post.objects.create(title="Hello", body="...", author=user, slug="hello")

# Read
post = Post.objects.get(pk=1)                             # 없으면 DoesNotExist
post = Post.objects.get(slug="hello")
posts = Post.objects.filter(published=True).order_by('-created')[:10]
posts = Post.objects.select_related('author').prefetch_related('tags')   # N+1 방지

# Update
Post.objects.filter(author=user).update(published=True)   # 한 번의 SQL
post.title = "Updated"
post.save()
post.save(update_fields=['title'])    # 지정 필드만

# Delete
post.delete()
Post.objects.filter(published=False).delete()
# ForeignKey 정의: author = models.ForeignKey('auth.User', related_name='posts')
user.posts.all()                     # User 의 모든 Post
user.posts.filter(published=True)

# related_name 미지정 시 기본: post_set
# user.post_set.all()

# M2M 역참조
tag.posts.all()                      # 태그에 달린 모든 포스트

N+1 쿼리 방지.

# ForeignKey, OneToOne → SQL JOIN (1 쿼리)
Post.objects.select_related('author', 'author__profile')

# ManyToMany, 역참조 ForeignKey → 별도 IN 쿼리 (2 쿼리)
Post.objects.prefetch_related('tags', 'comments__author')

# 조합
Post.objects.select_related('author').prefetch_related('tags')

자세히: django-orm-advanced

View + Form + Template 흐름

# views.py
from django.shortcuts import render, redirect
from .forms import PostForm
from .models import Post

def post_create(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm()
    return render(request, 'post_form.html', {'form': form})

Form 이 검증 (validation) + 정제 (cleaning) 담당. form.is_valid() 는 Django 의 binding + validation 관용구.

Model Manager

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(published=True)

class Post(models.Model):
    objects = models.Manager()          # 기본
    published = PublishedManager()      # 커스텀

# 사용
Post.published.all()                    # published=True 만
Post.published.order_by('-created')[:5]

함정

WARNING

auto_now vs auto_now_add: auto_now 는 저장할 때마다 갱신, auto_now_add 는 생성 시 1회만. editable=False 이므로 form 에서 직접 설정 불가.

WARNING

N+1 쿼리: for post in Post.objects.all(): post.author.username 은 Post 수만큼 추가 쿼리 발생. select_related('author') 로 해결.

CAUTION

get() 예외: 결과가 0개면 DoesNotExist, 2개 이상이면 MultipleObjectsReturned. filter().first() 를 쓰거나 get_object_or_404() 활용.

from django.shortcuts import get_object_or_404
post = get_object_or_404(Post, slug='hello')

관련 위키

이 글의 용어 (9개)
[Django] 개요와 프로젝트 구조django
정의 Django는 2003년 시작된 Python 풀스택 웹 프레임워크. "Web framework for perfectionists with deadlines"라는 모토대로 b…
[Django] 마이그레이션: makemigrations, migrate, 데이터 마이그레이션django
정의 Django 마이그레이션은 모델 변경을 DB 스키마 변경으로 변환·기록하는 시스템. 는 모델과 현재 마이그레이션 상태를 비교해 새 마이그레이션 파일 생성. 는 DB에 적용.…
[Django] 테스트: TestCase, Client, factory_boydjango
정의 Django는 표준 라이브러리 기반 TestCase를 제공한다. 트랜잭션 격리, fixtures, test client, DB 셋업 자동화. 실무는 + 가 사실상 표준. 기…
[Django] Form과 ModelFormdjango
정의 Django Form은 사용자 입력 정의·검증·렌더링·바인딩을 통합. 일반 은 임의 필드, 은 모델 자동 매핑. 검증과 보안(CSRF, SQL 인젝션 방지) 내장. Form…
[Django] Model과 Field 타입django
정의 Django 모델은 DB 테이블의 Python 표현이다. 상속, 클래스 속성 = 컬럼, 메타 클래스 = 테이블 설정. 모델 정의 후 + 로 DB 스키마 자동 생성. 기본 정…
[Django] ORM 심화: N+1, prefetch, Subquery, Windowdjango
정의 Django ORM은 SQL을 잘 모르는 개발자에게도 안전하지만 비효율을 숨기기 쉽다. N+1, 비효율 JOIN, 큰 결과셋 메모리 폭발이 흔한 함정. selectrelat…
[Django] QuerySet과 Managerdjango
정의 QuerySet은 DB 쿼리를 lazy하게 빌드하는 객체. , 같은 메서드를 체이닝해도 실제 SQL 실행은 평가 시점( , , , slice 등)에 한 번 발생. Manag…
[Django] View: 함수 기반 vs 클래스 기반django
정의 Django View는 HTTP 요청을 받아 응답을 반환하는 callable. 두 스타일이 공존. - FBV (Function-Based View): 일반 함수. 간단하고 …
Djangodjango
정의 Django 는 2003년 시작된 Python 풀스택 웹 프레임워크. batteries-included (ORM, 관리자, 인증, 폼, 마이그레이션, 캐싱 등 기본 내장)와…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기