[DB] Many to Many relationships 1

Jingi·2024년 4월 8일

Web

목록 보기
20/40
post-thumbnail

Many to Many raltionships 1

Many to Many raltionships N:M or M:N

  • 한 테이블의 0개 이상의 레코드가 다른 테이블의 0개 이상의 레코드와 관련된 경우
    • 양쪽 모두에서 N:1 관계를 가짐

M:N 관계의 역할과 필요성 이해하기

  • '병원 진료 시스템 모델 관계'를 만들며 M:N 관계의 역할과 필요성 이해하기
  • 환자와 의사 2개의 모델을 사용하여 모델 구조 구상하기
  • 제공된 '99-mtm-practice' 프로젝트를 기반으로 진행

N:1의 한계

의사와 환자 간 모델 관계 설정

  • 한명의 의사에게 여러 환자가 예약할 수 있도록 설계
# doctors/models.py
class Doctor(models.Model):
    name = models.TextField()
    
    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'
    
class Patient(models.Model):
    doctor = models.ForeignKey(Doctor, on_delte=models.CASCADE)
    name = models.TextField()
    
    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'

N:1의 한계 상황

  • 1번 환자가 두 의사 모두에게 진료를 받고자 한다면 환자 테이블에 1번 환자 데이터가 중복으로 입력될 수 밖에 없음
  • 동시에 예약이 불가
    • 예약 테이블 따로 생성

중개 모델

예약 모델 생성

  • 환자 모델의 외래 키를 삭제하고 별도의 예약 모델을 새로 생성
  • 예약 모델은 의사와 환자에 각각 N:1 관계를 가짐
# doctors/models.py
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_delte=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delte=models.CASCADE)
    
    def __str__(self):
        return f'{self.doctor_id}번 환자 {self.patient_id}'
  • 데이터 베이스 초기화 후 Migartion 진행 및 shell_plus 실행
  • 의사와 환자 생성 후 예약 만들기

ManyToManyField

  • Django에서는 ManyToManyField로 중개모델을 자동으로 생성
  • M:N 관계 설정 모델 필드
  • 환자 모델에 ManyToManyField 작성
    • 의사모델에 작성해도 상관 없으며 참조/역참조 관계만 잘 기억할 것
class Patient(models.Model):
    doctor = models.ManyToManyField(Doctor)
    name = models.TextField()
    
    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'

through argument

  • 예약 정보에 병의 증상, 예약일 등 추가 정보 포함이 필요할 때
  • 중개 테이블에 추가 데이터를 사용해 M:N 관계를 형성하려는 경우에 사용
class Patient(models.Model):
    doctor = models.ManyToManyField(Doctor, through='Reservation')
    name = models.TextField()
    
    def __str__(self):
        return f'{self.pk}번 환자 {self.name}'

class Reservation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delte=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delte=models.CASCADE)
    symptom = models.TextField()    
    reserved_at = models.DateTimeField(auto_now_add = True)    
    def __str__(self):
        return f'{self.doctor_id}번 환자 {self.patient_id}'

M:N관계 주요 사항

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

ManyToManyField(to, **options)

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

대표인자

  • realted_name
  • symmetrical
  • through

realted_name arguments

  • 역참조시 사용하는 manager name을 변경
class Patient(models.Model):
    doctor = models.ManyToManyField(Doctor, realted_name ='patients')
    name = models.TextField()

# 변경 전
doctor.patient_set.all()

# 변경 후
doctor.patients.all()

symmetrical arguments

  • 관계 설정 시 대칭 유무 설정
  • ManyToManyField가 동일한 모델을 가리킨는 정의에서만 사용
  • 기본 값 : True
class Person(models.Model):
    friends = models.ManyToManyFiled('self')
    # friends = models.ManyToManyFiled('self', symmetrical = False)
  • source 모델
    • 관계를 시작하는 모델
  • target 모델
    • 관계의 대상이 되는 모델
  • True일 경우
    • source 모델의 인스턴스가 target 모델의 인스턴스를 참조하면 자동으로 target 모델 인스턴스도 source 모델 인스턴스를 자동으로 참조하도록 함
    • 즉, 내가 당신의 친구라면 자동으로 당신도 내 친구가 됨
  • False일 경우
    • True와 반대

through arguments

  • 사용하고자 하는 중개 모델을 지정
  • 일반적으로 추가 데이터를 M:N 관계와 연결하려는 경우에 활용
class Patient(models.Model):
    doctor = models.ManyToManyField(Doctor, through ='Reservation')

class Resrvation(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
    symptom = models.TextField()
    reserved_ad = models.DateTimeField(auto_now_add = True)

M:N엔서의 대표 methods

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

좋아요 기능 구현

모델 관계 설정

  • Many to many relationships
    • 한 테이블의 0개 이상의 레코드가 다른 테입르의 0개 이상의 레코드와 관련된 경우
      • 양쪽 모두에서 N:1 관계를 가짐
  • Article(M) - User(N)
    • 0개 이상의 게시글은 0명 이상의 회원과 관련
      • 게시글은 회원으로부터 0개 이상의 좋아요를 받을 수 있고, 회원은 0개 이상의 게시글에 좋아요를 누를 수 있음
# articles/models.py
class Article(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    # like_user 추가
    like_users = models.ManyToManyField(settings.AUTH_USER_MODEL)
    title = models.CharField(max_length=10)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

관계 에러

  • N:1
    • 유저가 작성한 게시글
    • user.article_set.all()
  • M:N
    • 유저가 좋아요 한 게시글
    • user.article_set.all()
  • 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 작성 필요
# articles/models.py
class Article(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    # like_user 추가
    like_users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='like_articles')
    title = models.CharField(max_length=10)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
  • article.user
    • 게시글을 작성한 유저 - N:1
  • user.article_set
    • 유저가 작성한 게시글(역참조) - N:1
  • article.like_users
    • 게시글을 좋아요 한 유저 - M:N
  • user.like_articles
    • 유저가 좋아요 한 게시글(역참조) - M:N

기능 구현

url 작성

# articles/urls.py
urlpatterns = [
    ...
    path('<int:article_pk>/likes/', views.likes, name='likes'),
]

view 함수 작성

# articles/views.py

@login_required
def likes(request, article_pk):
    article = Article.objects.get(pk = article_pk)
    if request.user in article.like_users.all():
        article.like_users.remove(request.user)
    else:
        article.like_users.add(request.user)
    return redirect('articles:index')

index template에 작성

{% for article in articles %}
    ...
    <form action = "{% url 'articles:likes' article.pk}" method = "POST">
        {% csrf_token %}
        {% if reqeust.user in article.like_users.all %}
            <input type="submit" value = "좋아요 취소">
        {% else %}
            <input type = "submit" value="좋아요">
        {% endif %}
    </form>
    <hr>
{% endfor %}
profile
데이터 분석에서 백엔드까지...

0개의 댓글