[The model layer] Introduction to models

구글 번역기의 도움을 (많이) 받아 장고 공식 문서를 번역하였습니다.

Models

모델은 데이터에 대한 단일 정보 소스다. 모델에는 저장하려는 데이터의 필수적인 필드와 동작들을 포함하고 있다. 일반적으로 각 모델은 단일 데이터베이스 테이블을 매핑한다.

The basics:

  • 각 모델은 django.db.models.Model의 하위 파이썬 클래스이다.
  • 각 모델의 속성은 데이터베이스 필드를 나타낸다.
  • Django는 자동적으로 생성되는 database-access API를 제공한다.

Quick example

이 예제 모델은 first_name과 last_name을 가지고 있는 Person을 정의한다.

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
  • 테이블 이름은 myapp_person으로 저절로 정의되나 재정의할 수 있다.
  • id(기본 키) field는 자동으로 생성되지만 다른 이름으로 덮어질 수 있다. (기본 키는 관계형 DB에서 레코드의 식별자로 이용됨)

Using models

모델을 정의했다면 해당 모델을 사용하겠다고 Django에게 말해줘야한다. settings파일에서 INSTALLED_APPS에 해당 모델을 추가해주면 된다.

INSTALLED_APPS = [
    #...
    'myapp',
    #...
]

(애플리케이션은 모델을 포함하고 있으므로 앱을 추가하는 것은 모델을 추가하는 것과 같다고 생각하면 된다)

새로운 앱을 추가했다면 manage.py migrate를 실행하여 바뀐 셋팅을 적용하고 manage.py makemigrations를 이용하여 migrations를 생성한다.

Fields

모델에서 가장 중요한 부분은 모델이 정의하는 데이터베이스의 필드 목록이다.
필드들은 class속성으로 명시된다. clean, save 또는 delete처럼 모델 API와 충돌되는 이름을 사용하지 않는 것이 좋다.

Example:

from django.db import models

class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)

class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

Field types

모델의 각 필드는 적절한 필드 클래스의 인스턴스이어야 한다. 장고는 몇가지를 결정하기 위해 필드 클래스 타입을 사용한다.

  • column type! 저장할 데이터가 어떤 데이터타입인지 DB에 말해준다.
  • default HTML widget! 폼 필드를 렌더링할 때 사용한다.
  • minimal validation requirements! 장고 어드민과 자동생성되는 폼에서 사용된다.

장고에는 수십가지 내장 필드 유형이 있다. CharField, DateTimeField, EmailField 그리고 IntegerField 등등

Field options

각 필드는 그 필드에만 특화된 인자들을 취한다. 예를 들면, CharField는 VARCHAR 데이터베이스 필드에 사이즈를 정의하는 max_length 인자를 받는다.

그리고 공통적으로 받는 인자들도 존재한다.

null : 비어 있는 값을 허용함, 데이터가 존재하지 않음

blank : 마찬가지로 빈 값을 허용하나 데이터가 공백으로 존재함, null과의 차이점을 꼭 이해해야함

choices : 튜플 안에 튜플이 존재하는 형태로, 이 것이 주어지면 일반적인 text field가 아닌 select box 폼 위젯이 형성된다.

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)

첫 번째 원소는 데이터베이스에 저장되는 형태, 두 번째 원소는 필드의 폼 위젯에 보여지는 형태이다.

모델 인스턴스가 주어졌을 때, choices가 사용된 필드의 보여지는 값(두 번째 원소)은 get_필드이름_disply() 메소드를 사용하여 확인할 수 있다.

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)

Shell에서 실행해보기

>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large'

default : 기본값 설정하기

help_text : 폼 위젯아래에 ‘help’텍스트를 보일 수 있다.

primary_key : primary_key가 True면 기본 키가 모델에 사용되는 것을 의미한다. 하지만 설정하지 않더라도 자동으로 IntegerField가 추가되어 기본 키가 설정 될 것이다. pk는 읽기만 가능하다. 만약에 바꾸고 값을 저장한다면 새로운 객체가 만들어진다.

unique : 유니크 속성이 설정되면 중복되지 않은 고유한 값만 가질 수 있다.

Automatic primary key fields

기본적으로 장고는 다음과 같은 필드가 자동적으로 주어진다

id = models.AutoField(primary_key=True)

특정 필드에 pk를 주고 싶으면 primary_key=True속성을 주면 된다.
각 모델은 primary_key를 가진 단 하나의 필드를 필요로 한다.

Verbose field names

ForeignKey, ManyToManyField, OneToOneField를 제외한 필드 타입의 선택적 첫번째 인수 ex)’이름’ -> 자세한 이름 속성

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField('이름', max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)

