Published on

Django ManyToManyField 4가지 메서드로 쉽게 이해하기

Authors

다대다관계(M:N 관계)의 필드(이하 Field)를 Django에서는 ManyToManyField 라고 합니다. ManyToManyField를 사용하여 데이터를 삽입하고 갱신하는 기본적인 절차와 예제 코드를 통해 이해하겠습니다.

1. 다대다(ManyToMany) 관계 이해하기

한 작가(Author)가 여러 책(Book)을 쓸 수 있고, 한 책(Book)이 여러 작가(Author)에 의해 공동으로 작성될 수 있는 경우를 생각해 봅시다.

이때, 작가(Author)와 책(Book) 사이에는 다대다 관계가 존재합니다.

다대다 관계는 두 모델 간의 관계에서 한 인스턴스가 다른 모델의 여러 인스턴스와 관계를 맺을 수 있고, 그 반대의 경우도 가능할 때 다대다 관계 라고 합니다.

1.1. 여러표현으로 보는 다대다 개념과 용어

  1. 다대다는 다 : 다 입니다.
  2. ORM 모델의 ManyToManyField 는 Django, SQLAlchemy 같은 객체 관계 매핑 라이브러리에서 표현되는 용어 입니다.
  3. M2MManyToManyField 의 줄임표현 입니다.
  4. 중간 테이블/연결 테이블 은 관계형 데이터베이스에서 주로 표현되는 용어 입니다.
  5. 외래 키 배열 이라고도 하는데, 일부 관계형 데이터베이스 시스템에서 종종 이렇게 말합니다.
  6. 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)

    ...(생략)

  • hongreat 블로그의 글을 봐주셔서 감사합니다!^^
  • 내용에 잘못된 부분이나 의문점이 있으시다면 댓글 부탁 & 환영 합니다~!
  • (하단의 버튼을 누르시면 댓글을 보거나 작성할 수 있습니다.)
Buy Me A Coffee