Django 1.7 및 데이터 마이그레이션으로 초기 데이터로드


95

최근에 Django 1.6에서 1.7로 전환했고 마이그레이션을 사용하기 시작했습니다 (South는 사용하지 않았습니다).

1.7 이전 에는 (데이터베이스를 만들 때) 명령 fixture/initial_data.json으로로드 된 파일로 초기 데이터를 로드했습니다 python manage.py syncdb.

이제 마이그레이션을 사용하기 시작했으며이 동작은 더 이상 사용되지 않습니다.

응용 프로그램이 마이그레이션을 사용하는 경우 고정 장치가 자동으로로드되지 않습니다. Django 2.0의 애플리케이션에는 마이그레이션이 필요하므로이 동작은 더 이상 사용되지 않는 것으로 간주됩니다. 앱에 대한 초기 데이터를로드하려면 데이터 마이그레이션에서 수행하는 것이 좋습니다. ( https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures )

공식 문서는 내 질문은, 그래서 그것을 수행하는 방법에 대한 명확한 예를 가지고 있지 않습니다

데이터 마이그레이션을 사용하여 이러한 초기 데이터를 가져 오는 가장 좋은 방법은 무엇입니까?

  1. mymodel.create(...),에 대한 여러 호출로 Python 코드 작성
  2. Django 함수 ( 예 : 호출loaddata )를 사용하거나 작성 하여 JSON 픽스처 파일에서 데이터를로드합니다.

두 번째 옵션을 선호합니다.

Django가 이제 기본적으로 할 수있는 것처럼 보이기 때문에 South를 사용하고 싶지 않습니다.


3
또한 OP의 원래 질문에 또 다른 질문을 추가하고 싶습니다. 애플리케이션에 속하지 않는 데이터에 대해 데이터 마이그레이션을 어떻게해야합니까? 예를 들어, 누군가 사이트 프레임 워크를 사용하는 경우 사이트 데이터가 포함 된 고정 장치가 있어야합니다. 사이트 프레임 워크는 애플리케이션과 관련이 없으므로 데이터 마이그레이션을 어디에 두어야합니까? 감사 !
Serafeim

여기에서 아직 누구도 다루지 않은 중요한 점은 데이터 마이그레이션에 정의 된 데이터를 가짜 마이그레이션이있는 데이터베이스에 추가해야 할 때 발생하는 일입니다. 마이그레이션이 위조되었으므로 데이터 마이그레이션이 실행되지 않으며 직접 수행해야합니다. 이 시점에서 조명기 파일에서 loaddata를 호출 할 수도 있습니다.
hekevintran

또 다른 흥미로운 시나리오는 예를 들어 auth.Group 인스턴스를 생성하기위한 데이터 마이그레이션이 있고 나중에 시드 데이터로 생성하려는 새 그룹이있는 경우 발생합니다. 새 데이터 마이그레이션을 만들어야합니다. 그룹 시드 데이터가 여러 파일에 있으므로 성 가실 수 있습니다. 또한 마이그레이션을 재설정하려는 경우 시드 데이터를 설정하고 이식하는 데이터 마이그레이션을 찾아야합니다.
hekevintran

@Serafeim 데이터가로드되는 방식 만 변경하기 때문에 픽스쳐 대신 데이터 마이그레이션을 사용하는 경우 "타사 앱의 초기 데이터를 저장할 위치"라는 질문은 변경되지 않습니다. 나는 이와 같은 일을 위해 작은 맞춤형 앱을 사용합니다. 타사 앱이 "foo"인 경우 데이터 마이그레이션 / 수정 사항이 포함 된 간단한 앱을 "foo_integration"이라고합니다.
guettli

@guettli 예, 추가 응용 프로그램을 사용하는 것이 가장 좋은 방법입니다!
Serafeim

답변:


81

업데이트 :이 솔루션으로 인해 발생할 수있는 문제에 대해서는 아래 @GwynBleidD의 의견을 참조하고 향후 모델 변경에 더 내구성이있는 접근 방식은 아래 @Rockallite의 답변을 참조하십시오.


