중개 모델(Intermediate Model) 사용하기
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}번 환자'
Doctor 모델과 Patient 모델을 Reservation이라는 관계로 묶었다. Reservation 모델(테이블)은 각 레코드에 pk, doctor_id, patient_id를 갖는다. Doctor와 Reservation은 1:N이고, Patient 또한 Reservation 테이블과 1:N 관계를 갖는다. Reservation의 매니저를 호출해 레코드를 생성한다(ex. Reservation.objects.create(...)
).
ManyToManyField 사용
from django.db import models
class Doctor(models.Model):
name = models.TextField()
def __str__(self):
return f'{self.pk}번 의사 {self.name}'
class Patient(models.Model):
# ManyToManyField 작성
doctors = models.ManyToManyField(Doctor)
name = models.TextField()
def __str__(self):
return f'{self.pk}번 환자 {self.name}'
Reservation 모델을 빼고 대신 Patient 모델에 doctors(ManyToManyField) 필드를 만든다. 여전히 중개 테이블이 만들어진다(hospitals_patient_doctors). RelatedManager를 사용해 중개 테이블에 레코드를 작성할 수 있다. 예를 들어 Doctor.patient_set
또는 Patient.doctors
로 호출하며, 사용할 수 있는 메서드는 다음과 같다:
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set.set(new_list)
진료 차트에 다른 필드가 있다면, 중개 모델을 직접 작성해야한다.
from django.db import models
class Doctor(models.Model):
name = models.TextField()
def __str__(self):
return f'{self.pk}번 의사 {self.name}'
class Patient(models.Model):
doctors = 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_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}번 환자'
📝 through
: 인자에 전달한 값을 통해서 중개 모델이 작성되도록 한다.
예약 생성하기
# 직접 생성
reservation1 = Reservation(doctor=doctor1, patient=patient1, symptom='flu')
# Patient 객체를 통해
patient2.doctors.add(doctor1, through_defaults={'symptom': 'flu'})
# Doctor 객체를 통해
doctor1.patient_set.add(patient2, through_defaults={'symptom': 'flu'})
모델 간 관계가 복잡해 질 경우, manager 이름이 충돌할 수 있다. related_name
인자에 값을 지정해 해결한다.
class Patient(models.Model):
doctors = models.ManyToManyField(Doctor, related_name='patients')
name = models.TextField()
Doctor.patient_set
대신 Doctor.patients
라고 호출할 수 있다.
symmetrical
(기본값 True)
모델이 자기 자신과 MTM 관계를 가질 수 있다. 예를 들어, 유저간 팔로우, 일촌과 같은 경우 User 모델이 자기 자신과 MTM 관계가 생긴다. 이때 관계가 단방향일지 양방향일지 symmetrical 인자에 False, True를 전달해 지정할 수 있다.
class Person(models.Model):
friends = models.ManyToManyField('self')
# friends = models.ManyToManyField('self', symmetrical=False)
자신을 참조할 때 to 인자에 'self'를 전달한다. symmetrical=True
로 지정하면 관계 1 → 2가 생성되면 2 → 1 또한 레코드에 작성된다.
_set
매니저를 추가하지 않음models.py
두 가지 방법 중 하나를 선택할 수 있다:
User 모델 변경(accounts/models.py)
class User(AbstractUser):
like_articles = models.ManyToManyField('articles.Article', related_name='like_users')
like_comments = models.ManyToManyField('articles.Comment', related_name='like_users')
pass
Article과 Comment 모델 변경(articles/models.py)
class Article(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
like_users = models.ManyToManyField('articles.Article', related_name='like_articles')
title = models.CharField(max_length=80)
content = models.TextField(null=False)
class Comment(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
like_users = models.ManyToManyField('articles.Comment', related_name='like_comments')
article = models.ForeignKey(Article, on_delete=models.CASCADE)
content = models.TextField(null=False)
related_name과 필드 명을 어떻게 지정했는지 잘 구분하자. 두 경우 모두 같은 이름으로 related manager를 호출할 수 있다.
articles/urls.py
urlpatterns = [
path('<int:article_pk>/likes', views.like_article, name='like_article'),
path('<int:article_pk>/comments/<int:comment_pk>/likes', views.like_comment, name='like_comment'),
]
articles/views.py
@login_required
def like_article(request, article_pk):
if Article.objects.filter(pk=article_pk).exists():
article = Article.objects.get(pk=article_pk)
if article.like_users.filter(pk=request.user.pk).exists():
article.like_users.remove(request.user)
else:
article.like_users.add(request.user)
return redirect('articles:detail', article_pk)
@login_required
def like_comment(request, article_pk, comment_pk):
if Article.objects.filter(pk=article_pk).exists() and Comment.objects.filter(pk=comment_pk).exists():
comment = Comment.objects.get(pk=comment_pk)
if comment.like_users.filter(pk=request.user.pk).exists():
comment.like_users.remove(request.user)
else:
comment.like_users.add(request.user)
return redirect('articles:detail', article_pk)