Jak przenieść model z jednej aplikacji django do nowej?

Mam aplikację django z czterema modelami. Zdaję sobie sprawę, że jeden z tych modeli powinien być w osobnej aplikacji. Mam zainstalowane south dla migracji, ale nie sądzę, że jest to coś, co może obsługiwać automatycznie. Jak mogę przenieść jeden z modeli ze starej aplikacji na nowy?

Również, należy pamiętać, że będę musiał to być powtarzalny proces, tak, że mogę przenieść system produkcyjny i takie.

Author: Welbog, 2009-08-11

7 answers

Jak migrować na południe.

Powiedzmy, że mamy dwie aplikacje: wspólną i specyficzną:

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

Teraz chcemy przenieść model common.models.cat do konkretnej aplikacji (dokładnie do specific.models.cat). Najpierw wprowadź zmiany w kodzie źródłowym, a następnie uruchom:

$ 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

Teraz musimy edytować oba pliki migracji:

#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

Teraz obie aplikacje są świadome zmiany i życie jest do bani tylko trochę mniej :-) Ustalenie tej relacji między migracjami jest kluczem do sukces. Teraz jeśli zrobisz:

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

Zrobi zarówno migrację, jak i

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

Będzie migrować rzeczy w dół.

Zauważ, że do aktualizacji schematu używałem wspólnej aplikacji, a do obniżania, używałem konkretnej aplikacji. To dlatego, że zależność tutaj działa.

 181
Author: Potr Czachur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-27 00:30:21

Bazując na odpowiedzi Potr Czachur , sytuacje, które dotyczą obcokrajowców, są bardziej skomplikowane i powinny być traktowane nieco inaczej.

(poniższy przykład opiera się na aplikacjach common i specific, o których mowa w bieżącej odpowiedzi).

# common/models.py

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

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

Zmieniłby się na

# common/models.py

from specific.models import Cat

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

# specific/models.py

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

Running

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

Wygenerowałoby następujące migracje (celowo ignoruję zmiany typu contentType Django-patrz poprzednio odwołane odpowiedź jak sobie z tym poradzić):

# 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')

Jak widać, FK musi zostać zmieniony, aby odwołać się do nowej tabeli. Musimy dodać zależność tak, że wiemy, w jakiej kolejności będą zastosowane Migracje (a tym samym, że tabela będzie istnieć, zanim spróbujemy dodać FK do niej), ale musimy również upewnić się, że rolling backwards działa zbyt ponieważ zależność stosuje się w odwrotnym kierunku .

# 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

Na Południowej dokumentacji, depends_on zapewni, że 0004_auto__add_cat działa przed 0009_auto__del_cat podczas migracji do przodu, ale w odwrotnej kolejności podczas migracji do tyłu. Jeśli zostawimy db.rename_table('specific_cat', 'common_cat') w specific rollback, common rollback nie powiedzie się podczas próby migracji ForeignKey, ponieważ tabela, do której odnosi się tabela, nie istnieje.

Mam nadzieję, że jest to bliższe "realnej sytuacji" niż istniejące rozwiązania i ktoś uzna to za pomocne. Zdrowie!

 35
Author: Matt Briançon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:29:40

Modele nie są ściśle powiązane z aplikacjami, więc przenoszenie jest dość proste. Django używa nazwy aplikacji w nazwie tabeli bazy danych, więc jeśli chcesz przenieść swoją aplikację, możesz albo zmienić nazwę tabeli bazy danych za pomocą instrukcji SQL ALTER TABLE, albo-jeszcze prościej-po prostu użyć db_table parametr w klasie Meta Twojego modelu odnosi się do starej nazwy.

Jeśli do tej pory używałeś ContentTypes lub generic relations gdziekolwiek w kodzie, prawdopodobnie będziesz chciał zmienić nazwę app_label contenttype wskazuje na model, który się porusza, tak aby istniejące relacje były zachowane.

Oczywiście, jeśli nie masz żadnych danych do zachowania, najprostszą rzeczą do zrobienia jest całkowite porzucenie tabel bazy danych i ponowne uruchomienie ./manage.py syncdb.

 7
Author: Daniel Roseman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-11 06:40:17

Oto jeszcze jedna poprawka do doskonałego rozwiązania Potr. Dodaj do specific / 0003_create_cat

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

Jeśli ta zależność nie jest ustawiona na południe, nie zagwarantuje, że tabela common_cat istnieje w momencie uruchomienia specific/0003_create_cat, wywołując błąd django.db.utils.OperationalError: no such table: common_cat.

South uruchamia migracje w porządku leksykograficznym, chyba że zależność jest jawnie ustawiona. Ponieważ common jest przed specific Wszystkie migracje common zostaną uruchomione przed zmianą nazwy tabeli, więc prawdopodobnie nie odtworzyłaby się w oryginalnym przykładzie pokazanym przez Potr. Ale jeśli zmienisz nazwę common na app2 i specific na app1 napotkasz ten problem.

 4
Author: Ihor Kaharlichenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-24 10:38:22

Proces, który obecnie rozwiązałem, odkąd byłem tu kilka razy i zdecydowałem się go sformalizować.

To zostało zbudowane na odpowiedź Potra Czachura i odpowiedź Matta Briançona , using South 0.8.4

Krok 1. Poznaj relacje dziecka z kluczami obcymi

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

Więc w tym rozszerzonym przypadku, odkryliśmy inny powiązany model, taki jak:

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

    ...

Krok 2. Tworzenie migracji

# 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

Krok 3. Kontrola źródła: Commit zmiany na razie.

Sprawia, że proces jest bardziej powtarzalny, jeśli napotkasz konflikty scalające, takie jak koledzy z zespołu piszący migracje w zaktualizowanych aplikacjach.

Krok 4. Dodaj zależności między migracjami.

W zasadzie create_kittycat zależy od aktualnego stanu wszystkiego, a wszystko zależy od 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'),
    )
    ...

Krok 5. Zmiana nazwy tabeli, którą chcemy wprowadzić.

# 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

Krok 6. Tylko jeśli potrzebujesz backward() do działania i uruchomienia KeyError do tyłu.

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

Krok 7. Przetestuj - to, co działa dla mnie, może nie wystarczyć do twojej prawdziwej sytuacji życiowej:)

python manage.py migrate

# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>
 4
Author: pzrq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:13

Więc użycie oryginalnej odpowiedzi od @Potr powyżej nie zadziałało dla mnie na South 0.8.1 i Django 1.5.1. Zamieszczam co zrobił pracuj dla mnie poniżej w nadziei, że jest to pomocne dla innych.

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';")
 3
Author: Tim Sutton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-13 10:07:16

Podam bardziej wyraźną wersję jednej z rzeczy, które Daniel Roseman zasugerował w swojej odpowiedzi...

Jeśli po prostu zmienisz atrybut Meta db_table modelu, który przeniosłeś, aby wskazać istniejącą nazwę tabeli (zamiast nowej nazwy, którą podałby Django, gdybyś upuścił i zrobił syncdb), możesz uniknąć skomplikowanych migracji na południe. eg:

Oryginalny:

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

Po przeniesieniu:

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

Teraz wystarczy wykonać migrację danych, aby zaktualizować app_label dla {[6] } w tabeli django_content_type i powinno być dobrze, aby przejść...

Uruchom ./manage.py datamigration django update_content_type a następnie Edytuj plik, który South utworzy dla Ciebie:

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()
 1
Author: Anentropic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-23 08:00:57