ForeignKEy, ManyToManyField, OneToOneField의 필드 타입은 항상 첫 번째 인수로 모델의 클래스를 받기 때문에 verbose_name 키워드를 사용해서 인자로 받아야한다.

poll = models.ForeignKey(
    Poll,
    on_delete=models.CASCADE,
    verbose_name="the related poll",
)
sites = models.ManyToManyField(Site, verbose_name="list of sites")
place = models.OneToOneField(
    Place,
    on_delete=models.CASCADE,
    verbose_name="related place",
)

Relationships

관계형 데이터의 힘은 테이블을 서로 연관시키는 데 있다. Django는 세가지 관계형 데이터베이스를 정의하는 방법을 제공함

  1. many-to-one
  2. many-to-many
  3. one-to-one

Many-to-one relationships

다대일 관계는 ForeignKey를 사용하여 정의할 수 있음. 클래스 속성으로 추가되는 걸로 여느 다른 필드 타입처럼 사용될 수 있다.

ForeignKey는 모델과 관련한 위치인자를 인수로 받는다.

class Person(models.Model):
	# 자기 자신을 다대일로 연결하는 필드
	# 비어있어도 무관, 연결된 객체가 삭제되어도 함께 삭제되지 않는다.
	# 해당 필드를 비움
	name = models.CharField(max_length=60)
	teacher = models.ForeignKey(
		'self',
		on_delete=models.SET_NULL,
		null=True,
		blank=True,
	)
	
	def __str__(self):
		return f'{self}'

Many-to-many relationships

ManyToManyField를 이용해서 many-to-many 관계를 정의할 수 있다.

마찬가지로 위치인자를 받는다.

예를 들어서 토핑은 여러가지 피자 메뉴에 들어갈 수 있고 피자 메뉴에는 여러가지 토핑이 들어갈 수 있다. 이와 같은 관계를 many-to-many관계라고 한다.

class Topping(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

    def __str__(self):
        return self.name

쉘에서 연습해보기

from many_to_many.models import Topping, Pizza
>>> cheese = Topping.objects.create(name='치즈')
>>> pepperoni = Topping.objects.create(name='페페로니')
>>> ham = Topping.objects.create(name='햄')
>>> pineapple = Topping.objects.create(name='파인애플')
>>> shrimp = Topping.objects.create(name='새우')
>>> cheese_pizza = Pizza.objects.create(name='치즈피자')
>>> cheese_pizza.toppings.add(cheese)
# many to many field에서 내용을 추가할 때는 save()가 필요 없다. 넣자마자 바로 save됨
>>> pepperoni_pizza = Pizza.objects.create(name= '페페로니 피자')
>>> pepperoni_pizza.toppings.add(cheese)
>>> pepperoni_pizza.toppings.add(pepperoni)
>>> master_pizza = Pizza.objects.create(name='피자왕')
>>> master_pizza.topping.add(cheese, pepperoni, shrimp, ham, mushroom, pineapple)

Extra fields on many-to-many relationships

그냥 단순히 피자와 토핑의 관계같은 단순한 다대다관계를 다루려면 standard ManyToManyField를 사용하면 그만이다. 하지만 두 모델 간의 연관된 정보가 필요할 경우도 있다.

예를 들면, 음악가들이 속해있는 음악단을 조사하는 경우를 생각해보자. 이러한 다대다 경우는 음악가와 음악단 사이의 입단날짜와 같은 세부정보가 존재할 수 있다.

이러한 경우에는 그냥 through를 사용한 중개 다대다관계 모델을 사용하면 된다.

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

이렇게 멤버쉽 클래스에 각 각 ForeignKey로 확실하게 두 모델의 관계를 정의할 수 있다.

쉘에서 연습해보기

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

One-to-one relationships

일대일 관계의 예를 먼저 들면 장소(Place)에 대한 데이터베이스를 생성한다고 할 때, 주소, 전화번호와 같은 고유한 것들이 함께 고려된다.

만약 레스토랑의 데이터베이스를 구축하기를 원한다면 레스토랑 모델에서 자신을 반복하고 해당 필드를 복제하는 대신 장소(Place)에 OneToOneField를 걸 수 있다.

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

    def __str__(self):
        return f'{self.name} the place'

class Restaurant(models.Model):
    place = models.OneToOneField(
        Place,
        on_delete=models.CASCADE,
        primary_key = True,
    )
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.place.name} the restaurant'

class Waiter(models.Model):
    restaurant = models.ForeignKey(
        Restaurant,
        on_delete=models.CASCADE)
    name = models.CharField(max_length=50)


    def __str__(self):
        return f'{self.name} the waiter at {self.restaurant}'

쉘에서 실습해보기

>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
>>> p1.save()
>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
>>> p2.save()

>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
>>> r.save()

장소와 레스토랑을 생성한다.

