TIL

내일배움캠프 7주차 WIL

수현조 2025. 1. 17. 22:45

ManyToManyField를 통한 데이터 조작

1. 게시글에 태그 추가하기

post = Post.objects.create(title="Django 튜토리얼", content="Django는 강력합니다.")
tag1 = Tag.objects.create(name="Django")
tag2 = Tag.objects.create(name="Python")

# 태그 추가
post.tags.add(tag1, tag2)

2. 게시글의 태그 가져오기

post = Post.objects.get(id=1)
tags = post.tags.all()  # 해당 게시글의 모든 태그 가져오기

for tag in tags:
    print(tag.name)

3. 태그에서 게시글 검색 (역참조)

tag = Tag.objects.get(name="Django")
posts_with_tag = tag.posts.all()

for post in posts_with_tag:
    print(post.title)

4. 태그 제거하기

post.tags.remove(tag1)

5. 모든 태그 삭제

post.tags.clear()

6. 특정 태그가 있는지 확인

if tag1 in post.tags.all():
    print("이 태그가 포함되어 있습니다.")

폼에서 ManyToManyField 처리 방법

Django의 ModelForm에서는 ManyToManyField를 기본적으로 SelectMultiple 위젯으로 처리합니다.

from django import forms
from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'content', 'tags', 'image']

템플릿 예시 (post_form.html)

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">저장</button>
</form>

태그 선택 UI 개선:

  • forms.CheckboxSelectMultiple를 사용하여 태그를 체크박스로 표시할 수도 있습니다.
class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'content', 'tags', 'image']
        widgets = {
            'tags': forms.CheckboxSelectMultiple(),
        }

커스텀 중개 테이블 사용하기

기본적으로 Django는 자동으로 중간 테이블을 생성하지만, 추가 정보를 저장하려면 중개 모델을 직접 정의할 수 있습니다.

