Django ORM이 없었더라면 난 여기까지 오지 못했다.
먼저, Django ORM은 엄청난 기능이라고 생각한다.
우리가 DB에 대해 자세히 몰라도 어느정도 수준까지 서비스를 운영할 수 있는 환경을 제공한다.
서비스가 커짐에 따라서 실제 ORM을 걷어내고 직접 데이터베이스 쿼리를 튜닝해도 될 것이다.
소규모의 개발팀이라면 많은 것들을 완벽하게 해낼 수 없기 때문에 고객과 직접적으로 맞닿아 있는 부분에 신경을 쓰고, 나머지 부분들은 범용적인 서비스를 통해서 조금은 초기 비용이 들더라도 그렇게 사용하는 것이 낫다고 생각한다.
이제 makemigrations과 migrate의 차이에 대해 알아보고자 한다.
자 모든 것들은 원리에 대한 이해가 중요하다.
ORM은 앞서 말한 것처럼 DB를 추상화하여 제어할 수 있는 기능이라고 생각하면된다.
추상화 했더라도 끝에서는 테이블 생성, 컬럼 생성, 삭제 등이 이뤄져야 한다.
즉 makemigrations은 DB 테이블 및 컬럼의 표현인 모델의 수정이 있을 때 수정 사항을 마이그레이션 해주기 위한 추상화된 파일을 만드는 것이다.
1. makemigrations
-
모델에 변경사항이 있으면 변경 사항을 데이터 베이스에 실제로 적용하기 위한 절차를 담고 있는 파일.
-
하지만 makemigrations 파일만으로 마이그레이션이 불가능한 경우도 있음. 이부분은 실제 서버를 운영할 때 매우 중요한 부분이며 아래에서 상세하게 다루겠다.
2. migrate
-
django가 생성한 migration 파일을 실제 database에 적용하는 명령.
-
migrate를 실행하면 migrations 파일의 Migration class에 정의된 절차에 따라 마이그레이션이 진행.
마이그레이션 과정에서 에러가 나는 대표적 사례
1. 이미 많은 row가 추가된 상황에서 새로운 컬럼을 추가하는 경우
- 이 경우 default 값이 있다면 다행이나 default값이 없다면 default 값을 어떻게 넣어야할지 묻는 창이 나올 것이다. 원하는 값을 입력해주면 해결이 가능하다. 당연히 없던 컬럼이 생성되면 기존에 있던 row에 어떤 값을 넣을 것인지 물어보는것이 당연하다.
2. 데이터 타입이 바뀌는 경우
-
예를들면 char 모델 필드를 char 모델 필드이지만 choices를 활용하여 그 값을 제한하려고 하는 경우 기존에 데이터가 있엇다면 해당 데이터를 char 타입의 어떤 값으로 바꿔야할지를 고민해야할 것 이다. 이것은 1번보다 조금 더 복잡할 수 있다.
-
예를들면 나는 회원가입 시 은행 정보를 선택적으로 입력하게 해뒀다. django에서 charfield는 none이 없기 때문에 '' 이와같이 빈값으로 들어가있는데 비워둔 사람은 그대로 빈값을 넣어두면 되지만, 그게 아닌 사람들은 수기로 입력해뒀던 데이터를 제대로된 값으로 변환해주는 파이썬 코드를 짜야할 것이다.
- 나는 결과적으로 은행명으로 저장된 데이터를 향후 계좌실명인증에 활용하기 위해서 표준화된 은행코드로 저장하길 원했다.
실제 DB에 저장된 데이터를 보여주겠다.
<QuerySet \[('우리은행',), ('국민',), ('',), ('',), ('',), ('',), ('',), ('',), ('기업은행',), ('',), ('',), ('',), ('',), ('',), ('',), ('',), ('',), ('',), ('',), ('',), '...(remaining elements truncated)...'\]>
현재 DB에는 이런식으로 저장되어있다
뒷부분이 짤려서 좀 더 자세히 보기 위해서for x in Profile.objects.all().values_list('bank_name'): print(x)
출력 결과는('우리은행',) ('국민',) ('',) ('',) ('',) ('',) ('',) ('',) ('기업은행',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('',) ('국민은행',) ('',) ('',) ('',) ('',)
역시나 국민은행을 어떤 회원은 국민으로 입력한 경우도 있었다.
이처럼 데이터가 이와 같다면 국민 이라는 값과 국민은행이라는 값 모두 같은 은행코드로 바뀌어야할 것이다.
위와같이 입력한 데이터가 아주 많다면 국민 = 국민은행을 같이볼 수 있는 예외적 코드를 추가해서 일괄적으로 변경하는 방법이 좋을것이고 데이터가 별로 없으면 추천하지는 않지만 수작업으로 국민 => 국민은행으로 먼저 변경 후 마이그레이션 하는 것도 하나의 방법이다.
BANK_CATEGORY = ( ('', ''), ('002', _('산업은행')), ('032', _('부산은행')), ('003', _('기업은행')), ('034', _('광주은행')), ('004', _('국민은행')), ('035', _('제주은행')), ('007', _('수협')), ('037', _('전북은행')), ('011', _('농협은행')), ('039', _('경남은행')), ('012', _('지역농축협')), ('045', _('새마을금고')), ('020', _('우리은행')), ('048', _('신용협동조합')), ('023', _('SC제일은행')), ('050', _('상호저축은행')), ('027', _('한국씨티은행')), ('064', _('산림조합')), ('081', _('KEB하나은행')), ('071', _('우체국')), ('088', _('신한은행')), ('089', _('K뱅크')), ('031', _('대구은행')), ('090', _('카카오뱅크')), )
대표적인 국내 은행 코드는 위와 같다.
빈값은 그냥 빈값으로 출력되도록 해뒀다.
이제 기존의 charfield에 BANK_CATEGORY를 적용해주면된다.
bank_name = models.CharField(max_length=3, choices=BANK_CATEGORY, blank=True)
그후 python manage.py makemigrations
을 실행하면,
migration 파일이 생성된다.
하지만 생성된 migration 파일을 적용하려고 하면 당연히 에러가 나거나 마이그레이션이 되더라도 정상적인 값이 출력되지 않을 것이다.
따라서 우리는 operation 코드를 추가로 작성해줘야한다.
class Migration(migrations.Migration):
dependencies = [
('profiles', '0009_auto_20210226_1102'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='bank_name',
field=models.CharField(blank=True, choices=[('', ''), ('002', '산업은행'), ('032', '부산은행'), ('003', '기업은행'), ('034', '광주은행'), ('004', '국민은행'), ('035', '제주은행'), ('007', '수협'), ('037', '전북은행'), ('011', '농협은행'), ('039', '경남은행'), ('012', '지역농축협'), ('045', '새마을금고'), ('020', '우리은행'), ('048', '신용협동조합'), ('023', 'SC제일은행'), ('050', '상호저축은행'), ('027', '한국씨티은행'), ('064', '산림조합'), ('081', 'KEB하나은행'), ('071', '우체국'), ('088', '신한은행'), ('089', 'K뱅크'), ('031', '대구은행'), ('090', '카카오뱅크')], max_length=3),
),
]
`
위와 같이 생성된 마이그레이션 클래스에서 operation 코드를 추가로 작성해줘야한다.
operations 내에서 마이그레이션이 진행되기 전이나 후에 RunPython을 통해서 사전 작업을 진행할 수 있다.
예를들면 우리는 국민은행을 004로 반영해주는 코드를 넣을 수 있을것이다.
대략 코드를 짜보면
def change_bankname_to_bankcode(apps, schema_editor):
Profile = apps.get_model('profiles', 'Profile')
bank_category_dict = dict((y, x) for x, y in Profile.BANK_CATEGORY)
for profile in Profile.objects.all():
profile.bank_name = bank_category_dict[profile.bank_name]
profile.save()
class Migration(migrations.Migration):
dependencies = [
('profiles', '0009_auto_20210226_1102'),
]
operations = [
migrations.RunPython(change_bankname_to_bankcode)
migrations.AlterField(
model_name='profile',
name='bank_name',
field=models.CharField(blank=True, choices=[('', ''), ('002', '산업은행'), ('032', '부산은행'), ('003', '기업은행'), ('034', '광주은행'), ('004', '국민은행'), ('035', '제주은행'), ('007', '수협'), ('037', '전북은행'), ('011', '농협은행'), ('039', '경남은행'), ('012', '지역농축협'), ('045', '새마을금고'), ('020', '우리은행'), ('048', '신용협동조합'), ('023', 'SC제일은행'), ('050', '상호저축은행'), ('027', '한국씨티은행'), ('064', '산림조합'), ('081', 'KEB하나은행'), ('071', '우체국'), ('088', '신한은행'), ('089', 'K뱅크'), ('031', '대구은행'), ('090', '카카오뱅크')], max_length=3),
),
]
이런식이 될거다 테스트를 위해서 shell에서 시뮬레이션 해보면
for profile in Profile.objects.all():
print(profile.bank_name, ':', bank_category_dict[profile.bank_name])
우리은행 : 020
국민은행 : 004
:
:
:
:
:
:
기업은행 : 003
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
:
국민은행 : 004
:
:
:
:
위와 같이 정삭적으로 은행코드가 적용되어 나온다.
이제 python manage.py migrate
를 실행해주면 된다.
migration전에 assertequal코드를 통해서 에러가 있으면 rollback할 수 있도록 해주면 더 좋다.
정상적으로 처리가 끝나면
Running migrations:
Applying profiles.0010_auto_20210226_1102... OK
이와 같은 메시지를 얻게되고 제대로 결과값이 들어갔는지 테스트해보자.
for profile in Profile.objects.all():
... print(profile.bank_name)
020
004
003
004
기존 은행명에서 코드로 정상적으로 값이 들어간것을 확인할 수 있다.
docs.djangoproject.com/en/3.1/ref/migration-operations/#django.db.migrations.operations.RunPython
Runpython은 위의 공식 문서를 참고하면된다.
이상 django migration을 기본적으로 생성되는 파일로 적용하기 어려울 때 customize하는 방법에 대해서 알아보았다.
'IT > Django' 카테고리의 다른 글
django timezone과 시간 안맞는 경우 (0) | 2021.03.29 |
---|---|
django datetime Asia/Seoul LMT+8:28:00 STD Why not KST+9:00 (2) | 2021.03.17 |
BrowsableAPI AttributeError 해결방법. (0) | 2021.03.13 |
SameSite=Lax set-cookie was blocked because it was not sent over a secure connection 보안이슈 (0) | 2021.02.26 |
DJANGO 보안의 중요성 - GPON 공격 (2) | 2021.02.16 |