brunch

매거진 Django doc

You can make anything
by writing

C.S.Lewis

by 장영석 Feb 13. 2018

Django Models

Introduction to models

Models

모델은 데이터 정보를 정의한 소스다. 저장 중인 데이터의 필수 필드와 동작들이 포함되어있다. 일반적으로 모델은 데이터베이스 테이블과 매핑된다.


Quick example

아래 예제 모델은 first_name과 last_name을 가진 Person을 정의한다.

first_name과 last_name은 모델의 필드다. 각 필드는 클래스 속성으로 지정되어있고 각 속성은 데이터베이스 칼럼과 매핑된다.


위 Person 모델은 아래와 같은 데이터베이스 테이블을 만든다.


Using models

모델을 정의했다면 Django에 해당 모델들을 알려줘야 한다. models.py 포함된 모듈의 이름을 세팅 파일에 INSTALLED_APPS 에 추가해야 한다.


애플리케이션의 모델이 myapp.models (manage.py startapp 스크립트를 통해 작성된 애플리케이션 패키지 구조) 모듈에 있을 경우 INSTALLED_APPS 해당 부분은 아래와 같을 것이다.


INSTALLED_APPS 에 새로운 애플리케이션을 추가하면 manage.py migrate를 실행해야 한다. 마이그레이션 파일을 만들기 위해 manage.py makemigrations를 실행해야 하는 경우도 있다.


Fields

모델에서 가장 중요하고 유일하게 필수인 부분은 데이터베이스 필드 목록 정의다. 필드는 클래스 속성에 의해 지정된다. 필드 이름을 모델 API의 메서드명인 clean, save, delete 등과  충돌하지 않도록해라.


Field types

모델의 각 필드는 해당 Field 클래스의 인스턴스여야 한다. Django는 필드 클래스 타입을 사용하여 몇 가지를 결정한다.

데이터베이스에 저장할 데이터의 종류에 따른 칼럼 타입 (e.g INTEGER, VARCHAR, TEXT).

폼 필드를 렌더링 할 때 사용하기 위한 기본 HTML 위젯 (e.g <input type="text">, <select>).

Django 어드민과 자동으로 생성된 폼의 기본적인 유효성 검사

Django에는 많은 빌트인 필드 타입이 있다. 모델 필드 참조 목록에서 확인할 수 있다.


Field options

각 필드는 특정 필드 지정 인수들이 있다. 예를 들어 CharField (해당 하위 클래스 포함)는 데이터를 저장할 때 VARCHAR 데이터베이스 필드의 크기를 지정하는 max_length 인수가 필요하다.


 또한 모든 필드 타입에서 사용 가능한 일반적인 인수들이 있다. 모두 선택 인수들이다. 모델 필드 참조 문서에 모두 설명되어있지만 아래에 자주 쓰이는 것들을 요약한다.


null

True 라면 Django는 데이터베이스에 빈 값을 NULL로 저장한다. 기본값은 False.


blank

True 라면 해당 필드는 비워둘 수 있다. 기본값은 False.


null과는 다르다. null은 순전히 데이터베이스와 관련되어있는 반면 blank는 유효성 검사와 관련되어있다. 필드에 blank=True 인수가 있다면 폼 유효성 검사 시 빈 값을 허용한다. 필드에 blank=False 인수가 있다면 해당 필드 값은 필수다.


choices

필드의 선택지로 2-tuples 의 iterable을 사용한다. choice 속성이 주어지면 기본 폼 위젯은 표준 텍스트 필드 대신 셀렉트 박스가 되고 선택항목이 해당 iterable로 제한된다.

각 tuple의 첫 번째 요소는 데이터베이스에 저장될 값이다. 두 번째 요소는 필드의 폼 위젯에 보인다.


모델 인스턴에서 get_FOO_dispay()  메서드를 사용해서 선택된 choices 필드의 디스플레이 값에 접근할 수 있다.


default

필드의 기본값이다. 값 또는 callable  객체일 수 있다. callable 일 경우 새로운 객체가 생성될 때마다 호출된다.


help_text

폼 위젯과 함께 보이는 추가 "help" 텍스트다. 필드가 폼에 사용되지 않더라도 문서화 시 유용하다.


primary_key

True 일 경우 이 필드는 모델의 primary key 가 된다.


모델에 어떤 필드에도 primary_key=True 지정하지 않는다면, Django는 자동으로 기본키를 보유할 IntegerField를 추가하므로, primary_key=True 설정은 기본 primary-key 동작을 재정의하는 경우만 사용한다.


primary key 필드는 읽기 전용이다. 기존 객체에 primary key값을 변경하고 저장하면, 이전 객체가 유지되고 새로운 객체가 생성된다.


