문제 사항
커뮤니티 관련 사이드 프로젝트를 진행하면서 모델링에서 발생한 문제를 정리해보려고 합니다.
예를 들어, 댓글 기능을 글, 사진, 비디오 등 다양한 콘텐츠에 적용할 수 있는 서비스를 만든다고 가정해봅시다.
이 경우, 댓글을 저장하는 테이블이 있고, 이를 각각의 콘텐츠(글, 사진, 비디오) 테이블과 개별적으로 연결해야 하는 문제가 발생합니다.
즉, 매번 새로운 콘텐츠 모델을 추가할 때마다 댓글 테이블과 별도의 관계(ForeignKey)를 설정해야 하는 불편함이 생깁니다.
이 문제를 해결할 방법을 고민하던 중, Django의 GenericRelation을 활용하면 유연한 설계가 가능하다는 것을 발견했습니다.
이번 글에서는 GenericRelation을 적용하기 전 테스트했던 코드와 함께, 어떻게 더 효율적으로 댓글 기능을 구현할 수 있는지 소개하고자 합니다.
GenericRelation이란?
GenericRelation은 한 모델이 여러 다른 모델과 일대다(One-to-Many) 관계를 맺을 수 있도록 하는 Django의 기능입니다.
일반적인 ForeignKey는 특정한 하나의 모델과만 관계를 맺을 수 있지만, GenericRelation을 사용하면 여러 개의 모델을 동적으로 참조할 수 있습니다.
즉, 하나의 테이블(예: 댓글)이 여러 다른 테이블(예: 글, 사진, 비디오 등)과 유연하게 연결될 수 있도록 도와줍니다.
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
# 공통으로 연결될 Comment 모델
class Comment(models.Model):
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
text = models.TextField()
# BlogPost 모델: GenericRelation을 사용
class BlogPost(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
comments = GenericRelation(Comment)
# Video 모델: GenericRelation을 사용
class Video(models.Model):
title = models.CharField(max_length=255)
video_url = models.URLField()
comments = GenericRelation(Comment)
related_query_name이란?
GenericRelation을 사용할 때, 역참조 시 사용되는 이름을 지정하는 옵션입니다.
기본적으로 GenericRelation의 필드 이름이 역참조 이름으로 사용되지만, related_query_name을 설정하면 더 명확한 이름을 부여하거나, 다른 모델과의 충돌을 방지할 수 있습니다.
class BlogPost(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
comments = GenericRelation(Comment, related_query_name='blog_comments')
# 관련된 Comment를 쿼리하는 방법
blog_posts_with_comments = BlogPost.objects.filter(blog_comments__text__icontains="interesting")
# 기본 GenericRelation 사용 시 기본 필드 명 사용
blog_posts_with_comments = BlogPost.objects.filter(comments__text__icontains="interesting")
GenericForeignKey는 Django의 contenttypes 프레임워크를 이용하여 여러 모델을 동적으로 참조할 수 있도록 도와주는 기능입니다.
즉, ForeignKey가 특정 모델만 참조할 수 있는 제한을 가지는 반면, GenericForeignKey는 여러 모델을 하나의 필드에서 참조 가능하게 만들어줍니다.
이 기능을 구현할 때, 아래의 두 개의 필드가 핵심적인 역할을 합니다
Content_type이란?
content_type 필드는 Django의 ContentType 모델과 연결된 ForeignKey입니다.
이 필드는 현재 Comment(또는 사용자가 정의한 다른 모델)가 어떤 모델(BlogPost, Video 등)을 참조하는지 식별하는 역할을 합니다.
즉, content_type을 통해 참조 대상 모델의 종류를 알 수 있으며, 이를 object_id와 함께 사용하여 특정 객체를 찾아낼 수 있습니다.
Content_object이란?
GenericForeignKey를 사용하여, content_type과 object_id 필드 값을 바탕으로 실제 참조되는 모델 객체를 반환
GenericForeignKey를 사용하지 않았을때
class BlogPost(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
class Video(models.Model):
title = models.CharField(max_length=255)
video_url = models.URLField()
# GenericForeignKey 없이 수동으로 관계 구현
class Comment(models.Model):
# 각 모델에 대한 ForeignKey 추가
blogpost = models.ForeignKey(BlogPost, null=True, blank=True, on_delete=models.CASCADE)
video = models.ForeignKey(Video, null=True, blank=True, on_delete=models.CASCADE)
text = models.TextField()
# Comment 모델에 새 필드 추가
class Comment(models.Model):
blogpost = models.ForeignKey(BlogPost, null=True, blank=True, on_delete=models.CASCADE)
video = models.ForeignKey(Video, null=True, blank=True, on_delete=models.CASCADE)
photo = models.ForeignKey(Photo, null=True, blank=True, on_delete=models.CASCADE)
def get_related_object(self):
if self.blogpost is not None:
return self.blogpost
elif self.video is not None:
return self.video
elif self.photo is not None:
return self.photo
return None
# Comment를 통해 관련된 객체를 가져오기
print(comment1.get_related_object()) # BlogPost 객체 반환
print(comment2.get_related_object()) # Video 객체 반환
# 각각의 객체의 속성에 접근
print(comment1.get_related_object().title) # Output: Django 강의
print(comment2.get_related_object().title) # Output: Django 영상
마무리
Django의 GenericForeignKey와 GenericRelation은 유연한 모델링을 가능하게 하는 강력한 도구입니다.
하지만 데이터 무결성을 보장하지 않기 때문에 사용 시 신중해야 합니다.
언제 활용하면 좋을지 고민하며 프로젝트에 적용하면 더욱 효과적으로 사용할 수 있습니다!
'프로그래밍 > Django' 카테고리의 다른 글
ElastiCache(Redis) Redis Insight EC2를 활용하여 접근하기 (0) | 2025.02.26 |
---|---|
하나? 여러 개? 대량? 😵 DRF Serializer 활용법 총정리! (0) | 2025.02.24 |
View가 깔끔해지는 Django 필터링 전략! get_queryset() vs filter_queryset() (0) | 2025.02.18 |
Django 대량의 list 요청시 Cursor-based Pagination (0) | 2025.02.18 |
[Django]filter_fields로 URL 파라미터 사용하기 (0) | 2022.05.13 |