본문 바로가기
django

모델링과 마이그레이션 (2)

by shinminkyoung 2025. 1. 15.

2.3 장고의 필드

모델링할 때 가장 자주 쓰이는 필드를 사전식으로 정리할 것. 역시 암기하지 않는 것을 추천한다.

2.3.1 Primary Key 관련 필드

  • 장고 모델로 생성되는 테이블을 primary key를 반드시 가지도록 설계되어 있음
  • 지정하지 않으면, 장고가 알아서 primary_key=True 옵션을 가지는 필드를 생성
  • 암묵적으로 생성되는 필드라서 직접 선언할 일은 거의 없지만 장고 내부에 이러한 필드가 생성됨
  1. AutoField
    • 파이썬 자료형: int
    • 데이터베이스 자료형: int
    class DjangoModel(Model):
    		id = models.AutoField(
    						auto_created=True, primary_key=Ture, serialize=False, verbose_name='ID',
    		)
    
    • 이렇게 굳이 선언하지 않더라도 primary_key=True인 필드가 존재하지 않으면 AutoField를 알아서 생성
  2. 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 문자열 자료형 관련 필드

  1. CharField
    • 파이썬 자료형: str(문자열)
    • 데이터베이스 자료형: varchar
  2. TextField
    • 파이썬 자료형: str(문자열)
    • 데이터베이스 자료형: PostgreSQL - TEXT | MYSQL - LONGTEXT
    • CharField와 동일한 str이지만 데이터베이스의 자료형이 다름
    • 이는 매우 큰 문자열을 저장할 수 있지만 데이터베이스의 성능이 떨어질 수 있음
    • 어쩔 수 없이 데이터 문자열 길이를 제한할 수 없는 경우에만 사용해야 함
    • TextField에 index 옵션을 설정하거나 unique 제약을 거는 것도 데이터베이스 성능에 좋지않은 영향을 줄 수 있음
  3. TextChoices
    • 장고 문자열 타입 Enum (장고 3.0 이상에서만 지원되는 기능)
    • 사용 방법: 변수명 = “실제 db에 들어가는 값”, “라벨 값 (사람이 읽기 편한 값)”
    • 위와 같이 선언해주고 TextField와 CharField의 choices라는 옵션을 명시
    • 장고 모델은 choices 옵션을 사용하는 CharField나 TextField가 존재하면 get_필드명_display() 메서드를 묵시적으로 생성

… 생략 …

2.3.7 모델 간 매핑 필드

지금부터는 필드가 아닌 모델과 모델 사이의 관계를 매핑하는 방법

  1. ForeignKey (1:N 관계 매핑)
    • ForeignKey는 모델 간 관계가 1:N일 때 사용
    • 사용 예시
      • 1개의 상정은 N개의 상품을 가지고 있다.
      class Product(models.Model):
      		
      		store = models.ForignKey(
      				to="Store", null=False, on_delete=models.CASCADE,
      		)
      
    • ForeignKey의 요소
      1. to: 모델 간 관계가 1:N 일 때 1에 해당하는 쪽에 to 옵션을 줌
      • 가급적 객체가 아닌 문자열로 모델 클래스명을 매핑하는 것을 추천
      • models.py 간 import가 하나둘씩 생기면 향후에 **순환 참조 에러 (circular import error)**가 발생할 수 있으니 models.py 간의 임포트는 최대한 줄이는 게 좋음
      • (정리) 객쳋를 직접 매핑하는 게 아니라 문자열로 모델 이름을 매핑하는 이유는 순환 참조 에러를 방지하기 위해서다.
      1. 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), # 이런 식으로 함수를 인자로 부여할 수도 있음
      )
      
  2. 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와 같음
    • 두 가지 표현 방식은 데이터베이스 관점에서 보면 동일함
      1. 평범한 OneToOneField 사용 방식
      2. store_info = models.OneToOneField( to="StoreInfo", null=True, on_delete=models.CASCADE )
      3. OneToOneField 역할을 흉내 내는 ForeignKey 사용 방식
      4. 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을 호출해서 매번 리스트로 감싸줘야 함
  1. 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=를 선언할 것을 권장 그 이유는 다음과 같음.
        1. 예측할 수 없는 확장 가능성
        2. 직관적이지 않은 테이블명
          1. 장고는 테이블명이 중복되는 상황을 방지하는 작명을 함.
        3. 장고가 존재 여부를 알 수 없는 테이블
  • through 없이 ManyToManyField를 선언한 후 through로 모델을 등록할 때 발생할 수 있는 문제와 해결책 부분 생략

2.3.8 장고 모델의 모듈 사용

  1. 관리되지 않은 모델 (Unmanaged Model)
  2. … 생략 …

2.3.9 커스텀 필드 개발하기

  1. 암복호화 필드 (Encrypted Field)
    • 개인 정보에 속하는 데이터는 데이터베이스에 저장할 때 반드시 암호화되어야 함
    • 조회할 때는 장고 애플리케이션에서 이 값을 복호화해야 함
    • 이러한 암복호화 로직이 소스 코드 내에 어지럽게 사용되고 있으면 안 됨
    • 예를 들어 주민등록번호와 같은 개인 정보를 담은 칼럼이 존재한다면,
    • 이것을 데이터베이스에 저장할 때 매번 암호화해야 한다면 프로젝트의 소스 코드 내에서 아래와 같은 로직이 빈번하게 사용됨
    encrypted_data = encrypt("010823-3234567")
    User.objects.create_user(
    		username="username111", password="1234", registration_number=encrypted_data, ...
    )
    
  • 커스텀 필드를 만들기 전에 우선 암복호화 모듈부터 개발해야 함
  • 암복호화를 위한 라이브러리 cryptography를 사용
    • 암복호화 라이브러리 설치
    -> poetry add cryptography
    
    • 암복호화 모듈 예시
    … 생략 …
  1. max_length
    • 암호화 수행 결과는 byte
    • 이때 한글(3byte), 영어, 숫자, 특수 문자는 byte 타입에서 크기가 다름
    • 또한 암호화 수행 과정에서 padding 값이 추가될 수도 있고 안 될 수도 있기에 암호화 데이터의 max_length를 정확하게 예측하기 어려움
    • 이 함수는 평문 길이가 암호화되었을 때 가질 수 있는 최대 길이를 계산
  2. 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