Django 1.7 ve veri geçişleri ile ilk verileri yükleme


96

Yakın zamanda Django 1.6'dan 1.7'ye geçtim ve geçişleri kullanmaya başladım (Güney'i hiç kullanmadım).

1.7'den önce, ilk verileri komutla fixture/initial_data.jsonyüklenen bir dosya ile python manage.py syncdbyüklerdim (veritabanı oluştururken).

Şimdi, taşımaları kullanmaya başladım ve bu davranış artık kullanılmıyor:

Bir uygulama geçişler kullanıyorsa, fikstürlerin otomatik olarak yüklenmesi söz konusu değildir. Django 2.0'daki uygulamalar için geçişler gerekeceğinden, bu davranış kullanımdan kaldırılmış olarak kabul edilir. Bir uygulama için ilk verileri yüklemek istiyorsanız, bunu bir veri taşıma işlemi sırasında yapmayı düşünün. ( https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures )

Resmi belgeler benim sorum bu yüzden, bunu yapmak için nasıl bir net örnek yok:

Veri geçişlerini kullanarak bu tür ilk verileri içe aktarmanın en iyi yolu nedir:

  1. Birden çok çağrı ile Python kodu yazın mymodel.create(...),
  2. JSON fikstür dosyasından veri yüklemek için bir Django işlevi ( arama gibiloaddata ) kullanın veya yazın .

İkinci seçeneği tercih ederim.

Güneyi kullanmak istemiyorum, çünkü Django artık bunu yerel olarak yapabiliyor.


3
Ayrıca OP'nin orijinal sorusuna bir soru daha eklemek istiyorum: Uygulamalarımıza ait olmayan veriler için veri geçişlerini nasıl yapmalıyız? Örneğin, birisi site çerçevesini kullanıyorsa site verileriyle bir fikstüre sahip olması gerekir. Sitelerin çerçevesi uygulamalarımızla ilgili olmadığından, bu veri geçişini nereye koymalıyız? Teşekkürler !
Serafeim

Henüz burada kimsenin değinmediği önemli bir nokta, bir veri taşıma işleminde tanımlı verileri, üzerinde geçiş taklit ettiğiniz bir veritabanına eklemeniz gerektiğinde ne olduğudur. Taşıma işlemleri sahte olduğundan, veri taşıma işleminiz çalışmayacaktır ve bunu elle yapmanız gerekir. Bu noktada, bir fikstür dosyasındaki yük verilerini de çağırabilirsiniz.
hekevintran

Bir başka ilginç senaryo da, örneğin kimlik doğrulama grubu örnekleri oluşturmak için bir veri geçişiniz varsa ve daha sonra çekirdek verileri olarak oluşturmak istediğiniz yeni bir Gruba sahipseniz ne olacağıdır. Yeni bir veri geçişi oluşturmanız gerekecek. Bu can sıkıcı olabilir çünkü Grup çekirdek verileriniz birden çok dosyada olacaktır. Ayrıca, geçişleri sıfırlamak istemeniz durumunda, çekirdek verilerini ayarlayan ve bunları taşıyan veri geçişlerini bulmanız gerekir.
hekevintran

@Serafeim "Üçüncü taraf bir uygulama için ilk verileri nereye koymalı" sorusu, fikstürler yerine veri geçişi kullanırsanız değişmez, çünkü yalnızca verilerin yüklenme şeklini değiştirirsiniz. Bunun gibi şeyler için küçük bir özel uygulama kullanıyorum. Üçüncü taraf uygulama "foo" olarak adlandırılıyorsa, veri geçişini / fikstürünü içeren basit uygulamama "foo_integration" adını veriyorum.
guettli

@guettli evet, muhtemelen fazladan bir uygulama kullanmak bunu yapmanın en iyi yoludur!
Serafeim

Yanıtlar:


82

Güncelleme : Bu çözümün neden olabileceği sorunlar için aşağıdaki @ GwynBleidD'nin yorumuna bakın ve gelecekteki model değişikliklerine daha dayanıklı bir yaklaşım için @ Rockallite'ın aşağıdaki cevabına bakın.


İçinde bir fikstür dosyanız olduğunu varsayarsak <yourapp>/fixtures/initial_data.json

  1. Boş geçişinizi oluşturun:

    Django 1.7'de:

    python manage.py makemigrations --empty <yourapp>
    

    Django 1.8+ sürümünde bir ad sağlayabilirsiniz:

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
    
  2. Taşıma dosyanızı düzenleyin <yourapp>/migrations/0002_auto_xxx.py

    2.1. Django'dan esinlenen özel uygulama loaddata(ilk cevap):

    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'ın önerisine göre) için daha basit bir çözüm :

    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) 
    

    Özel bir dizin kullanmak istiyorsanız kullanışlıdır.

    2.3. En basit: ile arama loaddata, app_labelfikstürleri <yourapp>'s fixturesdizininden otomatik olarak yükleyecektir:

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

    Belirtmezseniz app_label, loaddata tüm uygulama fikstür dizinlerinden fixturedosya adını yüklemeye çalışacaktır (muhtemelen istemediğiniz).

  3. Çalıştır

    python manage.py migrate <yourapp>
    

