[Django] N:M (1)

송수빈·2026년 5월 31일

SSAFY

목록 보기
16/18

Django Many to Many Relationships (M:N)

다대다 관계의 개념부터 중개 모델, ManyToManyField 주요 인자 정리


1. M:N 관계란?

  • 한 테이블의 0개 이상의 레코드가 다른 테이블의 0개 이상의 레코드와 연결되는 관계
  • 예시: 영화 ↔ 배우, 의사 ↔ 환자

2. N:1의 한계

문제 상황내용
데이터 중복1번 환자가 두 의사에게 진료받으면 환자 레코드가 중복 생성됨
동시 저장 불가두 의사 정보를 하나의 외래 키에 동시 저장 시 에러 발생

해결책: 의사, 환자 양쪽을 외래 키로 참조하는 별도 예약 테이블(중개 모델) 생성


3. 중개 모델 (Intermediate Model)

개념

  • 다대다 관계에서 두 모델을 연결하는 별도의 모델
  • 단순 연결 정보 외에 예약 일시, 증상 등 추가 데이터도 함께 저장 가능
  • Django의 ManyToManyField는 이 중개 테이블을 자동 생성

직접 중개 모델 작성

class Doctor(models.Model):
    name = models.TextField()

    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'

class Patient(models.Model):
    name = models.TextField()  # 외래 키 없음

    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'

class Reservation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)

    def __str__(self):
        return f'{self.doctor_id}번 의사의 {self.patient_id}번 환자'
  • Patient 모델에서 외래 키 제거
  • Reservation 모델이 Doctor, Patient 각각에 N:1 관계를 가짐

데이터 생성 및 조회

# 데이터 생성
doctor1 = Doctor.objects.create(name='allie')
patient1 = Patient.objects.create(name='carol')
Reservation.objects.create(doctor=doctor1, patient=patient1)

patient2 = Patient.objects.create(name='duke')
Reservation.objects.create(doctor=doctor1, patient=patient2)
# 역참조로 예약 조회
doctor1.reservation_set.all()
# 출력: <QuerySet[<Reservation: 1번 의사의 1번 환자>, <Reservation: 1번 의사의 2번 환자>]>
# → doctor1이 담당하는 모든 환자 예약 내역 확인 가능

patient1.reservation_set.all()
# 출력: <QuerySet[<Reservation: 1번 의사의 1번 환자>]>
# → patient1의 예약 내역 확인 가능

4. ManyToManyField

기본 사용법

class Patient(models.Model):
    doctors = models.ManyToManyField(Doctor)  # 중개 테이블 자동 생성
    name = models.TextField()
  • 어느 모델에 정의해도 관계는 동일하게 유지
  • 필드명은 복수형으로 작성 권장
  • Django가 자동으로 중개 테이블 생성 및 관리

주요 메서드

메서드역할예시
.add()중개 테이블에 관계 추가patient1.doctors.add(doctor1)
.remove()중개 테이블에서 관계 삭제patient1.doctors.remove(doctor1)

.remove() 사용 시 대상 객체 자체는 삭제되지 않음, 중개 테이블의 연결만 제거됨

기본 ManyToManyField의 한계

  • 자동 생성된 중개 테이블은 외래 키 정보만 저장
  • 증상, 예약 일정, 방문 횟수 등 추가 데이터 저장 불가
  • 추가 데이터가 필요할 경우 직접 중개 테이블을 정의해야 함
  • 단, 직접 정의하면 .add(), .remove() 사용 불가 → through 속성으로 해결

5. through 속성 — 커스텀 중개 모델 + 메서드 유지

class Patient(models.Model):
    doctors = models.ManyToManyField(Doctor, through='Reservation')
    name = models.TextField()

class Reservation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
    symptom = models.TextField()                              # 추가 데이터
    reserved_at = models.DateTimeField(auto_now_add=True)     # 추가 데이터

    def __str__(self):
        return f'{self.doctor.pk}번 의사의 {self.patient.pk}번 환자'

예약 생성 방법 두 가지

# 방법 1: Reservation 클래스 직접 사용
reservation1 = Reservation(doctor=doctor1, patient=patient1, symptom='headache')
reservation1.save()

# 방법 2: through_defaults로 .add() 사용
patient2.doctors.add(doctor1, through_defaults={'symptom': 'flu'})
# → 중개 모델의 추가 필드(symptom)에 값을 넣으면서 .add() 활용 가능

예약 삭제 방법 두 가지

# 방법 1: 중개 테이블 인스턴스 직접 삭제
reservation1.delete()

# 방법 2: .remove() 사용
doctor1.patient_set.remove(patient2)
# 또는 patient2.doctors.remove(doctor1)

6. ManyToManyField 주요 인자 3가지

doctors = models.ManyToManyField(Doctor, related_name='patients')
  • 역참조 시 사용할 manager name 변경
  • 기본값: 역참조모델명_set
  • 변경 후 기본값(역참조모델명_set) 사용 불가

symmetrical

class Person(models.Model):
    friends = models.ManyToManyField('self')  # 기본값: symmetrical=True
값동작
True (기본)A → B 관계 추가 시 B → A 자동 추가 (대칭)
FalseA → B 추가해도 B → A 자동 추가 안 됨 (비대칭)
  • 동일 모델을 참조하는 경우에만 사용 가능
  • 예시: 팔로우(비대칭, False) vs 친구(대칭, True)

through

  • 직접 정의한 중개 모델을 ManyToManyField에 등록하는 인자
  • 추가 데이터가 필요한 M:N 관계에서 사용

7. M:N 관계 핵심 정리

항목내용
물리적 변화M:N 관계를 맺은 두 테이블 자체에는 변화 없음
중개 테이블ManyToManyField 정의 시 자동 생성
정의 위치어느 모델에 정의해도 관계 동일, 참조/역참조 방향만 달라짐
N:1과의 차이N:1은 종속 관계, M:N은 대등 관계 (양방향 표현 가능)
profile
🌱 🐜

0개의 댓글