레스토랑에서 장소에 접근할 수 있다.

>>> r.place
<Place: Demon Dogs the place>

장소에서도 레스토랑에 접근할 수 있다.

>>> p1.restaurant
<Restaurant: Demon Dogs the restaurant>

할당 개념으도 장소를 설정할 수 있다. 장소는 레스토랑의 primary key로 있기 때문이다(?)

You can query the models using lookups across relationships: (검색을 이용해 관계들 사이를 뛰어넘어 질의할 수 있다?)

>>> Restaurant.objects.get(place=p1)
<Restaurant: Demon Dogs the restaurant>
>>> Restaurant.objects.get(place__pk=1)
<Restaurant: Demon Dogs the restaurant>
>>> Restaurant.objects.filter(place__name__startswith="Demon")
<QuerySet [<Restaurant: Demon Dogs the restaurant>]>
>>> Restaurant.objects.exclude(place__address__contains="Ashland")
<QuerySet [<Restaurant: Demon Dogs the restaurant>]>

Models across files

다른 앱으로 부터 하나의 모델에 관계를 맺는 것은 당연히 가능하다. 이것을 하기 위해선 관계된 모델을 불러오고 참조하면 된다.

from django.db import models
from geography.models import ZipCode

class Restaurant(models.Model):
    # ...
    zip_code = models.ForeignKey(
        ZipCode,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )

Field name restrictions

장고는 필드 이름에 두가지 제한을 둔다.

  1. 파이썬 예약어는 사용할 수 없다. Syntax에러를 초래할 것이다
class Example(models.Model):
    pass = models.IntegerField() # 'pass' is a reserved word!
  1. 두 개 이상의 언더스코어(_)를 연속으로 사용할 수 없다. 장고의 쿼리 검색 syntax이기때문이다.
class Example(models.Model):
	foo__bar = models.IntegerField() # 'foo__bar'에 언더스코어 두 개가 쓰인다.

그러나 필드 이름이 데이터베이스 column 이름과 반드시 일치 할 필요는 없기때문에 이러한 제한사항을 피할 수 있다. db_column 옵션을 참조

장고의 모든 기본 SQL쿼리의 모든 데이터베이스 테이블 이름과 열 이름을 이스케이프 처리하므로 SQL예약어(예: join, where 또는 select)는 모델 필드 이름으로 사용할 수 있다. 특정 데이터베이스 엔진의 인용 구문을 사용한다.


Custom field types

이미 생성된 모델 필드 중 하나가 용도에 맞게 사용될 수 없거나 잘 사용되지 않는 데이터베이스 column type을 이용하고 싶다면 커스텀 필드 클래스를 만들 수 있다.

커스텀 필드 생성에 대한 전체 내용은 커스텀 모델 필드 정의에 나와있다.

Meta options

내부 클래스 메타를 시용하여 당신의 모델에 메타데이터를 추가할 수 있다.

모델의 메타데이터는 필드를 제외한 모든 것이라고 말할 수 있다. 옵션의 순서나 데이터베이스 테이블 이름, verbose_name또는 verbose_name_plural같은 것들이다.

Model attributes

objects

가장 중요한 모델 속성은 Manager이다. 장고 모델에 데이터베이스 쿼리 실행을 제공하고 데이터베이스에서 인스턴스를 검색하는데 사용되는 인터페이스이다. Custom Manager가 정의되지 않았을 경우 디폴트 이름은 objects이다. Manager들은 모델 클래스가 아닌 모델 인스턴스를 통해서만 액세스 할 수 있다.

Model methods

모델에 사용자 정의 메소드를 정의하여 오브젝트에 row-level 기능을 추가함. Manager메소드는 테이블 차원의 일을 수행하기 위한 것이지만, 모델 메소드는 특정 모델 인스턴스에서 작동해야한다.

모델 한 곳에 비지니스 로직을 두기 위한 중요한 기술이다.

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "Returns the person's baby-boomer status."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    @property
    def full_name(self):
        "Returns the person's full name."
        return '%s %s' % (self.first_name, self.last_name)

마지막 메소드는 property이다. 모델 인스턴스 참조에는 각 모델에 자동으로 부여되는 메소드의 전체 목록이 있다. 아래에서 미리 정의된 모델 메소드를 재정의하는 방법을 참고하면 대부분 메소드를 다시 정의할 수 있다.

그 중에 항상 다시 정의하는 몇가지가 있다.

__str__()

매직 메소드라고 불리며 오브젝트의 문자열 표현을 반환한다. 쉘이나 어드민에서 나타내지는 오브젝트 이름을 알아보기 쉽게 정의하는 것이 좋다.

get_absolute_url()

장고에게 객체를 위한 URL을 계산하는 방법을 알려준다. admin interface에서 사용하며 언제든지 객체의 URL을 찾을 수 있어야한다.