1
tamam, haklısın ... Ayrıca arama loaddata('loaddata', fixture_filename, app_label='<yourapp>')da doğrudan uygulama fikstür dizinine gidecek (bu nedenle fikstürün tam yolunu oluşturmaya gerek yok)
n__o

15
Bu yöntemi kullanarak, serileştirici, models.pybazı ekstra alanlara veya başka değişikliklere sahip olabilen mevcut dosyalardan model durumu üzerinde çalışacaktır . Geçiş oluşturulduktan sonra bazı değişiklikler yapılırsa, başarısız olur (bu nedenle, bu geçişten sonra şema geçişleri bile oluşturamayız). Bunu düzeltmek için, serileştiricinin üzerinde çalıştığı uygulama kaydını ilk parametrede taşıma işlevine sağlanan kayıt defterine geçici olarak değiştirebiliriz. Kayıt yolu, adresinde bulunur django.core.serializers.python.apps.
GwynBleidD

3
Bunu neden yapıyoruz? Neden Django'nun çalıştırılması ve bakımı gittikçe daha zor hale geliyor? Buna rağmen gitmek istemiyorum, bu sorunu benim için çözen basit bir komut satırı arayüzü istiyorum, yani eskiden armatürlerde olduğu gibi. Django'nun bu şeyleri zorlaştırmak yerine kolaylaştırması gerekiyor :(
CpILL

1
@GwynBleidD Bu, yaptığınız çok önemli bir nokta ve bu kabul edilen cevapta görünmesi gerektiğini düşünüyorum. Dokümantasyonun veri taşıma kodu örneğinde yorum olarak görünenle aynı açıklama . Genel app registrybir değişkeni değiştirmeden sağlanan serileştiricileri kullanmanın başka bir yolunu biliyor musunuz (paralel veritabanı geçişleri ile varsayımsal bir gelecekte sorunlara neden olabilir).
İlan N

3
Bu cevabın kabul ile birlikte kazoo'ya yükseltilmesi, tam olarak bu yüzden insanlara stackoverflow kullanmamalarını öneriyorum. Şimdi bile yorumlar ve anekdotlarla #django'da buna atıfta bulunanlar var.
shangxiao

52

Kısa versiyon

Sen gerektiğini DEĞİL kullanmak loaddatabir veri geçişine doğrudan yönetim komutu.

# 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),
    ]

Uzun versiyon

loaddatakullandığı django.core.serializers.python.Deserializerbir göç tarihsel veri serisini çoğunu güncel modellerini kullanır. Bu yanlış bir davranış.

Örneğin, loaddatabir fikstürden veri yüklemek için yönetim komutunu kullanan bir veri geçişi olduğunu varsayalım ve bu, geliştirme ortamınıza zaten uygulandı.

Daha sonra, ilgili modele yeni bir zorunlu alan eklemeye karar verirsiniz , böylece bunu yaparsınız ve güncellenmiş modelinize göre yeni bir geçiş yaparsınız (ve muhtemelen ./manage.py makemigrationssizi sorduğunda yeni alana bir kerelik bir değer sağlarsınız).

Bir sonraki geçişi siz yönetirsiniz ve her şey yolunda.

Son olarak, Django uygulamanızı geliştirmeyi tamamladınız ve onu üretim sunucusuna yerleştirdiniz. Artık tüm geçişleri üretim ortamında sıfırdan çalıştırmanın zamanı geldi.

Ancak veri geçişi başarısız olur . Bunun nedeni, loaddatamevcut kodu temsil eden komuttan serileştirilmiş modelin, eklediğiniz yeni gerekli alan için boş verilerle kaydedilememesidir . Orijinal fikstür bunun için gerekli verilerden yoksundur!

Ancak fikstürü yeni alan için gerekli verilerle güncelleseniz bile , veri geçişi yine de başarısız olur . Veri geçişi çalışırken, ilgili sütunu veritabanına ekleyen bir sonraki geçiş henüz uygulanmaz. Mevcut olmayan bir sütuna veri kaydedemezsiniz!