unique

True 일 경우, 이 필드는 테이블을 통틀어서 유일해야만 한다.


Automatic primary key fields


Django는 모델마다 기본으로 아래와 같은 필드를 제공한다.

자동으로 증가되는 primary key이다.


사용자 정의 primary key를 지정하려면, 필드 중 하나에 primary_key=True를 지정하면 된다. Django가  사용자 정의 Field.primary_key를  설정했다고 판단하면, 자동으로 id 칼럼을 추가하지 않는다.


각 모델은 반드시 primary_key=True(사용자가 지정 또는 자동으로 추가된)를 가진 필드가 하나여야 한다.


Verbose field names


ForeignKey, ManyToManyFieldOntToOneField를 제외한 각 필드 타입은 선택적으로 첫 번째 위치 인수에 verbose name을 가진다. verbose name이 주어지지 않으면 Django는 필드의 속성 이름의 밑줄을 공백으로 바꿔 사용해 자동으로 생성한다.


아래 예제는 verbose name이 "person's first name"이다.

아래 예제는 verbose name이 "first name"이다.


ForeignKey, ManyToManyField, OneToOneField는 첫 번째 인수로 모델 클래스를 요구하기 때문에, verbose_name 키워드 인수를 사용한다.

컨벤션은 verbose_name에 첫 문자를 대문자로 사용하지 않는 것이다. Django가 필요한 곳에 첫 문자를 자동으로 대문자로  표시해준다.


Relationships


명백히, 관계형 데이터베이스의 힘은 테이블이 서로 관계를 맺는 데 있다. Django는 가장 일반적인 세 가지(many-to-one, many-to-many and one-to-one) 관계에 대해  정의하는 방법을 제공한다.


Many-to-one relationships


다대일 관계 정의는 django.db.models.ForeignKey를 사용한다. 다른 필드 타입과 같이 모델에 클래스 속성으로 포함하여 사용한다.


ForeignKey는 위치 인수가 필요하다. 모델이 인수에 관련된 클래스다.


예를 들면, Car 모델이 Manufacturer를 가지고 있다면 - 즉, Manufacturer는 여러 개의 Car를 만들지만 Car는 하나의 Manufacturer 만을 갖는 경우 - 다음 정의를 사용한다.


또한 재귀 관계 또는 아직 정의되지 않은 모델과의 관계를 만들 수 있다.


ForeignKey 필드(위 예제에서 manufacturer)의 이름을 모델 이름의 소문자로 권장하지만 필수는 아니다. 물론 원하는 데로 필드를 정의할 수 있다.


Many-to-many relatioships


다대다 관계 정의는 ManyToManyField를 사용한다. 다른 필드 타입과 같이 모델에 클래스 속성으로 포함하여 사용한다.


ManyToManyField는 위치 인수가 필요하다. 모델이 인수에 관련된 클래스다.


예를 들면, Pizza에 여러 개의 Topping객체가 있을 경우 - Topping이 여러 개의 Pizza에 있을 수 있고 Pizza에 여러 개의 Topping이 있을 경우 - 여기에 그 피자를 표현하는 방법이 있다.

ForeignKey와 마찬가지로, 재귀 관계와 아직 정의되지 않은 모델과 관계를 만들 수 있다.


ManyToManyField(위 예제에서 toppings)의 이름은 관련된 모델 객체의 세트를 설명하는 복수형으로 권장되지만 필수는 아니다.


ManyToManyField가 어느 모델에 있는지는 중요하지 않지만, 반드시 하나의 모델에 있어야 한다.


일반적으로 ManyToManyField 인스턴스는 폼에서 편집될 객체에 있어야 한다. 위 예제에서, toppingsPizza에 있다. 피자에 토핑이 있다고 생각하는 것이 토핑이 여러 개의 피자에 올라가는 것이 더 자연스럽기 때문이다.


위 설정대로 Pizza 폼을 사용하면 사용자들이 토핑을 선택하게 될 것이다.


Extra fields on many-to-many relationships


단지 피자와 토핑을 혼합하고 매치하는 단순한 다대다 관계만을 다룰 때는, 표준 ManyToManyField만 있으면 된다. 하지만 때로는 두 모델 간의 관계와 데이터가 추가로 필요할 수도 있다.


예를 들어, 뮤지션이 소속된 뮤지컬 그룹을 추적하는 애플리케이션의 상황을 생각해보자. 사람과 멤버들이 있는 그룹 간에는 다대다 관계가 있다. 그러므로 이 관계를 표현하는데 ManyToManyField를 사용할 수 있다. 하지만 그룹에 가입한 날짜와 같이 회원에 대해 수집하길 원하는 많은 상세정보가 있다.


