Bir modeli bir django uygulamasından yenisine nasıl geçiririm?


126

İçinde dört model bulunan bir django uygulamam var. Şimdi bu modellerden birinin ayrı bir uygulamada olması gerektiğini anlıyorum. Geçişler için güneye kurulum yaptım, ancak bunun otomatik olarak üstesinden gelebileceği bir şey olduğunu sanmıyorum. Modellerden birini eski uygulamadan yeni bir uygulamaya nasıl geçirebilirim?

Ayrıca, bunun tekrarlanabilir bir süreç olmasına ihtiyacım olacağını unutmayın, böylece üretim sistemini ve benzeri şeyleri taşıyabilirim.


Yanıtlar:


184

Güneyi kullanarak nasıl göç edilir.

Diyelim ki iki uygulamamız var: ortak ve özel:

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   `-- 0002_create_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   `-- 0002_create_dog.py
    `-- models.py

Şimdi model common.models.cat'i belirli bir uygulamaya (tam olarak specific.models.cat'e) taşımak istiyoruz. Önce kaynak koddaki değişiklikleri yapın ve ardından çalıştırın:

$ python manage.py schemamigration specific create_cat --auto
 + Added model 'specific.cat'
$ python manage.py schemamigration common drop_cat --auto
 - Deleted model 'common.cat'

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   |-- 0002_create_cat.py
|   |   `-- 0003_drop_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   |-- 0002_create_dog.py
    |   `-- 0003_create_cat.py
    `-- models.py

Şimdi her iki taşıma dosyasını da düzenlememiz gerekiyor:

#0003_create_cat: replace existing forward and backward code
#to use just one sentence:

def forwards(self, orm):
    db.rename_table('common_cat', 'specific_cat') 

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='common',
            model='cat',
        ).update(app_label='specific')

def backwards(self, orm):
    db.rename_table('specific_cat', 'common_cat')

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='specific',
            model='cat',
        ).update(app_label='common')

#0003_drop_cat:replace existing forward and backward code
#to use just one sentence; add dependency:

depends_on = (
    ('specific', '0003_create_cat'),
)
def forwards(self, orm):
    pass
def backwards(self, orm):
    pass

Artık her iki uygulama geçişi de değişikliğin farkındadır ve hayat biraz daha az berbat :-) Taşıma işlemleri arasında bu ilişkiyi kurmak başarının anahtarıdır. Şimdi yaparsan:

python manage.py migrate common
 > specific: 0003_create_cat
 > common: 0003_drop_cat

hem geçişi yapacak hem de

python manage.py migrate specific 0002_create_dog
 < common: 0003_drop_cat
 < specific: 0003_create_cat

her şeyi aşağı taşıyacak.

Şemayı yükseltmek için ortak uygulamayı ve sürüm düşürmek için belirli bir uygulamayı kullandığıma dikkat edin. Çünkü buradaki bağımlılık nasıl işliyor.


1
Vay canına, teşekkürler. Bu soruyu sorduğumdan beri güneyi kendi başıma öğrendim, ama eminim bu başkalarına çok yardımcı olacaktır.
Apreche

11
Ayrıca django_content_type tablosundaki veri geçişlerini yapmanız gerekebilir.
spookylukey

1
Gerçekten harika bir rehber @Potr. Merak ediyorum orm['contenttypes.contenttype'].objects.filter , arka kısmında da bir çizgi olması gerekmez 0003_create_catmi? Ayrıca bir ipucu paylaşmak istiyorum. Dizinleriniz varsa, bunların da değiştirilmesi gerekecektir. Benim durumumda bunlar benzersiz indekslerdi, bu yüzden ilerlemem thiS gibi görünüyor: db.delete_unique('common_cat', ['col1']) db.rename_table('common_cat', 'specific_cat') db.delete_unique('specific_cat', ['col1'])
Brad Pitcher

2
Erişim için orm['contenttypes.contenttype'], ayrıca eklemem gerekiyor --freeze contenttypessenin seçeneği schemamigrationkomutları.
Gary

1
Benim durumumda (Django 1.5.7 ve Güney 1.0) .. python manage.py schemamigration specific create_cat --auto --freeze commonOrtak uygulamadan kedi modeline erişmek için yazmak zorunda kaldım .
geoom

