일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- javascript
- MAC
- c++
- 알고리즘 연습
- 알고리즘 문제
- 알고리즘 풀이
- js
- form
- Django
- es6
- 장고
- react
- AWS
- API
- 파이썬 알고리즘
- java
- 백준
- Git
- 알고리즘
- 파이썬
- django ORM
- web
- DRF
- HTML
- Algorithm
- PYTHON
- django widget
- CSS
- Baekjoon
- django rest framework
- Today
- Total
수학과의 좌충우돌 프로그래밍
[Django] 한 모델이 여러 개의 모델과 관계를 맺어야하는 순간, contenttypes framework 본문
[Django] 한 모델이 여러 개의 모델과 관계를 맺어야하는 순간, contenttypes framework
ssung.k 2020. 8. 21. 06:15모델링을 하다보면 한 모델이 여러 개의 모델과 관계를 맺어야하는 순간이 있습니다.
많이 접해봤을 블로그 프로젝트만 해도 게시물과 댓글에 좋아요 기능을 넣기 위해서는 다음과 같은 모델링이 필요합니다.
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
로 나눌 수도 있고 방법은 다양합니다.
하지만 위에 소개한 방법은 post
나 comment
필드 중 하나는 Null이 들어가야한다는 문제점이 있고 그렇다고 모델을 나누는 것도 마음에 들지가 않았습니다.
그러다가 Django의 contenttypes framework
을 찾게 되어서 이를 정리해보았습니다.
contenttypes framework
contenttypes framework
는 위와 같은 상황, 즉 하나의 테이블에 선택적으로 다른 테이블과 관계를 맺고 싶을 때 사용할 수 있습니다.
별도의 설치없이 django에서 기본적으로 제공하고 있습니다.
INSTALLED_APPS = [
'django.contrib.contenttypes',
# 생략
]
따라서 migrate만 해주면 db에서 확인할 수 있습니다.
python manage.py migrate
이제 contenttypes framework
을 적용해봅시다.
우선 완성된 전체코드를 확인한 후 각각에 대해서 알아봅시다.
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')
-
cotent_type
cotent_type 필드는
ContentType
모델과 FK로 연결되는 필드입니다.ContentType
는 Django에서 기본적으로 제공하는 모델입니다.아래를 보면 id, app_label, model을 필드로 가지며 app_label에는 app이름이, model에는 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;
그렇기 때문에
ContentType
과 FK를 맺었다는 것은 현재 프로젝트 안에 특정 모델과 범용적으로 관계를 맺을 수 있는 것을 의미합니다. -
object_id
관련될 모델의 PK를 저장할 수 있는 필드입니다.
-
content_object
GenericForeignKey
에 위에 설명한 두 필드를 전달합니다. 위에서 설명한cotent_type
과object_id
는 db에 반영되지만 해당 필드는 db와는 무관하게 django에서 사용됩니다.
다음으로는 Post와 Comment 는 동일하게 사용가능하니 Post만 다루도록 하겠습니다.
class Post(models.Model):
# 생략
like = GenericRelation('Like', related_query_name='post')
related_query_name
를 통해서 관련 개체에서 조회를 할 수 있습니다.
post의 title이 '첫번째' 라는 키워드를 포함하고 있는 Like 객체들을 필터링합니다.
like = Like.objects.filter(post__title__contains='첫번째')
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)
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
)
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)
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()
SELECT COUNT(*) AS `__count`
FROM `post_like`
WHERE (`post_like`.`content_type_id` = 9 AND `post_like`.`object_id` = 2);
args=(9, 2)
'웹프로그래밍 > Django' 카테고리의 다른 글
[Django] ORM Cookbook, 정보를 조회하고 필요한 항목을 선별하는 방법(2) (0) | 2020.08.27 |
---|---|
[Django] ORM Cookbook, 정보를 조회하고 필요한 항목을 선별하는 방법(1) (0) | 2020.08.27 |
[Django] Logging 설정 및 SQL 쿼리 확인 (0) | 2020.08.15 |
[Django] AWS, S3를 이용한 이미지 업로드 (0) | 2020.08.12 |
[Django] Model Field의 Null vs Blank (0) | 2020.08.06 |