Django는 이러한 상황에서  다대다 관계를 관리하는 데 사용될 모델을 지정할 수 있다. 그러고 나서 중간 모델에 추가 필드를 입력할 수 있다. 중간 모델은 중개인 역할을 하는 모델을 가리키는 through 인수를 사용해서 ManyToManyField와 연결된다. 뮤지선 예제 코드는 다음과 같다. 

중개 모델을 설정할 때, 다대다 관계에 관여하는 모델들을 위한 외래 키를 명시적으로 지정한다. 이 명시적 선언은 두 모델이 관련되는 방식을 정의한다.


중간 모델에는 몇 가지 제한사항이 있다.

중간 모델에는 원본 모델에 대한 외래 키가 하나만 포함되야한다(예제에서 Group). 또는 Django에서 ManyToManyField.through_fields를 사용해서 관계에 사용해야 하는 외래 키를 명시적으로 지정해야 한다. 두 개 이상의 외래 키가 있고 through_fields를 정의하지 않는다면, 유효성 검증 오류가 발생한다. 외래 키에 대한 비슷한 제한사항이 대상 모델(예제에서 Person)에도 적용된다.

중개 모델을 통해 다대다 관계를  가지고 있는 모델의 경우 동일한 모델에 대한 두 개의 외래 키가 허용되지만 다대다 관계와는 다른 측면으로 처리된다. 두 개 이상의 외래 키가 있는 경우, 위처럼 through_fields를 지정해야 한다. 그렇지 않으면 유효성 검증 에러가 발생한다.

중개 모델을 사용하여 자신의 모델로부터 다대다 관계를  정의할 때는 symmetrical=False를 사용해야 한다. 

이제 중개 모델(예제에서 Membership)을 사용하여 ManyToManyField를 설정했으므로 다대다 관계를 만들 준비가 되었다. 중간 모델의 인스턴스를 만들어 실행한다.

일반적인 다대다 필드와는 다르게 add(), create(), set()을 사용하여 관계를 만들 수 없다.

왜일까? PersonGroup 간의 관계를 만들 수 없다. Membership 모델 관계에 필요한 모든 세부 정보를 지정해야 한다. 간단한 add, create 및 할당 호출은 추가 세부사항을 지정하는 방법을 제공하지 않는다. 결과적으로 중간 모델을 사용한 다대다 관계에서는 비활성화된다. 이런 관계의 유형을 생성하기 위한 유일한 방법은 중간 모델의 인스턴스를 생성하는 것이다.


비슷한 이유로 remove() 메서드가 비활성화된다. 예를 들면, 중간 모델에 의해 정의된 사용자 정의 테이블을 통해 (model1, model2)의 유일성이 적용되지 않은 경우 remove() 호출은 지워야 할 중간 모델 인스턴스에 대한 충분한 정보를 제공하지 않는다.

하지만, clear() 메서드를 사용하여 모든 인스턴스에 다대다 관계를 삭제할 수 있다.

중간 모델의 인스턴스를 생성하여 다대다 관계를 설정하면 쿼리를 실행할 수 있다. 일반적인 다대다 관계같이 다대다 관계 모델의 속성을 사용하여 쿼리 할 수 있다.

중간 모델을 사용할 때 해당 속성에 대해서도 쿼리 할 수 있다.

회원의 정보에 접근이 필요할 경우 직접 Membership 모델에 쿼리 하여 수행할 수 있다.

같은 정보에 접근하는 다른 방법은 Person 객체에서 다대다 reverse relationship를 쿼리 하는 것이다.


One-to-one relationships


일대일 관계는 OneToOneField를 사용해 정의한다. 다른 필드 타입과 같이 모델의 클래스 속성을 포함하여 사용한다.


객체를 어떤 방법으로 다른 객체로 확장할 때 매우 유용하다.


OneToOneField에는 위치 인수가 필요하다. 모델이 관련 클래스이다.


예를 들어, 주소, 전화번호, 기타 등등의 속성이 포함된 "places"의 데이터베이스를 구축한 후 장소위에 레스토랑의 데이터베이스를 구축하길 원한다면 해당 장소의 정보를 복사해서 새로운 레스토랑 데이터베이스를 구축하는 대신에 Restaurant에 OneToOneField(대상은 Place)로 만들 수 있다. 사실 일반적으로 암시적인 일대일 관계가 포함된 상속을 사용한다.


ForeignKey처럼, 재귀 관계가 정의될 수 있고 아직 정의되지 않은 모델도 참조할 수 있다.