overriding predefined model methods

커스터마이징 할 데이터베이스 동작을 캡슐화하는 또다른 모델 메소드 셋이 있다. 특히 save(), delete()등을 변경하는 경우가 많다

내장된 save()메소드 같은 경우 이렇게 바꿀 수 있다

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)  # Call the "real" save() method.
        do_something_else()

또는 특정 이름에 save()를 방지할 수도 있다

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def save(self, *args, **kwargs):
        if self.name == "Yoko Ono's blog":
            return # Yoko shall never have her own blog!
        else:
            super().save(*args, **kwargs)  # Call the "real" save() method.

Super 클래스 메소드를 호출을 기억하는 것은 중요하다. 확실하게 데이터베이스에 저장한다. Super 클래스 메소드를 호출하지 않으면 기본 작업수행이 일어나지 않고 데이터베이스에 손 대지 않는다.

장고는 수시로 내장 모델 메소드의 기능을 확장하여 새로운 인수를 추가한다. 메소드를 정의할 때 *args,**kwargs를 사용하면 코드가 추가 될 때 해당 인수를 자동으로 지원한다는 보장을 받는다.

재정의 된 모델 메소드는 일괄 작업(bulk operations)에서 호출되지 않는다

Queryset을 사용하여 일괄적으로 객체를 삭제할 때나 단계적으로 삭제할 때 객체의 delete() 메소드가 호출될 필요는 없다. 사용자 정의된 delete logic이 실행되도록 하려면 pre_delete 및 post_delete 시그널을 사용할 수 있다.

안타깝게도 save(), pre_save 및 post_save가 호출되지 않기 때문에 객체를 일괄적으로 만들거나 업데이트할 때는 방법이 없다.


Executing custom SQL

또 다른 공통패턴 중 하나는 모델 메소드 및 모듈 수준의 메소드에서 custom SQL문을 작성할 수 있다.

Model inheritance

장고의 모델 상속은 파이썬에서 일반적인 클래스 상속과 방식이 비슷하지만 페이지 시작 부분에서의 기본 사항들을 따라야한다. 모델 클래스는 django.db.models.Model의 하위 클래스여야 한다는 것을 의미한다.

부모 모델이 자신의 모델(데이터베이스 테이블 포함)이 될지 또는 부모가 자식 모델을 통해서만 볼 수 있는 공통 정보를 보유하고 있는지 여부만 결정하면된다.

장고에서 가능한 3가지 방법의 상속은 다음과 같다

  1. 보통 부모 클래스를 사용하여 각 하위 모델마다 굳이 입력하지 않은 정보를 갖게하길 원할것이다. 이 클래스는 따로 분리되어 사용하지 않으므로 Abstract base classes의 개념이 사용된다.

  2. 기존 모델을 하위 클래스화하고 각 모델에 자체 데이터베이스 테이블을 갖기 원하면 Multi-table interitance를 사용해야한다.

  3. 마지막으로는, 모델 필드를 변경하지 않고 모델의 파이썬 수준 동작만 수정하려는 경우 Proxy 모델을 사용하면 된다.

Abstract base classes

이 클래스는 몇 가지 공통된 정보를 여러 다른 모델에 넣으려할 때 유용하다. 기본 클래스를 작성하고 Meta클래스에 abstract=True를 설정하면 된다. 이 모델은 데이터베이스 테이블을 만들지 않고 대신 다른 모델의 기본 클래스로 사용될 때 해당 필드는 자식 클래스의 필드에 추가된다. 자식의 이름과 같은 이름을 가진 abstract base class의 필드를 갖는 것은 오류(장고는 예외를 발생시킴)

from django.db import models

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True

class Student(CommonInfo):
    home_group = models.CharField(max_length=5)

Student모델은 이름과 나이 그리고 home_group 세 개의 필드를 갖는다. CommonInfo 모델은 abstract base class이므로 일반 장고 모델로 사용되지 않는다.
데이터베이스 테이블을 생성하거나 관리자가 없으므로 직접 인스턴스화하거나 저장할 수 없다.

이러한 방식의 모델 상속을 많이 사용하게 될 것이다. 파이썬 레벨에서 공통정보를 제외시키는 방법을 제공하면서 데이터베이스 레벨에서 하위 모델 당 하나의 테이블만 생성함

Meta inheritance

abstract base class가 생성되면 장고는 기본 클래스에서 선언한 Meta 내부 클래스를 속성으로 사용할 수 있게 한다.

자식 클래스가 Meta 클래스를 선언하지 않으면 부모 클래스의 Meta 클래스를 상속받는다. 자식이 부모의 Meta 클래스를 확장하려고 하면 하위 클래스로 만들 수 있다.