class PostTag(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
    added_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ('post', 'tag')

그 후 ManyToManyField를 정의할 때 through 옵션을 추가합니다.

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    tags = models.ManyToManyField(Tag, through='PostTag')

사용 예시:

post = Post.objects.create(title="새로운 글", content="내용")
tag = Tag.objects.create(name="Python")
PostTag.objects.create(post=post, tag=tag)

쿼리 최적화 (Prefetching)

ManyToMany 관계는 기본적으로 추가적인 쿼리를 발생시킵니다. 성능 최적화를 위해 prefetch_related를 사용할 수 있습니다.

posts = Post.objects.prefetch_related('tags').all()
for post in posts:
    print(post.title, post.tags.all())  # 최소 쿼리 발생

ManyToManyField 사용 시 팁

  1. 최적화 필요:
    • select_related()는 외래 키에 대해 유용하고, prefetch_related()는 다대다 관계에 효과적입니다.
  2. 데이터 일관성 유지:
    • ManyToManyField를 사용하면 중복 관계가 방지되지만, 추가적인 제약 조건이 필요할 경우 커스텀 중개 모델을 사용하는 것이 좋습니다.
  3. 폼에서의 사용자 경험 개선:
    • CheckboxSelectMultiple 등을 사용하여 태그 선택을 직관적으로 개선할 수 있습니다.

 

 

Django의 ManyToManyField와 ForeignKey는 데이터베이스의 관계를 정의하는 데 사용되지만, 각각의 용도가 다르고 동작 방식에도 차이가 있습니다. 두 필드를 비교하면서 언제 어떤 것을 사용해야 하는지 살펴보겠습니다.


1. 개요 및 차이점

특징 ManyToManyField ForeignKey
관계 유형 다대다 (Many-to-Many) 일대다 (One-to-Many)
중간 테이블 생성 자동 생성되거나 커스텀 가능 추가적인 테이블 필요 없음
데이터 저장 방식 중간 테이블에 두 개의 외래 키 저장 외래 키(FK)를 직접 저장
관계 참조 방법 model.related_name.all() model.foreign_key
성능 쿼리 최적화 필요 (prefetch_related) 성능 우수 (select_related 사용 가능)
예제 Post.tags.add(tag1, tag2) Comment.objects.filter(post=post1)

2. ForeignKey (일대다 관계)

설명

ForeignKey는 한 개의 객체가 여러 개의 관련 객체를 가질 수 있는 "일대다(1:N)" 관계를 정의합니다.

예제

모델 정의 (Post와 Comment)

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    text = models.TextField()

설명

  • 하나의 Post는 여러 개의 Comment를 가질 수 있습니다.
  • post 필드가 외래 키 역할을 하며, 각 댓글은 특정 게시글과 연결됩니다.
  • on_delete=models.CASCADE는 게시글이 삭제될 경우 연결된 댓글도 삭제됨을 의미합니다.

데이터 조작 예시

post = Post.objects.create(title="Django 관계 이해", content="ForeignKey vs ManyToManyField")

# 댓글 생성 (일대다 관계)
comment1 = Comment.objects.create(post=post, text="첫 번째 댓글")
comment2 = Comment.objects.create(post=post, text="두 번째 댓글")

# 해당 게시글의 모든 댓글 가져오기 (역참조)
comments = post.comments.all()  
for comment in comments:
    print(comment.text)

쿼리 최적화

posts_with_comments = Post.objects.select_related('comments').all()
  • select_related는 외래 키 관계에서 SQL JOIN을 사용하여 쿼리 최적화를 수행합니다.

3. ManyToManyField (다대다 관계)

설명

ManyToManyField는 객체가 여러 개의 관련 객체를 가질 수 있으며, 반대로 관련 객체도 여러 개의 객체와 연결될 수 있는 "다대다(N:N)" 관계를 정의합니다.

예제

모델 정의 (Post와 Tag)

class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

class Post(models.Model):
    title = models.CharField(max_length=200)
    tags = models.ManyToManyField(Tag, related_name='posts')

데이터 조작 예시

post = Post.objects.create(title="Django 튜토리얼")
tag1 = Tag.objects.create(name="Python")
tag2 = Tag.objects.create(name="Django")

# 태그 추가 (ManyToMany 관계)
post.tags.add(tag1, tag2)

# 특정 게시글의 태그 조회
tags = post.tags.all()
for tag in tags:
    print(tag.name)

# 특정 태그가 달린 모든 게시글 조회 (역참조)
django_posts = tag2.posts.all()

쿼리 최적화

posts_with_tags = Post.objects.prefetch_related('tags').all()
  • prefetch_related는 다대다 관계에서 쿼리를 미리 로드하여 최적화를 수행합니다.

4. 언제 ForeignKey와 ManyToManyField를 사용할까?

ForeignKey를 사용해야 할 때

  • 관계가 일대다(1:N) 형태일 때.
  • 예: 블로그 게시글과 댓글, 유저와 주문 내역, 카테고리와 제품 등.
  • 성능이 중요한 경우 (SQL JOIN을 사용하여 쿼리 효율적).

ManyToManyField를 사용해야 할 때

  • 관계가 다대다(N:N) 형태일 때.
  • 예: 블로그 게시글과 태그, 학생과 수업, 영화와 장르 등.
  • 관계의 양방향 검색이 필요한 경우.

5. 중간 테이블(through 모델) 활용

기본적으로 ManyToManyField는 자동으로 중간 테이블을 생성하지만, 관계에 추가적인 정보를 저장하려면 직접 중개 모델을 정의해야 합니다.

class Enrollment(models.Model):
    student = models.ForeignKey('Student', on_delete=models.CASCADE)
    course = models.ForeignKey('Course', on_delete=models.CASCADE)
    enrolled_on = models.DateField(auto_now_add=True)
    grade = models.CharField(max_length=10)

class Student(models.Model):
    name = models.CharField(max_length=100)
    courses = models.ManyToManyField('Course', through='Enrollment')

class Course(models.Model):
    title = models.CharField(max_length=200)

데이터 조작 예시

student = Student.objects.create(name="Alice")
course = Course.objects.create(title="Python Basics")

# 중개 테이블을 통해 수강 정보 추가
Enrollment.objects.create(student=student, course=course, grade='A')

# 수강한 모든 코스 조회
student_courses = student.courses.all()

6. 주요 차이점 요약

특징 ForeignKey (일대다) ManyToManyField (다대다)

관계 유형 1:N N:N
예제 관계 게시글 - 댓글 게시글 - 태그
성능 JOIN 최적화 가능 추가 쿼리 필요
관계 탐색 단순 복잡
커스텀 중간 테이블 필요 없음 가능

결론 및 선택 가이드

  • ForeignKey는 관계가 **"일대다"**일 때 사용하며, 성능이 중요할 때 적합합니다.
  • ManyToManyField는 두 모델 간 "다대다" 관계가 필요할 때 사용하며, 태그나 관심 목록 같은 경우 적합합니다.
  • 관계에 추가 필드가 필요하다면 중간 모델(through)을 적극 활용하세요.