programing

Django 1.7+를 사용한 초기 데이터 로드 및 데이터 마이그레이션

golfzon 2023. 3. 10. 22:59
반응형

Django 1.7+를 사용한 초기 데이터 로드 및 데이터 마이그레이션

저는 최근에 장고 1.6에서 1.7로 전환하여 이행을 시작했습니다(남쪽은 사용한 적이 없습니다).

.7 데이터를 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-module)

공식 문서에는 그 방법에 대한 명확한 예가 없기 때문에 다음과 같이 질문합니다.

데이터 이행을 사용하여 이러한 초기 데이터를 Import하는 가장 좋은 방법은 무엇입니까?

  1. 콜을 가진 Python 를 Python에 씁니다.mymodel.create(...) ,
  2. JSON 고정 파일에서 데이터를 로드하려면 Django 함수(호출 등)를 사용하거나 씁니다.

나는 두 번째 옵션이 더 좋다.

장고는 이제 토종적으로 할 수 있을 것 같아서 남쪽은 쓰고 싶지 않아요.

업데이트: 이 솔루션에 의해 발생할 수 있는 문제에 대해서는 다음 @GwynBleidD의 코멘트를 참조해 주세요.또, 장래의 모델 변경에 대해서 보다 내구성이 높은 어프로치에 대해서는, 다음의 @Rockallite의 코멘트를 참조해 주세요.


파일이 합니다.<yourapp>/fixtures/initial_data.json

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

    장고 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

    .1. ' 2.1에서 을 얻은 커스텀 '장고'는 '장고'에서 영감을 얻은 입니다.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.2를 위한 간단한 .load_fixture에 따라 (@seciocesar)

    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. 가장 심플한: 통화loaddataapp_label에서 고정장치를 로드합니다.<yourapp>의 »fixturesautomatically : dir 자 、 dir 、 dir dir dir dir dir dir dir dir dir 。

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

    를 ,app_label는 load, loaddata는 load를 합니다.fixture모든 앱 고정 장치 디렉토리의 파일 이름(원하지 않을 수도 있음)입니다.

  3. 실행하다

    python manage.py migrate <yourapp>
    

쇼트 버전

사용해서는 안 됩니다.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),
    ]

롱 버전

loaddatadjango.core.serializers.python.Deserializer최신 모델을 사용하여 마이그레이션에서 기록 데이터를 역직렬화합니다.그건 잘못된 행동이에요.

를 들어, 데이터 합니다.loaddata되어 있습니다.management 명령어, management 명령어, management 명령어, management 명령어, management 명령어, management 명령어, management 명령어, management 명령어.

나중에 대응하는 모델에 새로운 필수 필드를 추가하기로 결정했기 때문에 이를 수행하고 갱신된 모델에 대해 새로운 이행을 수행합니다(또한 새로운 필드에 일회성 값을 제공할 수도 있습니다)../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),
    ]

가지 와 내가 코멘트를 있다는 을 얻었다.initial_data.*파일이 여러 앱에 분산되어 있습니다.이러한 데이터 이행을 용이하게 하는 Django 앱을 만들기로 결정했습니다.

django-migration-fluse를 사용하면 다음 관리 명령어를 실행할 수 있습니다.이 명령어는 모든 관리 명령어를 검색합니다.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-fluse」를 참조해 주세요.

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

이 방법은 권장되지 않으므로 loaddata 명령어는 쓰지 마십시오.

데이터 마이그레이션은 한 번만 실행됩니다.이행은 이행 순서입니다.003_xxxx.py 마이그레이션을 실행하면 django 마이그레이션은 데이터베이스에 이 앱이 이 앱(003)까지 마이그레이션되며 다음 마이그레이션만 실행됩니다.

유감스럽게도 위의 해결방법은 효과가 없었습니다.모델을 변경할 때마다 고정 장치를 업데이트해야 한다는 것을 알게 되었습니다.대신 데이터 마이그레이션을 작성하여 생성된 데이터와 고정 장치에 로드된 데이터를 비슷하게 수정하는 것이 이상적입니다.

이것을 용이하게 하기 위해서, 나는 빠른 함수를 썼다.fixtures현재 앱의 디렉토리와 픽스쳐를 로드합니다.이 함수를 마이그레이션의 필드와 일치하는 모델 기록 지점에서 마이그레이션에 넣습니다.

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_APPSsettings.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를 시작했기 때문에 이것이 최선의 해결책은 아닐 수도 있지만, 이 "How-to"를 여러분들과 공유하고자 했습니다. 왜냐하면 저는 이 것에 대해 구글을 검색하면서 많은 정보를 찾지 못했기 때문입니다.