Sonuç: Bir veri geçişindeloaddatakomut, model ve veritabanı arasında olası tutarsızlığı ortaya çıkarır. Veri geçişindekesinlikledoğrudan KULLANMAMALISINIZ .

Çözüm

loaddatakomutu django.core.serializers.python._get_model, ilgili modeli bir fikstürden almak için işleve dayanır ve bu, bir modelin en güncel sürümünü döndürür. Tarihsel modeli alması için onu maymun yamalı yapmalıyız.

(Aşağıdaki kod Django 1.8.x için çalışır)

# 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, çok güçlü bir noktaya değindin. Cevabınız beni meraklandırdı, ancak @ n__o / @ mlissner'ın cevabından 2.1'in çözümü objects = serializers.deserialize('json', fixture, ignorenonexistent=True)aynı sorundan muzdarip loaddatami? Yoksa ignorenonexistent=Truetüm olası sorunları kapsıyor mu?
Dário

7
Kaynağa bakarsanız , ignorenonexistent=Trueargümanın iki etkisi olduğunu göreceksiniz : 1) en güncel model tanımlarında olmayan bir fikstür modellerini görmezden gelir, 2) bir fikstür modelinin alanlarını görmezden gelir. en güncel ilgili model tanımında. Hiçbiri modelde yeni gerekli alan durumunu ele almıyor. Yani evet, bence açıklıkla aynı sorunu yaşıyor loaddata.
Rockallite

Bu, eski json'umun diğer modellere başvuran modellere natural_key()sahip olduğunu ve bu yöntemin desteklemediğini anladığımda harika çalıştı - sadece natural_key değerini referans alınan modelin gerçek kimliğiyle değiştirdim.
dsummersl

1
Muhtemelen bu yanıt kabul edilen yanıt olarak daha yararlı olacaktır, çünkü test senaryolarını çalıştırırken yeni bir veritabanı oluşturulur ve tüm geçişler sıfırdan uygulanır. Bu çözüm, veri taşıma sırasında _get_model'in değiştirilmemesi durumunda unittest'e sahip bir projenin karşılaşacağı sorunları düzeltir. Tnx
Mohammad ali baghershemirani

Güncelleme ve açıklamalar için teşekkürler, @ Rockallite. İlk cevabım, geçişlerin Django 1.7'de tanıtılmasından birkaç hafta sonra yayınlandı ve nasıl devam edileceğine dair belgeler belirsizdi (ve son kontrol ettiğimde hala öyle). Umarım Django bir gün model geçmişini hesaba katmak için yükleme verilerini / geçiş mekanizmasını günceller.
n__o

6

Bazı yorumlardan (yani n__o'lardan) ve initial_data.*birden fazla uygulamaya dağılmış çok sayıda dosyam olduğu gerçeğinden ilham alarak , bu veri geçişlerinin oluşturulmasını kolaylaştıracak bir Django uygulaması oluşturmaya karar verdim.

Kullanılması django-göç-fikstürü sadece aşağıdaki yönetim komutunu çalıştırabilirsiniz ve söz konusu e tüm aracılığıyla arayacaktır INSTALLED_APPSiçin initial_data.*dosya ve veri taşıma bunları açmak.

./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.

Bkz Django-göç-fikstür yüklemek / kullanımını talimatları için.


2

Veritabanınıza bazı başlangıç ​​verileri vermek için, bir veri geçişi yazın. Veri geçişinde, verilerinizi yüklemek için RunPython işlevini kullanın.

Bu yöntem kullanımdan kaldırıldığı için herhangi bir loaddata komutu yazmayın.

Veri taşıma işlemleriniz yalnızca bir kez yapılacaktır. Göçler sıralı bir geçiş dizisidir. 003_xxxx.py geçişleri çalıştırıldığında, django geçişleri veritabanına bu uygulamanın bu uygulamaya (003) kadar taşındığını yazar ve yalnızca aşağıdaki geçişleri çalıştırır.


Yani beni myModel.create(...)RunPython işlevinde çağrıları tekrarlamamı (veya bir döngü kullanarak) teşvik ediyor musunuz?
Mickaël

hemen hemen evet. Transaactionnal veritabanları bunu mükemmel bir şekilde halledecek :)
FlogFR

1

Yukarıda sunulan çözümler maalesef benim için işe yaramadı. Modellerimi her değiştirdiğimde armatürlerimi güncellemem gerektiğini öğrendim. İdeal olarak, bunun yerine, oluşturulan verileri ve fikstür tarafından yüklenen verileri benzer şekilde değiştirmek için veri geçişleri yazardım.