OneToOneField는 또한 선택적 인수 parent_link를 제공한다.


OneToOneField 클래스는 모델에서 자동으로 primary key 된다. 원한다면 수동으로 primary_key 인수를 설정할 수 있다. 그리하여 한 모델이 여러 개의 OneToOneField 타입의 필드를 포함할 수 있다.


Models across files


모델을 다른 앱의 모델과 관계하는 것도 가능하다. 이것을 하려면 모델이 정의된 파일의 맨 위에 관련된 모델을 import 한다. 그다음 필요한 곳에 다른 모델 클래스를 참조해라.


Field name restrictions


Django는 모델 필드 이름에 두 가지를 제한한다.


파이썬  예약어는 파이썬 구문 오류가 발생하기 때문에 필드 이름이 될 수 없다.

필드 이름은 한 행에 두 개 이상의 밑줄이 포함할 수 없다. Django의 쿼리 조회 구문의 동작 방법 같은 두 개의 밑줄은 포함될 수 없다.


필드 이름이 데이터베이스 칼럼 이름과 일치할 필요는 없기 때문에 이러한 제한사항은 해결될 수 있다.


Django는 모든 쿼리에 데이터베이스 테이블 이름과 칼럼 이름을 이스케이프 처리하기 때문에 모델 필드 이름으로 join, where, select와 같은 SQL 예약어를 허용한다.


Custom field types


기존 모델 필드 중 하나가 목적에 맞게 사용될 수 없거나 일반적인 데이터베이스 칼럼이 아닌 경우 자신만의 필드 클래스를 작성 가능하다.


Meta options


다음과 같이 inner class Meta를 사용하여 모델 메타데이터를 전달해라.

모델 메타데이터는 정렬 옵션 (ordering), 데이터베이스 테이블 이름 (db_table), 또는 사람이 읽을 수 있는 단수와 복수 이름(verbose_name verbose_name_plural) 등등처럼 "필드가 아닌 모든 것이다". 아무것도 필수가 아니며 모델의 클래스 메타를 추가하는 것은 완전히 선택사항이다.


Model attributes


objects

모델의 속성 중에 가장 중요한 것이 Manager이다. Django 모델에 대해 데이터베이스 쿼리 작업이 제공되고 데이터베이스에서 인스턴스를 조회하는 데 사용되는 인터페이스다. 사용자 정의 Manager를 정의하지 않으면 기본 이름이 objects이다. 매니저는 모델 인스턴스가 아닌 모델 클래스를 통해서만 접근할 수 있다.


Model methods

모델에 사용자 메서드를 정의하여 객체의 "row-level" 기능을 추가한다. Manager 메서드는 "table-wide"의 일을 처리하기 위함이지만 모델 메서드는 특정 모델 인스턴스에서 동작해야 한다.


모델 메서드는 비즈니스 로직을 한 곳(모델)에서 유지하게 해주는 중요한 기술이다.


사용자 지정 메서드가 있는 모델 예제

예제에서 마지막 메서드는 property다.


각 모델에 자동으로 부여된 메서드들이 있고 재정의 가능하다. 하지만 대부분 아래 두 가지 메서드의 재정의를 한다.


__str__()

모든 객체의 문자 표현을 리턴하는 파이썬 "magic method". 파이썬과 Django에서 모델 인스턴스를 강제로 문자열로 표시되어야 할 경우 사용된다. 특히 대화형 콘솔 또는 관리자 화면에서 객체를 보여줄 때 발생한다.


매번 이 메서드 정의를 원할 것이다. 기본 메서드는 전혀 도움되지 않는다.


get_absolute_url()

Django는 객체의 URL을 계산하는 방법을 제공한다. Django 관리자 인터페이스에서 이것을 사용하고 언제든 객체의 URL을 찾을 필요가 있다.


고유하게 식별할 URL을 가진 객체는 이 메서드를 정의해야 한다.


Overring predefined model methods


사용자 정의할  데이터베이스 동작들을 캡슐화하는 다른 모델 메서드 집합이 있다. 특히 save()delete() 동작 변경을 원하는 경우가 많다.


동작을 변경하기 위해 이 메서드들을 자유롭게 재정의할 수 있다.


내장 메서드를 재정의하는 고전적인 사용 사례는 객체가 저장될 때마다 무언가를 원하는 경우다.

저장하는 것을 막을 수도 있다.

객체가 항상 안전하게 저장되는 위해 superclass 메서드를 호출하는 것을 기억하는 것이 중요하다. superclass 메서드 호출하는 것을 잊으면, 기본 동작과 데이터베이스를 손대지 않는다.


