DRF 등 django를 다양하게, 많이 사용하면서 OneToOneField
를 접할 일이 많아졌다.
우선 onetoonefield는 관계적 의미로는 서로 하나의 row만 가질 수 있는 관계다.
그래서 이론적으로는 unique=True
속성을 갖고 있는 ForeignKey 관계와도 같다.
Conceptually, this(OneToOneField) is similar to a ForeignKey with unique=True, but the “reverse” side of the relation will directly return a single object.
(django 공식문서)
하지만 OneToOneField
와 ForeignKey
는 관계 유형과 반환하는 값이 다르다.
역참조 시 OneToOneField
는 객체 1개를 반환하지만 ForeignKey
는 Queryset 형태로 반환하는데, 사실 이는 단어와 각각이 정의하는 테이블 관계를 생각하면 당연한 것이다.
예를 들어, Patient이라는 테이블이 있고 환자 별 알레르기 유무 정보를 담는 Allergen 테이블이 있다고 가정해보자.
class Allergen(models.Model):
peanut = models.BooleanField(default=False)
pollen = models.BooleanField(default=False)
milk = models.BooleanField(default=False)
class Patient(models.Model):
name = models.CharField(max_length=10)
age = models.IntegerField(default=0)
allergen = models.OneToOneField(Allergen, on_delete=models.CASCADE)
각 테이블을 표로 나타내면 아래와 같다.
<Patient 테이블>
ID | name | age | allergen_id |
---|---|---|---|
1 | 이몽룡 | 25 | 1 |
2 | 성춘향 | 32 | 2 |
<Allergen 테이블> - Patient와 1:1 관계(ID)
ID | peanut | pollen | milk | crab | kiwi |
---|---|---|---|---|---|
1 | 1 | 0 | 0 | 0 | 1 |
2 | 0 | 0 | 0 | 0 | 0 |
ForeignKey
관계의 object인데 unique=True
속성이 들어간 것과 같다.
(unique하다는 것은, 곧 1개만 존재한다는 의미다)
#이몽룡 데이터를 p1에 담는다
p1 = Patient.objects.get(id=1)
#이몽룡의 알레르기 정보를 호출한다 - object 1개를 호출함
p1.allergen
>>> <Allergen: Allergen object(1)>
반대로 호출해도 결과는 동일하다(object 1개 호출)
#알레르기 정보 하나를 변수에 담는다
a1 = Allergen.objects.get(id=1)
#해당 알레르기 정보를 갖는 환자를 호출한다 - object 1개를 호출함
a1.patient
>>> <Patient: Patient object(1)>
여기서 위에서 언급한 Foreignkey
과의 차이가 드러나는데,OneToOneField
가 single object를 반환하는 것과 달리 Foreignkey
는 역참조 시 queryset 형태를 반환한다.
즉, 1인 테이블에서 N인 테이블로 역참조하면 queryset으로 묶인 객체들이 반환된다는 뜻.
#기존 예시(1:1 관계)
p1.allergen
>>> <Allergen: Allergen object(1)>
#만약, Patient - Allergen이 1:N의 관계였다면, 아래와 같이 반환되었을 것이다
p1.allergen
>>> <Queryset: [<Allergen: Allergen object(1)>, <Allergen: Allergen object(2)>...]>
참고로, related_name
을 따로 정의하지 않았기 때문에 호출 시lowercase 버전의 테이블명을 사용한다.
If you do not specify the related_name argument for the OneToOneField, Django will use the lowercase name of the current model as default value. (django 공식문서)
Place, Restaurant, Waiter 3개 클래스가 정의되어 있으며, 이 중 Place - Restaurant는 1:1 관계다.
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(models.Model):
place = models.OneToOneField(Place, on_delete=models.CASCADE, primary_key=True)
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
class Waiter(models.Model):
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
Place에 아래 object를 추가한다.
p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
p1.save()
Restaurant 에 object를 추가해보자.
r1 = Restaurant(place=p1, serves_hot_dogs=True, serves_pizze=False)
r1.save()
r1은 Place에 접근할 수 있고, p1은 Restaurant에 접근할 수 있다.
#example
p1.Restaurant
p1.restaurant.serves_hot_dogs
#example - reverse
r1.Place
r1.place.name
따라서, 아래와 같은 lookup도 가능하다.
#example
Place.objects.get(restaurant=r1)
Place.objects.get(restaurant__place=p1)
Place.objects.get(restaurant__place__name__startswith="Daemon")
#example - reverse
Restaurant.objects.get(place=p1)
Restaurant.objects.get(place__pk=1)
Restaurant.objects.get(place__name__startswith="Daemon")
django가 제공하는 User model을 그대로 사용하면서, User 클래스를 확장할 때 OneToOneField
역시 유용한 옵션이다.
#AbstractUser 상속받고, 추가 field를 작성한다
class RestaurantStaff(AbstractUser):
phone = models.CharField(max_length=20, default="010-0000-0000")
level = models.CharField(max_length=20, default="Junior")
위와 같이 작성하면, RestaurantStaff 클래스는 아래와 같은 필드를 갖는다.
column | 비고 | |
---|---|---|
1 | id | |
2 | username | |
3 | first_name | |
4 | last_name | |
5 | ||
6 | password | |
7 | is_staff | |
8 | is_activate | |
9 | is_superuser | |
10 | last_login | |
11 | date_joined | |
12 | phone | 추가 |
13 | level | 추가 |
참고자료
https://docs.djangoproject.com/en/4.0/ref/models/fields/#django.db.models.OneToOneField
https://docs.djangoproject.com/en/4.0/topics/db/examples/one_to_one/