Bunu kolaylaştırmak için , mevcut uygulamanın dizinine bakacak ve bir fikstür yükleyecek hızlı bir işlev yazdımfixtures . Bu işlevi, geçişteki alanlarla eşleşen model geçmişi noktasında bir geçişe yerleştirin.


Bunun için teşekkürler! Python 3 ile çalışan (ve katı Pylint'imizi geçen) bir sürüm yazdım. İle fabrika olarak kullanabilirsiniz RunPython(load_fixture('badger', 'stoat')). gist.github.com/danni/1b2a0078e998ac080111
Danielle Madeley

1

Kanımca fikstürler biraz kötü. Veritabanınız sık sık değişirse, onları güncel tutmak yakında bir kabusa dönüşecektir. Aslında sadece benim fikrim değil, "Django'nun İki Kaşığı" kitabında çok daha iyi açıklanıyor.

Bunun yerine, ilk kurulumu sağlamak için bir Python dosyası yazacağım. Eğer daha fazlasına ihtiyacın olursa, Fabrika çocuğuna bakmanı öneririm .

Bazı verileri taşımanız gerekiyorsa, veri taşıma işlemlerini kullanmanız gerekir .

Orada da "Sizin Fikstür, Kullanım Modeli Fabrikalar Yanık" armatürleri kullanmayla ilgili.


1
"Sık sık değişirse bakımı zor" dediğiniz noktaya katılıyorum, ancak burada fikstür yalnızca projeyi kurarken ilk (ve asgari) verileri sağlamayı amaçlamaktadır ...
Mickaël

1
Bu, tek seferlik bir veri yükü içindir ve geçiş bağlamında yapılırsa mantıklıdır. Bir migrasyon içinde olduğu için json verilerinde değişiklik yapılmasına gerek yoktur. Yolun ilerleyen kısımlarındaki verilerde değişiklik gerektiren herhangi bir şema değişikliği, başka bir taşıma yoluyla ele alınmalıdır (bu noktada, veritabanındaki diğer veriler de değiştirilmeye ihtiyaç duyabilir).
mtnpaul

0

Django 2.1'de bazı modelleri (örneğin ülke adları gibi) ilk verilerle yüklemek istedim.

Ancak bunun ilk geçişlerin yürütülmesinden hemen sonra otomatik olarak gerçekleşmesini istedim.

Bu yüzden sql/her uygulamada ilk verilerin yüklenmesini gerektiren bir klasörün olması harika olur diye düşündüm .

Daha sonra bu sql/klasörün içinde .sql, ilk verileri ilgili modellere yüklemek için gerekli DML'lere sahip dosyalara sahip olurdum, örneğin:

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

Daha açıklayıcı olmak gerekirse, sql/klasör içeren bir uygulama şu şekilde görünür: görüntü açıklamasını buraya girin

Ayrıca, sqlkomut dosyalarının belirli bir sırada çalıştırılmasına ihtiyaç duyduğum bazı durumlar buldum . Bu nedenle, yukarıdaki resimde görüldüğü gibi dosya adlarının önüne ardışık bir sayı koymaya karar verdim.

Ardından, SQLsherhangi bir uygulama klasörünün içindeki mevcut herhangi birini yaparak otomatik olarak yüklemek için bir yola ihtiyacım vardı python manage.py migrate.

Bu yüzden adında başka bir uygulama yarattı initial_data_migrationsve sonra ben listesine bu uygulama ekledi INSTALLED_APPSiçinde settings.pydosyanın. Sonra migrationsiçinde bir klasör oluşturdum ve run_sql_scripts.py( Aslında özel bir geçiş ) adlı bir dosya ekledim . Aşağıdaki resimde görüldüğü gibi:

görüntü açıklamasını buraya girin

Her uygulamada mevcut olan run_sql_scripts.pytüm sqlkomut dosyalarını çalıştıracak şekilde oluşturdum . Bu daha sonra biri koştuğunda ateşleniyor python manage.py migrate. Bu özel migration, ilgili uygulamaları bağımlılıklar olarak da ekler, böylece sqlifadeleri yalnızca gerekli uygulamalar 0001_initial.pygeçişlerini gerçekleştirdikten sonra çalıştırmayı dener (Var olmayan bir tabloya karşı bir SQL ifadesi çalıştırmayı denemek istemiyoruz).

İşte o komut dosyasının kaynağı:

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))
    ]

Umarım birisi bunu yararlı bulur, benim için gayet iyi çalıştı !. Herhangi bir sorunuz varsa lütfen bana bildirin.

NOT: Bu, django'ya yeni başladığım için en iyi çözüm olmayabilir, ancak bu konuda Google'da çok fazla bilgi bulamadığım için bu "Nasıl Yapılır" ı yine de sizinle paylaşmak istedim.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.