from django.db import models

class CommonInfo(models.Model):
    # ...
    class Meta:
        abstract = True
        ordering = ['name']

class Student(CommonInfo):
    # ...
    class Meta(CommonInfo.Meta):
        db_table = 'student_info'

장고는 abstract base class의 Meta클래스에 한 가지 적용 사항을 만드는데, Meta 속성을 설치하기 전에 abstract=False로 설정한다. 즉, abstract base class의 자식들은 자동으로 abstract base class가 되지 않는다.

물론 다른 abstract base class에서 상속받아 abstract base class를 만들 수 있다. abstract = True를 명시적으로 설정하는 것을 기억해라

일부 속성은 abstract base class의 Meta클래스에 포함되는 것이 타당하지 않다. 예를 들면, db_table을 포함하면 모든 자식 클래스(자신의 Meta를 지정하지 않은 클래스)가 동일한 데이터베이스 테이블을 사용하는 것을 의미한다. 이는 확실히 사용자가 원하는 일이 아닐 것이다.


ForeignKey 또는 ManyToManyField에서 related_name 또는 related_query_name을 사용하는 경우 고유한 역 이름과 쿼리 이름을 항상 지정해야한다.

이 클래스의 필드는 각 하위 클래스에 포함되며 매번 속성에 대해 정확히 동일한 값이 포함되므로 일반적으로 abstract base class에서 문제가 생긴다.

이 문제를 해결하려면 abstract base class에서 related_name 또는 related_query_name을 사용할 때 값의 일부에 ‘%(app_label)’s 및 ‘%(class)s’가 포함되어야 한다.

  • ’%(class)s’는 필드가 사용되는 하위 클래스의 소문자 이름으로 바뀐다.
  • ’%(app_label)s’는 하위 클래스가 포함된 소문자 이름으로 바뀐다. 설치된 각 응용프로그램 이름은 고유해야하며 각 응용 프로그램 내의 모델 클래스 이름도 고유해야하므로 결과 이름이 달라진다.
from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(
        OtherModel,
        related_name="%(app_label)s_%(class)s_related",
        related_query_name="%(app_label)s_%(class)ss",
    )

    class Meta:
        abstract = True

class ChildA(Base):
    pass

class ChildB(Base):
    pass

또다른 앱 rare/models.py에서

from common.models import Base

class ChildB(Base):
    pass

common.ChildA.m2m 필드의 역 이름은 common_childa_related이고 역방향 쿼리 이름은 common_childas이며

common.ChildB.m2m 필드의 역 이름은 common_childb_related이고 역방향 쿼리 이름은 common_childbs가 된다.

마지막으로, rare.ChildB.m2m 필드의 역 이름은 rare_childb_related이고 역방향 쿼리 이름은 rare_childbs이다.

’%(class)s’ 및 ‘%(app_label)s’ 부분을 사용하여 related name 또는 related query name을 작성하는 방법은 사용자에게 달려있지만 사용하는 것을 잊어버리면 시스템 검사를 수행하거나 migrate를 실행할 때 오류가 발생한다.

만약에 abstract base class의 필드에 related_name속성을 지정하지 않으면 기본적으로 역방향 이름은 자식 클래스의 이름 뒤에 ‘_set’이 온다. 자식 클래스에서 필드를 직접 선언했다면 일반적으로 그렇게 된다.

예를 들어, 위 코드에서 related_name속성을 생략하면 m2m필드의 역 이름은 ChildA의 경우 childa_set이 되고 ChildB의 경우 childb_set이 된다.


Multi-table inheritance

장고가 지원하는 모델 상속의 두 번째 유형은 계층 구조의 각 모델이 모두 하나의 모델일 때이다.

각 모델은 자체 데이터베이스 테이블에 해당하며 개별적으로 쿼리하고 생성할 수 있다. 상속 관계는 자동으로 생성된 OneToOneField를 통해 자식 모델과 각 모델간의 링크를 도입한다.

from django.db import models
class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

데이터는 서로 다른 데이터베이스에 있지만 Place의 모든 필드는 Restaurant에서 사용할 수 있다. 그래서 다음 두 구문은 모두 가능하다.

>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

Restaurant가 있는 Place의 경우 모델 이름의 소문자 버전을 사용하는 것으로 Place 객체에서 Restaurant 객체로 가져올 수 있다.

>>> p = Place.objects.get(id=22)
# 만약 p가 Restaurant 객체라면, 자식 클래스를 제공한다.
>>> p.restaurant
<Restaurant : ...>

그러나 위 예제의 p가 Restaurant가 아닌 경우 (즉, Place 객체로 직접 생성되었거나 다른 클ㅅ래스의 부모인 경우), p.restaurant를 참조하면 Restaurant.DoesNotExist 예외를 발생한다.

