RDB를 사용하여 모델을 만들다 보면 한 모델이 여러 모델과의 관계를 맺어야 하는 순간이 생기기 마련입니다. 일반적으로 생각한다면 ForeignKey를 이용하여 모델링을 하는 방법이 떠오를 것입니다.
위의 설명으로는 부족하니 하나의 모델을 통해 예시를 들어 설명해보겠습니다.
class Post(models.Model):
title = models.CharField(max_length=100)
# 여러 필드들
class Comment(models.Model):
content = models.CharField(max_length=100)
# 여러 필드들
class Recommend(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
comment = models.ForeignKey(Comment, on_delete=models.CASCADE)
하나의 포스트와 댓글이 있고, 각각 추천수가 존재할 수 있다고 가정해보겠습니다.
이 때, ForeignKey를 활용하여 컬럼을 생성하여 처리할 수 있는데 이 경우에는 참조할 모델의 수만큼 컬럼이 생성된다는 단점이 존재합니다. (지금은 2개에 불과하지만 더 많아질 가능성이 존재함)
Django에는 이를 해결하기 위한 GenericForeignKey라는 것이 존재합니다.
GenericForeignKey, ContentType 사용하기
자세한 설명은 장고 공식 Document를 참조하면 될 것 같습니다.
https://docs.djangoproject.com/en/3.2/ref/contrib/contenttypes/#id1
해당 포스팅에서는 직접 코드를 통해 사용하는 방법을 다뤄보겠습니다.
settings.py 설정
INSTALLED_APPS = [
'django.contrib.contenttypes',
# ...
]
GenericForeignKey를 사용하기 위해서는 django의 contenttypes가 필요합니다. 장고에 기본적으로 내장 되어있기 때문에 settings.py에 추가해줌으로써 간단하게 사용할 수 있습니다.
ContentType 란?
ContentType 모델은 기본적으로 장고에 내장되어 있는 모델입니다. 첫 마이그레이션시 django_content_type 라는 테이블 명으로 생성됩니다.
CREATE TABLE `django_content_type` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`app_label` varchar(100) NOT NULL,
`model` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `django_content_type_app_label_model_76bd3d3b_uniq` (`app_label`,`model`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
id, app_label, model 이라는 필드가 내장되어 있습니다. 따라서 ContentType과 FK를 맺게되면 특정한 모델과 범용적으로 관계를 맺을 수 있다는 것을 의미합니다.
모델 변경
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
class Post(models.Model):
title = models.CharField(max_length=100)
recommend = GenericRelation('Recommend', related_query_name='post')
# 여러 필드들
class Comment(models.Model):
content = models.CharField(max_length=100)
recommend = GenericRelation('Recommend', related_query_name='comment')
# 여러 필드들
class Recommend(models.Model):
content_type = models.ForeignKey(ContentType, on_delete.CASCADE)
object_id = models.PositiveIntegerField()
content_objct = GenericForeignKey('content_type', 'object_id')
# 여러 필드들
앞선 모델을 GenericForeignKey 와 ContentType 모델을 활용해 재정의 했습니다.
- content_type
content_type 필드는 ContentType 모델과 FK로 연결되는 필드입니다. - object_id
관련된 모델의 PK를 저장할 수 있는 필드입니다. (Post, Comment의 PK를 의미) - content_object
GenericForeignKey로 content_type, object_id 필드를 전달합니다. 이 필드는 db에는 반영되지 않습니다.
모델 사용
recommend = Recommend.objects.filter(post__title='leffe tistory')
Post 모델에 related_query_name을 정의해주었으므로 Recommend 모델에서 post라는 이름으로 접근이 가능합니다.
(Comment 모델일 경우에는 comment__content와 같은 방법으로 접근할 수 있습니다.)
남용은 금물
지금까지 보았던 것처럼 GenericForeignKey, ContentType을 활용하면 ForeignKey 필드를 여러개 생성하지 않고도 손쉽게 다양한 관계를 맺을 수 있게됩니다. 당장의 필드 갯수를 줄여주기에 좋아보일 수 있습니다.
하지만, 외부 키 제약조건 없이 사용하는 NoSQL DB와 거의 동일한 형태를 띄기 때문에 사용에 주의를 기울여야 합니다. 이 필드를 남용하게 된다면 다음과 같은 문제가 발생할 가능성이 있습니다.
- 모델 간의 인덱싱이 존재하지 않으면 쿼리 속도에 손해를 가져옵니다.
- 다른 테이블에 존재하지 않는 레코드를 참조할 수 있는 데이터 충돌의 위험성이 존재합니다.
따라서, 되도록이면 이용을 피하고 꼭 필요한 부분에만 적용시키는 것이 좋습니다.
이 관계를 사용하는 것 대신에 모델 디자인을 새롭게 하거나, PostgreSQL의 필드를 통해서 해결할 수 있는지 먼저 확인해보아야 합니다.
불가피하게 이용해야만 하는 경우라면 서드 파티 앱을 통해서 데이터를 깔끔하게 유지하는 방법도 좋을 것 입니다.
Django 에서 범용 관계(Generic Relation)을 맺는 방법을 알아보았습니다. 범용이라는 의미 만큼 쉽게 관계를 맺을 수 있지만 이에 따라오는 문제들을 한번쯤은 생각해보고 사용하는 것이 좋을 것 같습니다.
'SW개발 > Django' 카테고리의 다른 글
[Django]setUp() vs setUpTestData() 차이점 (0) | 2021.08.16 |
---|---|
[Django]get_object() 란? (0) | 2021.08.13 |
[Django]Django REST Framework - Permissions (0) | 2021.07.21 |
[Django]Docker-compose로 Django 환경 구축하기 6 - Celery (0) | 2021.06.11 |
[Django]Docker-compose로 Django 환경 구축하기 5 - Nginx (0) | 2021.06.09 |