웹프로그래밍/Django

[Django] ORM Cookbook, 모델을 정의하는 방법

ssung.k 2020. 8. 28. 21:20

해당 포스팅은 Django ORM CookBook 을 공부하며 새로 알게된 사실이나 더 나아가 추가적으로 같이 알면 좋을 내용을 정리하고 있습니다.

 

1. 자기 참조 외래 키

자기 자신을 참조하는 자기 참조 외래 키를 정의할 수 있습니다.

class Employee(models.Model):
    manager = models.ForeignKey('self', on_delete=models.CASCADE)
class Employee(models.Model):
    manager = models.ForeignKey("Employee", on_delete=models.CASCADE)

 

 

2. 기존 DB를 django Model로 옮기기

Django에서 models.py를 통해 DB를 생성할 수 도 있지만 경우에 따라 기존의 DB를 사용해야 할 수 있습니다.

이 때 데이터베이스를 분석하여 이에 맞게 모델을 생성할 수 있습니다.

python manage.py inspectdb

 

 

3. 데이터베이스 View

데이터베이스에는 View라는 개념이 존재합니다.

DB 내에서 조회할 수 있도록 질의문으로 정의된 객체이며 데이터를 물리적으로 저장하는 것은 아니지만 테이블과 같이 조회할 수 있습니다.

이를 통해 필요한 정보만 쉽게 조회 가능한 인터페이스를 만들 수 있습니다.

 

SQL 질의문으로 View를 정의한 후 이를 django 내에서 조회해보겠습니다.

create view temp_user as
select id, nickname
from auth_user;

 

django 내에서는 Meta 클래스를 조작하여 View를 조작할 수 있습니다.

class TempUser(models.Model):
    nickname = models.CharField(max_length=100)

    class Meta:
        managed = False
        db_table = "temp_user"

 

 

7. 범용모델 정의

Django-한-모델이-여러-개의-모델과-관계를-맺어야하는-순간-contenttypes-framework

 

 

8. 테이블 이름 정의

django는 기본적으로 appname_tablename 으로 데이터베이스의 테이블 이름을 정의합니다.

따라서 여태 예제들에서 SQL 질의문을 보면 위와 같은 형식인걸 확인할 수 있습니다.

테이블의 이름을 직접 정의하기 위해서는 Meta 클래스를 조작합니다.

class TempUser(models.Model):
    nickname = models.CharField(max_length=100)
    # 생략
    class Meta:
        db_table = "temp_user"

 

9. 열 이름 정의

DB table의 열 이름은 Django Model 클래스의 필드 이름이 디폴트로 사용됩니다.

이 때 db_column 속성을 사용하여 열 이름을 정의할 수 있습니다.

class TempUser(models.Model):
    nickname = models.CharField(max_length=100, db_column='column1')
    # 생략
    class Meta:
        db_table = "temp_user"

 

 

10. Null VS Blank

Django-Model-Field의-Null-vs-Blank

 

 

11. UUID를 Primary Key로 사용

Django에서 기본적으로 Model을 만들 경우 양의 정수를 값으로 가지는 ID 필드가 정의 됩니다.

이를 UUID로 대체하고 싶으면 UUIDField 를 사용합니다.

import uuid
from django.db import models

class TempUser(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    nickname = models.CharField(max_length=100)
TempUser.objects.first().id
# 3cd2b4b0c36f43488a93b3bb72029f46

 

 

12. SlugField 사용

path Variable로 객체를 구별할 때 id 값을 사용할 수 도 있지만 string type의 문자열로 구별해야할 경우도 있습니다.

/post/12 # id로 구분
/post/my first posting # title로 구분

 

하지만 URL에는 공백이 들어갈 수 없으므로 이는 %20로 치환됩니다.

/post/my%20first%20posting

 

이는 보기 좋지 않으므로 SlugField 를 사용하면 보기 좋게 표현할 수 있습니다.

이는 모든 문자를 소문자화 하고 공백은 - 로 표현해줍니다.

/post/my-first-posting

 

model 에서 이를 적용하기 위해서는 다음과 같습니다.

from django.utils.text import slugify


class Post(models.Model):
    title = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super(Post, self).save(*args, **kwargs)

 

post = Post.objects.create(title='my first posting')
post.slug
# my-first-posting

 

 

13. 여러 개의 데이터베이스 사용

Django에서 하나의 프로젝트에 여러 개의 DB를 사용할 수 있습니다.

현재 Django project에는 user_data , customer_data 라는 두 개의 app이 존재합니다.

# settings.py

DATABASE_ROUTERS = ['path.to.DemoRouter']
DATABASE_APPS_MAPPING = {'user_data': 'users_db',
                        'customer_data':'customers_db'}

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    },
    'users_db': {
        'NAME': 'user_data',
        'ENGINE': 'django.db.backends.postgresql',
        'USER': 'postgres_user',
        'PASSWORD': 'password'
    },
    'customers_db': {
        'NAME': 'customer_data',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_cust',
        'PASSWORD': 'root'
    }
}
  • DATABASE_ROUTERS

    database router의 경로를 기재합니다. database router에 대해서는 아래에서 다시 알아보겠습니다.

  • DATABASE_APPS_MAPPING

    딕셔너리 형태로, app과 DB를 매핑시킵니다.

    위와 같은 경우, user_data 앱은 users_db 에 해당하는 postgresql에 저장되고 customer_data 앱은 customers_db에 해당하는 mysql에 저장됩니다.

  • DATABASES

    딕셔너리 형태로 사용할 데이터베이스들을 정의합니다.

     

이제 router를 알아봅시다.

router를 통해 DB를 특정하지 않은 경우 위 DATABASES에서 정의한 default 에 해당하는 DB에 중계됩니다.

class DemoRouter:
    """
    user_data 앱의 모델에서 수행되는 모든 데이터베이스 연산을 제어하는 중계기
    """
    def db_for_read(self, model, **hints):
        """
        user_data 앱의 모델을 조회하는 경우 users_db로 중계한다.
        """
        if model._meta.app_label == 'user_data':
            return 'users_db'
        return None

    def db_for_write(self, model, **hints):
        """
        user_data 앱의 모델을 기록하는 경우 users_db로 중계한다.
        """
        if model._meta.app_label == 'user_data':
            return 'users_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        """
        user_data 앱의 모델과 관련된 관계 접근을 허용한다.
        """
        if obj1._meta.app_label == 'user_data' or \
           obj2._meta.app_label == 'user_data':
           return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        user_data 앱의 모델에 대응하는 표가 users_db 데이터베이스에만 생성되도록 한다.
        """
        if app_label == 'user_data':
            return db == 'users_db'
        return None

 

여러 개의 데이터베이스를 사용할 때 특정 데이터베이스에서 대해 마이그레이션 할 수 있습니다.

python manage.py migrate --database=users_db