본문 바로가기
Web/Django

[Django] 데이터베이스 모델링

by DUSTIN KANG 2023. 11. 18.

먼저, Django을 사용해봤다면 Migration이라는 단어를 잘 알고 있을 것이다.

Django은 기본적으로 ORM 기반으로 작동되는 웹 프레임워크이기 때문에 개발자가 SQL를 입력하지 않아도 프로그래밍 상에서 데이터를 입력할 수 있는 쉬운 구조로 이루어져 있다.

Django ORM

Django ORM은 Object Relational Mapping의 약자로 객체와 DB를 매핑 시켜주는 역할을 한다.

굳이 SQL을 작성할 필요 없이 ORM에서 제공하는 메서드를 사용해 데이터베이스의 데이터를 쉽게 접근할 수 있다.

그렇다고 편하다고 해서 이점만 있는 것은 아니다. Django ORM은 은근 생각보다 똑똑하지 않는데.. 짧게 작성했을 땐 편하지만 복잡한 쿼리를 작성할 땐 생각했던 SQL 쿼리보다 복잡하게 작성되기 때문에 성능을 저하시킬 수 있다는 단점도 있다. 이럴 땐 쿼리 최적화를 진행해야한다. 

Django에서는 마이그레이션 도구를 지원한다.

데이터베이스와 소통을 하려면 `python manage.py shell`을 이용해서 데이터에 접근하는 방법이 있다. 우리는 이 쉘을 Django Shell이라고 부른다. 일반 Shell을 이용하는 방법도 있지만 `shell_plus`를 사용하면 조금 더 향상된 버전으로 데이터를 접근할 수 있다. 반드시 IPython을 설치해야 한다. 자세한 내용은 공식문서를 참고하면 잘 나와있다. Django Shell을 이용하는 방법은  Query Set 포스팅에서 볼 수 있다. 

pip install Ipython & django-extensions
python manage.py shell_plus --ipython

마이그레이션

How to Migrate

모델을 생성하거나 변경하게되면 마이그레이션 작업을 거치게된다.

마이그레이션 파일을 만드는 명령어, 변경 내역 파일을 작성하는 명령어가 `makemigrations`이고 이를 DB에 매핑하는 작업을 `migrate`이라고 한다.

혹은 아래처럼, `sqlmigrate`를 사용하면 어떤 SQL이 수행되었는지 확인할 수 있다. 실제로, 현업에서는 운영서버에 배포를 할 때 migration이 필요한 경우sqlmigrate를 통해 적용할 sql을 확인하고나서 실제로 데이터베이스 내에서 수행한다고 한다.

sqlmigrate을 사용했을 때 어떻게 쿼리가 동작하는지 확인할 수 있다.

 

# 장고 한테 모델을 바꾸거나 새로 만들겠다(변경 내역이 있다)는 내용의 파일을 만들어줍니다.
python manage.py makemigrations

# 만들어낸 파일과 데이터베이스 내 장고테이블과 비교해 적용이 됬는지 확인합니다. 이후 새로 데이터베이스 테이블을 만들어냅니다.
python manage.py migrate

# 장고 쉘을 통해서 데이터를 확인할 수 있습니다.
# 그런데 shell 보다는 extensions의 shell_plus를 추천한다. import를 사용할 필요도 없고 tab키로 자동완성이 가능해서..
python manage.py shell

# from app.models import Task
# Choice.objects.all() # ORM 작성

 

마이그레이션 파라미터

  • `--dry-run` : 실제로 마이그레이션 파일이 생성되지 않고 어떤 마이그레이션이 적용될 지 보여준다.
  • `--name -n` : 마이그레이션 파일의 이름을 변경할 수 있다.
  • ` --merge` : 마이그레이션 충돌을 수정한다.
  • `--fake` :  migrate 할 때 사용되며 마이그레이션을 표시하지만 실제로 SQL에 변화를 주지 않는다.
  • `--plan` : migrate 할 때 사용되며 마이그레이션 작업들을 보여준다. 

마이그레이션 - 공식 문서