픽스쳐 파일이 있다고 가정합니다. <yourapp>/fixtures/initial_data.json

  1. 빈 마이그레이션을 만듭니다.

    Django 1.7에서 :

    python manage.py makemigrations --empty <yourapp>

    Django 1.8 이상에서는 이름을 제공 할 수 있습니다.

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
  2. 마이그레이션 파일 편집 <yourapp>/migrations/0002_auto_xxx.py

    2.1. Django에서 영감을 얻은 맞춤형 구현 ' loaddata(초기 답변) :

    import os
    from sys import path
    from django.core import serializers
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
    
        fixture = open(fixture_file, 'rb')
        objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
        for obj in objects:
            obj.save()
        fixture.close()
    
    def unload_fixture(apps, schema_editor):
        "Brutally deleting all entries for this model..."
    
        MyModel = apps.get_model("yourapp", "ModelName")
        MyModel.objects.all().delete()
    
    class Migration(migrations.Migration):  
    
        dependencies = [
            ('yourapp', '0001_initial'),
        ]
    
        operations = [
            migrations.RunPython(load_fixture, reverse_code=unload_fixture),
        ]

    2.2. 더 간단한 솔루션 load_fixture(@juliocesar의 제안에 따라) :

    from django.core.management import call_command
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
        call_command('loaddata', fixture_file) 

    사용자 지정 디렉터리를 사용하려는 경우 유용합니다.

    2.3. 가장 간단한 방법 :loaddata with app_label를 호출 하면 <yourapp>fixtures디렉토리 에서 조명기가 자동으로 로드 됩니다.

    from django.core.management import call_command
    
    fixture = 'initial_data'
    
    def load_fixture(apps, schema_editor):
        call_command('loaddata', fixture, app_label='yourapp') 

    을 지정하지 않으면 app_labelloaddata는 모든 앱 조명기 디렉터리 fixture에서 파일 이름 을로드하려고 시도합니다 (원하지 않을 수도 있음).

  3. 실행

    python manage.py migrate <yourapp>

1
좋아요, 맞아요 ... 또한 호출 loaddata('loaddata', fixture_filename, app_label='<yourapp>')은 앱 픽스쳐 디렉토리로 직접 이동합니다 (따라서 픽스쳐의 전체 경로를 빌드 할 필요가 없습니다)
n__o

15
이 방법을 사용하면 직렬 변환기는 models.py일부 추가 필드 또는 기타 변경 사항이있을 수있는 현재 파일의 모델 상태에서 작동합니다 . 마이그레이션을 생성 한 후 일부 변경 사항이 있으면 실패합니다 (따라서 해당 마이그레이션 후에는 스키마 마이그레이션도 생성 할 수 없습니다). 이를 수정하기 위해 serializer가 작업중인 앱 레지스트리를 첫 번째 매개 변수의 마이그레이션 기능에 제공된 레지스트리로 변경할 수 있습니다. 경로에 대한 레지스트리는에 있습니다 django.core.serializers.python.apps.
GwynBleidD

3
왜 이러는 거죠? Django가 실행 및 유지 관리가 점점 더 어려워지는 이유는 무엇입니까? 나는 이것으로 가고 싶지 않다. 나는이 문제를 해결하는 간단한 명령 줄 인터페이스를 원한다. Django는이 작업을 더 어렵지 않고 더 쉽게 만들도록되어 있습니다. :(
CpILL

1
@GwynBleidD 이것은 당신이 만들고있는 매우 중요한 요점이며,이 대답에 나타나야한다고 생각합니다. 문서데이터 마이그레이션 코드 예제 에서 주석으로 나타나는 것과 동일한 설명 입니다. app registry전역 변수를 변경하지 않고 제공된으로 serializer를 사용하는 다른 방법을 알고 있습니까 (병렬 데이터베이스 마이그레이션으로 가상 미래에 문제를 일으킬 수 있음).
Ad N

3
수락과 함께 kazoo에 대한이 답변은 내가 사람들에게 stackoverflow를 사용하지 말 것을 권장하는 이유입니다. 지금도 댓글과 일화로 #django에서 이것을 언급하는 사람들이 있습니다.
shangxiao 2017 년

50

짧은 버전

당신은해야 하지 사용하는 loaddata데이터 마이그레이션에서 직접 관리 명령을 사용합니다.

# Bad example for a data migration
from django.db import migrations
from django.core.management import call_command


def load_fixture(apps, schema_editor):
    # No, it's wrong. DON'T DO THIS!
    call_command('loaddata', 'your_data.json', app_label='yourapp')


class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(load_fixture),
    ]

긴 버전

loaddata가 이용하는 django.core.serializers.python.Deserializer마이그레이션에 기록 데이터를 역 직렬화하는 최신 모델을 사용합니다. 그것은 잘못된 행동입니다.

예를 들어 loaddata관리 명령을 사용하여 고정 장치에서 데이터를로드 하는 데이터 마이그레이션이 있고 이미 개발 환경에 적용되어 있다고 가정합니다.

나중에 해당 모델에 새 필수 필드 를 추가하기로 결정하고 이를 수행하고 업데이트 된 모델에 대해 새 마이그레이션을 수행합니다 ( ./manage.py makemigrations메시지가 표시 될 때 새 필드에 일회성 값을 제공 할 수 있음).

다음 마이그레이션을 실행하면 모든 것이 잘됩니다.

마지막으로 Django 애플리케이션 개발을 완료하고 프로덕션 서버에 배포합니다. 이제 프로덕션 환경에서 처음부터 전체 마이그레이션을 실행할 때입니다.

그러나 데이터 마이그레이션은 실패합니다 . loaddata현재 코드를 나타내는 명령 의 역 직렬화 된 모델 을 추가 한 새 필수 필드에 대해 빈 데이터로 저장할 수 없기 때문 입니다. 원래 조명기에는 필요한 데이터가 없습니다!

그러나 새 필드에 필요한 데이터로 설비를 업데이트하더라도 데이터 마이그레이션은 여전히 ​​실패합니다 . 데이터 마이그레이션이 실행 중일 때 해당 컬럼을 데이터베이스에 추가하는 다음 마이그레이션은 아직 적용되지 않습니다. 존재하지 않는 열에는 데이터를 저장할 수 없습니다!

결론 : 데이터 마이그레이션에서이loaddata명령은 모델과 데이터베이스간에 잠재적 인 불일치를 유발합니다. 당신은 확실히해야 하지 데이터 마이그레이션에 직접 사용합니다.

해결책

loaddata명령은 django.core.serializers.python._get_model기능에 의존 하여 조명기에서 해당 모델을 가져 오며, 이는 모델의 최신 버전을 반환합니다. 역사적 모델을 얻기 위해 원숭이 패치를해야합니다.

(다음 코드는 Django 1.8.x에서 작동합니다)

# Good example for a data migration
from django.db import migrations
from django.core.serializers import base, python
from django.core.management import call_command


def load_fixture(apps, schema_editor):
    # Save the old _get_model() function
    old_get_model = python._get_model

    # Define new _get_model() function here, which utilizes the apps argument to
    # get the historical version of a model. This piece of code is directly stolen
    # from django.core.serializers.python._get_model, unchanged. However, here it
    # has a different context, specifically, the apps variable.
    def _get_model(model_identifier):
        try:
            return apps.get_model(model_identifier)
        except (LookupError, TypeError):
            raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)

    # Replace the _get_model() function on the module, so loaddata can utilize it.
    python._get_model = _get_model

    try:
        # Call loaddata command
        call_command('loaddata', 'your_data.json', app_label='yourapp')
    finally:
        # Restore old _get_model() function
        python._get_model = old_get_model


class Migration(migrations.Migration):
    dependencies = [
        # Dependencies to other migrations
    ]

    operations = [
        migrations.RunPython(load_fixture),
    ]

1
Rockallite, 당신은 매우 강점을 지적합니다. 귀하의 답변은 의아해했습니다. @ n__o / @ mlissner의 답변에서 나온 솔루션 2.1 objects = serializers.deserialize('json', fixture, ignorenonexistent=True)은 다음과 같은 문제로 고통 받 loaddata습니까? 아니면 ignorenonexistent=True가능한 모든 문제를 다루나요?
Dário

7
source 를 보면 ignorenonexistent=True인수가 두 가지 효과가 있음을 알 수 있습니다. 1) 가장 최신 모델 정의 에없는 조명기의 모델을 무시하고 , 2) 그렇지 않은 조명기의 모델 필드를 무시합니다. 최신 해당 모델 정의에서. 그들 중 누구도 새로운 필수 현장 모델 상황을 처리하지 않습니다. 그래서 예, 나는 그것이 plain과 같은 문제를 겪고 있다고 생각합니다 loaddata.
Rockallite 2016 년

natural_key()방법은 지원하지 않는 것 같습니다. 그냥 natural_key 값을 참조 된 모델의 실제 ID로 대체했습니다.
dsummersl

1
아마도이 대답은 수락 된 대답으로 더 도움이 될 것입니다. 테스트 케이스를 실행할 때 새 데이터베이스가 생성되고 모든 마이그레이션이 처음부터 적용되기 때문입니다. 이 솔루션은 데이터 마이그레이션에서 _get_model을 대체하지 않는 경우 unittest가있는 프로젝트가 직면하게되는 문제를 해결합니다. Tnx
Mohammad ali baghershemirani

업데이트와 설명에 감사드립니다, @Rockallite. 내 초기 답변은 Django 1.7에 마이그레이션이 도입 된 후 몇 주 후에 게시되었으며 진행 방법에 대한 문서는 명확하지 않았습니다 (그리고 여전히 마지막으로 확인했습니다). 장고는 언젠가 모델 히스토리를 고려하기 위해로드 데이터 / 마이그레이션 메커니즘을 업데이트 할 것입니다.
n__o

6

일부 댓글 (즉 n__o 's)과 initial_data.*여러 앱에 많은 파일이 분산되어 있다는 사실에 영감을 받아 이러한 데이터 마이그레이션 생성을 용이하게하는 Django 앱을 만들기로 결정했습니다.

사용 장고 - 마이그레이션 - 고정은 간단히 다음과 같은 관리 명령을 실행할 수 있으며, 그것은 당신의 모든 통해 검색합니다 INSTALLED_APPS위해 initial_data.*파일 및 데이터 마이그레이션로 돌립니다.

./manage.py create_initial_data_fixtures
Migrations for 'eggs':
  0002_auto_20150107_0817.py:
Migrations for 'sausage':
  Ignoring 'initial_data.yaml' - migration already exists.
Migrations for 'foo':
  Ignoring 'initial_data.yaml' - not migrated.

설치 / 사용 지침 은 django-migration-fixture 를 참조하십시오 .


2

데이터베이스에 초기 데이터를 제공하려면 데이터 마이그레이션을 작성하십시오 . 데이터 마이그레이션에서 RunPython 함수를 사용하여 데이터를로드합니다.

이 방법은 더 이상 사용되지 않으므로 loaddata 명령을 작성하지 마십시오.

데이터 마이그레이션은 한 번만 실행됩니다. 마이그레이션은 순서가 지정된 마이그레이션 시퀀스입니다. 003_xxxx.py 마이그레이션이 실행되면 django 마이그레이션은이 앱이이 앱이 마이그레이션 될 때까지 (003) 마이그레이션되었음을 데이터베이스에 기록하고 다음 마이그레이션 만 실행합니다.


그래서 myModel.create(...)RunPython 함수에서 호출을 반복 하거나 루프를 사용 하도록 권장 합니까?
Mickaël 2014 년

네. Transaactionnal 데이터베이스는 :) 완벽하게 처리합니다
FlogFR

1

위에 제시된 솔루션은 불행히도 저에게 효과적이지 않았습니다. 모델을 변경할 때마다 조명기를 업데이트해야한다는 것을 알았습니다. 이상적으로는 데이터 마이그레이션을 작성하여 생성 된 데이터와 고정 장치로드 데이터를 비슷하게 수정합니다.

이를 용이하게하기 위해 현재 앱 의 디렉토리를 살펴보고 조명기를로드하는 빠른 함수작성했습니다fixtures . 마이그레이션의 필드와 일치하는 모델 히스토리 지점에서이 기능을 마이그레이션에 넣으십시오.


감사합니다! 저는 Python 3에서 작동하는 버전을 작성했습니다 (그리고 엄격한 Pylint를 통과 함). 를 사용하여 공장으로 사용할 수 있습니다 RunPython(load_fixture('badger', 'stoat')). gist.github.com/danni/1b2a0078e998ac080111
Danielle Madeley

1

제 생각에는 비품이 조금 나쁩니다. 데이터베이스가 자주 변경되면 최신 상태로 유지하는 것이 곧 악몽이 될 것입니다. 사실, 그것은 내 의견뿐만 아니라 "장고의 두 가지 특종"이라는 책에서 훨씬 더 잘 설명되어 있습니다.

대신 초기 설정을 제공하기 위해 Python 파일을 작성하겠습니다. 더 필요한 것이 있으면 Factory boy 를 보도록 권합니다 .

일부 데이터를 마이그레이션해야하는 경우 데이터 마이그레이션을 사용해야 합니다 .

도있다 "당신의 설비, 사용 모델 공장을 굽기" 기구를 사용하는 방법에 대해.


1
"빈번하게 변경하면 유지하기 어렵다"는 점에 동의하지만 여기서 고정 장치는 프로젝트를 설치할 때 초기 (최소한) 데이터 만 제공하는 것을 목표로합니다.
Mickaël

1
이는 한 번의 데이터로드를위한 것이며 마이그레이션 컨텍스트 내에서 수행되는 경우 의미가 있습니다. 마이그레이션 내에 있으면 json 데이터를 변경할 필요가 없습니다. 향후 데이터를 변경해야하는 스키마 변경은 다른 마이그레이션을 통해 처리해야합니다 (이 시점에서 데이터베이스에있는 다른 데이터도 수정해야 할 수 있음).
mtnpaul

0

Django 2.1에서는 초기 데이터로 일부 모델 (예 : 국가 이름 등)을로드하고 싶었습니다.

그러나 나는 이것이 초기 마이그레이션 실행 직후 자동으로 발생하기를 원했습니다.

그래서 sql/각 애플리케이션 안에 초기 데이터를로드해야하는 폴더 가 있으면 좋겠다고 생각했습니다 .

그런 다음 해당 sql/폴더 내에 .sql초기 데이터를 해당 모델에로드하는 데 필요한 DML 이있는 파일 이 있습니다 . 예를 들면 다음과 같습니다.

INSERT INTO appName_modelName(fieldName)
VALUES
    ("country 1"),
    ("country 2"),
    ("country 3"),
    ("country 4");

좀 더 설명하기 위해 sql/폴더가 포함 된 앱의 모습은 다음과 같습니다. 여기에 이미지 설명 입력

또한 sql특정 순서로 스크립트를 실행 해야하는 경우도 발견했습니다 . 그래서 위의 이미지에서 볼 수 있듯이 파일 이름 앞에 연속 번호를 붙이기로 결정했습니다.

그런 다음로드 할 방법이 필요했습니다. SQLs 을 수행하여 응용 프로그램 폴더 내에서 사용 가능한 모든 것을 자동으로python manage.py migrate .

그래서 이름이 다른 응용 프로그램을 만든 initial_data_migrations후, 나는 목록에이 응용 프로그램을 추가 INSTALLED_APPS에서 settings.py파일. 그런 다음 migrations내부 에 폴더를 만들고 run_sql_scripts.py( 실제로는 사용자 지정 마이그레이션 ) 이라는 파일을 추가했습니다 . 아래 이미지에서 볼 수 있듯이 :

여기에 이미지 설명 입력

각 응용 프로그램 내에서 사용 가능한 run_sql_scripts.py모든 sql스크립트 를 실행하도록 만들었습니다 . 이것은 누군가가 실행되면 해고됩니다 python manage.py migrate. 이 사용자 지정 migration은 또한 관련 응용 프로그램을 종속성으로 추가하여 sql필요한 응용 프로그램이 0001_initial.py마이그레이션 을 실행 한 후에 만 ​​명령문 을 실행하려고 시도 합니다 (존재하지 않는 테이블에 대해 SQL 문 실행을 시도하지 않음).

다음은 해당 스크립트의 소스입니다.

import os
import itertools

from django.db import migrations
from YourDjangoProjectName.settings import BASE_DIR, INSTALLED_APPS

SQL_FOLDER = "/sql/"

APP_SQL_FOLDERS = [
    (os.path.join(BASE_DIR, app + SQL_FOLDER), app) for app in INSTALLED_APPS
    if os.path.isdir(os.path.join(BASE_DIR, app + SQL_FOLDER))
]

SQL_FILES = [
    sorted([path + file for file in os.listdir(path) if file.lower().endswith('.sql')])
    for path, app in APP_SQL_FOLDERS
]


def load_file(path):
    with open(path, 'r') as f:
        return f.read()


class Migration(migrations.Migration):

    dependencies = [
        (app, '__first__') for path, app in APP_SQL_FOLDERS
    ]

    operations = [
        migrations.RunSQL(load_file(f)) for f in list(itertools.chain.from_iterable(SQL_FILES))
    ]

누군가이 도움이 되었기를 바랍니다. 궁금한 점이 있으면 알려주세요.

참고 : 방금 django를 시작하기 때문에 이것이 최선의 해결책이 아닐 수도 있지만, 인터넷 검색을하는 동안 많은 정보를 찾지 못했기 때문에이 "방법"을 모두와 공유하고 싶었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.