So verschieben Sie ein Modell zwischen zwei Django-Apps (Django 1.7)

131

Vor ungefähr einem Jahr habe ich ein Projekt gestartet und wie alle neuen Entwickler habe ich mich nicht wirklich auf die Struktur konzentriert, aber jetzt, wo ich weiter mit Django zusammen bin, scheint es, dass mein Projektlayout, hauptsächlich meine Modelle, eine schreckliche Struktur haben .

Ich habe Modelle, die hauptsächlich in einer einzelnen App gespeichert sind, und die meisten dieser Modelle sollten sich in ihren eigenen Apps befinden. Ich habe versucht, dies zu beheben und sie nach Süden zu verschieben, fand es jedoch schwierig und aufgrund von Fremdschlüsseln sehr schwierig.

Gibt es aufgrund von Django 1.7 und der integrierten Unterstützung für Migrationen jetzt einen besseren Weg, dies zu tun?

Sam Buckingham
quelle
4
Möglicherweise möchten Sie die akzeptierte Antwort ändern.
Babken Vardanyan
Für Leute, die in Zukunft darauf stoßen : Django 3.x hier und der unter realpython.com/move-django-model/… beschriebene Ansatz haben für mich funktioniert. Ich hatte mehrere Fremdschlüssel zwischen Modellen in der alten App und Modellen in der neuen App.
Pradeepcep

Antworten:

16

Ich entferne die alte Antwort, da dies zu Datenverlust führen kann. Wie Ozan erwähnte , können wir in jeder App zwei Migrationen erstellen. Die Kommentare unter diesem Beitrag beziehen sich auf meine alte Antwort.

Erste Migration zum Entfernen des Modells aus der 1. App.

$ python manage.py makemigrations old_app --empty

Bearbeiten Sie die Migrationsdatei, um diese Vorgänge einzuschließen.

class Migration(migrations.Migration):

    database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]

    state_operations = [migrations.DeleteModel('TheModel')]

    operations = [
      migrations.SeparateDatabaseAndState(
        database_operations=database_operations,
        state_operations=state_operations)
    ]

Zweite Migration, die von der ersten Migration abhängt und die neue Tabelle in der 2. App erstellt. Nach dem Verschieben des Modellcodes in die 2. App

$ python manage.py makemigrations new_app 

und bearbeiten Sie die Migrationsdatei in etwa so.

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
ChillarAnand
quelle
Ich habe vorhandene Daten und viele davon kann ich einfach nicht verlieren, ist es möglicherweise damit zu tun?
Sam Buckingham
@ KevinChristopherHenry Den Code geändert. Dadurch bleiben die vorhandenen Daten erhalten.
ChillarAnand
@SamBuckingham Ja, Sie können versuchen, mit dem geänderten Code zu migrieren, ohne die Daten zu verlieren.
ChillarAnand
2
Ich denke, das wird wirklich der beste Weg sein. Danke für all die Hilfe, Jungs, es war großartig.
Sam Buckingham
1
IMO ist dies eine falsche Lösung. Die Grundannahme von Migrationen ist, dass ./manage.py migratealles in einem guten Zustand endet , wenn Sie es ausführen . Manuelles Fälschen von Migrationen ist IMO ein falscher Weg.
jb.
341

Dies kann ziemlich einfach mit durchgeführt werden migrations.SeparateDatabaseAndState. Grundsätzlich verwenden wir eine Datenbankoperation, um die Tabelle gleichzeitig mit zwei Statusoperationen umzubenennen, um das Modell aus dem Verlauf einer App zu entfernen und in einer anderen zu erstellen.

Aus alter App entfernen

python manage.py makemigrations old_app --empty

In der Migration:

class Migration(migrations.Migration):

    dependencies = []

    database_operations = [
        migrations.AlterModelTable('TheModel', 'newapp_themodel')
    ]

    state_operations = [
        migrations.DeleteModel('TheModel')
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations)
    ]

Zur neuen App hinzufügen

Kopieren Sie zuerst das Modell in die model.py der neuen App, dann:

python manage.py makemigrations new_app

