Django ORM and Polymorphism

미니어스의 한 Feature로 사용자의 텍스트 입력으로 부터 서울의 지명을 찾아내고 해당 지명과 같은 행정 구(District) 안에 있는 다른 지명을 출력해야 했다.

서울 시 내 지명이 한정적이라고 생각하고 선형 시간 내에 메모리 상에서 패턴 매칭 탐색이 가능한 aho-corasick 알고리즘을 이용해 지명을 탐색하기로 결정했다.

문제는 aho-corasick을 통해 탐색해낸 지명이 구 단위 지명인지 동 단위 지명인지 모른다는 점이었다. 누군가 자신의 위치를 얘기할 때, 강남이라는 구 단위 지명을 이야기 할 수도 있고, 역삼이라는 동 단위 지명을 이야기 할 수도 있다.

지명을 통해 DB에서 데이터를 로드해야 하는데 구(District) 테이블에서 가져올지, 동(Town) 테이블에서 가져올지 판단해야 한다.

나는 이 문제를 Django ORM의 Polymorphism을 이용해 해결 해보기로 결정했다. (이 블로그를 쓰는 시점에서는 Location 테이블에 지명과 district를 속성으로 놓고 지명에 동과 구를 구분하지 않고 넣는 것이 단기적으로 더 빠른 해결책이라는 생각이 들었지만…..)

Model inheritance에는 세 가지 방식이 있다.

  • 기본 방식 상속
  • Multi-table 상속
  • Proxy model

이 중 나는 Django ORM 단계에서 Super class를 상속 받는 Multi table inheritance를 이용한다. (나머지에 대한 설명은 https://docs.djangoproject.com/en/1.11/topics/db/models/ 참조)

// models.py
class Location(models.Model):
    name = models.CharField(max_length=12)


class City(Location):
    pass


class District(Location):
    mother_city = models.ForeignKey(City, null=True, blank=True)


class Town(Location):
    mother_district = models.ForeignKey(District, null=True, blank=True)

여기서 나는 각 구가 소속 도시를 Foreign Key로 갖게 하고, 각 동이 소속 구를 Foreign Key로 갖게 했다. 이 때 주의할 점은 Foreign Key를 갖는 attribute name이 Foreign Key의 Model class 이름과 같아서 안된다는 점이다. 이 것은 City, District, Town이 모두 Location을 상속 받고 있기 때문이다.

Django ORM에서 Model간의 상속을 할 경우, Django는 Superclass와 subclass 간에 OneToOne relationship을 만든다. City와 District, Town 테이블에 location_ptr이 생성되고 Location 테이블은 관련 정보를 갖고 있지 않지만, ORM 단에서 reverse relation ship을 city, district, town 이라는 이름으로 생성한다. 그리고 City, District, Town의 model object에서 attribute에 접근 할 때, 자연스럽게 Superclass의 속성에도 접근 하는데, 이 때 같은 속성 이름이 있으면 clash가 발생한다. 이때 reverse relation을 생성하지 않는 조건을 줄 수 있지만 혹시 모를 유용성을 고려해 유지했고, 따라서 District class에 city 대신 mother_city라는 네이밍을, Town class에 district 대신 mother_district라는 네이밍을 하였다.

이젠 구(District)인지 동(Town)인진 몰라도 어떤 지명이 사용자 입력에서 탐색 되었을 때, 해당 정보를 데이터베이스에서 불러오는 작업이 필요하다. 이 때 다음과 같은 방법으로 일괄적으로 Query를 생성할 수 있다.

loc = Location.objects.get(name=loc_words[0])

하지만 이 때, 이 loc이 District인지 Town인지에 상관 없이 Location type의 object를 반환한다. 하지만 District인지 Town인지에 따라 처리가 달라져야 하는데, 이것을 직접 다루기는 약간 머리가 아프다. 이것을 쉽게 해주는 패키지가 있다.

pip install django-polymorphic

pip를 이용해 설치를 한다.

INSTALLED_APPS += (
    'polymorphic',
    'django.contrib.contenttypes',
    ...
)

settings.py의 installed app에 ‘polymorphic’을 추가하고 다음과 같이 model을 수정한다.

from polymorphic.models import PolymorphicModel


class Location(PolymorphicModel):
    name = models.CharField(max_length=12)


class City(Location):
    pass


class District(Location):
    mother_city = models.ForeignKey(City, null=True, blank=True)


class Town(Location):
    mother_district = models.ForeignKey(District, null=True, blank=True)

Superclass가 models.Model이 아닌 PolymorphicModel을 상속한다. 기존의 Django ORM의 모든 query를 동일하게 제공하지만, Superclass를 이용해 query를 보냈을 때, Subclass type으로 object를 리턴해준다.

이처럼 django-polymorphic 의 도움을 받으면 Django model inheritance를 이용해 비슷한 속성의 여러 테이블에 대한 복잡한 쿼리를 단순하게 처리할 수 있다.

#Django #ORM #Model #Inheritance #Polymorphism

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

search previous next tag category expand menu location phone mail time cart zoom edit close