본문으로 건너뛰기

Model

모델은 애플리케이션에서 다루는 데이터의 구조와 관계를 정의합니다. 모델의 각 필드는 명확한 데이터 타입을 가져야 하고, null=Trueblank=True 옵션은 데이터 무결성과 애플리케이션 로직에 미치는 영향을 고려해 신중하게 사용합니다.

Meta 클래스

모델은 데이터베이스 상태를 있는 그대로 표현하고 있어야 합니다. 유니크 키 제약, 인덱스 정의, 기본 정렬 순서, 테이블 이름 등 모든 메타데이터를 작성합니다.

class Product(models.Model):
# ... fields ...

class Meta:
verbose_name = "상품"
verbose_name_plural = "상품 목록"
ordering = ['-price']

__str__ 함수

모든 모델에는 __str__ 함수를 구현해 Django Admin이나 디버깅 과정에서 각 객체를 사람이 읽기 쉬운 형태로 쉽게 식별할 수 있도록 합니다.

class Article(models.Model):
title = models.CharField(max_length=255, verbose_name="제목")
content = models.TextField(verbose_name="내용")

def __str__(self):
return f"[{self.title}] {self.content.slice[:100]}..."

외래 키 제약

우리는 데이터베이스 수준의 외래 키 제약(Foreign Key Constraint)을 사용하지 않는 것을 원칙으로 합니다. 모델에서 ForeignKey를 정의할 때 db_constraint=False 옵션을 명시적으로 설정합니다.

대신 데이터의 무결성은 애플리케이션 로직에서 보장해야 합니다. 예를 들어, 새로운 데이터를 생성하거나 기존 데이터를 수정할 때 연관된 객체의 존재 유무를 애플리케이션 코드 내에서 확인하고, 특정 데이터를 삭제할 경우 이와 연관된 다른 데이터들을 어떻게 처리할지에 대한 로직은 유즈케이스 내에 명확히 구현합니다.

class Order(models.Model):
user = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
related_name='orders',
db_constraint=False
)
created_at = models.DateTimeField(auto_now_add=True)

속성

모델의 기존 필드 값을 조합하여 파생되는 값이나, 특정 조건에 따라 계산되는 값은 @property 데코레이터를 사용하여 명사형 이름의 읽기 전용(read-only) 속성으로 제공합니다.

class Product(models.Model):
price = models.DecimalField(
max_digits=10,
decimal_places=2,
verbose_name="가격"
)
vat_rate = models.DecimalField(
max_digits=5,
decimal_places=2,
default=0.1,
verbose_name="부가세"
)

@property
def list_price(self):
return self.price * (1 + self.vat_rate)

Choices

모델에서 사용되는 choices는 대부분의 경우 공유되지 않으므로, 해당 모델 클래스 내부에 작성하도록 합니다.

예시

Good 👍

class ConversationEvaluationQuestion(TimeStampedModel):
class QuestionType(models.TextChoices):
STAR = "STAR", _("별점")
LIKERT = "LIKERT", _("척도")

question_type = models.CharField(
"문항 type",
default=QuestionType.LIKERT,
choices=QuestionType.choices,
max_length=32,
)

Bad 👎

class UserProfileApprovalLog(TimeStampedModel):
approval = models.IntegerField(
verbose_name=_("승인 상태"),
choices=constants.UserApprovalTypeChoices.choices,
default=constants.UserApprovalTypeChoices.AWAITING,
)

동작

모델 인스턴스 자체의 상태를 변경하는 로직은 동사로 시작하는 이름의 인스턴스 메서드로 작성합니다. 이 함수 내에서는 일반적으로 self.save()를 호출해 변경된 내용을 데이터베이스에 즉시 반영합니다.

@classmethod 데코레이터는 사용하지 않습니다. 모델과 관련된 모든 로직은 모델의 인스턴스 메서드 또는 별도의 UseCase 계층을 통해 처리하도록 합니다.

class Article(models.Model):
title = models.CharField(max_length=255, verbose_name="제목")
content = models.TextField(verbose_name="내용")
published_at = models.DateTimeField(null=True, blank=True, verbose_name="발행일")

def publish(self):
self.published_at = timezone.now()
self.save(update_fields=['published_at'])

QuerySet

커스텀 QuerySet은 정의하지 않습니다. 모든 데이터 조회 로직은 유즈케이스 혹은 Django ORM의 기본 objects 매니저를 사용해 작성합니다.