TIL_20230307_Django 프레임워크 보강 01

창고·2023년 3월 7일
0

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)
    
    # 명시적으로 지정 안 할 경우 디폴트 Manager 속성은 다음과 같다
    # objects = models.Manager()
    
    class Meta: # Meta 내부 클래스
    	
        # 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 내부 클래스 속성
    • Meta 내부 클래스를 통해 모델에 대한 메타 데이터를 정의할 수 있음
    • 모델 클래스의 필드는 아니지만 모델 클래스에 필요한 항목을 정의
    • 주요 항목
      • ordering : 모델 객체의 리스트 출력 시 정렬하기 위해 사용하는 속성 지정, 기본값은 오름순이며 - 접두사를 붙이면 내림차순으로 정렬. DB 저장 순서는 아니며 데이터 출력 시의 순서 (sorting)
      # pub_date 필드를 기준으로 내림차순 후 author 필드 기준으로 오름차순 정렬
      ordering = [`-pub_date', 'author']
      • db_table : 데이터베이스에 저장되는 테이블 이름을 지정 (@Table(name = ""))
        • 미지정 시 기본값인 앱명_클래스명(소문자) 로 테이블명이 지정됨
      db_table = 'tb_post'
      • verbose_name : 모델 객체의 별칭을 지정
        • 미지정 시 기본값인 모델 클래스명을 변형(FavoritePost = favorite post)
      verbose_name = 'my favorite post`
      • verbose_name_plural : verbose_name의 복수 명칭을 지정
        • 미지정 시 verbose_name + "s"가 사용됨
  • Manager 속성
    • 모든 모델은 반드시 Manager 속성을 가져야 하며, 명시적으로 지정하지 않을 경우 Manager 속성의 디폴트 이름은 objects가 됨
    • Manager 속성은 모델 클래스를 통해서만 액세스 할 수 있으며 모델 객체를 통해서는 액세스 할 수 없음
    • Manager 속성은 models.Manager 타입으로 정의되며 Manager 클래스를 이해해야 함
    • Manager 클래스를 통해 데이터베이스 쿼리가 이뤄짐. 테이블 레벨에서의 동작은 Manager 클래스의 메서드를 통해 이뤄짐
    Album.objects.all() # Album의 Manager 속성(objects)의 메서드(all())을 사용
    • QuerySet 클래스의 메서드와 Manager 클래스의 메서드는 거의 비슷하기 때문에 다음과 같은 QuerySet 메서드는 Manager 메서드로도 사용이 가능
      • all()
      • filter()
      • exclude()
      • get()
      • count()
    • 모델 클래스에서는 Manager 속성을 여러 개 정의할 수 있으며 첫 번째로 정의된 Manager 속성을 디폴트 Manager라고 할 수 있음
      • 아래의 경우 Album.second_objects.all() 메서드는 필터링을 한번 거치게 됨
# 추가 Manager
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 모델을 지정
    • toon_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)
  • 1:N 관계 API 실습
# 앨범 객체를 조회해 소유자를 확인
a2 = Album.objects.all()[2]
a2.owner

# 앨범 객체를 통해 사용자 객체에 액세스해 사용자의 이름 확인
a2.owner.username

# 사용자 객체를 조회한 후 u1, u2 객체에 대입
u1 = User.objects.get(username='shkim')
u2 = User.objects.get(username='guest')

# 앨범을 만들고 소요자를 지정하는 방법
# 첫번째 방법 : 새로운 앨범을 만들고 해당 앨범의 소유자를 사용자로 지정 (add())
newa1 = Album(name="TestAlbum1")
newa1.save()
u1.album_set.add(newa1) # album_set이 album 리스트인듯
u1.album_set.all() # album_set 확인

# 두번째 방법 : 새로운 앨범을 만들 때부터 사용자를 지정 (create())
newa2 = u2.album_set.create(name="TestAlbum2")
u2.album_set.all() # album_set 확인

# 소유자를 변경
u2.album_set.add(newa1) # newa1의 소유자가 u1 -> u2로 변경됨 ㄷㄷ

# 소유자가 u2인 앨범의 개수를 확인
u2.album_set.count()

# 모델 간 관계에서도 필드 검색 오퍼레이션이 가능
u2.album_set.filter(name__startswith='Test')
Album.objects.filter(owner__username='guest')

# 조건이 2개 이상을 경우 AND 오퍼레이션 수행
Album.objects.filter(owner=u2, name__startswith='Test')

# 앨범 모델에서 소유자를 지정해서 조회하는 방법
Albums.objects.filter(owner__pk=1) # owner=1, owner=u1... 등

# 유저 모델에서 앨범의 소유자를 지정해서 조회하는 방법
User.objects.filter(album__pk=6) # album=6, album=a2... 등

# 1:N 관계에서 1쪽의 객체를 지울 경우 CASCADE로 동작해서 N쪽의 객체도 삭제됨
u3 = User.objects.create(username='guest3') # 유저 객체 생성
u3.album_set.create(name='TestAlbum3') # 소유자 지정하여 바로 앨범 생성
u3.delete() # 유저 삭제, 이 경우 CASCADE 동작하여 TestAlbum3도 삭제됨 
# delete() 메서드는 삭제된 개수를 반환
  • N:N 관계
    • 모델의 필드 정의 시 ManyToManyField 필드 타입을 사용, 모델 양 쪽에 모두 정의할 수는 있지만 한 쪽에만 정의해야 함
    • 1:N 관계와 달리 어느 한 쪽의 객체가 삭제되더라도 다른 쪽 객체가 삭제되지는 않음
class Publication(models.Model):

	title = models.CharField(max_length=30)
    albums = models.ManyToManyField(Album) # 이쪽에 지정
    
class Album(models.Model):

	# 위에서 작성한 내용과 동일
  • N:N 관계 API 실습
# 출판물 객체 생성 및 저장
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()

# 모델 간 관계에서도 filter()와 마찬가지로 exclude() 메서드가 가능
Publication.objects.exclude(albums=a1)

# 1:N 관계와 다르게 삭제 시 연관 객체가 삭제되지 않음
  • 1:1 관계
    • 모델 필드 정의 시 OneToOneField 필드 타입을 사용, 관계를 맺고자 하는 모델 클래스를 필수 인자로 지정
    • 개념적으로는 ForeignKey 필드 타입에 unique=True 옵션을 준 것과 유사하나, 반대 방향의 동작은 다름
    • toon_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}"
  • 1:1 관계 API 실습
# 객체 생성 단계에서 create()를 통해 연결하고자 하는 모델을 매개변수로 넣어 연결
p1 = Place(name='test1', address='test1')
r1 = Restaurant.objects.create(place=p1, name='TestRestaurant')

# 장소 변경 시 add() 메서드를 사용하지 않고 바로 대입
p2 = Place(name='test2', address='test2'
r1.place = p2
r1.save() # 대입하여 변경 시 save() 해야 함

(3) 관계 매니저(RelatedManager)

  • 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)
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글