[Django] - Model Inheritance

오동훈·2022년 8월 16일
0

Django

목록 보기
6/23

Django Model은 크게 3가지의 모델 상속 타입을 지원한다.

1. Abstract Base Classes

가장 특별하지 않은 상속 방법이다.

class Person(models.Model):    
	name = models.CharField(max_length = 100)
    sex = models.CharField(max_length = 10)
	birthday = models.DateField()

	class Meta:        
		abstract = True

class donghun(Person):    
    favoriteFood = models.CharField(max_length = 100)

class hong(Person):    
    pass

가장 위의 사람 전체를 포괄하는 필드들을 가진 Person 클래스가 선언됐고, 이 것을 상속받는 두 개의 모델 donghun, hong이 있다. Person 클래스를 부모, donghun, hong 자식 클래스들을 자식이라고 부르겠다. (원래 대문자로 선언했어야 했는데 소문자로 해버렸다ㅋ.ㅋ)

다음과 같이 선언됐으면 DB에는 Table이 어떻게 생성되어 있을까? 총 3개가 생성되어 있을까?!!

정답은 아니다!

메타 옵션에서 abstract = True를 설정해주면 부모 모델은 실제로 존재하지 않는 가상의 클래스로 변신하게 된다.
그리고 donghun, hong의 자식 클래스들은 부모의 필드를 모두 물려받아 실체가 있는 DB로 나타나게 된다.
그렇다면 DB에는 총 두 개의 DB 테이블을 갖게 되는 것이다.

abstract base를 사용한다는 것은 자식 모델들이 부모 없이 각각 독립적인 DB 테이블로써 존재하며, 자식과 부모의 상속관계는 실제로 없는 것이다. 공통된 필드가 많이 있는 모델 클래스들이 있을 때 코드를 효율적으로 사용하기에 편리한 기능이라고 생각한다.

추상 클래스 자식은 자동으로 추상 클래스가 되지 않음! 따로 abstract를 설정해 줄 필요 없음

2. Multi-table inheritance

기존 모델을 하위 클래스화하고, 각 모델이 자체 DB 테이블을 갖길 원하는 경우 주로 사용한다.

class Person(models.Model):    
	name = models.CharField(max_length = 100)
    sex = models.CharField(max_length = 10)
	birthday = models.DateField()

class donghun(Person):    
    favoriteFood = models.CharField(max_length = 100)

class hong(Person):    
    pass

위와 똑같은 예제를 사용했는데, abstract base classes와 다른 부분은 abstract = True 부분이 빠졌다는 점이다. 이로써 테이블을 상속해주는 부모까지 포함해 DB에서 보일 것이다.

우선 테이블을 만든 뒤 적용시켜보고 어떤 쿼리로 입력 되었나 확인해보면 다음과 같다.

알다시피 Person 테이블에는 우리가 잘 선언한 필드 name, sex, birthday가 들어간 것을 확인할 수 있고 추가로 id까지 들어간 것을 볼 수 있다. 그리고 dong 테이블은 선언한 필드 favoriteFood와 부모의 pk = id가 저장된 것을 알 수 있다.

Shell script를 통해 좀 더 자세히 이야기해보자.

>>> p = Person()
>>> p.name = 'Accent'			    # Person의 필드이므로 아무 문제 없다
>>> p.favoriteFood = 'meat'			# 당연히 에러
>>> p.save()						# 실제로는 나머지 필드인 birthday도 채우고 저장해야 할 것이다 (왜?)

>>> d = donghun()
>>> d.favoriteFood = 'meat' 	   	# OK
>>> d.name = 'donghun'    			# 부모의 필드까지 모두 가지고 있기 때문에 OK
>>> d.birthday = "1998-01-12"		# OK
>>> d.save()

부모는 자식의 필드 내용을 채우려 할 때 에러를 일으키지만, 부모의 필드를 상속받은 자식은 부모의 필드를 자유롭게 이용할 수 있다. 여기서 부모가 자식의 필드를 호출할 수는 있을까?

정답은 있다! 다음을 한 번 봐보자.