Restaurant에 자동으로 생성된 OneToOneField가 다음과 같이 표시된다.

place_ptr = models.OneToOneField(
	Place, on_delete=models.CASCADE,
	parent_link = True,
)

Restaurant에 parent_link = True로 자신의 OneToOneField를 선언하여 해당 필드를 재정의 할 수 있다.

Meta and multi-table inheriatnce

multi-table inheritance 상황에서 자식 클래스가 부모의 Meta클래스에서 상속받는 것은 의미가 없다.

모든 Meta 옵션들은 이미 상위 클래스에서 적용되어있으며 이를 다시 적용하면 일반적으로 모순된 실행으로 이어질 것이다.(이것은 기본 클래스가 자체적으로존재하지 않는 추상 기본 클래스의 경우와 대조적임)

따라서 자식 모델은 부모의 Meta클래스에 액세스 할 수 없다. 그러나 자식이 부모로부터 상속을 받는 한정된 경우가 몇 가지 있다.

자식이 순서 속성 또는 get_latest_by 속성을 지정하지 않은 경우에는 부모로부터 순서 속성을 상속받는다.

부모가 순서 속성을 가지고 있을 때 자식이 부모의 속성을 그대로 따르지 않으려면 명시적으로 disable하면 된다.

class ChildModel(ParentModel):
	# ...
	class Meta:
		# 부모의 순서 속성을 제거
		ordering = []

Inheritance and reverse relations

다중 테이블 상속은 암시적 OneToOneField를 사용하여 부모와 자식을 연결하기 때문에 위의 예와 같이 부모에서 자식으로 그대로 전해질 수 있다.

그러나, 이는 ForeignKey 및 ManyToManyField 관계에 대한 기본 related_name value인 이름을 사용한다.

이러한 관계 유형을 부모 모델의 하위 클래스에 배치하는 경우 해당 필드 각각에 related_name 속성을 지정해야한다.

그것을 잊게 되면 장고는 validation error를 일으킨다.

예를 들어, 위의 Place 클래스를 다시 사용하여 ManyToManyField를 사용하여 다른 하위 클래스를 만든다.

class Supplier(Place)
	customers = models.ManyToManyField(Place)

이 때 에러의 결과는

Reverse query name for 'Supplier.customers' clashes with reverse query name for 'Supplier.place_ptr'.

HINT : Add or change a related_name argument to the definition for 'Supplier.customers' or 'Supplier.place_ptr'.

customers_name 필드에 related_name을 추가하면 models.ManyToManyField (Place, related_name = ‘provider’)오류가 해결된다.


Specifying the parent link field

앞서 언급했듯이 장고는 자동적으로 자식 클래스를 임의의 추상이 아닌 부모 모델에 연결하는 OneToOneField를 만든다.

부모에게 다시 연결되는 속성의 이름을 제어하려는 경우 고유한 OneToOneField를 만들고 parent_link = True로 설정하여 필드가 부모 클래스로 되돌아가는 링크임을 나타낼 수 있다.


Proxy models

다중 테이블 상속을 사용하면 모델의 각 하위 클래스에 대해 새 데이터베이스 테이블이 만들어진다. 서브 클래스는 기본 클래스에 없는 추가 데이터 필드를 저장할 장소가 필요하기 때문에 이는 보통 사용자들이 원하는 동작이다.

그러나 가끔 모델의 파이썬 동작만을 번경하고 싶을 때도 있다. 기본 관리자를 변경하거나 새 메소드를 추가하는 것 등이 있다.

고유 모델에 대한 프록시(대리)를 만드는 것이 프록시 모델 상속의 존재이유이다. 프록시 모델의 인스턴스를 생성, 삭제 및 업데이트를 할 수 있으며 원래(비 프록시)모델을 사용하는 것처럼 모든 데이터가 저장된다.

차이점은 원본을 변경하지 않고 프록시의 기본 모델 순서 또는 기본 관리자와 같은 것을 변경할 수 있다는 것이다.

프록시 모델은 일반 모델처럼 선언된다. 장고에게 Meta클래스의 프록시 속성을 True로 설정하여 프록시 모델임을 알린다.

예를 들어, Person 모델에 메소드를 추가하려고 한다고 가정하면 다음과 같이 할 수 있다.

from django.db import models

class Person(models.Model):
	first_name = models.CharField(max_length=30)
	last_name = models.CharField(max_length=30)
	
class MyPerson(Person):
	class Meta:
		proxy = True
		
	def do_something(self):
		# ...
		pass

MyPerson 클래스는 상위 Person 클래스와 동일한 데이터베이스 테이블에서 작동한다. 특히 Person의 새로운 인스턴스는 MyPerson을 통해 액세스 할 수 있으며 그 반대의 경우도 가능하다.

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

