Django - Model

김기훈·2026년 4월 6일

Django

목록 보기
17/17

매개변수


on_delete

  • Django 모델에서 외래 키(ForeignKey)나 일대일 필드(OneToOneField)를 정의할 때
    • 참조하고 있는 부모 객체가 삭제될 경우 연결된 자식 객체들을 어떻게 처리할지 결정하는 필수 매개변수

CASCADE

부모가 삭제되면, 연관된 자식도 전부 삭제된다.

  • 사용처: 게시글이 삭제되면 해당 게시글의 댓글도 모두 삭제되어야 할 때 주로 사용
user = models.ForeignKey(User, on_delete=models.CASCADE)
- 1. User가 삭제되면 → 해당 User의 Account/Transaction 등 모두 삭제됨.
- 2. 가장 위험하지만, 가장 많이 쓰임.

PROTECT

참조된 객체를 삭제하려고 할 때, 참조하는 객체가 하나라도 존재하면 삭제를 막고 ProtectedError 예외를 발생시킴

  • 사용처: 결제 내역이 있는 사용자를 실수로 삭제하는 것을 방지할 때 유용
bank = models.ForeignKey(Bank, on_delete=models.PROTECT)
- 1. Bank에 연결된 Account가 하나라도 있으면 → Bank 삭제 시도 → 에러 발생 (ProtectedError)
- 2. 실수로 중요한 데이터가 삭제되는 것을 방지할 때 사용.

SET_NULL

부모가 삭제되면 자식의 ForeignKey 값을 NULL로 변경

  • 이 옵션을 사용하려면 모델 필드에 반드시 null=True 설정이 포함되어야 함
  • 사용처: 작성자가 탈퇴하더라도 그 작성자가 쓴 글은 "알 수 없음" 상태로 남겨두고 싶을 때 사용
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
- 1. Category 삭제 → Transaction.category = NULL로 변경
- 2. Transaction 자체는 삭제되지 않음 / 이 옵션은 null=True가 반드시 필요함

SET_DEFAULT

부모가 삭제되면 자식의 ForeignKey 값을 default 값으로 변경

  • 필드에 default 값이 설정되어 있어야 함
  • 사용처: 카테고리가 삭제되었을 때 해당 글들을 기본 카테고리(예: '미분류')로 이동시킬 때 사용
category = models.ForeignKey(
    Category,
    on_delete=models.SET_DEFAULT,
    default=1
)
- 1. Category(3)가 삭제됨 → 해당 카테고리 사용하던 Transaction은 모두 category=1(기본값)로 변경됨.

SET()

부모 삭제 시, 특정 함수나 값을 직접 지정해서 설정

  • 사용처: 삭제된 사용자의 글을 특정 '탈퇴한 사용자 전용 더미 계정'에 할당할 때 사용
def get_deleted_category():
    return Category.objects.get(name="삭제됨")

category = models.ForeignKey(
    Category,
    on_delete=models.SET(get_deleted_category)
)

- 1. Category가 삭제되면 category가 “삭제됨”이라는 카테고리로 자동 변경됨.
- 2. 데이터 복구/추적을 위해 자주 쓰는 방법.

DO_NOTHING

부모 삭제 시 자식에게 아무 처리도 하지 않음

  • 주의
    • 데이터베이스 백엔드에서 참조 무결성을 강제하고 있다면, 데이터베이스 수준에서 IntegrityError가 발생 가능
    • 매우 특수한 상황이 아니라면 사용을 권장하지 않음
user = models.ForeignKey(User, on_delete=models.DO_NOTHING)

- 1. User 삭제 → Transaction에 user_id가 남아있음 → FK 제약 때문에 DB 오류 발생 가능
- 2. 거의 사용하지 않음 (에러 위험)

RESTRICT (Django 3.1+)

부모가 삭제되기 전에 연관된 자식이 있으면 삭제를 금지

  • PROTECT와 유사하게 RestrictedError를 발생시켜 삭제를 막지만
    • 다른 CASCADE 관계에 의해 함께 삭제되는 상황에서는 삭제를 허용
