Django ORM
Django ORM Cookbook
일대일 관계
- 일대일 관계란 두 표에서 각 항목이 서로 다른 표의 항목 단 하나와 연결되는 관계
- 쉽게 이해할 수 있는 예를 들자면, 각자는 생부와 생모를 각각 하나씩만 가질 수 있음
from django.contrib.auth.models import User
class UserParent(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
primary_key=True,
)
father_name = models.CharField(max_length=100)
mother_name = models.CharField(max_length=100)
>>> u1 = User.objects.get(first_name='Ritesh', last_name='Deshmukh')
>>> u2 = User.objects.get(first_name='Sohan', last_name='Upadhyay')
>>> p1 = UserParent(user=u1, father_name='Vilasrao Deshmukh', mother_name='Vaishali Deshmukh')
>>> p1.save()
>>> p1.user.first_name
'Ritesh'
>>> p2 = UserParent(user=u2, father_name='Mr R S Upadhyay', mother_name='Mrs S K Upadhyay')
>>> p2.save()
>>> p2.user.last_name
'Upadhyay'
on_delete
메서드는 그 필드에 연결된 항목이 삭제될 때 그 항목을 가리키는 항목들을 어떻게 처리해야 할지 설정
on_delete=models.CASCADE
(하위 삭제)는 연결된 항목이 삭제될 때 해당 항목을 함께 삭제
u2.delete()
를 실행하면 User 모델의 항목(u2) 뿐 아니라 UserParent 의 항목(p2)도 함께 삭제됨
일대다 관계
- 일대다 관계란 한 표의 상위 항목이 다른 표의 여러 하위 항목에서 참조되는 관계
- 일대다 관계에서 상위 항목이 반드시 하위 항목을 가진다는 보장은 없음
- 상위 항목은 하위 항목을 0개, 1개, 여러 개 가질 수 있음
- 장고 모델에서 일대다 관계를 정의할 때는
ForeignKey
필드를 사용
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reporter')
def __str__(self):
return self.headline
class Meta:
ordering = ('headline',)
>>> u1 = User(username='johny1', first_name='Johny', last_name='Smith', email='johny@example.com')
>>> u1.save()
>>> u2 = User(username='alien', first_name='Alien', last_name='Mars', email='alien@example.com')
>>> u2.save()
>>> from datetime import date
>>> a1 = Article(headline="This is a test", pub_date=date(2018, 3, 6), reporter=u1)
>>> a1.save()
>>> a1.reporter.id
13
>>> a1.reporter
<User: johny1>
- 상위 객체를 데이터베이스에 저장하지 않은 채로 하위 객체에 할당하려 하면
ValueError
예외가 발생
>>> u3 = User(username='someuser', first_name='Some', last_name='User', email='some@example.com')
>>> Article.objects.create(headline="This is a test", pub_date=date(2018, 3, 7), reporter=u3)
Traceback (most recent call last):
...
ValueError: save() prohibited to prevent data loss due to unsaved related object 'reporter'.
>>> Article.objects.create(headline="This is a test", pub_date=date(2018, 3, 7), reporter=u1)
>>> Article.objects.filter(reporter=u1)
<QuerySet [<Article: This is a test>, <Article: This is a test>]>
- u1 하나에 여러 개의
Article
이 연결되어 있음(일대다 관계)을 확인할 수 있음
다대다 관계
- 다대다 관계란 한 표의 항목이 다른 표의 항목 여러 개를 가리킬 수 있고, 반대로 다른 표의 항목이 그 표의 항목을 여러 개 가리킬 수도 있는 관계
- 실제로 실행 가능한 예로, 트위터 앱을 다뤄 볼 예정
- 필드 몇 개와 ManyToMany 필드만 있으면 간단한 트위터 앱을 만들 수 있음
- 트위터의 핵심 기능으로 ‘트윗’, ‘팔로우’, ‘마음에 들어요’가 있음
class User(AbstractUser):
tweet = models.ManyToManyField(Tweet, blank=True)
follower = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
pass
class Tweet(models.Model):
tweet = models.TextField()
favorite = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='user_favorite')
def __unicode__(self):
return self.tweet
- 이 모델로 할 수 있는 일
- 사용자가 다른 사용자를 '팔로우' 및 취소할 수 있음
- 사용자가 팔로우하는 다른 사용자가 작성한 트윗을 볼 수 있음
- 사용자가 트윗에 '마음에 들어요' 및 취소할 수 있음
>>> t1 = Tweet(tweet="I am happy today")
>>> t1.save()
>>> t2 = Tweet(tweet="This is my second Tweet")
>>> t2.save()
>>> u1 = User(username='johny1', first_name='Johny', last_name='Smith', email='johny@example.com')
>>> u1.save()
>>> u2 = User(username='johny1', first_name='Johny', last_name='Smith', email='johny@example.com')
>>> u2.save()
>>> u3 = User(username='someuser', first_name='Some', last_name='User', email='some@example.com')
>>> u3.save()
>>> u2.tweet.add(t1)
>>> u2.save()
>>> u2.tweet.add(t2)
>>> u2.save()
>>>
>>> u2.follow.add(u1)
>>> u2.save()
>>>
>>>
>>> t1.favorite.add(u1)
>>> t1.save()
>>> t1.favorite.add(u3)
>>> t1.save()
>>>
>>> t1.favorite.remove(u1)
>>> t1.save()
자기 참조 외래 키 정의
- 자기 참조 외래 키를 이용하여 중첩 관계·재귀 관계를 표현할 수 있음
- 일대다 관계와 유사하지만, 이름에서 알 수 있듯이 모델이 자기 자신을 참조한다는 특징이 있음
class Employee(models.Model):
manager = models.ForeignKey('self', on_delete=models.CASCADE)
class Employee(models.Model):
manager = models.ForeignKey("app.Employee", on_delete=models.CASCADE)
기존 데이터베이스를 장고 모델로 옮기기
- 장고에는 기존 데이터베이스를 분석하여 그에 맞는 모델을 생성해주는
inspectdb
명령이 있음
- 셸에서 다음 명령을 실행하여 결과를 확인할 수 있음
$ python manage.py inspectdb
- 이 명령을 실행하려면 먼저
settings.py
파일에 분석하려는 데이터베이스의 접속 정보를 설정해 두어야 함
- 출력 결과는 생성된 모델의 파이썬 코드
- 코드를 파이썬 모듈 파일로 저장하려면 셸의 스트림 리디렉션 기능을 이용
$ python manage.py inspectdb > models.py
- 위 명령을 실행하면 분석된 모델이 파이썬 모듈 파일로 현재 디렉토리에 저장됨
- 이 파일을 앱의 올바른 위치로 옮긴 뒤, 적절히 수정하여 사용하면 됨
데이터베이스 뷰에 대응하는 모델 정의
- 데이터베이스 뷰는 데이터베이스 내에서 조회할 수 있도록 질의문으로 정의된 객체
- 뷰가 데이터를 물리적으로 저장하는 것은 아니지만, 실제 표와 같이 조회할 수 있기 때문에 ‘가상 표’라고 불리기도 함
- 뷰는 여러 표를 결합(JOIN)한 정보를 보여줄 수도 있고, 한 표의 부분 집합만을 보여줄 수도 있음
- 이를 활용하면 복잡한 질의문을 감추고 필요한 정보를 쉽게 조회하는 인터페이스를 만들 수 있음
class TempUser(models.Model):
first_name = models.CharField(max_length=100)
class Meta:
managed = False
db_table = "temp_user"
>>>
>>> TempUser.objects.all().values()
<QuerySet [{'first_name': 'Yash', 'id': 1}, {'first_name': 'John', 'id': 2}, {'first_name': 'Ricky', 'id': 3}, {'first_name': 'Sharukh', 'id': 4}, {'first_name': 'Ritesh', 'id': 5}, {'first_name': 'Billy', 'id': 6}, {'first_name': 'Radha', 'id': 7}, {'first_name': 'Raghu', 'id': 9}, {'first_name': 'Rishabh', 'id': 10}, {'first_name': 'John', 'id': 11}, {'first_name': 'Paul', 'id': 12}, {'first_name': 'Johny', 'id': 13}, {'first_name': 'Alien', 'id': 14}]>
>>>
>>> TempUser.objects.create(first_name='Radhika', id=15)
Traceback (most recent call last):
...
django.db.utils.OperationalError: cannot modify temp_user because it is a view
- 장고 앱에서는 모델을 정의할 때 메타(
Meta
) 클래스에 managed = False
, db_table="temp_user"
와 같이 옵션을 설정하여 뷰를 가리키는 모델로 사용할 수 있음
범용 모델 정의
- 아무 모델이나 가리킬 수 있는 범용 모델을 정의하는 방법
class Category(models.Model):
name = models.CharField(max_length=100)
class Meta:
verbose_name_plural = "Categories"
class Hero(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class Villain(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class FlexCategory(models.Model):
name = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class Hero(models.Model):
name = models.CharField(max_length=100)
flex_category = GenericRelation(FlexCategory, related_query_name='flex_category')
class Villain(models.Model):
name = models.CharField(max_length=100)
flex_category = GenericRelation(FlexCategory, related_query_name='flex_category')
- 수정한 코드에서는
FlexCategory
모델에 외래 키 필드(ForeignKey
) 하나와 양의 정수 필드(PositiveIntegerField
) 하나를 정의하여 범용 외래 키 필드(GenericForeignKey
)를 사용할 수 있도록 함
- 분류를 이용할 모델에 범용 관계 필드(
GenericRelation
)를 추가
>>> hero = Hero.objects.create(name='Hades')
>>> FlexCategory.objects.create(content_object=hero, name="mythic")
>>> Hero.objects.filter(flex_category__name='ghost')
SELECT "entities_hero"."name"
FROM "entities_hero"
INNER JOIN "entities_flexcategory" ON ("entities_hero"."id" = "entities_flexcategory"."object_id"
AND ("entities_flexcategory"."content_type_id" = 8))
WHERE "entities_flexcategory"."name" = ghost