프로덕션 마이그레이션 전략

  • 만약 기존에 있던 필드나 모델 명을 바꿔야한다면 어떻게 하면 좋을까?
    • 그냥 막 바꿨다가는 운영 서버(Production)에 혼란이 올 수 있다. 이를 방지하기위해 새로운 이름의 필드를 생성해서 기존 필드의 값을 복사한 다음, 나중에 안정화가 되었다 싶을 때 기존 필드를 삭제하는 방법도 나쁘지 않을 것 같다.  
  • ORM을 반드시 사용해야 할까?
    • 마이그레이션 순서를 맞춰야한다는 단점 때문에 SQL 상에서 직접 DDL을 작성하는 경우도 있다.
    • 마이그레이션을 사용해 쿼리하는게 반드시 옳은 건 아니라는 게 최적화를 통해 잘... 알 수 있다.
  • 마이그레이션 파일은 지우면 안되는지?
    • 운영서버(Production) DB에 이미 반영된 마이그레이션 파일의 경우 조심히 다뤄야 한다.
    • 만약 마이그레이션을 파일을 지우지 않게 되면 당연히 git conflict가 발생하게 된다.
      • 삭제된 테이블에 컬럼을 추가하려는 경우 : 삭제의 변경점을 무시하거나 컬럼 추가의 변경점을 무시하고 dev 브랜치에 merge하면 된다. 
      • 양쪽에서 같은 컬럼을 추가하려는 경우 : 하나의 마이그레이션을 롤백하고 파일을 삭제한다.  그 다음 변경사항을 커밋후 병합하면 끝!
      • 참고 자료 https://velog.io/@hanqyu/django-migration-파일은-커밋되어야-할까

💡 Django에서는 데이터베이스를 어떻게 사용하는가?
settings.py을 보시면 아래와 같이 데이터베이스를 설정할 수 있는 딕셔너리를 볼 수 있습니다. 기본적으로 sqlite3에 저장하고 있다는 것을 확인할 수 있습니다. 데이터베이스 설정 - 공식 문서

 

더보기

DB에 마이그레이션이 되지 않는 오류에 대한 해결

 

프로젝트 도중 DB에 마이그레이션을 하려고 했으나 제대로 마이그레이션이 되지 않는 문제가 발생했다.

마이그레이션 초기화해봤지만 결국엔 앱 내부에 `migrations` 폴더를 gitignore에 추가해서 다른 PC에서 마이그레이션을 하려고 했더니 커밋이 되지않은 파일이라 안되었던 것이다.

 

그럼 migrations 폴더를 `.gitignore`에 포함해야 하나?

 

StackOverflow↗를 보면 의견이 분분한데 개발 할 땐 .gitignore에 추가하고 배포할 땐 ignore 하지 않는 대신 makemigration을 하지 않다는 것 같다. 다른 PC에서 해당 레포를 가져와 작업할 땐 `migrations` 폴더가 먼저 있는지 확인해야할 것 같다.

 

Gitignore for a Django Project - Djangowaves.com

 

마이그레이션 초기화 방법

1. 먼저 migrations 폴더 내 init 파일을 제외한 모든 파일을 제거한다.

find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
find . -path "*/migrations/*.pyc"  -delete

 

2. 그 다음 DB와의 연결을 끊어놓거나 `db.sqlite3` 파일을 제거한다.

3. 다시 처음 마이그레이션 파일을 생성하고 마이그레이션한다.

python manage.py makemigrations
python manage.py migrate

 

만약 도중 과 `ModuleNotFoundError: No module named 'django.db.migrations.migration'`같은 오류가 발생한다면 Django 자체 /django/db/migrations/도 삭제한 것이다. 이럴 때는 Django을 다시 설치할 필요 있다.

pip install --upgrade --force-reinstall Django

 

 참고자료 : StackOverflow

 


Django Model

Django의 모델은 테이블을 설계하기 위한 클래스이다. `models.Model` 클래스를 상속받아 클래스를 생성 한후, 만들어진 테이블로 마이그리이션을 통해 DB에 데이터를 반영한다.

# app/models.py
from django.db import models

# Create your models here.

class Task(models.Model):
    title = models.CharField(max_length=200)
    details = models.TextField(blank=True, null=True)
    created_at = models.DateTimeField('Created', auto_now_add=True)
    update_at = models.DateTimeField('Updated', auto_now_add=True)
    check_completed = models.BooleanField(default=False)
    
    class Meta:
    	ordering = ['-no'] # 내림차 순
        db_table = 'mytask' # 테이블 이름을 mytask로 변경
        verbose_name = '일정' # 단수형
        verbose_name_plural = '일정들' # 복수형
    
    def __str__(self):
        return f'{self.id} - {self.title}'

 