또한 모델 메서드에 전달할 수 있는 인수들을 전달하는 것이 중요하다. - *args, **kwargs. Django는 수시로 내장 모델 메서드의 기능을 확장하고 새로운 인수를 추가한다. 메서드 정의에서 *args, **kwargs를 사용한다면 코드가 추가될 때 자동으로 해당 인수들을 지원하는 것을 보장받는다.


Overridden model methods are not called on bulk operations

QuerySet을 사용해서 대량으로 객체를 삭제하거나 cascading 삭제의 결과로 해당 객체의 delete() 메서드가 반드시 호출되지는 않는다. 안전한 사용자 정의 로직을 위해서는 pre_deletepost_delete signal을 사용한다. 불행하게도, 객체를 대량으로 만들거나 수정할 때는 save(), pre_save, post_save가 호출되지 않기 때문에 해결방법이 없다.


Executing custom SQL

또 다른 일반적인 패턴은 모델 메서드와 모듈 수준의 메서드에 사용자 정의 SQL문을 작성하는 것이다.


Model inheritance


장고에서 모델 상속은 일반적인 파이썬에서의 클래스 상속과 거의 동일하게 동작하지만, 페이지 시작 부분의 기본적인 부분은 따라야만 한다. 이는 기본 클래스는 django.db.models.Model의 서브클래스여야 한다는 것을 의미한다.


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


Django에서 가능한 상속의 세 가지 스타일이 있다.

흔히 각 자식 모델에서의 입력을 원하지 않는 정보를 부모 클래스를 사용하여 보유하길 원할 것이다. 이 클래스는 독립적으로 사용되지 않으므로 abstract base class를 사용한다.

기존 모델을 하위 클래스화(다른 애플리케이션의 일부분일 수도 있음)하고 각 모델이 데이터베이스 테이블을 가지길 원한다면, 다중 테이블 상속이 필요하다.

마지막으로, 모델 필드의 수정 없이 오직 모델의 파이썬 레벨의 행동만을 수정하길  원한다면, 프락시 모델을 사용할 수 있다.


Abstract base classes

abstract base class는 공통 정보를 다른 모델들도 가지길 원할 때 유용하다. 기본 클래스를 작성하고 메타 클래스 안에 abstract=True를 넣는다. 이 모델은 데이터베이스 테이블을 생성하는 데 사용되지 않을 것이다. 대신에, 다른 모델을 위한 base class로 사용될 때 자식 클래스에 해당 필드들이 추가될 것이다. 자식 필드 이름을 추상 기본 클래스도 갖는 것은 오류다(DJango가 예외를 발생시킬 것이다).

Student 모델은 name, age, home_group 세 개의 필드를 가질 것이다. CommonInfo 모델은 abstract base class가 된 이후로는 일반적인 Django 모델로 사용할 수 없다. 데이터베이스 테이블을 생성하고나 매니저를 가질 수 없기 때문에 직접 인스턴스 화하거나 저장할 수 없다.


많은 용도로 이 모델 상속의 유형을 원한다. 파이썬 레벨에서 공통 정보를 제외시키고, 데이터베이스 레벨에서 자식 모델당 하나의 데이터베이스 테이블을 생성한다.


Meta inheritance

abstract base class가 생성되었을 때, Django는 기본 클래스에 선언된 Meta inner 클래스를 속성으로 사용할  수 있게 만든다. 자식 클래스가 자신의 Meta 클래스를 선언하지 않는다면, 부모의 Meta를 상속받을 것이다. 자식이 부모의 Meta 클래스의 확장을 원한다면 서브클래스를 사용할 수 있다.

Django는 abstract base class의 Meta 클래스에서 단 한 가지를 수정한다. Meta 속성을 설치하기 전에 abstract=False로 설정한다. abstract base class의 자식 자신은 자동으로 추상 클래스가 되지 않는다는 의미다. 물론 다른 추상 기본 클래스를 상속받은 추상 기본 클래스로 만들 수 있다. 매번 abstract=True를 설정하는 것만 기억하면 된다.


어떤 속성들은 추상 기본 클래스의 Meta 클래스에 포함하는 것이 타당하지 않다. 예를 들어, db_table을 포함하는 것은 모든 자식 클래스들(자신의 Metadb_table을 지정하지 않은 클래스)이 같은 데이터베이스 테이블을 사용하는 것과 같다. 이는 분명히 원하는 바가 아닐 것이다.


Be careful with related_name and related_query_name