제 생각에는 고정관념이 좀 안 좋아요.데이터베이스가 자주 변경되면 최신 상태로 유지하는 것이 곧 악몽이 될 것입니다.사실 제 의견뿐만 아니라 "장고의 두 스쿠프"라는 책에서 훨씬 더 잘 설명되어 있습니다.

대신 Python 파일을 작성하여 초기 셋업을 제공합니다.더 필요한 게 있으면 팩토리 보이에게 물어보는 게요

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

고정장치 사용에 관한 "Burn Your Fixits, Use Model Factory"도 있습니다.

천연열쇠는요?

@rockallite의 답변은 훌륭하지만 정수 대신 자연 키에 의존하는 고정 장치를 다루는 방법은 설명되지 않습니다.pk★★★★★★ 。

간이판

첫 번째로 @rockallite의 솔루션은unittest.mock.patch로서, 또 「」패치에 , 「」가 됩니다.apps_get_model:

...
from unittest.mock import patch
...

def load_fixture(apps, schema_editor):
    with patch('django.core.serializers.python.apps', apps):
        call_command('loaddata', 'your_data.json', ...)

...

고정 장치자연 키에 의존하지 않는 한 이 방법은 잘 작동합니다.

만약 그렇게 된다면, 당신은 아마 볼 수 있을 것이다.DeserializationError: ... value must be an integer....

내추럴 키의 문제

후드 밑에loaddatadjango.core.serializers.deserialize()고정 객체를 로드합니다.

자연 기반으로 한 고정 장치의 역직렬화는 다음 두 가지 사항에 의존합니다.

  1. 모델의 기본 매니저에 get_by_natural_key() 메서드가 있습니다.
  2. 모델 자체에 natural_key() 메서드가 있습니다.

get_by_natural_key()가 정수 위해 합니다.pkdiscloss.discloss 。

은 모두 합니다.get여기서도 설명하듯이 데이터베이스의 기존 개체를 자연 키로 지정합니다.

「」, 「」는apps이행에서 사용할 수 있는 레지스트리에서는 이력 모델이 사용되며 이러한 모델은 다음과 같은 커스텀 매니저나 커스텀 메서드에 액세스할 수 없습니다.natural_key().

가능한 해결책: 1단계

get_by_natural_key() 해결할 수 .그냥 설정하기만 됩니다.use_in_migrations=True매뉴얼에 설명된 대로 커스텀 매니저에 접속합니다.

에 액세스 할 수 .get_by_natural_key()이제 고정 장치의 로딩이 성공합니다.

역사 지, 신, 신, 신, 신, 델, however, however, however, however, however, however, however, however, however, however, however, however가 없습니다.natural_key() 。 그 됩니다.그 결과 고정장치가 데이터베이스에 이미 있더라도 새 객체로 취급됩니다.이로 인해 데이터 이행이 다시 적용되면 다음과 같은 다양한 오류가 발생할 수 있습니다.

  • 고유 제약 조건 위반(모델에 고유 제약 조건이 있는 경우)
  • 중복 고정 객체(모델에 고유 구속조건이 없는 경우)
  • "복수의 오브젝트를 반환한다" 오류(이전에 작성된 중복된 픽스쳐 오브젝트로 인해)

따라서 사실상 역직렬화 중에 get_or_create와 같은 동작을 아직 놓치고 있습니다.

이를 수행하려면 위에서 설명한 대로 (테스트 환경에서) 데이터 마이그레이션을 적용한 후 동일한 데이터 마이그레이션을 롤백하고(데이터를 제거하지 않고) 데이터 마이그레이션을 다시 적용하기만 하면 됩니다.

가능한 해결책: 2단계

natural_key()모델 자체의 방법이 좀 더 어렵네요. 중 의 해결책은 '어디로 하다'를 입니다.natural_key()예를 들어 다음과 같이 현재 모델에서 과거 모델로 이동합니다.

...
from unittest.mock import patch

from django.apps import apps as current_apps
from django.core.management import call_command
...


def load_fixture(apps, schema_editor):
    def _get_model_patch(app_label):
        """ add natural_key method from current model to historical model """
        historical_model = apps.get_model(app_label=app_label)
        current_model = current_apps.get_model(app_label=app_label)
        historical_model.natural_key = current_model.natural_key
        return historical_model

    with patch('django.core.serializers.python._get_model', _get_model_patch):
        call_command('loaddata', 'your_data.json', ...)

...

주의:

  • 알기 쉽게 하기 위해서, 에러 처리나 속성 확인등을 예에서 생략했습니다.필요에 따라서, 그것들을 실장할 필요가 있습니다.
  • 이 솔루션에서는 현재 모델의natural_key는 특정 할 수 있지만, 이는 의 '장고에 대해서도 입니다.use_in_migrations모델 매니저를 위한 옵션입니다.

언급URL : https://stackoverflow.com/questions/25960850/loading-initial-data-with-django-1-7-and-data-migrations

반응형