본문 바로가기

Django/DRF

[Django] ContentType

 

모델링을 설계하다보면 ManyToMany 필드처럼 클래스 모델을 추가로 생성하지 않고, 

한 모델이 여러 개의 모데로가 관계를 맺어야 하는 경우가 있다. 예를 들어, 댓글이나 좋아요와 같은 기능을 구현할 때 주로 사용된다.

 

class Post(models.Model):
    # 생략

    
class Comment(models.Model):
    # 생략
    
    
class Like(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    comment = models.ForeignKey(Comment, on_delete=models.CASCADE)

다양한 모델링이 있지만, Like 를 PostLike 와 CommentLike 로 나눌 수 있고 방법은 다양하다.

 

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericRelation


class Post(models.Model):
    # 생략
    like = GenericRelation('Like', related_query_name='post')
    
    
class Comment(models.Model):
    # 생략
    like = GenericRelation('Like', related_query_name='comment')
    
    
class Like(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField() 
    content_object = GenericForeignKey('content_type', 'object_id')
    # contenttypes과는 무관한 user
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.CASCADE)

 

먼저, Like 모델부터 봅시다.

class Like(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField() 
    content_object = GenericForeignKey('content_type', 'object_id')
  • content_type
    • -> content_type 필드는 ContentType 모델과 ForeignKey로 연결되는 필드이다.
    • -> content_type에는 'id', 'app_label', 'model' 인자가 들어가있다.
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;
  • objects_id
    • 관련될 모델의 PK를 저장할 수 있는 필드입니다.
  • content_object
    • GenericForeignKey 에 위 두가지 필드를 전달한다.

 

Post 모델을 보겠습니다. (Comment 모델과 동일함으로 Post만 다루겠음)

class Post(models.Model):
    # 생략
    like = GenericRelation('Like', related_query_name='post')

 

related_query_name 를 통해서 관련 개체에서 조회를 할 수 있다.

post의 title이 '첫번째' 라는 키워드를 포함하고 있는 Like 객체들을 필터링합니다.

 

orm, sql example

아래는 contenttypes을 사용할 시 사용할 여러 orm과 그에 해당되는 sql에서 대해서 정리해보았습니다.

 

Like 객체 생성

user = User.objects.get(id=user_id)
post = Post.objects.get(id=post_id)
like = Like.objects.create(content_object=post, user=user)

SQL

INSERT INTO `post_like` (`content_type_id`, `object_id`, `user_id`, `created_at`, `updated_at`) 
VALUES (9, 2, 2, '2020-08-20 18:20:02.885304', '2020-08-20 18:20:02.885373'); 
args=[9, 2, 2, b'2020-08-20 18:20:02.885304', b'2020-08-20 18:20:02.885373']

 

 

Like 객체 조회

post = Post.objects.get(id=post_id)
ct = ContentType.objects.get_for_model(post)
like = Like.objects.get(
    content_type=ct,
    object_id=post.id,
    user=request.user
)

SQL

SELECT `post_like`.`id`, `post_like`.`content_type_id`, `post_like`.`object_id`, `post_like`.`user_id`, `post_like`.`created_at`, `post_like`.`updated_at` 
FROM `post_like` 
WHERE (`post_like`.`content_type_id` = 9 AND `post_like`.`object_id` = 2 AND `post_like`.`user_id` = 2); 
args=(9, 2, 2)

 

특정 User의 특정 Post에 대한 Like 조회

post = Post.objects.get(id=post_id)
ct = ContentType.objects.get_for_model(post)
likes = Like.objects.filter(content_type=ct, user=request.user)

SQL

SELECT post_like.id, post_like.content_type_id, post_like.object_id, post_like.user_id, post_like.created_at, post_like.updated_at 
FROM post_like
WHERE (post_like.content_type_id = 9 AND post_like.user_id = 2)  
LIMIT 21; 
args=(9, 2)

 

 

특정 Post에 대한 Like의 수 조회

post = Post.objects.get(id=post_id)
ct = ContentType.objects.get_for_model(post)
like_cnt = Like.objects.filter(content_type=ct, object_id=post.id).count()

SQL

SELECT COUNT(*) AS `__count` 
FROM `post_like` 
WHERE (`post_like`.`content_type_id` = 9 AND `post_like`.`object_id` = 2); 
args=(9, 2)