35

Üzerinde yapı için POTr Czachur 'ın cevabı , ForeignKeys içeren durumlar daha karmaşık ve biraz daha farklı ele alınmalıdır.

(Aşağıdaki örnek , mevcut yanıtta atıfta bulunulan commonve specificuygulamalara dayanmaktadır).

# common/models.py

class Cat(models.Model):
    # ...

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

sonra değişecek

# common/models.py

from specific.models import Cat

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

# specific/models.py

class Cat(models.Model):
    # ...

Koşu

./manage.py schemamigration common --auto
./manage.py schemamigration specific --auto # or --initial

aşağıdaki geçişleri oluşturacaktır (Django ContentType değişikliklerini kasıtlı olarak görmezden geliyorum - bunun nasıl işleneceğiyle ilgili daha önce başvurulan yanıta bakın):

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.delete_table('common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.create_table('common_cat', (
            # ...
        ))
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.create_table('specific_cat', (
            # ...
        ))

    def backwards(self, orm):
        db.delete_table('specific_cat')

Gördüğünüz gibi, FK yeni tabloya referans olacak şekilde değiştirilmelidir. Biz göçler (biz ona bir FK eklemek çalışmadan önce tablo var olacağını ve dolayısıyla) uygulanacak sırayı biliyoruz ki bir bağımlılık eklemeniz gerekir ama biz de emin de çünkü geriye işleri haddeleme yapmak gerekir bağımlılık ters yönde geçerlidir .

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):

    depends_on = (
        ('specific', '0004_auto__add_cat'),
    )

    def forwards(self, orm):
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat')

    def backwards(self, orm):
        pass

Başına Güney belgelerinde , depends_onsağlayacaktır 0004_auto__add_catönce çalışır 0009_auto__del_cat ileriye taşıma gerçekleştirirken ancak geriye doğru geçiş yaparken ters sırayla . Biz bırakılırsa db.rename_table('specific_cat', 'common_cat')içinde specificgeri alma, commonmasa başvurulan tablo yok çünkü ForeignKey geçirmek çalışırken geri alma başarısız olur.

Umarım bu, "gerçek dünya" durumuna mevcut çözümlerden daha yakındır ve birisi bunu faydalı bulacaktır. Şerefe!


Bu cevaptaki sabit kaynaklar, Potr Czachur'un cevabında bulunan içerik türlerini güncelleme satırlarını çıkarır. Bu yanıltıcı olabilir.
Shai Berger

@ShaiBerger Bunu özellikle ele aldım: "Django ContentType değişikliklerini kasıtlı olarak görmezden geliyorum — bunun nasıl işleneceği konusunda daha önce başvurulan cevaba bakın."
Matt Briançon

9

Modeller uygulamalara çok sıkı bir şekilde bağlı değildir, bu nedenle hareket etmek oldukça kolaydır. Django, uygulama adını veritabanı tablosunun adında kullanır; bu nedenle, uygulamanızı taşımak istiyorsanız, veritabanı tablosunu bir SQL ALTER TABLEifadesi aracılığıyla yeniden adlandırabilir veya daha basit bir şekilde modelinizin sınıfındaki db_tableparametreyiMeta kullanarak eski ad.

Şimdiye kadar kodunuzun herhangi bir yerinde ContentTypes veya genel ilişkiler kullandıysanız, muhtemelen app_labelhareket eden modeli gösteren içerik türünü yeniden adlandırmak isteyeceksiniz , böylece var olan ilişkiler korunacaktır.

Elbette, saklanacak hiç veriniz yoksa, yapılacak en kolay şey veritabanı tablolarını tamamen bırakıp ./manage.py syncdbtekrar çalıştırmaktır .


2
Bunu güneye göçle nasıl yaparım?
Apreche

4

İşte Potr'un mükemmel çözümüne bir düzeltme daha. Aşağıdakileri belirli / 0003_create_cat'e ekleyin

depends_on = (
    ('common', '0002_create_cat'),
)

Bu bağımlılık Güney olarak ayarlanmadıkça, common_cattablonun belirli / 0003_create_cat çalıştırıldığı anda var olduğunu garanti etmeyecek django.db.utils.OperationalError: no such table: common_catve size bir hata atacaktır .