ForeignKeyManyToManyFieldrelated_name이나 related_query_name을 사용한다면 필드의 고유한 reverse 이름과 쿼리 이름을 지정해야 한다. 이 클래스에 필드는 각 자식 클래스에 포함되고 매번 속성(related_namerelated_query_name 포함)에 대해 같은 값이 저장되므로 일반적으로 abstract base class에서 문제가 발생한다.


이 문제를 해결하기 위해, 추상 기본 클래스에서 related_name 또는 related_query_name을 사용할 때, 값의 일부분에 '%(app_label)s'와 '%(class)s'가 포함되어야 한다. 

'%(class)s'는 해당 필드가 사용되는 자식 클래스의 소문자 이름으로 교체된다.

'%(app_label)s'는 자식 클래스가 포함된 앱의 소문자 이름으로 교체된다. 각 설치된 애플리케이션 이름은 고유해야 하고 각 앱 안의 모델 클래스 이름 또한 고유해야 하므로 결과적으로 이름은 달라지게 된다.

common/models.py 예제

위 예제와 다른 앱의 rare/models.py

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을 구성하는 방법은 당신에게 달렸다. 하지만 사용하는 것을 잊는다면 Django가 시스템 체크를 수행할 때(또는 migrate 실행) 에러가 발생할 것이다.


abstracst base class 필드에 related_name 속성을 지정하지 않는다면, 기본 리버스 이름이 클래스에 직접 필드를 선언한 것처럼 자식 클래스 이름에 이어 '_set'을 붙인 것이 된다. 예를 들어, 위 코드에서 related_name 속성을 생략한다면, m2m필드의 리버스 이름은 ChildA 경우에 chila_set, ChildB 필드는 childb_set 이 된다.


Multi-table inheritance

Django가 지원하는 모델 상속의 두 번째 형식은 계층의 각 모델이 모두 하나의 모델일 때이다. 각 모델은 자체 데이터베이스 테이블과 일치하고, 개별로 쿼리하고 생성 가능하다.

상속 관계는 자동으로 생성된 OneToOneField를 통해 자식 모델과 각 부모 사이의 링크를 끼워 넣는다.

Place의 모든 필드는 Restaurant에서 사용 가능하지만, 데이터는 다른 데이터베이스 테이블에 존재한다. 그래서 다음의 두 가지 모두 가능하다.

또한 PlaceRestaurant이 있다면, Place 객체로 모델 이름의 소문자 버전을 사용해서 Restaurant 객체를 얻을 수 있다.

하지만, 위 예제의 pRestaurant (Place객체로 직접 생성했거나 다른 클래스의 부모인 경우) 가 없다면, p.restaurant를 참조하면 Restaurant.DoesNotExists 예외가 발생될 것이다.


Restaurant에 자동으로 생성된 OneToOneField는 다음과 같다.

Restaurantparent_link=True를 사용해서 자체에 OneToOneField를 선언하여 필드를 재정의 할 수 있다.


Meta and multi-table inheritance

다중 테이블 상속 상황의 관계에서, 자식 클래스가 부모의 Meta 클래스를 상속받는 것은 의미가 없다. 모든 Meta 옵션은 이미 부모 클래스에서 적용되었고 다시 적용한다면 모순된 행동일 뿐이다 (이는 base class가 자체가 존재하지 않는 abstract base class의 경우와 대조적이다).


그러므로 자식 모델은 부모의 Meta 클래스에 접근할 수 없다. 하지만, 자식이 부모로부터 행동을 상속받는 몇몇 한정된 케이스가 있다. 자식이 ordering 속성 또는 get_latest_by 속성을 지정하지 않으면, 부모로부터 상속받는다.


부모에 정렬 옵션이 있지만 자식이 상속을 원하지 않는다면 명시적으로 기능을 중지시킬 수 있다.


Inheritance and reverse relations

멀티 테이블 상속은 자식과 부모의 링크에 암시적으로 OneToOneField를 사용하기 때문에 위 예와 같이 부모에서 자식으로 이동이 가능하다. 그러나 related_name을 ForeignKeyManyToManyField 관계에서 사용되는 기본값을 동일하게 사용한다. 이러한 관계 유형을 부모 모델의 서브클래스에 넣는다면(상속과 관계 유형을 둘 다 포함), 반드시 related_name 속성을 각 필드에 지정해야 한다. 지정하지 않는다면 Django는 유효성 검사 에러를 발생시킨다.


Place 클래스를 예제에서 다시 사용한다. ManyToManyField를 가진 다른 서브클래스를 만들어보자.

에러의 결과는 아래와 같다.

customers필드에 related_name을 추가하면 에러가 해결된다.

models.ManyToManyField(Place, related_name='provider')


Specifying the parent link field

