사용자들이 자신의 여행기를 업로드 할 수 있는 게시판을 개발하고 있습니다.
게시물을 저장할 객체 테이블의 마이그레이션 작업은 예전에 완성해두었고,
그 당시 게시물 테이블은 User 테이블과 외래키로 연결되어 있었습니다.
게시물 DB와 사용자 DB를 분리하여 운영하고,
게시물 테이블의 작성자 필드를 사용자 DB와 연결한 것입니다.
이 당시 User테이블은 장고에서 기본으로 제공하는 User클래스였습니다.
문제는, 게시물 테이블을 모두 완성하고 한참이 지나서,
커스텀 유저 모델을 생성하였다는 것입니다.
당연히 게시물 클래스가 참조하는 User클래스를,
제가 생성한 커스텀 유저 클래스로 변경해주었습니다.
이후 커스텀 유저 클래스를 pgAdmin에 등록하기 위해 마이그레이션을 진행하다, 아래와 같은 에러를 마주하였습니다.
에러 메세지를 읽어보면, 제 DB에서 admin.0001 마이그레이션 파일이 의존성을 가진 user.0001 파일보다 먼저 적용되었다고 합니다.
즉, user.0001 마이그레이션 파일이 먼저 실행되고, 그 후 admin의 파일이 실행되어야 하는데, 순서가 바뀌어 생긴 에러라는 것입니다.
이는 구글링을 통해 간단히 해결하였습니다.
이 때까지는 에러의 근본적인 발생 원인을 이해하지 못했습니다.
하지만, 이를 해결하고 나서 발생한 추가적인 에러를 해결하며
해당 에러의 근본적인 발생원리를 이해하게 되었습니다.
우선 자세한 원리는 이 에러를 해결하고 나면 등장할 에러를 해결하며 설명하겠습니다.
INSTALLED_APPS = [
"corsheaders",
# "django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"myblog",
"blog_api",
"rest_framework",
'rest_framework_simplejwt',
"user"
]
urlpatterns = [
# path("admin/", admin.site.urls),
path('', include('myblog.urls')),
path('api/', include('blog_api.urls')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('user', include("user.urls"))
]
자, 이제 마이그레이션 작업을 다시 수행해줍니다.
python manage.py makemigrations user
이후 에러 메세지를 보니, admin이 아니라, myblog.0001이라는 마이그레이션 파일이 user.0001 마이그레이션 파일보다 먼저 적용되어 에러가 발생했다고 합니다.
제 상황을 다시 정리해봅시다.
myblog app 마이그레이션 완료(User 테이블 참조) >>> User스키마 새로 생성>>> 변경된 user app 마이그레이션 >>> 에러!
서비스 유저의 게시글을 저장하는 myblog app의 테이블은, 커스텀 유저 모델이 들어있는 user app의 테이블을 참조합니다.
아래는 myblog app 내의 models.py 파일의 일부 코드입니다.
Post 클래스는 사용자가 업로드한 게시물을 객체 형태로 DB에 저장합니다.
게시물 객체를 생성하는 Post클래스의 author 프로퍼티는, 게시물의 작성자를 기록합니다.
이때, 해당 프로퍼티는 사용자 테이블인 User 모델을 참조합니다.
class Post(models.Model):
#코드 생략
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="myblog_posts"
)
여기서의 User는 개발자가 작성하지 않아도 장고에서 기본적으로 제공하는 디폴트 유저 클래스입니다.
이후 저는 아래와 같이 커스텀 유저 클래스를 생성하였고,
class TravelCommunityUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(_('enter your email here'), unique=True)
password = models.CharField(max_length=20, unique=True)
user_name = models.CharField(max_length=50, unique=True)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
joined_date = models.DateField(default=timezone.now)
about_this_user = models.TextField(max_length=1000, blank=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
is_premium_member = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['user_name']
objects = TravelCommunityUserManager()
def __str__(self):
return self.user_name
프로젝트의 모든 디폴트 유저 클래스를 제가 생성한 디폴트 유저 클래스로 변경하였습니다.
당연히, User를 참조하던 Post 클래스도 이젠 TravelCommunityUser 클래스를 참조하도록 변경했습니다.
변경된 Post 클래스의 코드는 아래와 같습니다.
class Post(models.Model):
#코드 생략
author = models.ForeignKey(
🔥TravelCommunityUser🔥, on_delete=models.CASCADE, related_name="myblog_posts"
)
불꽃으로 감싼 부분이 변경된 부분입니다.
이 코드가 정상적으로 동작하려면, 커스텀 user app이 먼저 DB에 옮겨져야 myblog app에서 이를 참조할 수 있습니다.
하지만, 저의 경우 myblog app은 마이그레이션이 되어있으나 새로 생성한 TravelCommunityUser 테이블은 마이그레이션 되지 않아 Post테이블에서 이를 참조하지 못하고 있는 것입니다. 아직까진, 기존에 사용하던 기본 User 모델만이 마이그레이션 되어있는 상황인 것이죠.
즉, InconsistentMigrationHistory 에러는 테이블의 참조 관계에 있어, 참조해야 하는 테이블이 DB에 존재하지 않을 때 발생합니다.
저의 경우 Post 테이블에서 TravelCommunityUser테이블을 참조해야 하니, TravelCommunityUser를 먼저 마이그레이션 하고 그 후에 Post클래스를 마이그레이션 해야 하는 것입니다.
해결 방법은 간단합니다.
[1] Post가 있는 myblog app의 migration을 취소해버린다.
[2] TravelCommunityUser 가 있는 user app을 migration한다
[3] 다시 myblog app을 migration한다.
이를 해결하기 위해, myblog app 폴더의 0001 migration 파일을 삭제해주겠습니다.
이후 다시 user app의 migration을 진행해주면, 문제 없이 수행됩니다.
이제, user app에 의존하는, 방금 삭제한 myblog app을 다시 마이그레이션 해줍니다.
이제, myblog app의 테이블에선, 제가 생성한 user model이 먼저 DB에 존재하기에, 이를 정상적으로 참조할 수 있어 더이상의 에러를 발생시키지 않고 있음을 확인할 수 있습니다.
DB상에서 테이블들이 외래키로 참조 관계를 맺는 상황에선,
어떤 테이블을 먼저 마이그레이션 해야할지 생각해 순서에 맞게 마이그레이션 해주는 것이 좋겠습니다.
당연한 말이지만, 모든 작업 완료 후에는 주석처리한 admin관련 항목들의 주석을 풀어주세요.
감사합니다.