product = models.ForeignKey(Product, on_delete=models.RESTRICT)

- 1. PROTECT와 비슷하지만 예외 메시지가 더 명확함
- 2. Product가 사용 중이면 삭제 불가 / PROTECT보다 더 명확한 제어 가능

예시

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

# SET() 옵션에서 사용할 함수 정의 (삭제된 사용자의 소유권을 이전할 더미 사용자 반환)
def get_deleted_user_instance():
    # 'deleted_user'라는 유저를 가져오거나 없으면 생성하여 반환합니다.
    return User.objects.get_or_create(username='deleted_user')[0]

class Category(models.Model):
    name = models.CharField(max_length=100)

class Article(models.Model):
    """
    CASCADE 예시: 
    작성자(User)가 삭제되면 이 게시글(Article)도 데이터베이스에서 함께 삭제됩니다.
    """
    author = models.ForeignKey(
        User, 
        on_delete=models.CASCADE 
    )
    
    title = models.CharField(max_length=200)
    content = models.TextField()

class Comment(models.Model):
    """
    SET_NULL 예시: 
    게시글(Article)이 삭제되더라도 댓글 데이터 자체는 남기고 싶을 때 사용합니다.
    article_id 값은 비워집니다(NULL). null=True 설정이 필수입니다.
    """
    article = models.ForeignKey(
        Article, 
        on_delete=models.SET_NULL, 
        null=True 
    )
    text = models.CharField(max_length=500)

class Payment(models.Model):
    """
    PROTECT 예시: 
    결제 내역은 법적 보존 의무가 있을 수 있으므로, 
    Payment 객체를 가진 User를 삭제하려고 시도하면 에러를 발생시키고 삭제를 차단합니다.
    """
    user = models.ForeignKey(
        User, 
        on_delete=models.PROTECT
    )
    amount = models.DecimalField(max_digits=10, decimal_places=2)

class Document(models.Model):
    """
    SET() 예시:
    문서의 소유자가 삭제되었을 때, 문서를 삭제하거나 NULL로 두는 대신
    사전에 정의한 특정 사용자(get_deleted_user_instance)에게 소유권을 넘깁니다.
    """
    owner = models.ForeignKey(
        User,
        on_delete=models.SET(get_deleted_user_instance)
    )
    file_path = models.CharField(max_length=255)

참조 무결성

정의

  • 외래 키(Foreign Key) 값이 항상 유효한 부모 테이블의 기본 키(Primary Key)를 가리켜야 한다는 DB규칙

왜 필요?

  • 만약 Article(게시글) 테이블이 User(사용자) 테이블의 특정 ID를 참조하고 있는데
    • 해당 사용자가 삭제되어 데이터베이스에 존재하지 않게 된다면 데이터의 모순(고아 레코드 발생)이 생김

Django의 역할

  • Django의 on_delete 매개변수는 바로 이 데이터베이스 레벨의
    • 참조 무결성 제약 조건(Constraint)을 애플리케이션 레벨에서 어떻게 안전하게 처리할 것인지 정의하는 도구

역참조

정의

  • Django에서 ForeignKey를 정의하면
    • 기본적으로 '자식 객체(참조하는 쪽)'에서 '부모 객체(참조되는 쪽)'로 접근할 수 있음
    • 예: article.author
  • 하지만 그 반대 방향인 '부모 객체'에서 '자식 객체들'을 조회하는 것을 역참조(Reverse Relation)라고 함

모델 예시

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Article(models.Model):
    # 1. related_name을 설정하지 않은 기본 상태
    # 역참조 시 자동 생성되는 이름: article_set
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)

class Comment(models.Model):
    # 2. related_name을 'comments'로 명시적으로 설정한 상태
    # 역참조 시 사용할 이름: comments
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
    text = models.CharField(max_length=500)

실무에서의 데이터 조회(Query) 예시

# 특정 유저 객체를 가져옵니다.
my_user = User.objects.get(id=1)