모델을 생성할 때는 Django의 모델 클래스를 상속받아 필드를 정의할 수 있다.

💡 Django에서는 id 를 별도로 만들 필요 없습니다.
무조건 id는 쓰지마라는 것은 아닙니다. Django에서는 id 필드를 생성하지 않아도 기본적으로 id 필드가 생성되어 PK가 됩니다.

모델 필드

모델 필드에는 CharField, IntegerField 말고도 다양한 필드를 사용할 수 있다.

숫자형 `IntegerField` `FloatField`, `PositiveIntegerField`
문자형 `CharField` `EmailField` `TextField` `URLField`
날짜형 `DateTimeField` `DateField` `TimeField`
기타형 `BooleanField` `FileField` `ImageField` `SlugField`
참조 키 `ForeignKey`
  • SlugField : 문자나 숫자, 밑줄 하이픈만 포함하는 짧은 레이블을 의미한다. 일반적으로 URL에 사용한다.
  • ImageField : 이미지를 업로드할 땐 반드시 `Pillow`라이브러리를 설치해야한다. 아래 오류를 방지하기 위해서이다.

ImageField를 사용할 땐 반드시 Pilow라이브러리를 설치하자.

 

필드 속성

만약 모델을 한번 생성한 후에 새로운 필드를 생성해야하는 경우가 있다. 이럴 땐, 해당 필드에 `default` 값을 넣어주거나 아니면 `null=True`를 설정해서 이전에 만들어놓은 데이터에 대해 정리를 해주면 된다. 

  • primary_key : 모델의 기본 키 값으로 사용할 필드를 나타낸다. (테이블의 식별자 역할)
  • max_length : 문자열 필드의 최대 길이를 나타낸다.
  • blank, null : 공백허용 혹은 미존재 값을 나타낸다. (Boolean)
  • default : 모델에 신규 데이터 입력시 입력하지 않는 경우 지정할 기본값을 의미한다.
  • uploade_to : 파일이나 이미지 필드에 사용하고 업로드 시 경로 지정 용도 사용한다.

 

메타 클래스(Meta)

장고 모델 클래스 내에서 Meta(메타)라고 하는 내부 클래스를 통해 어떻게 취급할 것인지 설정할 수 있다.

  • ordering : 리스트의 정렬 순서를 의미합니다. 하이픈을 사용해서 표시하면 내림차순(최신순)으로 정렬된다.
  • db_table : 모델 데이터베이스의 테이블 이름을 변경합니다. 변경 후 반드시 마이그레이션을 해야한다.
  • verbose_name, verbose_name_plural : 관리자 페이지(Admin)에서 모델을 어떻게 표기할 것인지를 나타낸다.
  • __str__ : 이것도 관리자 페이지에서 각 데이터마다  어떻게 표기할 것인지 보여준다.
  • abstract : 메타클래스에 `abstract=True`를 사용하면 공통된 필드를 하나의 모델로 재사용할 수 있다. 상속받은 모델 클래스는 재사용할 필드가 있는 클래스를 상속하면 된다. 상속할 클래스는 DB에 저장되진 않는다. (하단 추상 베이스 클래스 부분에서 자세하게 다룰 예정이다.)

관리자 페이지

 

💡 이외 다양한 필드 옵션을 정의하자
공식문서에 보면 숫자나 문자열을 활용해 선택 필드를 생성할 수 있다. 관리자(Admin)페이지에서는 드롭다운 메뉴로 보여진다.
    class LanguageChoices(models.TextChoices):
        KR = ("kr", "Korean")
        EN = ("en", "English")

    class CurrencyChoices(models.TextChoices):
        WON = "won", "Korean Won"
        USD = "usd", "Dollar" # 반드시 괄호가 필요없다
        
    language = models.CharField(max_length=2, choices=LanguageChoices,)
    currency = models.CharField(max_length=5, choices=CurrencyChoices,)​

 

모델 관리자 페이지 등록

데이터 모델을 생성하였으면 관리자용 페이지에도 적용할 수 있게끔 아래 코드를 작성해준다.

