- Published on
Django ManyToManyField 4가지 메서드로 쉽게 이해하기
- Authors
- Name
- hongreat
- ✉️hongreat95@gmail.com
다대다관계(M:N 관계)의 필드(이하 Field)를 Django에서는 ManyToManyField 라고 합니다. ManyToManyField를 사용하여 데이터를 삽입하고 갱신하는 기본적인 절차와 예제 코드를 통해 이해하겠습니다.
1. 다대다(ManyToMany) 관계 이해하기
한 작가(Author)가 여러 책(Book)을 쓸 수 있고, 한 책(Book)이 여러 작가(Author)에 의해 공동으로 작성될 수 있는 경우를 생각해 봅시다.
이때, 작가(Author)와 책(Book) 사이에는 다대다 관계가 존재합니다.
다대다 관계는 두 모델 간의 관계에서 한 인스턴스가 다른 모델의 여러 인스턴스와 관계를 맺을 수 있고, 그 반대의 경우도 가능할 때 다대다 관계 라고 합니다.
1.1. 여러표현으로 보는 다대다 개념과 용어
- 다대다는 다 : 다 입니다.
- ORM 모델의
ManyToManyField
는 Django, SQLAlchemy 같은 객체 관계 매핑 라이브러리에서 표현되는 용어 입니다. M2M
는ManyToManyField
의 줄임표현 입니다.- 중간 테이블/연결 테이블 은 관계형 데이터베이스에서 주로 표현되는 용어 입니다.
- 외래 키 배열 이라고도 하는데, 일부 관계형 데이터베이스 시스템에서 종종 이렇게 말합니다.
- ERD에서 다이아몬드 모양으로도 표기하며 데이터 모델링과 설계 시 이렇게 쓰곤 합니다.
1.2. ManyToManyField 정의하기
Django 의 Model 에서 아래와 같이 정의 할 수 있습니다.
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, related_name='books')
2. ManyToManyField 에 ORM 적용하기
구체적인 코드를 통해 ManyToManyField 에 적용하는 orm 을 알아보겠습니다.
먼저, 관계를 맺을 각 모델의 인스턴스를 생성하고 저장합니다.
(만약 생성되어 있는 인스턴스라면 굳이 생성할 필요 없겠습니다.)
author1 = Author(name='작가 1')
author1.save()
author2 = Author(name='작가 2')
author2.save()
book = Book(title='Sample Book')
book.save()
2.1 add() : 새 관계 생성
add() 메서드를 사용하여 다대다 필드에 새로운 인스턴스를 추가할 수 있습니다.
(add() 메서드는 이미 존재하는 인스턴스에만 사용할 수 있으며, 인스턴스가 데이터베이스에 저장되어 있어야 합니다.)
new_author = Author(name='신규 작가')
new_author.save()
book.authors.add(new_author)
2.2 remove() : 기존관계 제거
인스턴스와의 관계를 제거합니다.
book.authors.remove(author1)
2.3 clear() : 모든관계 제거
한 번에 모든 묶여있는 관계를 제거할 수 있습니다.
book.authors.clear()
2.4 set() : 모든관계 갱신
set() 메서드를 사용하면 다대다 관계에서 관계를 간편하게 갱신할 수 있습니다.
이는 다른 관계를 리스트 하나로 모두 갱신하는 것으로, 기존의 모든 관계를 제거하고 주어진 인스턴스 목록으로 관계를 대체합니다.
또, 코드의 가독성과 유지 관리가 용이해집니다.
book.authors.set([author2, new_author])
3. Validate 와 Create 에 적용
DRF에서 ManytoMany 필드에 대한 입력을 검증하는 예시 입니다. 해당 코드의 목적은 Nested로 입력받은 data(many=True)에 대한 데이터 관계를 생성해주는 것 입니다. data 중 잘 못 입력된 값에 대한 에러를 내려보내줍니다.
3.1 validate
many to many validate
def validate(self, attrs):
some_many_to_manys_data = attrs.pop("some_many_to_many_field", [])
# id(pk)를 리스트에 담아줍니다.
extracted_ids = []
for some_many_to_many in some_many_to_manys_data:
if isinstance(some_many_to_many, dict):
extracted_ids.append(some_many_to_many.get("id"))
else:
extracted_ids.append(some_many_to_many)
# 실제로 있는 데이터인지 검증합니다.
some_many_to_many_ids = SomeManyToManyRelationModel.objects.filter(id__in=extracted_ids).values_list(
"id", flat=True
)
for extracted_id in extracted_ids:
if extracted_id not in some_many_to_many_ids:
raise ValidationError(f"{extracted_id}는 존재하지 않는 some_many_to_many id 입니다.")
attrs["some_many_to_manys"] = extracted_ids
...(생략)
- id(pk)를 리스트에 담아줍니다.
- 실제로 있는 데이터인지 검증합니다.
- 존재하지 않는 id, 잘못된 입력에 대한 에러를 내려보내줍니다.
- (개인적으로는 잘못된 것이 여러개 있을 수 있으므로, 이 또한 담아서 보내는 것을 선호합니다.)
3.2 create
validate가 통과 하면 create 함수에서 생성(ManyToMany 에 대한 연결) 을 해줍니다.
def create(self, valdated_data):
# 검증 통과된 것들 추출
some_field_datas = validated_data.pop("some_many_to_manys", [])
# Parent 데이터(instance) 생성
instance = super().create(validated_data)
# Parent 에 세팅
instance.some_field_data.set(some_field_datas)
...(생략)