본문 바로가기
개발/Django

[Django] Designing Better Models

by 유다110 2019. 5. 12.
반응형

이 문서는 Designing Better Models 를 번역한 것입니다.

 



더 나은 Django 모델을 디자인하기 위한 몇 가지 팁을 공유하려 합니다. 특히 이 글에서 많은 비중을 차지하는 명명법에 관련된 팁은 당신의 코드를 훨씬 읽기 쉽게 만들어 줄 것입니다.


프로젝트를 진행할 땐 파이썬 생태계에서 널리 쓰이고 있는 PEP8을 따르는 것이 좋지만, 전 PEP8 외에도 Django 개발자를 위한 Django's Coding Style 또한 선호합니다.

우리가 살펴볼 항목들은 이렇습니다.

 

  • Model 이름 짓기
  • Model 정렬
  • 역관계
  • Blank와 Null 필드

 


 

Model 이름 짓기


모델은 클래스로 정의되므로 항상 CapWords를 사용해야 합니다.(_ 없이) User, Permission, ContentType처럼요.
모델의 어트리뷰트에는 first_name, last_name과 같은 snake_case를 사용합니다.

예시:

from django.db import models

class Company(models.Model):
    name = models.CharField(max_length=30)
    vat_identification_number = models.CharField(max_length=20)


모델명은 항상 단수형으로 작성합니다. Companies 대신 Company를 사용하세요. 모델을 정의하는 것은 하나의 오브젝트(이 경우엔 하나의 company)에 대한 표현이지 companies의 컬렉션을 가리키는 것이 아닙니다.

물론 이는 가끔 혼돈을 야기합니다. 왜냐하면 모델은 데이터베이스 테이블로 간주되곤 하기 때문이죠. 하나의 모델은 하나의 테이블로 옮겨지니까요. 모델과 달리 테이블은 오브젝트의 컬렉션을 나타내기 때문에 복수형을 사용하여 명명하는 것이 좋습니다.

Django 모델에서는 Company.objects를 통해 오브젝트에 접근할 수 있는데, 이때 models.Managerobjects를 다르게 이름 지을 수 있습니다.

from django.db import models

class Company(models.Model):
    # ...
    companies = models.Manager()


이렇게 하면 Company.companies.filter(name='Google')과 같은 식으로 companies의 컬렉션에 접근할 수 있습니다. 하지만 저는 일관성을 위해 objects 어트리뷰트를 유지하는 것을 선호하기에 이 방법은 거의 쓰지 않습니다.



Model 정렬

 

Django Coding Style은 내부 클래스, 함수, 어트리뷰트에 대해 다음과 같은 순서를 추천합니다.

 

  • 만약 고정된 모델 필드를 위한 choices가 있다면, 모든 선택사항들을 tuple of tuples로 정의합니다.
    모든 선택사항의 이름은 클래스명처럼 대문자여야 합니다.
  • 모든 데이터베이스 필드들
  • 커스텀 manager 어트리뷰트들
  • class Meta
  • def __str__()
  • def save()
  • def get_absolute_url()
  • 커스텀 메서드들

 

예시:

from django.db import models
from django.urls import reverse

class Company(models.Model):
    # CHOICES
    PUBLIC_LIMITED_COMPANY = 'PLC'
    PRIVATE_COMPANY_LIMITED = 'LTD'
    LIMITED_LIABILITY_PARTNERSHIP = 'LLP'
    COMPANY_TYPE_CHOICES = (
    	(PUBLIC_LIMITED_COMPANY, 'Public limited company'),
    	(PRIVATE_COMPANY_LIMITED, 'Private company limited by shares'),
    	(LIMITED_LIABILITY_PARTNERSHIP, 'Limited liability partnership'),
    )
    
    # DATABASE FIELDS
    name = models.CharField('name', max_length=30)
    vat_identification_number = models.CharField('VAT', max_length=20)
    company_type = models.CharField('type', max_length=3, choices=COMPANY_TYPE_CHOICES)
    
    # MANAGERS
    objects = models.Manager()
    limited_companies = LimitedCompanyManager()
    
    # META CLASS
    class Meta:
    	verbose_name = 'company'
    	verbose_name_plural = 'companies'
    
    # TO STRING METHOD
    def __str__(self):
    	return self.name
    
    # SAVE METHOD
    def save(self, *args, **kwargs):
    	do_something()
    	super().save(*args, **kwargs)  # Call the "real" save() method.
    	do_something_else()
    
    # ABSOLUTE URL METHOD
    def get_absolute_url(self):
    	return reverse('company_details', kwargs={'pk': self.id})
    
    # OTHER METHODS
    def process_invoices(self):
    	do_something()

 