관리자용 페이지는 Django에서 UI로 데이터를 관리할 수 있는 관리자용 페이지다.

`python manage.py createsuperuser`를 입력하여 username, Email, password를 입력하면 admin페이지에서 모델들을 확인할 수 있다.

from django,contrib import admin
from .models import Task

#admin.site.register(Task)

@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
	list_display = [
    	"title",
        "details"
    ]
    
    list_filter = (
    	"title",
        "details",
    )

모델 클래스 상속

Django에서 모델을 상속하는 것은 Python 클래스 상속과 유사하다.

상속을 제공하는 스타일은 아래 3가지가 있다.

추상 베이스 클래스(Abstract Base Class)

부모 클래스를 사용해서 자식 모델이 하나하나 같은 정보를 작성하지 않게끔, 공통된 정보를 여러 모델에 넣을 때 사용한다.

아래 코드를 보면, Memo 모델과 Picture 모델에 시간 정보를 넣고 싶은데 중복이 되기 때문에 TimeStamp라는 모델을 생성하여 중복없이 상속할 수 있다. 간단히 추상 클래스 모델 Meta Class에 `abstract = True`를 추가하면 된다.

하위 클래스(Memo, Picture)에서 Meta 클래스를 사용하고자 할때 반드시 상위 클래스의 메타 클래스를 상속받아야 한다.

 

여기서 중요한 점은 자식 클래스 이름과 같은 추상 클래스의 필드를 가질 수 없으며 추상 클래스는 일반 모델로 사용할 수 없다.

 

멀티테이블 상속 (Multi Table Inheritance)

만약 TimeStamp 처럼 추상화를 위한 모델이 아니라 각자 역할을 하는 모델이라면 어떨까?

이럴 땐, 멀티 테이블 상속 방식을 사용한다. 각 모델이 자체적으로 모델인 경우 자식 부모 모델의 연결을 걸어놓으면 된다. 

 

from django.db import models 

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)
    
class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

 

`Place` 모델에 필드를 생성한다고 해서 `Restaurant` 모델에도 생성되진 않는다. 반면, `Restaurant` 모델에 이름과 주소를 설정해 생성하면 `Place` 모델에도 얻을 수 있다. 

왜 이렇게 두 테이블을 연결할 수 있었던 이유는 django에서 자동적으로 OneToOneField 관계를 적용해주기 때문이다.

만약, 부모 모델의 필드를 제어하고 싶으면 부모 모델과 OneToOneField를 갖는 필드를 생성하여 `parent_link=True`옵션을 설정해주면 된다.

 

 

 

프록시 모델(Proxy Model)

스키마를 그대로 사용하지만 동작을 변경하거나 추가 메소드를 제공할 때 사용하는 방식이다.

약간 대리인 같은 역할을 한다.

아래 코드를 보면 MyPerson이라는 클래스에서 proxy를 설정했더니 MyPerson 클래스에서 데이터를 가져올 수 있었다.

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    age = models.IntegerField()
    
    class Meta:
      ordering = ['id']

class NameOrderedPerson(Person):
    class Meta:
        proxy = True
        ordering = ['first_name', 'last_name']

    def full_name(self): 
        return f"{self.first_name} {self.last_name}"

 

`NameOrderedPerson`을 인스턴스로 생성하여 full_name 함수를 사용할 수 있다.

 


☕️ 마치며

설명이 부족하거나 이해하기 어렵거나 잘못된 부분이 있으면 부담없이 댓글로 남겨주시면 감사하겠습니다.

 

https://dongwooblog.tistory.com/137

 

Django Query Set

먼저 이전에 예시 프로젝트를 생성하여 가상의 음원플랫폼으로 Spotify Developer 사이트에서 앨범, 트랙, 아티스트 API 요청으로 데이터를 가져와 DB에 넣었다. 쿼리셋(QuerySet) 먼저 쿼리셋은 무엇일

dongwooblog.tistory.com

 

'Web > Django' 카테고리의 다른 글

[Django] CRUD 구현  (0) 2023.11.29
User Model 커스텀 하기  (0) 2023.11.28
Static 파일 관리하기 with AWS S3 연동하기  (0) 2023.11.27
[Django] 개발환경 세팅하기  (0) 2023.11.22
[Django] 애플리케이션 요청 및 처리  (0) 2023.11.21