Model들간의 circular import dependencies 해결하기

최근 Tcoops of Django 라는 책을 보며 Django를 처음부터 다시 복습하고 있다. 그리고 책에서 배운 것을 토대로 간단한 블로그를 만들어 보고 있는데, 특히 아래의 부분들을 중점적으로 신경쓰고 있다.

  • Django에서 제공하는 다양한 Generic class-based-view들을 최대한 활용하여 코드를 간결하게 하자.
  • Django에서 제공하는 Form을 이용하여, Form과 관련된 로직들을 View와 분리하자.(책의 말을 빌리자면, View는 오직 프레젠테이션(presentation) 로직만을 처리해야 한다. 비지니스(business) 로직은 Model이나 Form으로 이동시키자.)
  • 또 Form을 이용하여 모든 경우의 입력 데이터에 대하여 유효성 검사를 하자.
  • Class, Mixin의 상속을 통해 코드의 재사용성을 극대화하자.
  • username없이 email만으로도 인증이 가능한 User 모델을 만들어 보자.

블로그에 게시물에 대한 좋아요 기능도 넣고 싶어, 아래와 같이 Like라는 모델을 생성하고, Post 모델을 수정하였다.

# posts/models.py

from django.db import models  
from django.conf import settings

from likes.models import Like


class Post(models.Model):  
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    title = models.CharField(max_length=20)
    content = models.TextField()
    image = models.ImageField(
        blank=True,
        null=True,
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # 아래 필드 추가
    like_user_set = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name="like_post_set",
        through=Like,
    )
# likes/models.py

from django.db import models  
from django.conf import settings

from posts.models import Post


class Like(models.Model):  
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    post = models.ForeignKey(Post)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

그러고 migrate를 하려고 하니, 아래와 같은 에러가 발생하였다.

전혀 생각지도 못한 에러라 매우매우 당황했다. 오타가 있겠지 싶어 자세히 봤지만, 안타깝게도 틀린 글자도 없었다. 다른 코드에서도 항상 이렇게 import 하였는데, 왜 여기서만 에러가 뜨는건지 도무지 이해가 안 됐었다.

한참을 구글링한 끝에, 내가 현재 circular import dependencies 문제를 겪고 있음을 알 수 있었다.

Django는 settings.py 안의 INSTALLED_APPS라는 배열에 있는 app들을 위에서부터 순서대로 정의한다.

# settings.py

# Application definition

INSTALLED_APPS = [

    # 생략
    'users',
    'posts',
    'likes',

따라서 users => posts => likes 순서로 app을 정의하게 되는데, 이 때 posts라는 app을 정의하고 있다고 하자.

  1. posts/models.py에서 from likes.models import Like를 만나게 된다.

  2. Like를 import하기 위해서 likes/models.py로 가였더니 from posts.models import Post를 만나게 된다.

  3. 다시 posts/models.py로 가서.. 응?

이렇게 무한반복이 된다.

어떤 app의 정의가 채끝나기도 전에 다시 해당하는 app 자신이 참조되기 때문에 발생한 문제이다. 그렇다고 서로 다른 app에서 서로의 model를 참조하는 것이 불가능한 것은 아니다.
Django 공식 문서를 보면 아래와 같이 해결법이 나와있다.

If you need to create a relationship on a model that has not yet been defined, you can use the name of the model, rather than the model object itself

문서에 나와있는 대로, ForeignKey, ManyToManyField 안에 model 객체가 아닌 model의 이름을 문자열로 넣어주었더니 정상적으로 작동하였다.

# posts/models.py

like_user_set = models.ManyToManyField(  
    settings.AUTH_USER_MODEL,
    related_name="like_post_set",
    through="likes.Like",
)
# likes/models.py

post = models.ForeignKey("posts.Post")  

책을 보며 공부하면서, Django라는 프레임워크에 대해 더욱 자신감이 생기고, 조금씩 거만해질수도 있는 찰나, 아직도 내가 모르는 부분은 어마어마하게 많다는 것을 다시 한 번 느낄 수 있는 좋은 계기였다.