말한 바와 같이, Django는 자식 클래스가 추상이 아닌 부모 모델을 연결하는 OneToOneField를 자동으로 생성한다. 부모로 연결되는 속성의 이름을 제어하기를 원한다면, 자식 클래스에 OneToOneField를 만들고 parent_link=True를 설정하여 이 필드가 부모 클래스의 링크임을 표시할 수 있다.


Proxy models

다중 테이블 상속을 사용하면 모델의 서브클래스마다 새로운 데이터베이스가 생성된다. 보통 subclass는 base class에는 없는 추가적인 데이터 필드를 저장할 곳이 필요하기 때문에 바라는 동작이다. 하지만, 때로는 기본 매니저를 변경하거나 새로운 메서드의 추가 등 모델의 파이썬 동작의 변경만을 원할 때가 있다. 


오리지널 모델에 대한 프락시를 만든다. 프락시 모델의 인스턴스를 생성하고 삭제하고 수정할 수 있고 오리지널 모델을 사용하는 것처럼 모든 데이터를 저장할 수 있다. 차이점은 프락시에서 기본 모델의 정렬순서 또는 기본 매니저 등을 원본의 변경 없이 변경할 수 있다는 것이다.


프락시 모델은 일반 모델처럼 선언된다. Meta 클래스의 proxy 속성 True 설정으로 Django에 프락시 모델임을 알린다.


Person 모델에 메서드 추가를 원한다고 가정한다. 다음과 같이 할 수 있다.

MyPerson 클래스는 부모인 Person 클래스와 같은 데이터베이스 테이블에서 작동한다. 특히, Person의 모든 새로운 인스턴스는 MyPerson을 통해 접근할 수 있고 그 반대도 마찬가지다.

또한 모델의 기본 정렬을 정의하는데 프락시 모델을 사용할 수 있다. Person 모델의 정렬을 프락시를 사용할 때만 last_name 속성을 규칙으로 정렬한다.

일반 Person 쿼리는 정렬되지 않고 OrderedPerson 쿼리는 last_name에 의해 정렬된다.


Proxy 모델은 일반 모델과 같은 방법으로 Meta 속성을 상속한다.


QuerySets still return the model that was requested

Person 객체를 쿼리하면 Myperson 객체를 리턴하지 않는다. Person 객체의 쿼리셋이 리턴 객체의 타입을 결정한다. 프락시 모델의 요점은 본래 모델(Person)을 사용하는 코드는 이를 계속 사용하고 사용자 코드는 이를 확장하여 사용할 수 있다는 것이다. Person 모델을 어디서나 확장된 모델로 대체하는 방법이 아니다.


Base class restrictions

프락시 모델은 반드시 하나의 non-abstract 모델 클래스를 상속해야 한다. 프락시 모델은 다른 데이터베이스 테이블의 행 사이의 어떤 연결도 제공하지 않으므로 여러 개의 non-abstract 모델을 상속할 수 없다. 프락시 모델은 여러 개의 추상 모델 클래스(필드가 정의되지 않은 경우만)를 상속할 수 있다. 또한 프락시 모델은 여러 개의 프락시 모델(공통의 non-abstract 부모 클래스를 상속하는)을 상속할 수 있다.


Proxy model managers

프락시 모델에 어떤 모델 매니저도 정의하지 않는다면, 부모 모델의 매니저를 상속받는다. 프락시 모델에 매니저를 정의한다면, 부모 클래스의 정의된 매니저도 여전히 사용 가능하지만 프락시 모델의 매니저가 기본값이 된다. 


위 예제를 계속해서 Person 모델 쿼리에 사용될 기본 매니저를 다음과 같이 변경할 수 있다.

기존 기본값 교체 없이 프락시에 새로운 매니저 추가를 원한다면, 커스텀 매니저 문서에 설명된 기술을 사용할 수 있다. 새로운 매니저를 포함한 기본 클래스를 생성하고 메인 기본 클래스 다음에 상속한다.

아마도 자주 필요하지 않겠지만, 필요할 경우 가능하다.


Differences between proxy inheritance and unmanaged models

프락시 모델 상속은 모델의 Meta 클래스에 managed 속성을 사용하여 unmanaged 모델을 생성하는 것과 비슷해 보일 수도 있다.


Meta.db_table을 설정하면 unmanaged 모델을 생성해서 기존 모델을 가리고 파이썬 메서드를 추가할 수 있다. 하지만, 모든 원본 모델의 변경사항을 동일하게 수정해줘야 하기 때문에 반복적인 오류가 발생할 수 있다.


반면에, 프락시 모델은 프록싱중인 모델과 정확히 일치한다. 부모 모델의 필드와 매니저를 직접 상속받기 때문에 부모 모델과 항상 일치한다.


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

