1. 장고 핵심 기능 - Model
(1) 모델의 정의
- 모델 : 테이블을 정의하는 장고의 클래스 (Spring에서는 @Entity)
- models.py 파일에 테이블을 정의하는 것이 기본이나, 그 외에도 관련 변수 및 메소드를 추가적으로 정의할 수 있음 -> ORM 방식에 기반해 테이블을 클래스로 정의하고 있기 때문
class Album(models.Model):
name = models.CharField(max_length = 50)
description = models.CharField('One line Description', max_length=100, blank = True)
owner = models.ForeignKey(User, null = True)
class Meta:
ordering = ['name']
def __str__(self):
return self.nmae
def get_absolute_url(self):
return reverse('photo:album_detail', args=(self.id,))
- 모델 속성
- 모델의 필드들은 모델 클래스의 속성으로 정의되며 테이블의 컬럼으로 1:1 매칭됨
- 위의 모델은 objects 속성을 포함한 4개의 모델 속성을 정의하고 있음
- 필드 타입은 테이블의 컬럼 타입을 지정하며, 폼으로 렌더링 될 때 HTML 위젯을 지정
(예시 : CharField의 경우 폼으로 사용될 때 <input type="text">
로 변환)
- 커스텀 타입 필드 : Field 추상 클래스 또는 기존의 장고 필드 클래스를 상속 받아 관련 로직을 코딩하면 됨
- 모델 메서드
- 주의할 점은 클레스 메서드와 객체 메서드를 구분해야 함
- 클래스 메서드 : 테이블 레벨에서 동작하는 메서드
- 객체 메서드 : 레코드 레벨에서 동작하는 메서드
- 장고에서는 클래스 메서드를 사용하지 않고 객체 메서드만 사용
- 모델 클래스에 정의하는 메서드는 모두 객체 메서드이고 항상 self 인자를 가지고 있으며, 이 메서드들을 호출하면 테이블 단위가 아닌 레코드 단위에만 영향을 미침
- 테이블 레벨에서 동작하는 클래스는? : Manager 클래스를 정의하고 Manager 클래스의 메서드를 통해 테이블에 대한 CRUD 동작을 수행 (Spring의 @Repository 개념인듯)
- 주요 메서드 종류
__str__
: toString() 개념
get_absolute_url
: 자신이 정의된 객체를 지칭하는 URL 반환
get_next_by_FOO(kwargs)
: 필드 타입이 Date, DateTimeField일 경우 + null = True가 아닌 경우 사용 가능, FOO는 필드명을 의미, 필드 기준으로 다음 객체를 반환
get_previous_by_FOO(kwargs)
: 위의 메서드와 반대되는 기능
get_FOO_display()
: 필드 옵션에 choices 인자가 있을 경우 사용 가능, FOO는 choices 인자를 갖는 필드, FOO 필드의 설명 문자열을 반환
- Meta 내부 클래스 속성
- Manager 속성
- 모든 모델은 반드시 Manager 속성을 가져야 하며, 명시적으로 지정하지 않을 경우 Manager 속성의 디폴트 이름은 objects가 됨
- Manager 속성은 모델 클래스를 통해서만 액세스 할 수 있으며 모델 객체를 통해서는 액세스 할 수 없음
- Manager 속성은 models.Manager 타입으로 정의되며 Manager 클래스를 이해해야 함
- Manager 클래스를 통해 데이터베이스 쿼리가 이뤄짐. 테이블 레벨에서의 동작은 Manager 클래스의 메서드를 통해 이뤄짐
Album.objects.all()
- QuerySet 클래스의 메서드와 Manager 클래스의 메서드는 거의 비슷하기 때문에 다음과 같은 QuerySet 메서드는 Manager 메서드로도 사용이 가능
all()
filter()
exclude()
get()
count()
- 모델 클래스에서는 Manager 속성을 여러 개 정의할 수 있으며 첫 번째로 정의된 Manager 속성을 디폴트 Manager라고 할 수 있음
- 아래의 경우 Album.second_objects.all() 메서드는 필터링을 한번 거치게 됨
class SecondAlbumManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(owner_username = 'shkim')
class Album(models.Model):
...
objects = models.Manager()
second_objects = SecondAlbumManager()
...
(2) 모델 간 관계
- 장고는 1:N(one to many), N:N(many to many), 1:1(one to one) 관계를 지원하고 있음
- 장고에서는 관계에 대해 한쪽 클래스에서만 관계를 정의하면 이를 바탕으로 상대편 정의는 자동으로 정의해줌 (이 부분은 Spring과 다르다... Spring은 일대다 관계, 다대일 관계가 분리되어 있으며 양방향 관계의 경우 서로 일대다, 다대일 매핑을 해줘야 함)
- 관계 표현 시 장고는 ForeignKey 필드를 사용하며 ForeignKey 필드는 N 모델에 정의를 해야 함 (Spring에서는 N쪽에 @ManyToOne, @JoinColumn을 사용해서 One 쪽의 엔티티를 지정)
- 이로 인해 장고 공식에서는 1:N이 아닌 N:1 이라는 용어를 사용
- 1:N 관계
- N 모델에서 ForeignKey 필드를 정의하면서 필수 인자로 1 모델을 지정
to
와 on_delete
인자 필요
on_delete=models.CASCADE
설정 시 1인 모델이 삭제될 경우 연관된 모델들에 대해 CASCADE가 동작하면서 삭제 작업이 이뤄짐(orphanRemoval=True)
class Album(models.Model):
...
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE, verbose_name='OWNER', blank=True, null=True)
a2 = Album.objects.all()[2]
a2.owner
a2.owner.username
u1 = User.objects.get(username='shkim')
u2 = User.objects.get(username='guest')
newa1 = Album(name="TestAlbum1")
newa1.save()
u1.album_set.add(newa1)
u1.album_set.all()
newa2 = u2.album_set.create(name="TestAlbum2")
u2.album_set.all()
u2.album_set.add(newa1)
u2.album_set.count()
u2.album_set.filter(name__startswith='Test')
Album.objects.filter(owner__username='guest')
Album.objects.filter(owner=u2, name__startswith='Test')
Albums.objects.filter(owner__pk=1)
User.objects.filter(album__pk=6)
u3 = User.objects.create(username='guest3')
u3.album_set.create(name='TestAlbum3')
u3.delete()
- N:N 관계
- 모델의 필드 정의 시 ManyToManyField 필드 타입을 사용, 모델 양 쪽에 모두 정의할 수는 있지만 한 쪽에만 정의해야 함
- 1:N 관계와 달리 어느 한 쪽의 객체가 삭제되더라도 다른 쪽 객체가 삭제되지는 않음
class Publication(models.Model):
title = models.CharField(max_length=30)
albums = models.ManyToManyField(Album)
class Album(models.Model):
p1 = Publication(title='test1')
p2 = Publication(title='test2')
p3 = Publication(title='test3')
p1.save()
p2.save()
p3.save()
a1 = Album.objects.get(name='a1')
p1.albums.add(a1)
p1.albums.all()
a1.publication_set.all()
Publication.objects.exclude(albums=a1)
- 1:1 관계
- 모델 필드 정의 시 OneToOneField 필드 타입을 사용, 관계를 맺고자 하는 모델 클래스를 필수 인자로 지정
- 개념적으로는 ForeignKey 필드 타입에 unique=True 옵션을 준 것과 유사하나, 반대 방향의 동작은 다름
to
와 on_delete
인자 필요
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return f"Place-{self.name}"
class Restaurant(models.Model):
place = models.OneToOneField(Place, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
serves_pizza = models.BooleanField(default=False)
def __str__(self):
return f"Restaurant-{self.name}"
p1 = Place(name='test1', address='test1')
r1 = Restaurant.objects.create(place=p1, name='TestRestaurant')
p2 = Place(name='test2', address='test2'
r1.place = p2
r1.save()
- Manager 클래스는 데이터베이스에 쿼리를 보내고 그 응답을 받는 역할을 함
- 관계 매니저 : 매니저 클래스 중 모델 간 관게에 대한 기능 및 데이터베이스 쿼리를 담당하는 클래스. 객체들의 집합을 다룸
- 보통 1:N, N:N 관계에 대해서만 관계 매니저를 사용하며, 1:1 관계에서는 사용하지 않음 (1:1 관계는 상대 객체가 하나이기 때문)
- 관계 매니저 메서드
- 아래 메서드들은 실행 즉시 데이터베이스에 반영되므로 save() 메서드 호출이 필요 없음
add(*objs, bulk=True)
: 인자로 주어진 모델 객체들을 관계 객체의 집합에 추가, 즉 두 모델 객체 간에 관계를 맺어줌
- 1:N 관계에서는 관계 매니저가 자동으로 QuerySet.update() 메서드를 호출해 데이터베이스 업데이트를 수행. bulk=False일 경우엔 모델 객체마다 save()를 호출해서 업데이트
- 반면 N:N 관계에서는 update(), save() 를 사용하지 않고 QuerySet.bulk_create() 메서드를 호출해 관계를 생성. bulk_create() 메서드는 한 번의 쿼리로 여러 개의 객체를 데이터베이스에 삽입
b = Blog.objects.get(id=1)
e = Entry.objects.get(id=234)
b.entry_set.add(e)
create(*kwargs)
: 새로운 객체를 생성해서 데이터베이스에 저장하고 관계 객체 집합에 넣으며 새로 생성된 객체를 반환. 즉, 상대방 모델 객체를 새로 만들어 두 객체 간에 관계를 맺어줌
b = Blog.objects.get(id=1)
e = b.entry_set.create(headline='hello', .....)
remove(*objs, bulk=True)
: 인자로 지정된 모델 객체들을 관계 집합에서 삭제. 즉, 두 모델 객체 간의 관계를 해제 (상대 객체를 삭제하는 것은 아니며 관계만 끊는 것)
- 1:N 관계에서는 null=True인 경우에만 사용 가능 (e.blog=None 문장을 실행하는 것과 같으므로)
- 1:N 관계에서는 bulk 인자를 가질 수 있는데 bulk가 True(디폴트)이면 QuerySet.update() 메서드 사용, False이면 모델 객체마다 save() 메서드 호출
- N:N 관계에서는 remove() 메서드를 사용하면 bulk 인자를 사용할 수 없으며, QuerySet.delete() 메서드를 호출해 관계를 삭제
b = Blog.objects.get(id=1)
e = Entry.objects.get(id=234)
b.entry_set.remove(e)
clear(bulk=True)
- 관계 객체 집합에 있는 모든 객체를 삭제
- 상대 객체를 삭제하는 것은 아니며 관계만 끊는 것
- 마찬가지로 null=True인 경우에만 사용 가능하며 bulk 인자에 따라 실행 방법이 달라짐
b = Blog.objects.get(id=1)
b.entry_set.clear()
sets(objs, bulk=True, clear=False)
- 관계 객체 집합의 내용을 변경
- 내부적으로 add(), remove(), clear() 메서드가 적절히 조합되어 실행되므로 bulk 인자는 remove()에서 설명한 내용과 동일
- clear 인자가 False(Default)인 경우 기존 항목들을 체크하여 지울 항목은 remove()로, 추가할 항목은 add() 메소드로 실행
- clear 인자가 True라면 clear() 메서도르 기존 항목을 한꺼번에 모두 지운 후 add()로 new_list 내용을 새로 한꺼번에 추가
new_list = [obj1, obj2, obj3]
e.related_set.set(new_list)