[django] OneToOneField에 대하여

EMMA·2022년 8월 2일
1
post-custom-banner

One to One, 1:1 관계

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 공식문서)

하지만 OneToOneFieldForeignKey는 관계 유형과 반환하는 값이 다르다.
역참조 시 OneToOneField는 객체 1개를 반환하지만 ForeignKey는 Queryset 형태로 반환하는데, 사실 이는 단어와 각각이 정의하는 테이블 관계를 생각하면 당연한 것이다.

Quick example

예를 들어, 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 테이블>

IDnameageallergen_id
1이몽룡251
2성춘향322

<Allergen 테이블> - Patient와 1:1 관계(ID)

IDpeanutpollenmilkcrabkiwi
110001
200000

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")

User model을 커스터마이징할 때도 유용한 OneToOneField

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비고
1id
2username
3first_name
4last_name
5email
6password
7is_staff
8is_activate
9is_superuser
10last_login
11date_joined
12phone추가
13level추가


참고자료
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/

profile
예비 개발자의 기술 블로그 | explore, explore and explore
post-custom-banner

0개의 댓글