존재하는 모델 또는 데이터베이스 테이블을 미러링하고, 모든 원본 데이터베이스 칼럼을 원하지는 않는다면 Meta.managed=False를 사용한다. 이 옵션은 일반적으로 장고 제어 하에 있지 않은 데이터베이스 뷰와 테이블을 모델링할 때 유용하다.

오직 모델의 파이썬 동작만을 변경하길 원한지만 모든 원본 필드를 유지하고 싶다면,  Meta.proxy=True를 사용한다. 데이터 저장 시에 프락시 모델이 원본 모델의 저장구조와 정확히 일치하게 설정된다.


Multiple inheritance

파이썬의 서브 클래싱과 마찬가지로, Django에서는 모델이 여러 개의 부모 모델을 상속하는 것이 가능하다. 일반적인 파이썬 name resolution rule이 적용된다는 것을 명심해라. 예를 들어, 여러 부모가 모두 Meta 클래스를 포함한다면 첫 번째 부모의 Meta만 사용되고 나머지는 무시된다.


일반적으로는 여러 개의 부모로부터 상속할 필요가 없다. 다중 상속의 유용한 주요 사용케이스는 믹스인 클래스이다. 믹스인을 상속받은 모든 클래스에 특정 추가 필드나 메서드를 추가하는 것이다. 상속 계층을 가능하면 간단하고 직관적으로 유지하도록 노력하여 특정 정보들이 어디로부터 상속받았는지 쉽게 알도록 해라.


공통 id 기본키가 있는 여러 개 모델을 상속하면 에러가 발생한다. 다중 상속을 정확하게 사용하려면 기본 모델들에 AutoField를 사용해야 한다.

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


Field name "hiding" is not permitted

일반적인 파이썬 클래스 상속은 자식 클래스에서 부모 클래스로부터의 어떤 속성도 재작성하는 것이 허용된다. Django에서는 보통 모델 필드는 허용되지 않는다. non-abstract 기본 모델에 author라는 필드가 있다면 상속하는 다른 클래스에서 다른 모델 필드를 생성하거나 author 속성을 정의할 수 없다.


이 제약은 추상 모델로부터 상속받은 모델 필드에는 적용되지 않는다. 이러한 필드는 다른 필드나 값으로 재정의되거나 field_name = None을 설정하여 제거할 수 있습니다.


Warning

모델 매니저는 추상 기본 클래스로부터 상속된다. 상속된 관리자가 상속된 필드를 참조를 재정의하면 미묘한 버그가 발생할 수 있다. Django documentation의 custom managers and model inheritance를 참고해라. 추후 Manager 편에서 다룬다.


Note

몇몇 필드는 모델에 추가 속성을 정의한다. 예를 들어 ForeignKey는 필드 이름에 _id 가 추가된 추가 속성으로 foreign 모델에 related_name과 related_query_name과 같이 정의된다.


정의하는 필드가 변경되거나 삭제되어 정의할 필요가 없는 경우를 제외하면 추가 속성은 재작성될 수 없다.


부모 모델의 필드를 재정의하는 것은 새 인스턴스를 만드는 등의 영역(Model.__init__에 어떤 필드를 초기화할지 지정하는 것)과 직렬화가 어려워진다. 이러한 것들은 일반 파이썬 클래스 상속에서만 다루는 기능으로 Django에서는 다루지 않아도 된다.

 

이 제약은 오직 Field 인스턴스인 속성들만 적용된다. 원한다면 일반 파이썬 속성은 재정의 할 수 있다. 또한 오직 파이썬이 인식하는 속성의 이름에만 적용된다. 수동으로 데이터베이스 칼럼 이름을 지정하는 경우, 다중 테이블 상속을 위해 자식과 조상 모델 모두 같은 칼럼 이름을 가질  수 있다. (다른 두 개의 데이터베이스 테이블에 칼럼)


조상 모델에서 모델 필드를 재정의한다면 Django는 FieldError를 발생시킬 것이다.


Organizing models in a pacakage

manage.py startapp 명령으로 models.py 파일을 포함한 애플리케이션 구조를 생성한다. 모델이 많을 경우 별도의 파일로 구분하는 것이 유용할 것이다.


다음 할 일은 모델 패키지를 생성하는 것이다. models.py 제거하고 myapp/models/ 디렉터리에 __init__.py 파일과 모델이 저장될 파일을 생성한다.


예를 들어, 모델 디렉터리에 organic.pysynthetic.py가 있다면

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


작품 선택

키워드 선택 0 / 3 0

댓글여부

afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari