2018-06-18 TIL (ForeignKey, ManyToMany)

테이블 간의 관계는 many-to-one, many-to-many, one-to-one 3가지로 나타낼 수 있다. 이 세가지로 모든 관계를 나타낼 수 있다.

그 중 ForeignKey는 Many-to-one을 나타냄.

자동차 회사와 그 회사가 만들어내는 자동차의 관계. 현대(one)는 아반떼, 그랜져, 제네시스 등(many)를 갖고 있다. 그러한 관계를 Many-to-one이라고 한다.

class Manufacturer(models.Model):
    name = models.CharField(max_length=60)

    def __str__(self):
        return self.name

class Car(models.Model):
    manufacturer = models.ForeignKey(
        Manufacturer,
        on_delete=models.CASCADE,
    )
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

장고가 데이터베이스의 필드를 ForeignKey필드로 만들어줄 때 필드이름에 _id를 붙여서 이름을 만든다. 따라서 Car의 foreignKey 필드는 manufacturer_id라는 이름의 테이블이 된다.

만약에 같은 클래스안에서 many-to-one을 형성하는 경우를 생각해보자. 예를 들면, 선생과 학생이 될 수 있다. 다 같은 인간이지만 그 안에서 인간끼리의 관계가 형성될 수 있다는 의미이다. 이러한 재귀관계(?)를 형성하려면 ForeignKey의 첫번째 인수에 ‘self’를 주어 자기 자신을 참조하도록 한다.

class User(models.Model):
    name = models.CharField(max_length=50)
    instructor = models.ForeignKey(
        'self',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    
    def __str__(self):
    	return self.name
# User 인스턴스 생성
>>> lhy = User.objects.create(name='이한영')
>>> pch = User.objects.create(name='박찬혁')

# pch의 instructor 속성을 lhy로 설정한다.
>>> pch.instructor = lhy

여기서 lhy가 갖고있는 User인스턴스에 접근하려면 다음과 같이 입력해야한다.

>>> lhy.user_set.all()

user_set은 장고에서 기본으로 제공하는 관계 목록에 접근할 수 있도록 하는 매니저이름인데 이를 related_name을 이용해서 바꿀 수 있다.

다시 쉽게 말하면, 매니저는 어떤 모델의 클래스가 있을 때 데이터베이스에 접근할 수 있도록 도와주는 객체. 그 매니저의 이름이 user_set인거다.

이 이름이 적합하지 않거나 좀 더 직관적으로 접근할 수 있도록 related_name을 바꿔준다.

class User(models.Model):
    name = models.CharField(max_length=50)
    instructor = models.ForeignKey(
        'self',
        related_name = 'students',
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )
    
    def __str__(self):
    	return self.name

related_name = 'student'를 추가함으로써 instructor필드가 갖고있는 객체들에게 접근하기 위한 이름을 student로 변경한 것이다.

>>> lhy.students.all()
<QuerySet ['박찬혁']>

Many-to-Many

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=50)

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(
        Person,
        through='Membership',
        through_fields=('group', 'person'),
    )

class Membership(models.Model):
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    inviter = models.ForeignKey(
        Person,
        on_delete=models.CASCADE,
        related_name="membership_invites",
    )
    invite_reason = models.CharField(max_length=64)

ManyToManyField.through

중개모델을 추가한다. 중개모델은

ManyToManyField.through_fields

장고는 일반적으로 many-to-many 관계를 자동으로 생성하기 위해 어떤 중개모델의 필드를 사용할지 자동으로 결정한다.

하지만 위의 Membership에서 person과 inviter 두 개의 외래키를 Person 클래스로 갖는다. 그렇게 되면 Membership과 Person의 관계가 모호해지게 된다.

따라서 명시적으로 어떤 foreign key를 사용할 것인지 말해줘야 한다.

through_fields는 (‘field1’, ‘field2’) 튜플 형식으로 받는데 field1은 ManyToMany를 정의하는 소스가 되는 모델, field2는 foreign key의 타겟이 되는 모델의 이름을 적는다.

Comments