Django 모델을 위한 테스트 데이터를 자동으로 만들어보자!
이걸 하기 전에 custom django-admin command를 만들어보자
docs.djangoproject.com/en/3.1/howto/custom-management-commands/
아무 App 폴더에 management 폴더를 만들고 아래처럼 폴더를 구성해준다.
commands 폴더에 내가 명령어로 실행할 파일을 만들고(roomseed.py)
python manage.py를 실행하면... Command가 없다고 나옴
rooms/management/
├── __init__.py
└── commands
├── __init__.py
└── roomseed.py
python manage.py roomseed --times 50
AttributeError: module 'rooms.management.commands.roomseed' has no attribute 'Command'
roomseed.py에 아무것도 안해줬기 때문이다
class Command를 만들어주고 django에서 지원해주는 클래스 중 BaseCommand를 상속 받는다
from django.core.management.base import BaseCommand
class Command(BaseCommand):
print("hello")
BaseCommand의 정의를 살펴보면 다음과 같다
1. manage.py가 command class를 가져와서 run_from_argv() 메소드를 부른다
2. run_from_argv() 메소드는 create_parser() 메소드를 불러서 argument를 해석함
3. execute() 메소드는 해석한 argument와 함께 handle()을 부른다
그래서 handle() 메소드는 subclass의 시작 포인트로 쓰임
=> handle()에 어떤 명령을 내릴 지 적으면 됨.
class BaseCommand:
"""
The base class from which all management commands ultimately
derive.
Use this class if you want access to all of the mechanisms which
parse the command-line arguments and work out what code to call in
response; if you don't need to change any of that behavior,
consider using one of the subclasses defined in this file.
If you are interested in overriding/customizing various aspects of
the command-parsing and -execution behavior, the normal flow works
as follows:
1. ``django-admin`` or ``manage.py`` loads the command class
and calls its ``run_from_argv()`` method.
2. The ``run_from_argv()`` method calls ``create_parser()`` to get
an ``ArgumentParser`` for the arguments, parses them, performs
any environment changes requested by options like
``pythonpath``, and then calls the ``execute()`` method,
passing the parsed arguments.
3. The ``execute()`` method attempts to carry out the command by
calling the ``handle()`` method with the parsed arguments; any
output produced by ``handle()`` will be printed to standard
output and, if the command is intended to produce a block of
SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
4. If ``handle()`` or ``execute()`` raised any exception (e.g.
``CommandError``), ``run_from_argv()`` will instead print an error
message to ``stderr``.
Thus, the ``handle()`` method is typically the starting point for
subclasses; many built-in commands and command types either place
all of their logic in ``handle()``, or perform some additional
parsing work in ``handle()`` and then delegate from it to more
specialized methods as needed.
"""
def handle(self, *args, **options):
"""
The actual logic of the command. Subclasses must implement
this method.
"""
raise NotImplementedError('subclasses of BaseCommand must provide a handle() method')
옵션을 추가하고 싶으면 add_arguments() 함수를 오버라이드 해서
parser.add_argument()에 추가하면 됨.
필수 인자는 "--"없이 이름 설정해주고 옵션 인자는 이름 앞에 "--"를 붙여준다!
nargs="+" 를 추가하면 여러 개의 인자를 받을 수 있다
class Command(BaseCommand):
print("hello")
help = "This is help description"
def add_arguments(self, parser):
parser.add_argument(
"args", nargs="+", type=str, help="This is argument")
parser.add_argument(
"--times", help="How many times do you want to tell?")
def handle(self, *args, **options):
times = options.get('times')
print(args)
for t in range(int(times)):
self.stdout.write(self.style.SUCCESS("ee"))
❗ 이제 amenities Model의 테스트 데이터를 자동으로 만들어보자
-> Amenity models를 참고했을 때 데이터를 만드려면 name field만 있으면 된다
class Command(BaseCommand):
def handle(self, *args, **options):
amenities = [
"Air conditioning",
"Alarm Clock",
"Balcony",
"Bathroom",
"Bathtub",
"Free Parking",
"Free Wireless Internet",
"Freezer",
"Golf",
"Hair Dryer",
"Heating",
"Shopping Mall",
"Shower",
"Toilet",
"Towels",
"TV",
]
for a in amenities:
Amenity.objects.create(name=a)
self.stdout.write(self.style.SUCCESS("Amenities created!"))
Amenity.objects로 model의 Manager object를 불러와서 model이 갖고있는 메소드를 실행할 수 있당
docs.djangoproject.com/en/3.1/ref/models/instances/
>>> Amenity.objects
<django.db.models.manager.Manager object at 0x7f856a693370>
❗ room에 필요한 amenity와 facility를 만들었으니 user도 seed를 만들어보자~
user 테스트 데이터를 만들 땐 내용이 많으니 django-seed를 사용한당
pipenv install django_seed
seeder를 만들어서 add_entity에 테스트 데이터를 만들 모델, 생성 개수를 넣어준다.
seed에서 모든 걸 해줄 것 같지만 foreignkey로 연결된 변수는 자동으로 넣어주지 못한다!
dict를 만들어서 값을 일일이 지정해 줘야함
import random
from django.core.management.base import BaseCommand
from django_seed import Seed
from rooms import models as room_models
from users import models as user_models
class Command(BaseCommand):
help = "This command creates many rooms"
def add_arguments(self, parser):
parser.add_argument(
"--number", default=1, type=int, help="How many rooms do you want to create?")
def handle(self, *args, **options):
number = options.get("number")
seeder = Seed.seeder()
all_users = user_models.User.objects.all() # NOT Good
room_types = room_models.RoomType.objects.all()
seeder.add_entity(room_models.Room, number, {
"host": lambda x: random.choice(all_users),
"room_type": lambda x: random.choice(room_types),
"price": lambda x: random.randint(1, 300),
})
seeder.execute()
self.stdout.write(self.style.SUCCESS(f"{number} rooms created!"))
❗ room을 자동으로 만들 때 photo도 같이 만들 수 있게 해보자
seeder.execute()는 만들고 난 뒤에 class 생성자와 id를 dict로 리턴한다
{<class 'rooms.models.Room'>: [9]}
id만 필요하니까 .values()로 값만 가져오면 되는데 이중 list임
-> Django 내장 함수 중 flatten을 사용해서 단일 list로 만들어준다
Photo를 만들어야 하니까 Photo.objects.create()를 실행하는데,
Photo Models에 정의된 필드값 중 file 이름을 랜덤으로 정하면 끝!
room_models.Photo.objects.create(
caption=seeder.faker.sentence(),
file=f"room_photos/{random.randint(1,31)}.webp",
room=room
)
room마다 photo 만드는 것도 랜덤으로 돌려버렷!
import random
from django.contrib.admin.utils import flatten
from django_seed import Seed
from rooms import models as room_models
from users import models as user_models
class Command(BaseCommand):
def handle(self, *args, **options):
number = options.get("number")
seeder = Seed.seeder()
# --- #
created_rooms = seeder.execute()
created_clean = flatten(list(created_rooms.values()))
for pk in created_clean:
room = room_models.Room.objects.get(pk=pk)
for i in range(3, random.randint(5, 9)):
room_models.Photo.objects.create(
caption=seeder.faker.sentence(),
file=f"room_photos/{random.randint(1,31)}.webp",
room=room
)
print(room)
self.stdout.write(self.style.SUCCESS(f"{number} rooms created!"))
❗ room에 ManyToManyField 관계에 있는 것을 추가하려면?
-> room.<ManyToManyField name>.add() 를 사용하자
rules = room_models.HouseRule.objects.all()
for r in rules:
magic_number = random.randint(0, 15)
if magic_number % 2 == 0:
room.house_rules.add(r)
to_add가 QuerySet의 묶음이라 QuerySet 안에 있는 rooms[x] ~ rooms[y]를 넣고 싶은거니까
*로 요소에 접근해서 add()해줌
for pk in clean:
list_model = List.objects.get(pk=pk)
to_add = rooms[random.randint(0, 5):random.randint(6, 30)]
list_model.rooms.add(*to_add)
날짜를 랜덤으로 만들고 싶을 때 timedelta를 사용하면 됨
from datetime import datetime, timedelta
{
"check_in": lambda x: datetime.now() - timedelta(days=random.randint(0, 3)),
}
'개발 > AWS' 카테고리의 다른 글
AWS에 vscode 서버 만들기 (0) | 2021.03.16 |
---|---|
Django 앱 만들기 - DB에 저장된 내용을 보여주자! (0) | 2021.02.04 |
Django 앱 만들기 - model의 save와 admin의 save_model을 오버라이딩 하기 (0) | 2021.02.02 |
Django 앱 만들기 - Models method, upload image (0) | 2021.01.21 |
Django 앱 만들기 - admin 패널 꾸미기, QuerySet, UserManager (0) | 2021.01.21 |
댓글