Bağımlılık açıkça ayarlanmadıkça, güneyde geçişleri sözlüksel sırayla çalıştırır . Yana commonönce gelir specificbütün commonmuhtemelen POTr gösterdiği özgün örnekte yeniden olmaz bu yüzden 'ın göçler, masa yeniden adlandırma önce koşmak olacaktı. Yeniden adlandırmak Ama eğer commonhiç app2ve specifichiç app1bu sorun ile çalışacaktır.


Bu aslında Potr örneğiyle ilgili bir sorun değil. Yeniden adlandırmak için belirli geçişi ve belirli olana bağlı olarak ortak geçişi kullanır. Önce spesifik çalıştırılırsa, iyisiniz. Önce common çalıştırılırsa, bağımlılık ondan önce belirli bir çalıştırma yapacaktır. Bununla birlikte, bunu yaparken siparişi değiştirdim, bu nedenle yeniden adlandırma ortak ve özelde bağımlılık oldu ve sonra yukarıda açıkladığınız gibi bağımlılığı değiştirmeniz gerekiyor.
Emil Stenström

1
Seninle aynı fikirde değilim Benim bakış açıma göre çözüm sağlam olmalı ve sadece onu memnun etmeye çalışmadan çalışmalıdır. Yeni db ve syncdb / migrate'den başlarsanız orijinal çözüm çalışmaz. Benim teklifim onu ​​düzeltir. Her
halükarda

Bunu yapmamak, testlerin de başarısız olmasına neden olabilir (test veritabanlarını oluştururken her zaman tam güney geçişleri çalıştırıyor gibi görünürler). Daha önce benzer bir şey yaptım. İyi yakala Ihor :)
odinho - Velmont

4

Buraya birkaç kez döndüğümden ve resmileştirmeye karar verdiğimden beri şu anda üzerinde durduğum süreç.

Bu başlangıçta Potr Czachur'un cevabı ve Matt Briançon'un Güney 0.8.4 kullanarak cevabı üzerine inşa edildi.

Adım 1. Çocuk yabancı anahtar ilişkilerini keşfedin

# Caution: This finds OneToOneField and ForeignKey.
# I don't know if this finds all the ways of specifying ManyToManyField.
# Hopefully Django or South throw errors if you have a situation like that.
>>> Cat._meta.get_all_related_objects()
[<RelatedObject: common:toy related to cat>,
 <RelatedObject: identity:microchip related to cat>]

Bu genişletilmiş durumda, aşağıdaki gibi başka bir ilgili model keşfettik:

# Inside the "identity" app...
class Microchip(models.Model):

    # In reality we'd probably want a ForeignKey, but to show the OneToOneField
    identifies = models.OneToOneField(Cat)

    ...

2. Adım. Taşıma oluşturun

# Create the "new"-ly renamed model
# Yes I'm changing the model name in my refactoring too.
python manage.py schemamigration specific create_kittycat --auto

# Drop the old model
python manage.py schemamigration common drop_cat --auto

# Update downstream apps, so South thinks their ForeignKey(s) are correct.
# Can skip models like Toy if the app is already covered
python manage.py schemamigration identity update_microchip_fk --auto

Adım 3. Kaynak kontrolü: Şimdiye kadar değişiklikleri gerçekleştirin.

Güncellenen uygulamalarda taşıma işlemleri yazan ekip arkadaşları gibi birleştirme çatışmalarıyla karşılaşırsanız, bunu daha tekrarlanabilir bir süreç haline getirir.

Adım 4. Geçişler arasına bağımlılıklar ekleyin.

Temelde create_kittycather şeyin mevcut durumuna bağlıdır ve sonra her şey buna bağlıdır create_kittycat.

# create_kittycat
class Migration(SchemaMigration):

    depends_on = (
        # Original model location
        ('common', 'the_one_before_drop_cat'),

        # Foreign keys to models not in original location
        ('identity', 'the_one_before_update_microchip_fk'),
    )
    ...


# drop_cat
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...


# update_microchip_fk
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...

Adım 5. Yapmak istediğimiz tabloyu yeniden adlandırma değişikliği.