# [기본 역참조] 해당 유저가 작성한 모든 게시글(Article) 가져오기
# 공식: 참조된_모델명_소문자_set
user_articles = my_user.article_set.all() 

# 특정 게시글 객체를 가져옵니다.
my_article = Article.objects.get(id=1)

# [related_name 활용] 해당 게시글에 달린 모든 댓글(Comment) 가져오기
# related_name='comments'로 설정했기 때문에 직관적인 접근이 가능합니다.
# 만약 related_name이 없었다면 my_article.comment_set.all()을 써야 합니다.
article_comments = my_article.comments.all()

옵션

null=True

  • 검사 기준: 데이터베이스 스키마

  • 동작 계층 (Layer): Database

  • 설명

    • 데이터베이스 레벨에서 해당 컬럼에 NULL 값(빈 값)이 저장되는 것을 허용할지 결정함
    • 문자열 기반 필드(CharField, TextField)에서는 사용을 지양함
      • 빈 문자열 ''과 NULL 두 가지 빈 상태를 가지게 되기 때문

blank=True

  • 검사 기준: 폼(Form) 유효성 검사

  • 동작 계층 (Layer): Application

  • 설명

    • 애플리케이션(Django Form, Admin) 레벨에서 입력 폼을 비워두는 것을 허용할지 결정
    • 데이터베이스 스키마에는 영향을 주지 않음

  • ForeignKey(또는 OneToOne, ManyToMany) 를 통해 역참조 할 때 사용하는 이름을 직접 지정하는 옵션
  • 즉, "부모 → 자식" 방향의 조회 이름을 바꿔주는 것

사용하는 이유

  • Django는 ForeignKey를 만들면 자동으로 역참조 이름을 만들어 주는데
    • related_name을 사용하면 역참조 이름을 훨씬 직관적이고 깔끔하게 만듬
class Transaction(models.Model):
    account = models.ForeignKey(Account, on_delete=models.CASCADE)

- 1. 이 경우 Django는 자동으로 account.transaction_set 이라는 이름을 만들어줌
- 2. 조회방법: account.transaction_set.all()

class Transaction(models.Model):
    account = models.ForeignKey(Account, on_delete=models.CASCADE, 
    related_name="transactions")

- 1. 역참조 이름을 깔끔하게 만들어서 아래 처럼 사용 가능
- 2. account.transactions.all() 
  • 동일 모델에 여러 ForeignKey가 있을 때 충돌을 피함
class Message(models.Model):
    sender = models.ForeignKey(User, on_delete=models.CASCADE)
    receiver = models.ForeignKey(User, on_delete=models.CASCADE)
    
- 1. 이렇게 사용시 에러 발생
  - Django는 자동으로 user.message_set을 만듬
  - 하지만, sender도 user고 receiver도 user라서 충돌이 발생

class Message(models.Model):
    sender = models.ForeignKey(User, on_delete=models.CASCADE, 
    related_name="sent_messages")
    receiver = models.ForeignKey(User, on_delete=models.CASCADE, 
    related_name="received_messages")
    
- 1. 이렇게 사용하면 
- 2. user.sent_messages.all() / user.received_messages.all()
  • Serializer, View에서 직관적 코드 작성 가능
    • user.transactions.filter(...) = 의미가 바로 전달
    • user.transaction_set.filter(...) = 직관성이 확 떨어짐
class Transaction(models.Model):
    account = models.ForeignKey(
        Account,
        on_delete=models.PROTECT,
        related_name="transactions",
    )

- 1. account.transactions.all() 로 최근 거래 조회 가능

verbose_name

  • 모델 필드(또는 모델 전체)의 “사람이 읽기 쉬운 이름”을 지정하는 옵션
    • 개발자가 쓰는 “코드 이름(field name)” 이 아닌 관리자(admin)나 폼(form)에서 표시할 때 사용
      • 즉, "유저가 보는 필드 이름"
created_at = models.DateTimeField(auto_now_add=True)

- 1. Admin 화면: Created at
  - 자동으로 snake_case → Title Case 로 바꿔서 보임