프록시 모델을 사용하여 모델에서 기본 정렬을 새롭게 정의할 수도 있다. Person 모델을 항상 정렬하고 싶지 않을 수도 있다. last_name을 기준으로 정렬하는 프록시를 만드는 일 같은 것을 할 수 있다.

class OrderedPerson(Person):
	class Meta:
		ordering = ["last_name"]
		proxy = True

이제 일반 Person 쿼리는 순서가 지정되지 않고 OrderedPerson 쿼리는 last_name에 의해 정렬된다. 프록시 모델은 일반 모델과 동일한 방식으로 Meta속성을 상속한다.

QuerySets still return the model that was requested

장고가 Person 객체를 쿼리할 때마다 MyPerson객체를 반환할 방법은 없다. Person객체에 대한 queryset은 이러한 유형의 객체를 반환한다.

프록시 객체의 요점은 원래의 모델인 Person의 코드가 이를 사용하고 사용자 코드가 포함된 확장을 사용할 수 있다는 것이다.(다른 코드는 그다지 의존하지 않음)

그것은 Person(또는 다른) 모델을 어디서나 자신의 것으로 대체하는 방법이 아니다.


Base class restrictions

프록시 모델은 정확히 하나의 비 추상적 모델 클래스에서 상속해야한다. 프록시 모델은 다른 데이터베이스 테이블의 행 사이에 연결을 제공하지 않으므로 여러 비 추상적 모델에서 상속받을 수 없다.

프록시 모델은 모델 필드를 정의하지 않으면 추상 모델 클래스를 상속할 수 있다. 프록시 모델은, 공통의 비 추상 부모 클래스를 공유하는 임의의 수의 프록시 모델로부터 상속 할 수 있다.


Proxy model managers

프록시 모델에 manager를 지정하지 않으면 부모 모델로부터 상속받는다. 프록시 모델에서 manager를 정의하면 그것이 기본값이 된다. 부모 클래스에 정의된 관리자는 계속 사용할 수 있다.

위의 예를 계속 사용하면 Person 모델을 쿼리할 때 사용되는 기본 manager를 다음과 같이 변경할 수 있다.

from django.db import models

class NewManager(models.Manager):
	# ...
	pass

class MyPerson(Person):
	objects = NewManager()
	
	class Meta:
		proxy = True

기존 기본 값을 바꾸지 않고 프록시에 새 manager를 추가하려면 custom manager 문서에 설명된 기술을 사용하면 된다. 새로운 manager를 포함하는 base 클래스를 만들고 최초의 base클래스를 상속한다.

# 새로운 manager에 대한 추상 클래스를 생성한다
class ExtraManagers(models.Model):
	secondary = NewManager()
	
	class Meta:
		abstract = True
		
class MyPerson(Person, ExtraManagers):
	class Meta:
		proxy = True

아마도 이 작업을 자주 수행할 필요는 없지만 어쨌든 가능하다.


Differences between proxy inheritance and unmanaged models

프록시 모델 상속은 모델의 Meta 클래스에서 managed 특성을 사용하여 관리되지 않는 모델(unmanaged model)을 만드는 것과 매우 비슷하다.

신중하게 Meta.db_table을 설정하면 관리되지 않는 모델을 만들어 기존 모델을 음영처리하고 Python 메소드를 추가할 수 있다. 그러나 변경 작업을 수행할 경우 두 복사본을 동기화된 상태로 유지해야하므로 반복적인 오류가 발생할 수 있다.

반대로, 프록시 모델은 프록싱하고 있는 모델의 동작과 정확히 동일하게 동작한다. 필드와 관리자를 직접 상속하므로 부모 모델과 항상 동기화된다.

일반적인 규칙은 다음과 같다

  1. 기존 모델 또는 데이터베이스 테이블을 미러링하고 원래 데이터베이스 테이블 column을 모두 원하지 않는 경우 Meta.managed = False를 사용하라. 이 옵션은 일반적으로 장고가 제어하지 않는 데이터베이스 뷰와 테이블을 모델링 할 때 유용하다.

  2. 모델의 파이썬 전용 동작을 변경하면서 원본과 동일한 필드를 유지하려면 Meta.proxy = True를 사용하면 된다. 이렇게하면 데이터를 저장할 때 프록시 모델이 원본 모델의 저장소 구조와 정확히 일치하도록 설정된다.


Multiple inheritance

파이썬의 서브 클래싱과 마찬가지로, 장고 모델이 여러 부모 모델로부터 상속받을 수 있다. 일반적인 파이썬 이름 해석 규칙이 적용되는 것을 명심해야한다. 특정 이름(예를 들면 Meta)이 나타나는 첫 번째 기본 클래스가 사용된다. 예를 들어 상속받는 여러 부모에 Meta 클래스가 있다면 첫 번째 부모만 상속받고 나머지 부모는 무시된다.

