[번역] Django - GenericForeignKey

폐쇄맨·2020년 8월 5일
0

WeCode

목록 보기
16/22

서론

다음과 같이 User, Project, Ticket 그리고 TimeLine 모델들이 있다.

class Ticket(models.Model):
    name = models.CharField(max_length=200, verbose_name=_("name"))
    slug = models.SlugField(max_length=250, null=False, blank=True, verbose_name=_("slug"))
    
class Project(models.Model):
    name = models.CharField(max_length=200, verbose_name=_("name"))
    slug = models.SlugField(max_length=250, null=False, blank=True, verbose_name=_("slug"))
    
class User(models.Model):
    name = models.CharField(max_length=200, verbose_name=_("name"))
    slug = models.SlugField(max_length=250, null=False, blank=True, verbose_name=_("slug"))
    
class Timeline(models.Model):
    involved_object = *****
    event_type = models.CharField(max_length=250, default="created")

만약 타임라인에 "유저 생성, 프로젝트 생성, 태스크 생성" 과 같은 유저의 활동을 저장하고 싶다고 한다면, 우리는 세 개의 모델(User, Project, Task)을 ForeignKey 필드로 지정해야한다. 이것은 좋은 프로그래밍 습관이 아니다. 이를 해결하기 위해, Django의 contenttypes 는 어떤 모델들과도 관계를 정의하게 해주는 GenericForeignKey 라는 특별한 필드 타입을 제공한다.

GenericForeignKey 사용하기

위의 시나리오를 해결할 수 있는 방법이 있다. GenericForeignKey를 세팅하는 것은 세 단계로 나뉜다.

  • ContentType 객체를 ForeignKey 로 정의한다. 이 필드의 이름은 대부분 content_type 이다.
  • 연관된 모델의 기본키 값을 저장할 수 있는 필드를 지정한다. 대부분의 경우 PositiveIntegerField 타입이다. 이 필드의 이름은 대부분 object_id 이다.
  • GenericForeignKey 타입 필드를 만들고 위의 두 필드의 이름을 인자로 넘겨준다. 이 인자들의 값은 content_typeobject_id 이며, 기본값이기 때문에 생략이 간읗다.

위의 세 단계를 따라하면, TimeLine 모델은 다음과 같이 변한다.

class Timeline(models.Model):
    content_type = models.ForeignKey(ContentType, related_name="content_type_timelines")
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')
    event_type = models.CharField(max_length=250, default="created")

만약 프로젝트 생성 후, "프로젝트 생성" 이라는 이벤트를 저장하고 싶다면 아래의 코드가 우리를 도와줄 것 이다.

t1 = TimeLine(content_object=project_object)
t1.save()

하지만 GenericForeignKey 가 구현된 방식 때문에, filter()exclude() 같은 필터에서 content_object 필드를 데이터베이스 API를 통해 직접적으로 사용할 수 없다. GenericForeignKey 가 일반적인 필드가 아니기 때문에, 다음과 같은 코드는 동작하지 않는다.

TimeLine.objects.filter(content_object=project_object)
# This will also fail
TimeLine.objects.get(content_object=project_object)

Project 객체로 TimeLine 객체를 얻으려면, 다음과 같은 단계를 거쳐야 한다.

  • ContentType 객체를 얻어온다.
from django.contrib.contenttypes.models import ContentType
	contenttype_obj = ContentType.objects.get_for_model(project_object)
  • object_idproject_object.id 로 저장되어 있다.
TimeLine.objects.filter(object_id=project_object.id, content_type=contenttype_obj)

이 포스팅의 원문은 MicroPyramid 블로그에 있습니다.

profile
폐쇄맨

0개의 댓글