Dies erzeugt eine Migration mit einer naiven CreateModelOperation als einziger Operation. Schließen Sie das in eine SeparateDatabaseAndStateOperation ein, damit wir nicht versuchen, die Tabelle neu zu erstellen. Schließen Sie auch die vorherige Migration als Abhängigkeit ein:

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
Ozan
quelle
14
Wirklich gute Erklärung. Dies sollte die Antwort sein. Durch das Umbenennen der Tabelle vermeiden Sie den Verlust von Daten.
Remiz
11
Dies ist der beste Weg, und es ist weitaus besser als meiner. Anmerkung zu meiner Antwort hinzugefügt.
ChillarAnand
4
Ich habe dies getan, aber wenn ich danach "makemigrations" auf der newapp ausführe, wird eine AlterModelTable-Migration generiert, die in None umbenannt wird.
Diego Ponciano
4
Ich habe anhand dieser Anweisungen einen Weg gefunden, mein Problem zu lösen. Das Problem ist komplizierter, wenn Sie Fremdschlüsselreferenzen haben, die Pflichtfelder sind. Ich musste ein paar Schritte hinzufügen, um die Referenzen zu verschieben.
Nostalg.io
14
Aufgrund mehrerer Anfragen habe ich eine detaillierte Antwort auf FK-Modellmigrationen mit einem GitHub-Beispiel erstellt. stackoverflow.com/questions/30601107/…
Nostalg.io
26

Ich bin auf das gleiche Problem gestoßen. Ozans Antwort hat mir sehr geholfen, war aber leider nicht genug. In der Tat hatte ich mehrere ForeignKey-Links zu dem Modell, das ich verschieben wollte. Nach einigen Kopfschmerzen fand ich die Lösung und beschloss, sie zu veröffentlichen, um die Zeit der Leute zu lösen.

Sie benötigen 2 weitere Schritte:

  1. Bevor Sie etwas tun, ändern Sie alle Ihre ForeignKeyLinks TheModelin Integerfield. Dann rennepython manage.py makemigrations
  2. Nachdem Sie Ozans Schritte ausgeführt haben, konvertieren Sie Ihre Fremdschlüssel erneut: Zurücksetzen ForeignKey(TheModel)statt IntegerField(). Führen Sie dann die Migrationen erneut durch ( python manage.py makemigrations). Sie können dann migrieren und es sollte funktionieren ( python manage.py migrate)

Ich hoffe es hilft. Testen Sie es natürlich vor Ort, bevor Sie versuchen, schlechte Überraschungen zu vermeiden :)

otranzer
quelle
8
Was ist mit den ManyToManyField-Beziehungen?
Tomcounsell
1
@tomcounsell toller Kommentar, ich würde annehmen, indem ich ein bestimmtes Durchgangsmodell nur zum Zweck von Migrationen hinzufüge.
Viel
Da eine Viele-zu-Viele-Beziehung normalerweise nur eine Tabelle mit zwei Fremdschlüsseln ist, können Sie aus SQL-Sicht den Trick dieser Antwort anwenden. Um dies jedoch nur über Django zu erreichen, kann ich mir einen Ansatz vorstellen, der der Antwort von @ozan entspricht, mit der Ausnahme, dass der erste Schritt darin besteht, die an der MTM-Beziehung beteiligten Tabellen zu duplizieren (eine Version der Dupes in jeder App). Migrieren Sie alle Fremdschlüssel in die neue App und löschen Sie erst dann die Dupes in der alten App. Haftungsausschluss: Ich habe nicht getestet :)
Arnaud P
15

Wie ich es gemacht habe (getestet auf Django == 1.8, mit Postgres, also wahrscheinlich auch 1.7)

Lage

app1.IhrModell

aber Sie möchten, dass es zu: app2.YourModel geht

  1. Kopieren Sie YourModel (den Code) von App1 nach App2.
  2. füge dies zu app2 hinzu. DeinModell:

    Class Meta:
        db_table = 'app1_yourmodel'
  3. $ python manage.py makemigrations app2

  4. In app2 wird eine neue Migration (z. B. 0009_auto_something.py) mit einer Anweisung migrations.CreateModel () durchgeführt. Verschieben Sie diese Anweisung in die anfängliche Migration von app2 (z. B. 0001_initial.py) (es wird so sein, wie es immer dort war). Und jetzt entfernen Sie die erstellte Migration = 0009_auto_something.py

  5. So wie Sie sich verhalten, wie es app2.IhrModel schon immer war, entfernen Sie jetzt die Existenz von app1.IhrModell aus Ihren Migrationen. Bedeutung: Kommentieren Sie die CreateModel-Anweisungen und jede Anpassung oder Datenmigration aus, die Sie danach verwendet haben.

  6. Und natürlich muss jeder Verweis auf app1.YourModel durch Ihr Projekt in app2.YourModel geändert werden. Vergessen Sie auch nicht, dass alle möglichen Fremdschlüssel für app1.YourModel in Migrationen in app2.YourModel geändert werden müssen

  7. Wenn Sie nun $ python manage.py migrieren, hat sich nichts geändert, auch wenn Sie $ python manage.py makemigrations durchführen, wurde nichts Neues erkannt.

  8. Jetzt der letzte Schliff: Entfernen Sie die Klassen-Meta aus app2.IhrModell und führen Sie $ python manage.py makemigrations app2 && python manage.py migrate app2 aus (wenn Sie sich diese Migration ansehen, sehen Sie so etwas :)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),

table = None bedeutet, dass der Standardtabellenname verwendet wird, in diesem Fall app2_yourmodel.

  1. FERTIG, mit gespeicherten Daten.

PS Während der Migration wird angezeigt, dass der Inhaltstyp app1.yourmodel entfernt wurde und gelöscht werden kann. Sie können dem zustimmen, aber nur, wenn Sie es nicht verwenden. Wenn Sie stark davon abhängig sind, dass FKs für diesen Inhaltstyp intakt sind, antworten Sie noch nicht mit Ja oder Nein, sondern gehen Sie zu diesem Zeitpunkt manuell in die Datenbank, entfernen Sie den Inhaltstyp app2.Ihr Modell und benennen Sie den Inhaltstyp app1 um. yourmodel to app2.yourmodel, und fahren Sie dann fort, indem Sie mit Nein antworten.

Michael van de Waeter
quelle
3
Obwohl diese Lösung definitiv "hackiger" ist als die von @ ozan und definitiv mehr Bearbeitung benötigt, hat sie für mich gut funktioniert (und es ist in Ordnung, Migrationen zu bearbeiten - sie sollen laut Dokumentation bearbeitbar sein).
pgcd
1
Verwenden Sie möglicherweise auch die app_label = 'app1'Meta-Option.
Wtower
Genius! Für ForeignKey-Beziehungen hat das sehr gut funktioniert. Ich nehme an, dass dies auch für ManyToMany-Felder funktioniert.
Babken Vardanyan
Ich habe Ihre Schritte befolgt, aber das Feld in einem Modell, das zu app1 gehört, besteht aus einem Fremdschlüssel mit einer rekursiven Beziehung zum zu verschiebenden Modell (myModel). Wie field1 = models.ForeignKey('app1.myModel').bei der Migration erhalte ich einen ValueError, der besagt, dassfield1 was declared with a lazy reference to 'app1.myModel' but app 'app1' doesn't provide model 'MyModel'
Deesha
12

Ich bekomme nervöse Handcodierungsmigrationen (wie in Ozans Antwort gefordert ), daher werden im Folgenden die Strategien von Ozan und Michael kombiniert , um den Umfang der erforderlichen Handcodierung zu minimieren:

  1. Stellen Sie vor dem Verschieben von Modellen sicher, dass Sie mit einer sauberen Grundlinie arbeiten, indem Sie ausführen makemigrations.
  2. Verschieben Sie den Code für das Modell von app1nachapp2
  3. Wie von @Michael empfohlen, verweisen wir das neue Modell mit der db_tableOption Meta auf das "neue" Modell auf die alte Datenbanktabelle :

    class Meta:
        db_table = 'app1_yourmodel'
  4. Ausführen makemigrations. Dies wird CreateModelin app2und DeleteModelin generieren app1. Technisch gesehen beziehen sich diese Migrationen auf genau dieselbe Tabelle und würden die Tabelle entfernen (einschließlich aller Daten) und neu erstellen.

  5. In Wirklichkeit wollen (oder müssen) wir nichts mit dem Tisch anfangen. Wir brauchen nur Django, um zu glauben, dass die Änderung vorgenommen wurde. Per @ Ozans Antwort macht die state_operationsFlagge in SeparateDatabaseAndStatedies. Also verpacken wir alle migrationsEinträge in BEIDE MIGRATIONSDATEIEN mit SeparateDatabaseAndState(state_operations=[...]). Beispielsweise,

    operations = [
        ...
        migrations.DeleteModel(
            name='YourModel',
        ),
        ...
    ]

    wird

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
  6. Sie müssen auch sicherstellen, dass die neue "virtuelle" CreateModelMigration von jeder Migration abhängt, die die ursprüngliche Tabelle tatsächlich erstellt oder geändert hat . Wenn Ihre neuen Migrationen beispielsweise app2.migrations.0004_auto_<date>(für Create) und app1.migrations.0007_auto_<date>(für Delete) sind, ist dies am einfachsten:

    • Öffnen app1.migrations.0007_auto_<date>und kopieren Sie die app1Abhängigkeit (z ('app1', '0006...'),. B. ). Dies ist die "unmittelbar vorherige" Migration in app1und sollte Abhängigkeiten von der gesamten tatsächlichen Modellbildungslogik enthalten.
    • Öffnen app2.migrations.0004_auto_<date>Sie die soeben kopierte Abhängigkeit und fügen Sie sie in die dependenciesListe ein.

Wenn Sie eine ForeignKeyBeziehung zu dem Modell haben, das Sie verschieben, funktioniert dies möglicherweise nicht. Dies geschieht, weil:

  • Abhängigkeiten werden für die ForeignKeyÄnderungen nicht automatisch erstellt
  • Wir möchten die ForeignKeyÄnderungen nicht einschließen, state_operationsdaher müssen wir sicherstellen, dass sie von den Tabellenoperationen getrennt sind.

HINWEIS: Django 2.2 hat eine Warnung ( models.E028) hinzugefügt , die diese Methode unterbricht. Sie können es vielleicht umgehen, managed=Falseaber ich habe es nicht getestet.

Die "minimalen" Vorgänge unterscheiden sich je nach Situation, aber das folgende Verfahren sollte für die meisten / alle ForeignKeyMigrationen funktionieren :

  1. KOPIEREN Sie das Modell von app1bis app2, setzen Siedb_table , aber ändern Sie KEINE FK-Referenzen.
  2. Führen Sie die makemigrationsgesamte app2Migration aus und schließen Sie sie einstate_operations (siehe oben).
    • Fügen Sie wie oben eine Abhängigkeit in app2 CreateTableder neuesten app1Migration hinzu
  3. Zeigen Sie mit allen FK-Verweisen auf das neue Modell. Wenn Sie keine Zeichenfolgenreferenzen verwenden, verschieben Sie das alte Modell nach unten models.py(entfernen Sie es NICHT), damit es nicht mit der importierten Klasse konkurriert.
  4. Führen Sie aus, makemigrationsaber wickeln Sie nichts ein state_operations(die FK-Änderungen sollten tatsächlich stattfinden). Fügen Sie eine Abhängigkeit in allen ForeignKeyMigrationen (dh AlterField) zur CreateTableMigration in hinzu app2(Sie benötigen diese Liste für den nächsten Schritt, behalten Sie sie im Auge). Beispielsweise:

    • Suchen Sie die Migration, die das CreateModeleg enthält, app2.migrations.0002_auto_<date>und kopieren Sie den Namen dieser Migration.
    • Finden Sie alle Migrationen, die einen ForeignKey für dieses Modell haben (z. B. indem Sie nach app2.YourModelMigrationen suchen wie:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
          ]
      
          operations = [
              migrations.AlterField(
                  model_name='relatedmodel',
                  name='fieldname',
                  field=models.ForeignKey(... to='app2.YourModel'),
              ),
          ]
    • Fügen Sie die CreateModelMigration als Abhängigkeit hinzu:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
              ('app2', '0002_auto_<date>'),
          ]  
  5. Entfernen Sie die Modelle aus app1

  6. Führen Sie makemigrationsdie app1Migration aus und schließen Sie sie ein state_operations.
    • Fügen Sie allen ForeignKeyMigrationen (dh AlterField) aus dem vorherigen Schritt eine Abhängigkeit hinzu (einschließlich Migrationen in app1und app2).
    • Als ich diese Migrationen erstellte, DeleteTablehing die bereits von den AlterFieldMigrationen ab, sodass ich sie nicht manuell erzwingen musste (dh Altervorher Delete).