일반적으로 여러 부모로부터 상속하지 않아도 된다. 이것이 주로 사용되는 ‘mix-in’클래스이다. mix-in은 상속받은 모든 클래스에 특정 추가 필드나 메소드를 추가하는 것이다. 상속 단계를 가능한 간단하고 직관적으로 유지하여 필요한 정보가 어디에서 왔는지 쉽게 알 수 있도록 하는 것이 좋다.

공통 ID primary key필드가 있는 여러 모델을 상속하면 오류가 발생한다. 다중 상속을 제대로 사용하러면 기본 모델에서 명시적인 Autofield를 사용하면 된다.

class Article(models.Model):
	article_id = models.AutoField(primary_key=True)
	...
	
class Book(models.Model):
	book_id = models.AutoField(primary_key=True)
	...
	
class BookReview(book, Article):
	pass

또는 공통 조상을 사용하여 AutoField를 유지한다. 이렇게 하려면 자동으로 생성되는 필드와 자식에 의해 상속되는 필드사이의 충돌을 피하기 위해 각 부모 모델에서 공통 조상으로 명시적으로 OneToOneField를 사용해야한다.

class Piece(models.Model):
	pass
	
class Article(Piece):
	article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
	...
	
class Book(Piece):
	book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True)
	...
	
class BookReview(Book, Article):
	pass

Field name “hiding” is not permitted

일반적인 파이써너 클래스 상속에서는 자식 클래스가 부모 클래스의 모든 특성을 재정의 할 수 있다. 장고에서는 일반적으로 모델 필드에는 허용되지 않는다. 비 추상적 모델 기본 클래스(non-abstract model base class)에 author라는 필드가 있는 경우 해당 기본 클래스에서 상속하는 클래스에서 다른 모델 필드를 만들거나 author라는 속성을 정의할 수 없다.

이 제한은 추상 모델에서 상속된 모델 필드에는 적용되지 않는다. 이러한 필드는 다른 필드나 값으로 무시되거나 field_name = None을 설정하여 제거할 수 있다.

Warning
모델 manager는 추상 기본 클래스에서 상속된다. 상속된 manager가 참조하는 상속된 필드를 무시하면 버그가 생길 수 있다. custom manager and model inheritance를 참조

Note 일부 필드는 모델의 추가 속성을 정의한다. ForeignKey는 필드 이름에 _id가 추가된 속성과 외부 모델의 related_name 및 related_query_name을 정의한다

이러한 추가 속성을 정의하는 필드가 변경되거나 제거되어 더 이상 추가 속성을 정의하지 않는 한 이러한 추가 속성을 겹쳐쓸 수 없다.

상위 모델의 필드를 재정의하면 새 인스턴스를 초기화(Model.__init__에서 초기화 할 필드 지정) 및 직렬화(serialization)와 같은 영역에서 어려움이 발생한다.

이것들은 일반적인 파이썬 클래스 상속이 똑같은 방식으로 처리할 필요가 없는 기능이므로 장고 모델 상속과 파이썬 클래스 상속의 차이점은 임의적이지 않다(isn’t arbitrary).

이 제약은 Field 인스턴스인 속성에만 적용된다. 원하는 경우 일반 파이썬 속성을 재정의할 수있다. 또한 파이썬이 보는 것 처럼 속성 이름에만 적용된다. 데이터베이스 column 이름을 수동으로 지정하는 경우 다중 테이블 상속을 위해 자식 및 조상 모델에 동일한 column이름을 사용할 수 있다 (두 개의 서로 다른 데이터베이스 테이블에 있는 column임).

장고는 조상 모델에서 모델 필드를 재정의하면 FieldError를 발생시킨다.

Organizing models in a package

manage.py startpp 명령은 models.py파일을 포함하는 응용 프로그램 구조를 만든다. 모델이 여러 개인 경우 별도의 파일로 구성하여 사용하는 것이 좋다.

그렇게 하려면 모델 패키지를 만들어야한다. models.py를 제거하고 mayapp/models/ 경로를 만들고 __init__.py 파일과 모델을 저장할 파일을 만든다. __init__.py 파일에서 모델들을 import해야한다.

from .organic import Person
from .synthetic import Robot

.models import *를 사용하지 않고 명시적으로 각 모델을 import하면 네임스페이스가 복잡해지지 않아 코드를 읽기 쉽고 코드 분석 도구를 유용하게 유지할 수 있다는 장점이 있다.

See also

모델 참조
모델 필드, 관련 객체 및 QuerySet을 포함한 모든 모델 관련 API를 포함함.


2018.02.26 장고 Models 문서 번역 끝

Comments