created_at = models.DateTimeField("생성 날짜", auto_now_add=True)

- 1. Django admin이나 폼: 생성 날짜

비유

  • '작가(Author)'와 그 작가가 쓴 '책(Book)'의 관계
    • 정방향 (Forward): 책 ➔ 작가
      • 책의 입장에서 "내 작가가 누구지?" 하고 찾는 것은 쉬움
      • Book 모델 안에 author라는 필드(외래 키)가 있기 때문 (book.author)
    • 역방향 (Reverse): 작가 ➔ 책
      • 작가의 입장에서 "내가 쓴 책이 뭐뭐 있지?" 하고 찾으려고 보면 문제가 생김
      • Author 모델 안에는 책과 관련된 필드가 전혀 적혀있지 않기 때문
  • 이때 Django ORM은 작가가 자신의 책들을 찾을 수 있도록 마법처럼 보이지 않는 '뒷문'을 하나 만들어 줌
    • 이 뒷문의 이름표를 개발자가 직접 예쁘게 지어주는 것이 바로 related_name

정의

  • Django 공식 문서의 ForeignKey.related_name 항목에서는 이를 다음과 같이 정의
    • "The name to use for the relation from the related object back to this one."
    • 관계된 객체(부모)에서 현재 객체(자식)로 되돌아오는 역참조 관계에 사용할 이름
  • 개발자가 이 이름표(related_name)를 직접 지어주지 않으면
    • Django는 규칙에 따라 임의로 이름표를 달아버림
    • 그 규칙이 바로 참조하는_모델명소문자_set (예: book_set)

예시

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Book(models.Model):
    # related_name이 없습니다.
    # Django가 자동으로 'book_set'이라는 역참조 이름을 만듭니다.
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)

# ==========================================
# [데이터 조회 시점]
# ==========================================
jk_rowling = User.objects.get(name='J.K. Rowling')

# ❓ 작가의 책들을 가져오기 위해 'book_set'이라는 자동 생성 이름을 써야 합니다.
# 문맥상 "조앤 롤링의 book_set"은 영문법이나 직관성에 맞지 않습니다.
her_books = jk_rowling.book_set.all()
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class Book(models.Model):
    # related_name='books'로 지정했습니다.
    # 이제 역참조 시 'books'라는 깔끔한 이름을 사용합니다.
    author = models.ForeignKey(
        User, 
        on_delete=models.CASCADE,
        related_name='books' # 이 외래 키의 역방향 이름은 'books'다!
    )
    title = models.CharField(max_length=200)

# ==========================================
# [데이터 조회 시점]
# ==========================================
jk_rowling = User.objects.get(name='J.K. Rowling')

# 💡 작가의 책들을 가져올 때 훨씬 직관적이고 자연스럽습니다.
# "조앤 롤링의 books를 모두(all) 가져와라" -> 읽기 쉬운 코드가 됩니다.
her_books = jk_rowling.books.all()

  • 하나의 모델 안에서 같은 모델을 두 번 이상 외래 키로 참조할 때

class Post(models.Model):
    title = models.CharField(max_length=100)
    
    # 1. 게시글 작성자
    author = models.ForeignKey(
        User, 
        on_delete=models.CASCADE, 
        related_name='written_posts' # 작가 입장에서: 내가 "작성한 글들"
    )
    
    # 2. 게시글 수정자
    editor = models.ForeignKey(
        User, 
        on_delete=models.SET_NULL, 
        null=True,
        related_name='edited_posts' # 수정자 입장에서: 내가 "수정한 글들"
    )
    • Django는 author의 역참조 이름도 post_set으로 만들려 하고
    • editor의 역참조 이름도 post_set으로 만들려다가 "이름이 충돌했습니다!" 라며
    • SystemCheckError를 발생시키고 실행을 멈춤
      • 따라서 각각의 관계가 어떤 의미인지 related_name을 통해 명확히 분리해 주어야 함


profile
안녕하세요.

0개의 댓글