2.3 장고의 필드
모델링할 때 가장 자주 쓰이는 필드를 사전식으로 정리할 것. 역시 암기하지 않는 것을 추천한다.
2.3.1 Primary Key 관련 필드
- 장고 모델로 생성되는 테이블을 primary key를 반드시 가지도록 설계되어 있음
- 지정하지 않으면, 장고가 알아서 primary_key=True 옵션을 가지는 필드를 생성
- 암묵적으로 생성되는 필드라서 직접 선언할 일은 거의 없지만 장고 내부에 이러한 필드가 생성됨
- AutoField
- 파이썬 자료형: int
- 데이터베이스 자료형: int
class DjangoModel(Model): id = models.AutoField( auto_created=True, primary_key=Ture, serialize=False, verbose_name='ID', )
- 이렇게 굳이 선언하지 않더라도 primary_key=True인 필드가 존재하지 않으면 AutoField를 알아서 생성
- BigAutoField
- 파이썬 자료형: int
- 데이터베이스 자료형: bigint
- AutoField에 비해 더 많은 자릿수 보장
class DjangoModel(Model): id = models.BigAutoField( auto_created=True, primary_key=True, serialize=False, verbose_name='ID', )
- 개발자가 이를 직접 선언할 일은 많지 않음
- 장고의 settings.py에서 pk로 쓰일 필드를 지정할 수 있음
- # settings.py default_auto_field = 'django.db.models.BigAutoField' # <- 이렇게 사용할 것을 권장 default_auto_field = 'django.db.modls.AutoField' # 장고의 기본 세팅 값
2.3.2 문자열 자료형 관련 필드
- CharField
- 파이썬 자료형: str(문자열)
- 데이터베이스 자료형: varchar
- TextField
- 파이썬 자료형: str(문자열)
- 데이터베이스 자료형: PostgreSQL - TEXT | MYSQL - LONGTEXT
- CharField와 동일한 str이지만 데이터베이스의 자료형이 다름
- 이는 매우 큰 문자열을 저장할 수 있지만 데이터베이스의 성능이 떨어질 수 있음
- 어쩔 수 없이 데이터 문자열 길이를 제한할 수 없는 경우에만 사용해야 함
- TextField에 index 옵션을 설정하거나 unique 제약을 거는 것도 데이터베이스 성능에 좋지않은 영향을 줄 수 있음
- TextChoices
- 장고 문자열 타입 Enum (장고 3.0 이상에서만 지원되는 기능)
- 사용 방법: 변수명 = “실제 db에 들어가는 값”, “라벨 값 (사람이 읽기 편한 값)”
- 위와 같이 선언해주고 TextField와 CharField의 choices라는 옵션을 명시
- 장고 모델은 choices 옵션을 사용하는 CharField나 TextField가 존재하면 get_필드명_display() 메서드를 묵시적으로 생성
… 생략 …
2.3.7 모델 간 매핑 필드
지금부터는 필드가 아닌 모델과 모델 사이의 관계를 매핑하는 방법
- ForeignKey (1:N 관계 매핑)
- ForeignKey는 모델 간 관계가 1:N일 때 사용
- 사용 예시
- 1개의 상정은 N개의 상품을 가지고 있다.
class Product(models.Model): store = models.ForignKey( to="Store", null=False, on_delete=models.CASCADE, )
- ForeignKey의 요소
- to: 모델 간 관계가 1:N 일 때 1에 해당하는 쪽에 to 옵션을 줌
- 가급적 객체가 아닌 문자열로 모델 클래스명을 매핑하는 것을 추천
- models.py 간 import가 하나둘씩 생기면 향후에 **순환 참조 에러 (circular import error)**가 발생할 수 있으니 models.py 간의 임포트는 최대한 줄이는 게 좋음
- (정리) 객쳋를 직접 매핑하는 게 아니라 문자열로 모델 이름을 매핑하는 이유는 순환 참조 에러를 방지하기 위해서다.
- on_delete: 1에 해당하는 모델의 데이터가 삭제되었을 때 N에 해당하는 모델 데이터를 어떻게 해야 할지를 정하는 옵션
- on_delete = models.CASCADE
- 1에 해당하는 모델 데이터가 삭제되었을 때 이와 매핑되어 있는 N에 해당하는 모델 데이터도 삭제
- on_delete = models.PROTECT
- 1에 해당하는 모델 데이터를 삭제하려고 할 때 N에 해당하는 모델이 이미 1을 참조하고 있다면 삭제하지 못하도록 데이터를 보호
- on_delete = models.SET_NULL
- 1에 해당하는 모델 데이터가 삭제되었을 때 이와 매핑되어 있는 N에 해당하는 모델 데이터를 null로 채움
- on_delete = models.SET_DEFAULT
- 1에 해당하는 모델 데이터가 삭제되었을 때 이와 매핑되어 있는 N에 해당하는 모델 데이터를 default로 채움
- on_delete = models.SET
- 1에 해당하는 모델 데이터가 삭제되었을 때 미리 설정된 함수를 사용해서 가져온 값 또는 미리 설정한 값으로 채움
def set_delete_owner(): return -1111 ... class Store(models.Model): ... owner = models.ForignKey( to="ShoppingMallUser", null=False, on_delete=models.SET(set_delete_owner), # 이런 식으로 함수를 인자로 부여할 수도 있음 )
- OneToOne (1:1 관계 매핑)
- 모델 간 관계가 1:1일 때 사용
- 사용 예시
- 1명의 학생은 1개의 라커룸을 가질 수 있다.
class Locker(models.Model): student = models.OneToOneField( to="Student", null=True, on_delete=models.SET_NULL, )
- OneToOneField는 ForeignKey를 상속받은 장고 필드임.
class OneToOneField(ForeignKey): """ A OneToOneField is essentially the same as a ForeignKey, with the exception that ... """
- → ForeignKey에서 언급했던 옵션들을 전부 사용할 수 있음
- 데이터베이스 관점에서 보면 OneToOneField는 unique=True라는 고정된 옵션을 가진 ForeignKey와 같음
- 두 가지 표현 방식은 데이터베이스 관점에서 보면 동일함
- 평범한 OneToOneField 사용 방식
- store_info = models.OneToOneField( to="StoreInfo", null=True, on_delete=models.CASCADE )
- OneToOneField 역할을 흉내 내는 ForeignKey 사용 방식
- store_info = models.ForeignKey( to="StoreInfo", null=True, on_delete=models.CASCADE, unique=True, # OneToOneField는 이 옵션이 강제로 True로 설정된 ForeignKey와 같다. )
- ForeignKey로 매핑된 경우에는 1:N 개의 관계이기 때문에 개발자는 양쪽 모델이 서로 1개씩만 가질 수 있다는 것을 알 수 있지만 코드상으로는 알 수 없음→ OneToOneField로 매핑된 경우라면 바로 store 객체를 가져올 수 있음
- 설계 의도 상 stroe에 매핑된 storeInfo 모델은 1개 뿐이어도 .all()[0]과 같은 코드를 매번 작성해야 하는 상황은 바람직하지 않음
- store_info.store # OneToOneField으로 매핑 시 바로 Object를 가져올 수 있음 store_info.store_set.all # ForeignKey로 매핑 시 매번 list()로 감싼 다음에 0번 째 값을 호출해야 함
- → store_set을 호출해서 매번 리스트로 감싸줘야 함
- ManyToMany (M:N 관계 매핑)
- 모델 간 관계가 M:N일 때 사용
- 사용 예시 1
- 주문과 상품은 서로 여러 개를 가질 수 있다. 1 개의 주문에는 N 개의 상품을 가지고 있고 반대로 1 개의 상품은 N 개의 주문에 포함될 수 있다.
class Product(models.Model): name = models.CharField(max_length='128', help_text="상품명") class Meta: db_table = 'product' class OrderedProduct(models.Model): """ 주문된 상품들""" order = models.ForeignKey(to='Order', on_delete=models.CASCADE) product = models.ForeignKey(to='Product', on_delete=models.CASCADE) count = models.IntegerField(help_text='주문한 해당 상품의 개수', default=1) class Meta: db_table = 'ordered_product' class Order(models.Model): # ... product_set = models.ManyToManyField(to='Product', through='OrderedProduct') class Meta: db_table = 'orderz'
- orderz
- SQL에 order by라는 문법이 존재하기에 개발자나 DBA가 order 대신 orderz라고 작명
- class 대신 clazz
- 하지만, ORM을 사용한다면 테이블 이름이 order라고 해도 문제 없음
- 사용 예시 2
- 1명의 학생은 여러 개의 수업을 수강할 수 있고 1개의 수업은 여러 명의 학생이 수강할 수 있다. 학생과 수업은 M:N 관계다.
- class Lecture(models.Model): # ... class Meta: db_table = "lecture" class StudentLectureRelation(models.Model): lecture = models.ForeignKey(to="Lecture", on_delete=models.CASCADE) student = models.ForeignKey(to="Student", on_delete=models.CASCADE) class Meta: # 이 db_table 작명법은 through를 사용하지 않았을 때 django가 자동으로 생성하는 table 이름 # 데이터베이스 입장에서 보면 through를 제거해도 이런 작명으로 직접 선언한 것과 동일한 결과를 얻습니다. # 하지만, 이러한 네이밍은 직관적이지 않기 때문에 클래스명과 동일하게 가져갈 것을 추천합니다. db_table = "student_lecture_set" class Student(models.Model): lecture_set = models.ManyToManyField(to="Lecture", through="StudentLectureRelation") class Meta: db_table = "student"
- through
- M:N 매핑은 구조상 매개 테이블이 생성되어야만 한다. 장고는 이 매개 테이블을 알아서 생성해주지만 개발자가 이것을 정확히 명시하고 싶다면 through 옵션을 사용해서 직접 선언할 수 있다. Order와 Product 간 M:N 매핑에서는 ‘주문한 해당 상품의 개수’라는 개념을 표현하기 위해 매개 테이블을 개발자가 직접 생성해야 했지만 Board와 HashTag, Lecture와 Student 관계에서는 굳이 매개 테이블을 직접 선언할 필요가 없을지도 모름.
- 하지만, ManyToManyField()를 사용한다면 반드시 through=를 선언할 것을 권장 그 이유는 다음과 같음.
- 예측할 수 없는 확장 가능성
- 직관적이지 않은 테이블명
- 장고는 테이블명이 중복되는 상황을 방지하는 작명을 함.
- 장고가 존재 여부를 알 수 없는 테이블
- through 없이 ManyToManyField를 선언한 후 through로 모델을 등록할 때 발생할 수 있는 문제와 해결책 부분 생략
2.3.8 장고 모델의 모듈 사용
- 관리되지 않은 모델 (Unmanaged Model)
- … 생략 …
2.3.9 커스텀 필드 개발하기
- 암복호화 필드 (Encrypted Field)
- 개인 정보에 속하는 데이터는 데이터베이스에 저장할 때 반드시 암호화되어야 함
- 조회할 때는 장고 애플리케이션에서 이 값을 복호화해야 함
- 이러한 암복호화 로직이 소스 코드 내에 어지럽게 사용되고 있으면 안 됨
- 예를 들어 주민등록번호와 같은 개인 정보를 담은 칼럼이 존재한다면,
- 이것을 데이터베이스에 저장할 때 매번 암호화해야 한다면 프로젝트의 소스 코드 내에서 아래와 같은 로직이 빈번하게 사용됨
encrypted_data = encrypt("010823-3234567") User.objects.create_user( username="username111", password="1234", registration_number=encrypted_data, ... )
- 커스텀 필드를 만들기 전에 우선 암복호화 모듈부터 개발해야 함
- 암복호화를 위한 라이브러리 cryptography를 사용
- 암복호화 라이브러리 설치
-> poetry add cryptography
- 암복호화 모듈 예시
- max_length
- 암호화 수행 결과는 byte
- 이때 한글(3byte), 영어, 숫자, 특수 문자는 byte 타입에서 크기가 다름
- 또한 암호화 수행 과정에서 padding 값이 추가될 수도 있고 안 될 수도 있기에 암호화 데이터의 max_length를 정확하게 예측하기 어려움
- 이 함수는 평문 길이가 암호화되었을 때 가질 수 있는 최대 길이를 계산
- EncryptedField로 선언된 경우 검색 조건으로 사용 불가
- ORM에서 해결하지 않고 데이터베이스의 내장 함수를 사용하면 검색이 가능하지만 이마저도 조회 성능은 급격히 떨어짐
'django' 카테고리의 다른 글
모델링과 마이그레이션 (3) (5) | 2025.01.17 |
---|---|
모델링과 마이그레이션 (1) (4) | 2025.01.15 |
장고에 대하여 (7) | 2025.01.07 |
3 - ORM과 Django Rest Framework (1) (2) | 2024.05.23 |
2 - Django Template과 Model (8) | 2024.05.22 |