User 모델과 Post 모델이 존재한다고 할 때, 좋아요 기능을 구현해보자
- 하나의 Post에 여러 User가 '좋아요'를 누를 수 있음
- 한 User는 여러 개의 Post에 '좋아요'를 누를 수 있음
=> 즉 User 모델과 Post 모델은 Many-to-Many로 연결되어있으며 Like가 그 중계테이블이다.
Django에서 ManyToMany 관계를 만드는 세 가지 방법
1️⃣ 중계 테이블을 직접 만들고, 해당 테이블이 Foreign Key로 두 개의 모델을 가리키는 방법
2️⃣ 연결해야하는 모델 중 하나에 ManyToManyField를 사용하여 Django가 자동으로 중계 테이블을 만들도록 하는 방법
3️⃣ 1+2 방법, 중계 테이블을 만들고 ManyToManyField에 직접 만든 중계 테이블을 지정해주는 방법
실제 현업에서는 2️⃣번과 같이 ****자동으로 생성되는 중계 테이블의 사용을 지양하고 있는데 아래와 같은 한계가 있기 때문입니다.
- 중간 테이블을 생성해주긴 하지만 묵시적으로 생성해주기 때문에 자기도 모르는 복잡한 쿼리가 발생하는 경우가 생길 수 있다.
- 우리는 중간 테이블에 두 테이블의 기본키를 기본키이자 외래키로 들고와서 추가로 필요한 컬럼이 존재할 확률이 크지만, 중간 테이블에 필요한 추가 컬럼을 사용할 수 없다. → 아까 풀었던 2번 퀴즈를 기억하시나요? 주문 관계에서 배송 방법, 주문 날짜 등을 추가할 수 있다고 했었죠! 이러한 내용들을 추가할 수 없다면.. 구현이 아주 어려워지겠죠..
따라서 현업에서는 다대다 관계를 3️⃣번과 같이 일대다, 다대일 관계로 풀어 직접 사용하는 것을 권장~~~
중계 테이블 지정을 통해 ManyToMany 관계 만들기
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=256)
content = models.TextField()
created_at = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
like_users = models.ManyToManyField(User, related_name="like_posts", blank=True, through="Like")
def __str__(self):
return self.title
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
post/models.py를 위와 같이 수정함
Like 모델을 정의함
- User 모델과 OneToMany 관계를 가지고, Post 모델과 OneToMany 관계를 가짐 (각 테이블의 pk 값을 foreign key로 가짐)
- Django가 자동으로 만들어주는 중계테이블과 달리, created_at 처럼 우리가 원하는 컬럼을 추가할 수 있음!
ManyToManyField()로 Post와 User 모델을 연결함
- 중간 테이블로 사용할 모델은 through='Like' 를 통해 Like로 지정함
- blank=True로 필드가 비어있어도 됨 (유효함)
- related_name="like_posts" 지정을 통해 반대편 User 모델에도 like_posts 라는 필드가 생기게 됨. post.like_users는 해당 post에 좋아요를 누른 유저들, user.like_posts는 해당 user가 좋아하는 posts
** related_name은 ForeignKey와 ManyToManyField에서 설정할 수 있는 옵션인데요, 만약 설정해주지 않는다면 related_name의 default 값은 (해당 모델 이름의 소문자)_set 입니다.
cf) null=True와 blank=True의 차이
https://wayhome25.github.io/django/2017/09/23/django-blank-null/
$ python manage.py makemigrations
$ python manage.py migrate
당연히 새로운 모델을 생성하고 수정했으니 migration 필요
Django가 자동으로 중계 테이블 지정하는 ManyToMany 관계 만들기
Post과 Tag Model 간의 관계를 생각해보자.
한 태그는 동시에 여러 게시물에 붙을 수 있고, 하나의 게시물은 여러 태그를 가질 수 있음 => ManyToMany 관계
Tag와 Post의 연결이고, 이를 중계하는 테이블은 별로 관심 가질 필요가 없음
django-admin startapp tag
tag라는 이름의 app 생성해줌
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'drf_yasg',
'post',
'account',
'tag',
]
settings.py 의 INSTALLED_APPS 목록에 추가해줌
from django.db import models
# Create your models here.
class Tag(models.Model):
content = models.TextField()
이렇게 tag/models.py 에 Tag 모델도 정의해주고 migration 함
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from tag.models import Tag
class Post(models.Model):
title = models.CharField(max_length=256)
content = models.TextField()
created_at = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, null=True, on_delete=models.CASCADE)
like_users = models.ManyToManyField(User,blank=True,related_name='like_posts',through='Like')
tags = models.ManyToManyField(Tag, blank=True, related_name="posts")
post/models.py도 수정함
- Tag를 import해온 뒤, models.ManyToManyField(Tag, ~) 를 통해 연결해줌
- through 옵션을 설정하지 않았으므로 Django가 자동으로 중간 테이블 생성해주고 연결함
migration 다시 해야겠죠
'web > django' 카테고리의 다른 글
Django User 모델과 admin page (0) | 2024.05.20 |
---|---|
Django serializer 활용하기 - advanced (0) | 2024.05.14 |
Django 모델 생성하고 One-To-Many 관계 형성하기 (0) | 2024.05.13 |
데이터베이스 핵심 개념들 (0) | 2024.05.13 |
Django SWAGGER 이용해 API 명세 작성하기 (0) | 2024.05.08 |