web/django

Django 모델 생성하고 Many-To-Many 관계 형성하기

민사민서 2024. 5. 14. 00:44

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️⃣번과 같이 ****자동으로 생성되는 중계 테이블의 사용을 지양하고 있는데 아래와 같은 한계가 있기 때문입니다.

  1. 중간 테이블을 생성해주긴 하지만 묵시적으로 생성해주기 때문에 자기도 모르는 복잡한 쿼리가 발생하는 경우가 생길 수 있다.
  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은 ForeignKeyManyToManyField에서 설정할 수 있는 옵션인데요, 만약 설정해주지 않는다면 related_name의 default 값은 (해당 모델 이름의 소문자)_set 입니다.

 

cf)  null=True와 blank=True의 차이 

https://wayhome25.github.io/django/2017/09/23/django-blank-null/

 

(번역) Django Tips #8 Blank or Null? · 초보몽키의 개발공부로그

(번역) Django Tips #8 Blank or Null? 23 Sep 2017 | django model blank field migration 장고를 공부하면서 많은 도움을 받고 있는 simple is better than complex의 Django Tips #8 Blank or Null? 번역글입니다. 기분좋게 선뜻 번역

wayhome25.github.io

 

$ 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 다시 해야겠죠