# create_kittycat
class Migration(SchemaMigration):

    ...

    # Hopefully for create_kittycat you only need to change the following
    # 4 strings to go forward cleanly... backwards will need a bit more work.
    old_app = 'common'
    old_model = 'cat'
    new_app = 'specific'
    new_model = 'kittycat'

    # You may also wish to update the ContentType.name,
    # personally, I don't know what its for and
    # haven't seen any side effects from skipping it.

    def forwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.old_app, self.old_model),
            '%s_%s' % (self.new_app, self.new_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.old_app,
                model=self.old_model,
            ).update(
                app_label=self.new_app,
                model=self.new_model,
            )

        # Going forwards, should be no problem just updating child foreign keys
        # with the --auto in the other new South migrations

    def backwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.new_app, self.new_model),
            '%s_%s' % (self.old_app, self.old_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.new_app,
                model=self.new_model,
            ).update(
                app_label=self.old_app,
                model=self.old_model,
            )

        # Going backwards, you probably should copy the ForeignKey
        # db.alter_column() changes from the other new migrations in here
        # so they run in the correct order.
        #
        # Test it! See Step 6 for more details if you need to go backwards.
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['common.Cat']))


# drop_cat
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Remove the db.delete_table(), if you don't at Step 7 you'll likely get
        # "django.db.utils.ProgrammingError: table "common_cat" does not exist"

        # Leave existing db.alter_column() statements here
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass


# update_microchip_fk
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Leave existing db.alter_column() statements here
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass

Adım 6. Sadece çalışmak için geriye doğru () ihtiyacınız varsa VE geriye doğru çalışan bir KeyError alıyorsanız.

# the_one_before_create_kittycat
class Migration(SchemaMigration):

    # You many also need to add more models to South's FakeORM if you run into
    # more KeyErrors, the trade-off chosen was to make going forward as easy as
    # possible, as that's what you'll probably want to do once in QA and once in
    # production, rather than running the following many times:
    #
    # python manage.py migrate specific <the_one_before_create_kittycat>

    models = {
        ...
        # Copied from 'identity' app, 'update_microchip_fk' migration
        u'identity.microchip': {
            'Meta': {'object_name': 'Microchip'},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
            'identifies': ('django.db.models.fields.related.OneToOneField', [], {to=orm['specific.KittyCat']})
        },
        ...
    }

Adım 7. Test edin - benim için işe yarayan şeyler sizin gerçek yaşam durumunuz için yeterli olmayabilir :)

python manage.py migrate

# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>

3

Dolayısıyla, yukarıdaki @Potr'dan gelen orijinal yanıtı kullanmak benim için Güney 0.8.1 ve Django 1.5.1'de işe yaramadı. Başkalarına yardımcı olması umuduyla benim için neyin işe yaradığını aşağıya yazıyorum.

from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat') 

        if not db.dry_run:
             db.execute(
                "update django_content_type set app_label = 'specific' where "
                " app_label = 'common' and model = 'cat';")

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
            db.execute(
                "update django_content_type set app_label = 'common' where "
                " app_label = 'specific' and model = 'cat';")

1

Daniel Roseman'ın cevabında önerdiği şeylerden birinin daha açık bir versiyonunu vereceğim ...

db_tableTaşıdığınız modelin Meta özniteliğini yalnızca mevcut tablo adını gösterecek şekilde değiştirirseniz (yeni ad yerine Django düşüp a syncdbyaparsanız verir), karmaşık Güney geçişlerinden kaçınabilirsiniz. Örneğin:

Orijinal:

# app1/models.py
class MyModel(models.Model):
    ...

Taşındıktan sonra:

# app2/models.py
class MyModel(models.Model):
    class Meta:
        db_table = "app1_mymodel"

Şimdi sadece güncellemek için bir veri taşıma yapmak gerekir app_labeliçin MyModelde django_content_typemasa ve iyi gitmek olmalı ...

Çalıştırın ./manage.py datamigration django update_content_typeve Güney'in sizin için oluşturduğu dosyayı düzenleyin:

def forwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app1', model='mymodel')
    moved.app_label = 'app2'
    moved.save()

def backwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app2', model='mymodel')
    moved.app_label = 'app1'
    moved.save()
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.