on
django model 동적으로 생성하기
Django에서 Model을 생성할 때, 동일한 스키마를 가진 테이블을 2개 이상 생성하는 것을 시도해보게 되었다.
은행의 거래 내역을 저장하는 테이블을 설계하는데, 한명이 하루에 5개의 거래내역을 생성한다고 가정하였을 때 1년에 1825개 (5*365), 10년에 18250개의 레코드가 생성된다. 사용자가 총 1,000,000명이라고 가정한다면 레코드가 총 18,250,000,000개 (182억 5천만개)가 된다.
실제로 백만명이 이렇게 쓰지는 않겠지만.. 하나의 테이블에 모두 저장하는 것 보다 여러 테이블에 나누어 저장하는 것이 좋을 것 같았다. (계좌별로 hash값을 생성하여, hash값에 매핑시켜놓은 거래내역 테이블에 사용자의 거래 내역을 저장하려고 한다.)
여러 테이블에 나누어 거래내역을 저장하는 것이 옳은 것인지는 아직 모르겠지만, 우선 Django Model 을 생성해보자.
Transaction(거래내역) Table
class AccountTransaction(models.Model):
class Meta:
abstract = True
account_num = models.ForeignKey('Account', on_delete=models.CASCADE)
tran_amt = models.PositiveBigIntegerField()
tran_type = models.CharField(max_length=10, null=False, blank=False)
tran_detail = models.CharField(max_length=100, null=True, default="")
tran_time = models.DateTimeField(auto_now_add=True)
account_num 은 Account Table을 참조하는 왜래키이고, 기타 필드는 위와 같다.
동일한 구조를 가진 테이블을 생성해보려고 하니
class AccountTransaction1(models.Model):
class Meta:
db_table = "account_transaction_1"
account_num = models.ForeignKey('Account', on_delete=models.CASCADE)
tran_amt = models.PositiveBigIntegerField()
tran_type = models.CharField(max_length=10, null=False, blank=False)
tran_detail = models.CharField(max_length=100, null=True, default="")
tran_time = models.DateTimeField(auto_now_add=True)
class AccountTransaction2(models.Model):
class Meta:
db_table = "account_transaction_2"
account_num = models.ForeignKey('Account', on_delete=models.CASCADE)
tran_amt = models.PositiveBigIntegerField()
tran_type = models.CharField(max_length=10, null=False, blank=False)
tran_detail = models.CharField(max_length=100, null=True, default="")
tran_time = models.DateTimeField(auto_now_add=True)
이렇게 생성해서는 영 마음에 들지 않는다. 테이블을 10개를 생성한다면 10개의 클래스를 각각 작성해야한다.
상속을 이용하여 아래와 같이 코드를 수정해보았다.
class AccountTransaction(models.Model):
class Meta:
abstract = True
account_num = models.ForeignKey('Account', on_delete=models.CASCADE)
tran_amt = models.PositiveBigIntegerField()
tran_type = models.CharField(max_length=10, null=False, blank=False)
tran_detail = models.CharField(max_length=100, null=True, default="")
tran_time = models.DateTimeField(auto_now_add=True)
class AccountTransaction1(AccountTransaction):
class Meta:
db_table = "account_transaction_1"
class AccountTransaction2(AccountTransaction):
class Meta:
db_table = "account_transaction_2"
For 루프를 이용하여 지정된 숫자만큼 테이블을 자동으로 생성을 하기위해서 코드를 변경하였다.
class AccountTransaction(models.Model):
class Meta:
abstract = True
account_num = models.ForeignKey('Account', on_delete=models.CASCADE)
tran_amt = models.PositiveBigIntegerField()
tran_type = models.CharField(max_length=10, null=False, blank=False)
tran_detail = models.CharField(max_length=100, null=True, default="")
tran_time = models.DateTimeField(auto_now_add=True)
def create_account_transaction(val):
class Model(AccountTransaction):
class Meta:
db_table = "account_transaction_{}".format(val)
return Model
tables = list()
for i in range(10):
tables.append(create_account_transaction(i))
이 상태에서 makemigrations을 실행해보면..
bank\migrations\0001_initial.py
- Create model Model
class Migration(migrations.Migration):
dependencies = [
('django_server', '0001_auto_20221017_1430'),
]
operations = [
migrations.CreateModel(
name='Model',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tran_amt', models.PositiveBigIntegerField()),
('tran_type', models.CharField(max_length=10)),
('tran_detail', models.CharField(default='', max_length=100, null=True)),
('tran_time', models.DateTimeField(auto_now_add=True)),
],
options={
'db_table': 'account_transaction_9',
},
),
]
AccountTransaction
를 상속받아 생성되는 클래스는 Model 이라는 model 1개 뿐이다.. Django에서 개별 클래스 모델을 생성하고 Meta class 데이터를 다르게 지정하더라도, 클래스명이 같으면 같은 model로 인식을 한다
.
migrations.CreateModel
클래스 내 options
의 db_table
값을 보면, 가장 마지막으로 생성된 클래스의 db_table
값이 적용되어있다.
CreateModel
의 name
필드와 options
필드 내 db_Table
값은 키-값과 같은 역할이 되는데, name
을 동일하게 설정하고 db_table
값을 다르게하거나, db_table
을 동일하게 설정하고 name 값을 다르게 할 수 없다.
서로 다른 클래스명을 가지는 클래스를 생성하여야하는데, globals
를 이용하여 해결하였다.
for i in range(10):
model_name = f'account_transaction_{i:02}'
globals()[model_name] = type(model_name, (AccountTransaction, ), {'__module__': AccountTransaction.__module__})
makemigrations을 실행해보면 10개 테이블이 생성된다!
bank\migrations\0001_initial.py
- Create model account_transaction_09
- Create model account_transaction_08
- Create model account_transaction_07
- Create model account_transaction_06
- Create model account_transaction_05
- Create model account_transaction_04
- Create model account_transaction_03
- Create model account_transaction_02
- Create model account_transaction_01
- Create model account_transaction_00
globals
globals
는 전역변수를, locals
는 지역변수의 값들을 dictionary 형태로 return 하는데, dictionary와 같은 방식으로 값을 생성 할 수 있다. 선언된 변수, 정의된 클래스 모두 globals
에서 확인 가능하다.
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
>>> a = 1
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'a': 1}
>>> type(globals()['a'])
<class 'int'>
>>> class Test:
... pass
...
>>> globals()['Test']
<class '__main__.Test'>
# a.py
globals()["value"] = 1
# b.py
print(value)
너무 당연한 것일 수 있지만. 동일 파이썬 패키지내에 a.py, b.py가 위와 같을 때 b.py를 실행하면 value라는 변수가 없다고 에러가 발생한다. b.py에서 value를 사용하려면 from a import value
를 추가 해주어야 한다. 당연하지만 globals
는 동일 파일 내에 전역변수를 생성한다.