An diesem Punkt ist Django gut zu gehen. Das neue Modell verweist auf die alte Tabelle und Djangos Migrationen haben ihn davon überzeugt, dass alles angemessen verschoben wurde. Die große Einschränkung (aus @ Michaels Antwort) ist, dass ContentTypefür das neue Modell ein neues erstellt wird. Wenn Sie ForeignKeymit Inhaltstypen verknüpfen (z. B. nach ), müssen Sie eine Migration erstellen, um die ContentTypeTabelle zu aktualisieren .

Ich wollte nach mir selbst aufräumen (Meta-Optionen und Tabellennamen), also habe ich das folgende Verfahren angewendet (von @Michael):

  1. Entferne das db_table Meta-Eintrag
  2. Lauf makemigrations erneut aus, um die Datenbankumbenennung zu generieren
  3. Bearbeiten Sie diese letzte Migration und stellen Sie sicher, dass sie von der DeleteTableMigration abhängt . Es scheint nicht notwendig zu sein, da Deletedies rein logisch sein sollte, aber ich bin auf Fehler gestoßen (zB app1_yourmodelexistiert nicht), wenn ich es nicht tue.
Claytond
quelle
Das hat perfekt funktioniert, danke! Ich denke nicht, dass das Bearbeiten der letzten Migration wichtig ist, da dies ohnehin am Ende des Abhängigkeitsbaums steht.
James Meakin
1
Gute Antwort! Ich denke, Sie müssen Migrationen eine schließende Klammer hinzufügen. SeparateDatabaseAndState, richtig?
atm
Das hat bei mir funktioniert. Ich habe auch die letzte Migration (Schritt 3, die allerletzte Zeile der gesamten Antwort) nicht wie @JamesMeakin bearbeitet und es hat immer noch gut funktioniert
Megawatt
Im zweiten Szenario, dem mit FKs, schlug der zweite Schritt für mich mit einem sinnvollen Fehler fehl:table_name: (models.E028) db_table 'table_name' is used by multiple models: app1.Model, app2.Model.
Mihai Zamfir
Ich habe das Verfahren ein paar Mal angewendet. Wenn Sie die Dokumentation für 2.2 ( docs.djangoproject.com/de/2.2/ref/checks ) und 2.1 ( docs.djangoproject.com/de/2.1/ref/checks ) vergleichen, sehen Sie, dass sie in 2.2 hinzugefügt wurde. Es kann möglich sein, damit umzugehen, managed=Falseaber ich bin nicht in der Lage, dies zu überprüfen.
Claytond
1

Eine andere hackige Alternative, wenn die Daten nicht groß oder zu kompliziert sind, aber dennoch wichtig zu pflegen sind, ist:

  • Holen Sie sich Daten-Fixtures mit manage.py dumpdata
  • Fahren Sie mit dem ordnungsgemäßen Modellieren von Änderungen und Migrationen fort, ohne die Änderungen in Beziehung zu setzen
  • Global Ersetzen Sie die Geräte von den alten Modell- und App-Namen zu den neuen
  • Laden Sie Daten mit manage.py loaddata
Wtower
quelle
1

Von meiner Antwort unter https://stackoverflow.com/a/47392970/8971048 kopiert

Wenn Sie das Modell verschieben müssen und keinen Zugriff mehr auf die App haben (oder keinen Zugriff mehr möchten), können Sie eine neue Operation erstellen und in Betracht ziehen, nur dann ein neues Modell zu erstellen, wenn das migrierte Modell dies nicht tut existieren.

In diesem Beispiel übergebe ich 'MyModel' von old_app an myapp.

class MigrateOrCreateTable(migrations.CreateModel):
    def __init__(self, source_table, dst_table, *args, **kwargs):
        super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
        self.source_table = source_table
        self.dst_table = dst_table

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        table_exists = self.source_table in schema_editor.connection.introspection.table_names()
        if table_exists:
            with schema_editor.connection.cursor() as cursor:
                cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
        else:
            return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_some_migration'),
    ]

    operations = [
        MigrateOrCreateTable(
            source_table='old_app_mymodel',
            dst_table='myapp_mymodel',
            name='MyModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=18))
            ],
        ),
    ]
Gal Sänger
quelle
Bitte fügen Sie nicht dieselbe Antwort auf mehrere Fragen hinzu. Beantworten Sie die beste Frage und markieren Sie den Rest als Duplikate. Siehe Ist es akzeptabel, mehrere Fragen doppelt zu beantworten?
Petter Friberg
0

