Django

Many to Many Relationships 01 [Database]

경상도상남자 2024. 10. 14. 14:48

Many to many relationships N:M or M:N

한 테이블의 0 개 이상의 레코드가 다른 테이블의 0개 이상의 레코드와 관련된 경우

 

→양쪽 모두에게 N:1 관계를 가짐

 

 

N:1의 한계 (예시로 설명)

  1. 한 명의 의사에게 여러 환자가 예약할 수 있도록 설계
  2. 2명의 의사와 환자를 생성하고 환자는 서로 다른 의사에게 예약
  3. 만약 1번 환자가 두 의사 모두에게 진료를 받고자 한다면 환자 테이블에 1번 환자 데이터가 중복으로 입력됨

외래 키 컬럼에 '1,2' 형태로 저장하는 것은 DB 타입 문제로 불가능하기 때문에 중개 모델(예약 테이블)을 따로 만들어서 해결해야함

 

 

중개 모델 

 

1. 예약 모델 생성

  • 환자 모델의 외래 키를 삭제하고 별도의 예약 모델을 새로 생성
  • 예약 모델은 의사와 환자에 각각 N:1 관계를 가짐

 

 

2. 예약 데이터 생성

  • 데이터베이스 초기화 후 Migration 진행 및 shell_plus 실행
  • 의사와 환자 생성 후 예약 만들기

shell_plus에서 생성

 

3. 예약 정보 조회

  • 의사와 환자가 예약 모델을 통해 각각 본인의 진료 내역 확인

의사 → 예약 정보 찾기

환자 → 예약 정보 찾기

 

4. 추가 예약 생성

5. 예약 정보 조회

 

의사 → 환자 목록

 

 

Django 에서는 'ManyToManyField'로 중개모델을 자동으로 생성

 

ManyToManyField()

 

M:N 관계 설정 모델 필드

 

1. Django ManyToManyField

  • 환자 모델에 ManyToManyField 작성
    • 의사 모델에 작성해도 상관 없으며 참조/역참조 관계만 잘 기억 할 것!!!
    • 다대다 관계는 복수형으로 하는 것이 좋다(doctors)

 

2. Django ManyToManyField

  • migration 실행후 생성된 중개 테이블 hospitals_patient_doctors 확인

 

3. Django ManyToManyField

  • 의사 1명과 환자 2명 생성

 

4. Django ManyToManyField

  • 예약 생성 (환자가 예약)

patient1이 doctor1에게 예약

 

patient1 - 자신이 예약한 의사목록 확인

 

doctor1 - 자신이 예약한 환자목록 확인

 

 

 

5. Django ManyToManyField

  • 예약 생성(의사가 생성)

doctor1이 patient2을 예약

 

doctor1 - 자신의 예약 환자목록 확인

 

patient1, 2 - 자신이 예약한 의사목록 확인

 

6. Django ManyToManyField

  • 중개 테이블에서 예약 현황 확인
  •  

 

7. Django ManyToManyField

  • 예약 취소하기 (삭제)
  • 이전에는 Reservation을 찾아서 지워야 했다면, 이제는 .remove()로 삭제 가능

doctor1이 pation1 진료 예약 취소

 

patient2가 doctor2 진료 예약 취소

 

 

'through' argument

중개 테이블에 '추가 데이터'를 사용해 M:N 관계를 형성하려는 경우에 사용

 

'through' argument (1/6)

  • Reservation Class 재작성 및 through 설정
    • 이제는 예약 정보에 "증상"과 "예약일" 이라는 추가 데이터가 생김

 

'through' argument (2/6)

  • 데이터베이스 초기화 후 Migration 진행 및 shell_plus 실해
  • 의사 1명과 환자 2명 생성

 

'through' argument (3/6)

  • 예약 생성 방법 - 1
    • Reservation class를 통한 예약 생성

 

'through' argument (4/6)

  • 예약 생성 방법 - 2
    • Patient 또는 Doctor의 인스턴스를 통한 예약 생성(through_defaults)

 

 

'through' argument (5/6)

  • 생성된 예약 확인

 

 

'through' argument (6/6)

  • 생성과 마찬가지로 의사와 환자 모두 각각 예약 삭제 가능

 

M:N 관계 주요 사항

 

  • M:N 관계로 맺어진 두 테이블에는 물리적인 변화가 없음
  • ManyToManyField는 중개 테이블을 자동으로 생성
  • ManyToManyField는 M:N 관계를 맺는 두 모델 어디에 위치해도 상관 없음
    • 대신 필드 작성 위치에 따라 참조와 역참조 방향을 주의할 것
  • N:1은 완전한 종속 관계였지만 M:N은 종속적인 관계가 아니며 '의사에게 진찰받는 환자 & 환자를 진찰하는 의사' 이렇게 2가지 형태 모두 표현 가능

 

ManyToManyField() 자세히 보기

 