>>> p = Person.objects.get(name="donghun")
>>> p.donghun
<donghun: donghun object (2)>
>>> p.donghun.favoriteFood
'meat'
>>> p.name
'donghun'
>>> p.birthday
datetime.date(1998, 1, 12)

소문자로 연결할 수 있는데, 여기서 문제는 p가 donghun 클래스를 갖고 있다는 사실을 이미 안 상태에서 내가 직접 p.donghun이라고 타이핑을 통해 연결한다는 것이다. 내가 원하는 시나리오는 일일이 누가 어느 클래스인지 모르는 상태에서도 Person 클래스에서 연결되어 있는 자식 클래스로 바로 연결되는 것이다. 그렇지 못하다면 자식부터 거꾸로 접근해야하는데 그건 너무 복잡하고 자연스럽지 못하다.

이 문제의 해결은 Django에서 바로 지원되지는 않지만 외부 모듈을 통해 해결할 수 있다.
Django Docs - InheritanceManager를 이용하면 다음과 같은 코드로 내가 바라는 바를 해결해준다.

우선 InheritanceManager를 사용하려면 django-model-utils을 install 해주어야한다.

pip install django-model-utils
from model_utils.managers import InheritanceManager

class Person(models.Model):    
	name = models.CharField(max_length = 100)
    sex = models.CharField(max_length = 10)
	birthday = models.DateField()
    
    objects = InheritanceManager()    # objects 매니저를 새걸로 바꿔준다.

class donghun(Person):    
    favoriteFood = models.CharField(max_length = 100)

class hong(Person):    
    pass

다음과 같이 작성하고 shell을 이용해 함 살펴보자.

>>> from name.models import *           
>>> from model_utils.managers import IngeritanceManager 

>>> Person.objects.select_subclasses() 
<InheritanceQuerySet [<Person: Person object (1)>, <donghun: donghun object (2)>]>
>>> Person.objects.select_subclasses()[0]               
<Person: Person object (1)>
>>> Person.objects.select_subclasses()[0].name
'dong'

>>> Person.objects.select_subclasses()[1].name
'donghun'
>>> Person.objects.select_subclasses()[1].favoriteFood
'meat'

>>> Person.objects.get_subclass(name="donghun")
<donghun: donghun object (2)>
>>> p = Person.objects.get_subclass(name="donghun")
>>> p.favoriteFood
'meat'

내가 원했던 결과를 확인할 수 있다.

InheritanceManager를 사용하기 이전에는 Person 모델을 상속하는 donghun, hong의 인스턴스를 반환하는 것이 아니라 Person의 인스턴스를 반환한다. 이러한 문제를 해결하기 위해 Person 모델에 IngeritanceManager를 붙여 select_subclasses() 같은 메소드를 사용했다.

  1. Person.objects.select_subclasses() # 부모를 통해 하위 클래스에 접근하는 방법
  2. Person.objects.get_subclass(name="donghun") # 하나만 갖고올 때 사용하는 명령어

3. Proxy models

Proxy model은 이미 존재하는 모델을 그대로 받아들이면서 새로운 기능을 추가할 수 있다. proxy model의 대표적인 사용 예제는 User model에 나만의 method를 추가하는 것이다.

from django.contrib.auth.models import User

class UserMethod(User):    
	class Meta:        
    	proxy = True    
        
    def my_custom_method(self):       
    	# ... do something ...

우선 proxy는 영어 사전에 대리인이라고 나와있다. built-in User model을 상속하는 UserMethod model을 만들었고, 거기에 아무런 필드를 추가하지 않고 proxy = True라는 Meta 옵션만 주었다. 이제 이 UserMethod model은 마치 대리인처럼 기존 User model의 내용을 모두 가지고 있는 instance를 뱉어낸다. 그리고 이 대리인은 proxy model에서 정의한 custom method를 사용할 수 있다.

proxyuser = UserMethod.objects.get(pk=1)
print proxyuser.username
# built-in User model의 필드 username에 접근할 수 있다.

method_result = proxyuser.my_custom_method() # custom method 실행

참고자료 📩

Django Docs - Model
Docs » django-model-utils - InheritanceManager
django-model-utils

profile
삽질의 기록들🐥

0개의 댓글