WEB/django

[Django] Single Table Inheritance

happykoa 2021. 7. 3. 18:41

회고록도 밀리고.. 여러모로 글을 안 쓰고 있다가, 조금씩 요즘 하고 있는 작업들을 정리할 필요가 있겠다 싶어서 정리해본다.

 

STI 개념을 처음 알게 된 건, 회사에서 사용하는 코드였었다.

이 개념은 최근에 레일즈만 하면서 다른 곳에서 쓸일이 없다 보니 루비 온 레일즈로 구현된 코드로만 할 줄 아는 개념이었다.

 

STI를 간단하게 말하면, "실제 DB상의 table은 하나지만, 코드 상으로는 여러 클래스로 나누어 사용한다" 정도로 말해볼 수 있을 것 같다.

 

하지만 한아름이라는 커뮤니티 사이트를 오랫동안 개발해오고 있는데, 이 사이트 내에서 게시판 구조를 STI로 구현하면 더 편하게 코드를 구성 할 수 있겠다 생각이 들었다.

 

왜냐하면

1. 일반적인 게시판이 있고, 점차 다른 부가적인 기능이 담긴 갤러리 게시판, 분실물 게시판 등등 특정 기능들이 추가되어야 하는 게시판이 필요하다고 이야기가 나왔다.

2. 하지만 일반 게시판 모델에 이 코드를 얹으면 쓸모없는 게시판에서도 이 기능들이 섞이게 된다. 그러면 하나의 게시판 모델에 여러 부류의 게시판 기능들이 다 모이기 때문이다.

3. 그렇다고 해서 아예 테이블을 분리한다? 이건 너무 비효율적이라고 생각했다. 분명히 갤러리 게시판, 분실물 게시판, 이것들도 하나의 게시판이며 기본적인 기능은 게시판의 기능을 따라가야 하기 때문이다.

 

서론이 길었다.

 

Django에서 STI는 어떻게 구현하는가?

 

django proxy model을 이용하면 된다.

 

먼저 이 방법을 알게 된 글은 아래 2013년에 적힌 글이다.

https://schinckel.net/2013/07/28/django-single-table-inheritance-on-the-cheap./

 

Django Single Table Inheritance on the cheap. - Schinckel.net

There was a recent question on Stack Overflow about Django Single Table Inheritance (STI). It got me thinking about how to use my FSM-proxy stuff to just be about STI. Note: this only works when all sub-classes have the same fields: the example we are goin

schinckel.net

 

다만, 너무 불필요한 기능도 있었으며, 현재 코드와 맞지 않는 부분들도 있어서 이를 조금 바꾸어서 사용하였다.

 

Board 클래스와 GalleryBoard 클래스가 있다고 하자.

 

1. 먼저 Board 클래스에 type 컬럼을 추가한다.

class Board(BaseModel):
  type = models.CharField(
    verbose_name='게시판 타입',
    max_length=20,
    blank=True,
    null=True,
    default='board',
  )

BaseModel은 한아름 프로젝트 상에서 사용하는 기본 모델 클래스다. models.Model을 사용해도 된다.

BaseModel의 코드는 여기에서 확인할 수 있다.

 

2. BoardManger 클래스와 GalleryBoard 클래스를 추가한다.

BoardQuerySet은 있어도 그만, 없어도 그만이다. 

가장 중요한건 objects 를 BoardModelManager.from_queryset(BoardQuerySet)() 로 지정하여, GalleryBoard.objects.all()과 같이 ORM을 사용할 때, type이 galleryboard 인 레코드들만 가져오는것이다.

그리고 다음으로 중요한건, self._meta.get_field('type').default = 'galleryboard' 와 같이 type 컬럼의 default 값을 바꾸지 않으면 GalleryBoard.new를 할 때, type을 지정하지 않으면 원래의 의도와 다르게 type에 board가 들어가면서 galleryboard가 만들어지지 않는다. (+ 그럼 GalleryBoard 에서 또 type 컬럼을 선언해도 되지 않냐고 할 수 있다. 이때는  다음과 같이, 같은 이름의 컬럼을 추가했다는 오류가 발생한다.  "django.core.exceptions.FieldError: Local field 'type' in class 'GalleryBoard' clashes with field of the same name from base class 'Board'.") 

class BoardModelManager(models.Manager):
    use_for_related_fields = True

    def get_queryset(self):
        return super().get_queryset().filter(deleted_at__isnull=True).filter(type=self.model.__name__.lower())

class BoardQuerySet(models.QuerySet):
    def published(self):
        return self.filter(status='p')

class GalleryBoard(Board):
    class Meta:
        proxy = True

    def __init__(self, *args, **kwargs):
        self._meta.get_field('type').default = 'galleryboard'
        super(GalleryBoard, self).__init__(*args, **kwargs)

    objects = BoardModelManager.from_queryset(BoardQuerySet)()

 

이제 완성이다.

 

추가적인 커스텀을은 따로 하면 되며, 위의 코드대로면 Board.objects.all()을 할 경우에는 type에 상관없이 모든 게시판을 가져오며, GalleryBoard.objects.all()을 하면 type이 galleryboard인 게시판 레코드만 가져오게 된다. 

 

Board.objects.all()을 할때도 type이 board인 경우에만 가져오고 싶다면 Board의 query_set을 만들어주면 되지만, 그다지 추천하지는 않는다. 만약 운영상에서 type을 잘못 만들어서 레코드를 생성한 경우, 해당 레코드를 어디서 확인하게 해줄 것인가? 

 

STI를 적용했으니 이제 갤러리 게시판을 열심히 만들어볼 차례다. ㅎㅎ

 

'WEB > django' 카테고리의 다른 글

[django] Django custom migration 하기  (0) 2021.07.03
singun11's Django Tutorial 목차  (0) 2020.10.26