Dies wird grob getestet, vergessen Sie also nicht, Ihre Datenbank zu sichern !!!

Zum Beispiel gibt es zwei Apps: src_appund dst_appwir möchten das Modell MoveMevon src_appnach verschieben dst_app.

Erstellen Sie leere Migrationen für beide Apps:

python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app

Nehmen wir an, dass neue Migrationen XXX1_src_app_newund XXX1_dst_app_new, die vorherigen Top-Migrationen XXX0_src_app_oldund sind XXX0_dst_app_old.

Fügen Sie eine Operation hinzu, die die Tabelle für das MoveMeModell umbenennt und das app_label in ProjectState in umbenennt XXX1_dst_app_new. Vergessen Sie nicht, die Abhängigkeit von der XXX0_src_app_oldMigration hinzuzufügen . Die resultierende XXX1_dst_app_newMigration ist:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

# this operations is almost the same as RenameModel
# https://github.com/django/django/blob/1.7/django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):

    def __init__(self, name, old_app_label):
        self.name = name
        self.old_app_label = old_app_label

    def state_forwards(self, app_label, state):

        # Get all of the related objects we need to repoint
        apps = state.render(skip_cache=True)
        model = apps.get_model(self.old_app_label, self.name)
        related_objects = model._meta.get_all_related_objects()
        related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
        # Rename the model
        state.models[app_label, self.name.lower()] = state.models.pop(
            (self.old_app_label, self.name.lower())
        )
        state.models[app_label, self.name.lower()].app_label = app_label
        for model_state in state.models.values():
            try:
                i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
                model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
            except ValueError:
                pass
        # Repoint the FKs and M2Ms pointing to us
        for related_object in (related_objects + related_m2m_objects):
            # Use the new related key for self referential related objects.
            if related_object.model == model:
                related_key = (app_label, self.name.lower())
            else:
                related_key = (
                    related_object.model._meta.app_label,
                    related_object.model._meta.object_name.lower(),
                )
            new_fields = []
            for name, field in state.models[related_key].fields:
                if name == related_object.field.name:
                    field = field.clone()
                    field.rel.to = "%s.%s" % (app_label, self.name)
                new_fields.append((name, field))
            state.models[related_key].fields = new_fields

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        old_apps = from_state.render()
        new_apps = to_state.render()
        old_model = old_apps.get_model(self.old_app_label, self.name)
        new_model = new_apps.get_model(app_label, self.name)
        if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
            # Move the main table
            schema_editor.alter_db_table(
                new_model,
                old_model._meta.db_table,
                new_model._meta.db_table,
            )
            # Alter the fields pointing to us
            related_objects = old_model._meta.get_all_related_objects()
            related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
            for related_object in (related_objects + related_m2m_objects):
                if related_object.model == old_model:
                    model = new_model
                    related_key = (app_label, self.name.lower())
                else:
                    model = related_object.model
                    related_key = (
                        related_object.model._meta.app_label,
                        related_object.model._meta.object_name.lower(),
                    )
                to_field = new_apps.get_model(
                    *related_key
                )._meta.get_field_by_name(related_object.field.name)[0]
                schema_editor.alter_field(
                    model,
                    related_object.field,
                    to_field,
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        self.old_app_label, app_label = app_label, self.old_app_label
        self.database_forwards(app_label, schema_editor, from_state, to_state)
        app_label, self.old_app_label = self.old_app_label, app_label

    def describe(self):
        return "Move %s from %s" % (self.name, self.old_app_label)


class Migration(migrations.Migration):

    dependencies = [
       ('dst_app', 'XXX0_dst_app_old'),
       ('src_app', 'XXX0_src_app_old'),
    ]

    operations = [
        MoveModelFromOtherApp('MoveMe', 'src_app'),
    ]

Fügen Sie die Abhängigkeit XXX1_dst_app_newzu hinzu XXX1_src_app_new. XXX1_src_app_newist eine No-Op-Migration, die erforderlich ist, um sicherzustellen, dass zukünftige src_appMigrationen danach ausgeführt werdenXXX1_dst_app_new .

Bewegen Sie sich MoveMevon src_app/models.pynach dst_app/models.py. Dann renne:

python manage.py migrate

Das ist alles!

Sergey Fedoseev
quelle
Beachten Sie, dass dieser Code wahrscheinlich nur für Django 1.7 nützlich ist. Der Versuch in Django 2.0 wird nicht funktionieren. Dies bedeutet auch, dass die Verwendung dieses Mechanismus zum Verschieben von Modellen den Wartungsaufwand für das Upgrade Ihrer Django-Version erhöht.
Paul in 't Hout
0

Sie können Folgendes versuchen (ungetestet):

  1. Bewegen Sie das Modell von src_appnachdest_app
  2. migrieren dest_app; Stellen Sie sicher, dass die Schemamigration von der neuesten src_appMigration abhängt ( https://docs.djangoproject.com/de/dev/topics/migrations/#migration-files ).
  3. Fügen Sie eine Datenmigration hinzu dest_app, von der alle Daten kopiert werdensrc_app
  4. migrieren src_app; Stellen Sie sicher, dass die Schemamigration von der letzten (Daten-) Migration von abhängt dest_app- dh von der Migration von Schritt 3

Beachten Sie, dass Sie sein werden die gesamte Tabelle kopieren , anstatt sie zu verschieben. Auf diese Weise müssen beide Apps jedoch keine Tabelle berühren, die zur anderen App gehört, was meiner Meinung nach wichtiger ist.

Webthusiast
quelle
0

Nehmen wir an, Sie verschieben das Modell TheModel von app_a nach app_b.

Eine alternative Lösung besteht darin, die vorhandenen Migrationen von Hand zu ändern. Die Idee ist, dass Sie jedes Mal, wenn Sie eine Operation sehen, die TheModel in den Migrationen von app_a ändert, diese Operation bis zum Ende der anfänglichen Migration von app_b kopieren. Und jedes Mal, wenn Sie in den Migrationen von app_a eine Referenz 'app_a.TheModel' sehen, ändern Sie diese in 'app_b.TheModel'.

Ich habe dies gerade für ein vorhandenes Projekt getan, bei dem ich ein bestimmtes Modell in eine wiederverwendbare App extrahieren wollte. Das Verfahren verlief reibungslos. Ich denke, es wäre viel schwieriger, wenn es Verweise von app_b auf app_a gäbe. Außerdem hatte ich eine manuell definierte Meta.db_table für mein Modell, die möglicherweise geholfen hat.

Insbesondere wird der Migrationsverlauf geändert. Dies spielt keine Rolle, selbst wenn Sie eine Datenbank mit den ursprünglich angewendeten Migrationen haben. Wenn sowohl die ursprüngliche als auch die umgeschriebene Migration dasselbe Datenbankschema haben, sollte eine solche Umschreibung in Ordnung sein.

akaariai
quelle
0
  1. Ändern Sie die Namen alter Modelle in 'model_name_old'.
  2. Auswanderungen
  3. Erstellen Sie neue Modelle mit dem Namen 'model_name_new' mit identischen Beziehungen zu den zugehörigen Modellen (z. B. hat das Benutzermodell jetzt user.blog_old und user.blog_new).
  4. Auswanderungen
  5. Schreiben Sie eine benutzerdefinierte Migration, die alle Daten in die neuen Modelltabellen migriert
  6. Testen Sie diese Migrationen, indem Sie Backups mit neuen Datenbankkopien vor und nach dem Ausführen der Migrationen vergleichen
  7. Wenn alles zufriedenstellend ist, löschen Sie die alten Modelle
  8. Auswanderungen
  9. Ändern Sie die neuen Modelle in den richtigen Namen 'model_name_new' -> 'model_name'
  10. Testen Sie die gesamte Anzahl der Migrationen auf einem Staging-Server
  11. Nehmen Sie Ihre Produktionsstätte für einige Minuten herunter, um alle Migrationen auszuführen, ohne dass Benutzer eingreifen

Tun Sie dies individuell für jedes Modell, das verschoben werden muss. Ich würde nicht vorschlagen, das zu tun, was in der anderen Antwort steht, indem Sie zu Ganzzahlen und zurück zu Fremdschlüsseln wechseln. Es besteht die Möglichkeit, dass neue Fremdschlüssel unterschiedlich sind und Zeilen nach den Migrationen möglicherweise andere IDs haben, und ich wollte kein Risiko eingehen von nicht übereinstimmenden IDs beim Zurückschalten auf Fremdschlüssel.

Tomcounsell
quelle