본문 바로가기
프로그래밍/Django

Django ORM 최적화: GenericForeignKey를 활용한 데이터 모델링

by 우주를놀라게하자 2025. 2. 19.
반응형
SMALL

문제 사항


커뮤니티 관련 사이드 프로젝트를 진행하면서 모델링에서 발생한 문제를 정리해보려고 합니다.

예를 들어, 댓글 기능을 글, 사진, 비디오 등 다양한 콘텐츠에 적용할 수 있는 서비스를 만든다고 가정해봅시다.

이 경우, 댓글을 저장하는 테이블이 있고, 이를 각각의 콘텐츠(글, 사진, 비디오) 테이블과 개별적으로 연결해야 하는 문제가 발생합니다.

즉, 매번 새로운 콘텐츠 모델을 추가할 때마다 댓글 테이블과 별도의 관계(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")

 

GenericForeignKeyDjango의 contenttypes 프레임워크를 이용하여 여러 모델을 동적으로 참조할 수 있도록 도와주는 기능입니다.

즉, ForeignKey가 특정 모델만 참조할 수 있는 제한을 가지는 반면, GenericForeignKey여러 모델을 하나의 필드에서 참조 가능하게 만들어줍니다.

이 기능을 구현할 때, 아래의 두 개의 필드가 핵심적인 역할을 합니다


Content_type이란?

content_type 필드는 Django의 ContentType 모델과 연결된 ForeignKey입니다.

이 필드는 현재 Comment(또는 사용자가 정의한 다른 모델)가 어떤 모델(BlogPost, Video 등)을 참조하는지 식별하는 역할을 합니다.

즉, content_type을 통해 참조 대상 모델의 종류를 알 수 있으며, 이를 object_id와 함께 사용하여 특정 객체를 찾아낼 수 있습니다.


Content_object이란?

GenericForeignKey를 사용하여, content_typeobject_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 영상

 

마무리


DjangoGenericForeignKeyGenericRelation유연한 모델링을 가능하게 하는 강력한 도구입니다.

하지만 데이터 무결성을 보장하지 않기 때문에 사용 시 신중해야 합니다.

언제 활용하면 좋을지 고민하며 프로젝트에 적용하면 더욱 효과적으로 사용할 수 있습니다!

반응형
LIST