Django ORM M:N 관계 참조

한성봉·2021년 6월 12일
0

Django ORM

ORM이란 Object-Relational Mapping의 약자로 객체(Object)와 관계형 데이터베이스(Relational Database)의 데이터를 매핑(Mapping)해주는 것을 의미한다.

간단히 말하면 데이터베이스에 저장된 정보를 조작하기 위해서는 SQL 쿼리문으로 직접 조작할 수 있다. 하지만 우리는 django 에서 ORM으로 DB의 정보들을 조작할 수 있다. 여기서는 ORM이 뭔지 정도만 알아보고 넘어가겠다.


정참조 & 역참조

  • 테이블 생성
    위에서 ORM으로 DB의 정보를 조작할 수 있다고 했다. 그렇게 때문에 여러 DB의 참조도 할 수 있다 오늘은 그 중 정참조와, 역참조를 알아보자.

그 중 ManytoMany 필드의 참조를 알아보자..

의사와 환자의 예약 관계를 나타내보자.

class Doctor(models.Model):
    name = models.CharField(max_length=10)

class Patient(models.Model):
    name = models.CharField(max_length=10)

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

의사 - 환자들 / 환자 - 의사들로 직접 접근하기 위해서는 ManyToManyField를 사용한다. through 옵션을 통해 중개 모델을 선언한다.

class Doctor(models.Model):
    name = models.CharField(max_length=10)

class Patient(models.Model):
    name = models.CharField(max_length=10)
    reservation = models.ManyToManyField('Doctor', through='Reservation')

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

이런식으로 관리해주면 가능하다.

지금은 다시 참조에 대해서 알아보자.

  • DATABASE

Doctor 테이블

idname
1Dr.kim
2Dr.han
3Dr.park

Patient 테이블

idname
1김코드
2한장고
3박장군

Reservation 중간 테이블

iddoctor_idpatient_id
111
212
323

예약 정보를 테이블에 만들고 싶다면 다음과 같이 만들면 된다.

d1 = Doctor.objects.create(name='Dr.kim')
d2 = Doctor.objects.create(name='Dr.han')
d3 = Doctor.objects.create(name='Dr.park')

p1 = Patient.objects.create(name='김코드')
p2 = Patient.objects.create(name='한장고')
p3 = Patient.objects.create(name='박장군')
Reservation.objects.create(doctor=d1, patient=p1)
Reservation.objects.create(doctor=d1, patient=p2)
Reservation.objects.create(doctor=d2, patient=p3)

만약 서로 참조하고 싶다면 다음과 다음같이 하면 된다, 다만 위에서 through로 중개 테이블을 설정한 경우와 하지 않은 경우는 참조에서 조금 차이가 난다. 먼저 through를 사용하지 않고 외래키로만 중간테이블을 설정하였을 경우 참조를 알아보자.

  1. through 사용하지 않았을 경우

이 경우 다대다 관계의 테이블로 직접 참조하지 못하고 중간테이블로 참조하여야한다.

  • 1번 의사의 예약목록을 보고싶은 경우
d1.reservation_set.all()
  • 1번 환자의 예약 목록
p1.reservation_set.all()
  • 1번 의사의 환자 이름 출력
for reservation in d1.reservation_set.all():
    print(reservation.patient.name)

정참조든 역참조든 참조를 하여 filter 나 all 메서드로 복수의 정보를 가져온다면 데이터는 QuerySet에 담기게 된다. 우리는 QuerySet에 담긴 데이터를 직접 보고싶다면 for문으로 풀어서 접근하여야한다.

만약 get 메서드를 이용하여 데이터를 가져온다면 바로 접근이 가능하다.

  1. through 사용하였을 경우
  • 객체 담기
p1 = Patient.objects.get(id=1)
d1 = Doctor.objects.get(id=1)
  • 1번 의사의 환자 목록
p1.doctors.all()

중개 모델을 활용하였기 때문에 바로 doctor 테이블에 접근이 가능하다.
정말 중요한 개념이다. through의 활용이 여기서 발생한다.
또한 all로 가져왔으니 담긴 데이터에 접근하려면 for문으로 풀어서 접근하여야한다.

  • 1번 의사의 환자 목록
d1.patient_set.all()

대신 의사의 환자 정보를 알려면 참조하고 있는 테이블로 역참조하여야한다.

역참조는 위의 양식대로 _set을 사용하면 역참조하겠다는 의미이다.

id값만으로 필드를 관리할 경우

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

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

중간테이블에서 id값만 가지고 테이블을 관리해도 될 떄 다음과 같이 생각하게 되면 patient_doctors 의 이름으로 django에서 테이블을 자동으로 만들어준다. 그렇게 되면 우리는 중간테이블의 column을 추가할 수 없고 id값으로만 관리하게 된다.

마지막으로 장고에서 역참조 설정이 반드시 되어야하는 상황이 있다. 그때는
related_name 옵션을 적용해주자.

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

class Patient(models.Model):
    name = models.TextField()
    # 역참조 설정. related_name
    doctors = models.ManyToManyField(Doctor, 
                        through='Reservation',
                        related_name='patients')

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

related_name 은 참조하고 있는 모델에서 역참조를 할 경우 직관적인 이름으로 참조하기 위해 사용한다. 그렇기 때문에 역참조할때 프로젝트에서 좀 더 직관적인 이름이 있다면 원하는 이름으로 사용하면 된다. 하지만 보통 클래스 네임으로 설정한 이름을 사용하는 것이 더 직관적이로 편리할 경우가 많아 그냥 사용하는 경우가 많다.

하지만 필수적으로 사용해야할 때가 있다.

class User(models.Model):
    name       = models.CharField(max_length = 50)
    job	       = models.ForeignKey('Occupation', on_delete = models.CASCADE)
[*] choice_2nd = models.ForeignKey('Occupation', on_delete = models.CASCADE, null = True)
    created_at = models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

다음과 같은 jobchoice_2nd 가 둘 다 Occupation을 참조하고 있다. 이럴 경우 related_name을 설정하지 않을 경우 애초에 마이그레이션이 되지 않고 related_name을 지정하라는 오류가 뜬다.

이럴 경우 related_name 설정이 필수적인 경우이다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    choice_2nd  = models.ForeignKey(
            'Occupation',
            on_delete    = models.CASCADE,
            null         = True
            related_name = 'second_appliers'
        )
    created_at	= models.DateTimeField(auto_now_add = True)

0개의 댓글