Django 의 models.ManyToManyField
에 대해 알기 전에는 그냥 A와 B 두 테이블을 동시에 참조하는 중간 테이블 C를 직접 만들어서 각각 C -> A, C -> B 구조를 만들었다.
하지만 이렇게 했을 때는, Django 에서 ManyToMany 로 제공해주는 메소드를 사용하지 못하기 때문에 효율성이 떨어진다.
그렇기 때문에 이번엔 ManyToMany 를 사용하여 모델링을 해보았다.
다음은 저번 스타벅스 페이지 모델링 과제에서 리뷰 받은 것을 토대로 Django 에서 모델링을 해보았다.
그 중 이번에 볼 것은 Drink, Allergy, AllergyDrink
테이블이다.
먼저 Drink 와 Allergy 테이블은 N:N 즉, ManyToMany relation 이다. 왜냐하면 한 음료는 여러가지의 알러지를 포함할 수 있고, 하나의 알러지도 여러가지 음료에 속할 수 있기 때문이다.
그래서 이전에 했던 방식은 AllergyDrink 테이블을 만들어서 Drink 와 Allergy 테이블을 참조하도록 했었다.
하지만 models.ManyToManyField
를 알고나서 바로 이 방법을 적용시켜보기로 했다.
Drink 클래스에서 allergy_drink 변수에 models.ManyToManyField
instance 를 할당했다.
왜 두개의 테이블 중 굳이 Drink 클래스에 ManyToManyField
를 넣어 두었냐면, 그럴만한 이유가 있다.
우리가 Drink 를 생각할 때 음료가 알러지를 포함한 성분이 있을 수 있다고 생각을 하지, 반대로 알러지가 어떤 음료 속에 포함된다고 생각하지 않기 때문이다. 전자가 훨씬 자연스럽기 때문에 그렇게 따른다. 이에 대한 설명은 Django 문서에도 나와있다.
ManyToManyField
의 첫 번째 파라미터에는 N:N 으로 관계를 지을 테이블명을 '테이블명' 또는 object 로 직접 받는다.
through
키워드 파라미터에 이미 만들어 놓은 중간 테이블(중개 모델) 클래스명을 입력하면 해당 클래스를 중간 테이블로 사용하겠다는 의미이다. 중간 테이블이 그저 두 테이블을 각각 참조하는 용도라면 이 파라미터에 값을 주지 않아도 자동으로 중간 테이블이 설정된다. 하지만 좀 더 detail 한 정보를 넣기 위해 추가적으로 컬럼을 넣은 중개 모델을 사용하는 경우에는 through
옵션에 자신이 만든 중간 테이블 클래스명을 넘겨주어야 한다.
지금 예제의 경우에는 내가 AllergyDrink 클래스를 중간 테이블로 쓸 것이기 때문에 through='AllergyDrink'
라고 했다.
중개모델을 직접 생성할 때, through_fields
옵션을 사용해서 관계를 형성하는 두 모델을 명시해줄 수 있다. 기능을 설명하기 위해 추가했지만 사실 이 예제의 경우 필요가 없다.
blank=
옵션은 필드 값이 넘어올 때 empty 값이 넘어와도 되는지의 여부를 결정한다. blank=True
는 필드에 empty 값이 넘어오더라도 오류 없이 데이터를 받는다.
이 때 헷갈리지 말아야 할 개념으로 null=True
가 있다. 이는 db 의 컬럼 값 즉 null=True
필드 값을 NULL
로 바꿔주는 것이다.
Null : DB와 관련되어 있다. (database-related) 주어진 데이터베이스 컬럼이 null 값을 가질 것인지 아닌지를 정의한다.
Blank : 유효성과 관련되어 있다. (validation-related) form.is_valid()가 호출될 때 폼 유효성 검사에 사용된다.
그러므로 즉, null=True, blank=False 옵션을 가진 필드를 정의하는 것에는 문제가 없다. 이는 DB레벨에서는 해당 필드가 NULL 될 수 있지만, application 레벨에서는 required 필드인 것을 의미한다.
출처 :https://wayhome25.github.io/django/2017/09/23/django-blank-null/
ManyToMany relation 으로 연결된 테이블은 서로가 서로를 참조할 수 있다.
nitro_coffee 는 Drink 테이블 안에서 korean_name 컬럼 값이 '나이트로 바닐라 크림'인 row 의 QuerySet object 이다. 이름이 잘 생각이 안나서 <column_name>__startswith
로 기억이 나는 단어만으로 가지고 왔다.
milk_allergy 는 Allergy 테이블에서 name 값이 '우유' 인 row 의 QuerySet object 이다.
이제 이 두개의 objects 를 AllergyDrink 중개모델을 통해 연결한다.
결과는 다음과 같다.
또한 중개모델 instance 에 접근하여 add(), create(), set()
메소드를 사용할 수 있다. 다음을 보자.
add()
create()
set()
remove()
claer()
다음과 같이 직접 AllergyDrink 모델에 쿼리를 날려서 특정 row 에 해당하는 정보를 가져올 수도 있다.
같은 정보를 역방향으로 가져오고 싶을 수도 있을 것이다. 이 때는 Allergy object (milk_allergy) 로 부터 many-to-many reverse relationship 의 쿼리를 통해 다음과 같이 정보를 가져올 수 있다.
milk_allergy
에서는 allergydrink_set
즉, Allergy
모델에는 allergy_drink
컬럼이 없지만, Allergy
모델의 object 에서 allergydrink_set
으로 AllergyDrink
object 에 접근가능하다.
filter()
메소드 사용이 사실 가장 헷갈리는 부분 중 하나였는데, 그 이유는 ManyToMany instance 가 있는 모델의 object 와, 그게 없는 instance 에서 서로에게 접근하는 방식이 좀 다르기 때문이다.
Drink.objects.filter(allergy_drink__name__startswith=='우유')
<참조의 주체가 되는 모델의 ManyToManyField object 변수명__<참조를 당하는 모델 컬럼명>__startswith='시작단어'>
다대다 필드 인스턴스가 포함된 모델에서 다른 모델로 접근하려면 무조건 한번 중개모델을 거쳐야된다.
Allergy.objects.filter(drink__korean_name__startswith=='나이트로')
<참조당하는 모델명__참조당하는 모델의 컬럼명__startswith='시작단어'>
다대다 필드 인스턴스가 포함되지 않은 모델에서 다른 모델로 접근하려면 중개모델 없이 바로 모델명으로 접근가능하다.
drink_set
and allergydrink_set
Allergy 모델 object milk_allergy 의
아무리 많은 블로그를 참고해봤지만, Django 공식문서만큼 가장 자세하고 정확히 나온 블로그는 보지 못했다. 즉 공식문서에 다 나와있다는 말이 사실이라는 뜻이다. 영어라서 한국말보다는 아무래도 조금 느리긴 하지만 대학생활동안 해외교환학생이랑 영어수업같은 것들이 도움이 정말 많이 되긴 한 것 같다.
이론도 중요하지만 아무리 외운다고해서 잘 외워지는 것이 아니라는 것을 깨달았다. 이론은 베이스로 깔고 가고 코딩은 체화되어야 하는 것 같다.
ManyToManyField 에서 헷갈리는 것은 참조의 주체와 참조의 대상 간 서로에게 접근하는 방법이 다르다는 것이기 때문에 이 점을 조심하자!