ManyToManyField(to, **options)

  • M:N 관계 설정 시 사용하는 모델 필드

 

ManyToManyField 특징

  • 양방향 관계
    • 어느 모델에서든 관련 객체에 접근할 수 있음
  • 중복 방지
    • 동일한 관계는 한 번만 저장됨

 

ManyToManyField의 대표 인자 3가지

  1. related_name
  2. symmetrical
  3. through

1. 'related_name' arguments

  • 역참조시 사용하는 manager name을 변경
  • 의사 → 환자 (역참조)
    • doctor.patient_set.메서드() 를 똑같이 doctor.patients.메서드() 처럼 맞춰주고 싶을 때 사용

 

 

 

2. 'symmetrical' arguments (1/2)

  • 관계 설정 시 대칭 유무 설정
  • ManyToManyField가 동일한 모델을 가리키는 정의에서만 사용
  • 기본 값:True
  • True일 경우
    • source 모델의 인스턴스가 target 모델의 인스턴스를 참조하면 자동으로 target 모델 인스턴스도 source 모델 인스턴스를 자동으로 참조하도록 함(대칭)
      • source 모델: 관계를 시작하는 모델
      • target 모델 : 관계의 대상이 되는 모델
    • 즉, 내가 당신의 친구라면 자동으로 당신도 내 친구가 됨
  • False일 경우
    • True와 반대 (대칭되지 않음)

 

3. 'through' arguments

  • 사용하고자 하는 중개모델을 지정
  • 일반적으로 "추가 데이터를 M:N 관계와 연결하려는 경우"에 활용

 

 

M:N에서의 대표 조작 methods

  • add()
    • 관계 추가
    • "지정된 객체를 관련 객체 집합에 추가"
  • remove()
    • 관계 제거
    • "관련 객체 집합에서 지정된 모델 객체를 제거"

 


 

좋아요 기능 구현

 

Article(M) - User(N)

 

0개 이상의 게시글은 0명 이상의 회원과 관련

 

→ 게시글은 회원으로부터 0개 이상의 좋아요를 받을 수 있고, 회원은 0개 이상의 게시글에 좋아요를 누를 수 있음

 

모델 관계 설정 (1/4)

  • Article 클래스에 ManyToManyField 작성

모델 관계 설정 (2/4)

  • 현재 Migration 진행 시 에러 발생
  • 게시글 작성한 유저 : Article - User (N:1) - N:1에서의 역참조 (U  A)
    • user.article_set
  • 게시글 좋아요한 유저 : Article - User (N:M) - N:M에서의 역참조 (U  A)
    • user.article_set
  • related_name argument가 서로 동일해  충돌이 났기 때문에 발생 
  • 해결방법
    1. 역참조의 이름을 바꾸기
    2. 다대다 필드를 user로 옮기기

 

 

역참조 매니저 충돌(1/2)

  • N:1
    • "유저가 작성한 게시글"
    • user.article_set.all()
  • M:N
    • "유저가 좋아요한 게시글"
    • user.article_set.all()

역참조 매니저 충돌(2/2)

  • like_users 필드 생성 시 자동으로 역참조 매니저 article_set가 생성됨
  • 그러나 이전 N:1 ( Article - User ) 관계에서 이미 같은 이름의 매니저를 사용중
    • user.article-set.all()  → 해당 유저가 작성한 모든 게시글 조회
  • 'user가 작성한 글 (user.article_set)'과 'user가 좋아요 누른 글(user.article_set)'을 구분할 수 없게 됨
  • user와 관계된 ForeignKey 혹은 ManyToManyField 둘 중 하나에 related_name 작성 필요

 

모델 관계 설정 (3/4)

  • related_name 작성 후 Migration 재진행

 

모델 관계 설정 (4/4)

  • 생성된 중개 테이블 확인

 

 

User - Article간 사용 가능한 전체 related manager

  • article.user
    • 게시글을 작성한 유저 - N:1
  • user.article_set
    • 유저가 작성한 게시글(역참조) - N:1
  • article.like_users
    • 게시글을 좋아요 한 유저 -M:N
  • user.like_articles
    • 유저가 좋아요 한 게시글(역참조) - M:N

 

기능 구현(1/5)

  • url 작성

 

기능 구현(2/5)

  • view 함수 작성


기능 구현(3/5)

  • index 템플릿에서 각 게시글 좋아요 버튼 출력

 

기능 구현(4/5)

  • index 템플릿에서 각 게시글 좋아요 버튼 출력

기능 구현(5/5)

  • 좋아요 버튼 클릭 후 테이블 확인

 

'Django' 카테고리의 다른 글

DRF 01 [Django]  (0) 2024.10.16
Many to many relationships 2 [Database]  (1) 2024.10.15
Many to one relationships 2 [Database]  (0) 2024.10.11
Many to one relationships 01 [Database]  (1) 2024.10.11
SQL 02 [Database]  (0) 2024.10.10