역관계

 

related_name


ForeignKey 필드 내의 related_name 어트리뷰트는 굉장히 유용합니다. 역관계에 있는 모델에 대해 의미 있는 명명을 할 수 있기 때문입니다.

만약 무엇이 related_name가 될지 확실치 않다면 ForeignKey를 갖고 있는 모델의 복수형을 쓰세요.

class Company:
    name = models.CharField(max_length=30)

class Employee:
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees')


이는 Company 모델이 employees라는 특별한 어트리뷰트를 가진다는 것을 의미합니다. 그리고 이 employees는 company와 관련된 모든 employees의 쿼리셋을 반환하죠.

google = Company.objects.get(name='Google')
google.employees.all()

또한 역관계를 사용하여 Employee 인스턴스의 company 필드를 수정할 수도 있습니다.

vitor = Employee.objects.get(first_name='Vitor')
google = Company.objects.get(name='Google')
google.employees.add(vitor)


related_query_name


이러한 관계는 쿼리 필터링에도 사용될 수 있습니다. 예를 들어 'Vitor'라는 이름의 employ가 있는 모든 companies를 가져오고 싶다면 이렇게 하면 됩니다.

companies = Company.objects.filter(employee__first_name='Vitor')

이 관계의 이름을 커스터마이징하고 싶다면 다음과 같이 하면 됩니다.

class Employee:
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    company = models.ForeignKey(
        Company,
        on_delete=models.CASCADE,
        related_name='employees',
        related_query_name='person'
    )

그럼 이렇게 사용할 수 있죠.

companies = Company.objects.filter(person__first_name='Vitor')


일관된 사용을 위해 related_name은 복수형으로, related_query_name은 단수형으로 짓는 것이 좋습니다.



Blank와 Null 필드

 

전에 Blank와 Null의 차이점에 대한 글을 올린 적 있지만 여기서 간단히 요약하려 합니다.

 

  • Null: database-related. 해당 데이터베이스 칼럼이 null 값을 받아들일지 말 것인지를 결정.
  • Blank: validation-related. form.is_valid()를 사용하여 폼을 확인할 때 쓰임

필수가 아닌 텍스트 기반 필드에는 null=True를 사용하지 마세요. 그게 아니면 해당 필드는 "데이터 없음"에 대해 None빈 문자열, 이렇게 두 개의 값을 가질 겁니다. "데이터 없음"에 대한 값을 쓸데없이 두 개나 둘 필요는 없습니다. Django는 NULL이 아닌 빈 문자열을 사용합니다.

예시:

# The default values of `null` and `blank` are `False`.
class Person(models.Model):
    name = models.CharField(max_length=255)  # Mandatory
    bio = models.TextField(max_length=500, blank=True)  # Optional (don't put null=True)
    birth_date = models.DateField(null=True, blank=True) # Optional (here you may add null=True)

 



더 읽을거리

 

모델 정의는 Django 애플리케이션에서 가장 중요한 부분 중 하나입니다. 필드 유형을 적절하게 정의하고 Django models field types를 검토하여 옵션들을 확인하세요. 필드 타입을 커스터마이징 할 수도 있습니다.

만약 코드 규약에 관심이 있다면, Django’s Coding Style을 둘러보는 것을 추천합니다. 또한 제가 전에 올렸던 flake8 library 튜토리얼은 당신이 PEP8을 따르는 데 도움될 것입니다.

 

반응형

댓글