Tak więc około rok temu zacząłem projekt i jak wszyscy nowi programiści nie skupiałem się zbytnio na strukturze, jednak teraz jestem dalej razem z Django zaczęło się okazywać, że układ mojego projektu głównie moje modele są okropne w strukturze .
Mam modele głównie przechowywane w jednej aplikacji i tak naprawdę większość z tych modeli powinna znajdować się w osobnych aplikacjach. Próbowałem rozwiązać ten problem i przenieść je na południe, ale uznałem to za trudne i naprawdę trudne z powodu obcych kluczy itp.
Jednak dzięki Django 1.7 i wbudowanej obsłudze migracji, czy istnieje lepszy sposób na zrobienie tego teraz?
Odpowiedzi:
Usuwam starą odpowiedź, ponieważ może to spowodować utratę danych. Jak wspomniano ozan , możemy utworzyć 2 migracje, po jednej w każdej aplikacji. Komentarze pod tym postem odnoszą się do mojej starej odpowiedzi.
Pierwsza migracja w celu usunięcia modelu z pierwszej aplikacji.
$ python manage.py makemigrations old_app --empty
Edytuj plik migracji, aby uwzględnić te operacje.
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) ]
Druga migracja, która zależy od pierwszej migracji i utwórz nową tabelę w drugiej aplikacji. Po przeniesieniu kodu modelu do drugiej aplikacji
i edytuj plik migracji na coś takiego.
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) ]
źródło
./manage.py migrate
wszystko skończy się w dobrym stanie. Ręczne fałszowanie migracji to zły sposób IMO.Można to zrobić dość łatwo za pomocą
migrations.SeparateDatabaseAndState
. Zasadniczo używamy operacji bazy danych do zmiany nazwy tabeli jednocześnie z dwiema operacjami stanu, aby usunąć model z historii jednej aplikacji i utworzyć go w historii innej.Usuń ze starej aplikacji
python manage.py makemigrations old_app --empty
W migracji:
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) ]
Dodaj do nowej aplikacji
Najpierw skopiuj model do pliku model.py nowej aplikacji, a następnie:
python manage.py makemigrations new_app
Spowoduje to migrację z
CreateModel
operacją naiwną jako jedyną operacją. Zawiń to wSeparateDatabaseAndState
operacji, aby nie próbować odtwarzać tabeli. Uwzględnij również wcześniejszą migrację jako zależność: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) ]
źródło
Napotkałem ten sam problem. Odpowiedź Ozana bardzo mi pomogła, ale niestety nie wystarczyła. Rzeczywiście, miałem kilka kluczy ForeignKey łączących się z modelem, który chciałem przenieść. Po pewnym bólu głowy znalazłem rozwiązanie, więc postanowiłem je opublikować, aby rozwiązać problem ludzi.
Potrzebujesz jeszcze 2 kroków:
ForeignKey
linkiTheModel
naIntegerfield
. Następnie uruchomićpython manage.py makemigrations
ForeignKey(TheModel)
zamiastIntegerField()
. Następnie wykonaj migracje ponownie (python manage.py makemigrations
). Możesz następnie przeprowadzić migrację i powinno działać (python manage.py migrate
)Mam nadzieję, że to pomoże. Oczywiście przetestuj go lokalnie, zanim spróbujesz w produkcji, aby uniknąć przykrych niespodzianek :)
źródło
Jak to zrobiłem (testowałem na Django == 1.8, z postgres, więc pewnie też 1.7)
Sytuacja
app1.YourModel
ale chcesz, aby trafił do: app2.YourModel
dodaj to do app2.YourModel:
Class Meta: db_table = 'app1_yourmodel'
$ python manage.py makemigrations app2
Nowa migracja (np. 0009_auto_something.py) jest wykonywana w app2 z instrukcją migrations.CreateModel (), przenieś tę instrukcję do początkowej migracji app2 (np. 0001_initial.py) (będzie tak, jak zawsze tam była). A teraz usuń utworzoną migrację = 0009_auto_something.py
Tak jak działasz, tak jak app2.YourModel zawsze tam był, teraz usuń istnienie app1.YourModel z migracji. Znaczenie: zakomentuj instrukcje CreateModel i każdą korektę lub migrację danych użytą później.
I oczywiście każde odniesienie do app1.YourModel musi zostać zmienione w ramach projektu na app2.YourModel. Nie zapominaj również, że wszystkie możliwe klucze obce do app1.YourModel w migracjach muszą zostać zmienione na app2.
Teraz, jeśli wykonasz $ python manage.py migrate, nic się nie zmieniło, również kiedy wykonujesz makemigracje $ python manage.py, nic nowego nie zostało wykryte.
Teraz ostatnie poprawki: usuń metę klasy z app2.YourModel i wykonaj $ python manage.py makemigrations app2 && python manage.py migrate app2 (jeśli spojrzysz na tę migrację, zobaczysz coś takiego :)
migrations.AlterModelTable( name='yourmodel', table=None, ),
table = None, oznacza, że przyjmie domyślną nazwę tabeli, która w tym przypadku będzie app2_yourmodel.
PS podczas migracji zobaczy, że ten content_type app1.yourmodel został usunięty i można go usunąć. Możesz powiedzieć tak, ale tylko wtedy, gdy go nie używasz. Jeśli w dużym stopniu zależy Ci na tym, aby elementy FK do tego typu zawartości pozostały nienaruszone, nie odpowiadaj jeszcze tak lub nie, ale przejdź do bazy danych w tym czasie ręcznie i usuń typ zawartości app2.yourmodel i zmień nazwę typu zawartości app1. yourmodel do app2.yourmodel, a następnie kontynuuj, odpowiadając nie.
źródło
app_label = 'app1'
opcji meta.field1 = models.ForeignKey('app1.myModel').
kiedyfield1 was declared with a lazy reference to 'app1.myModel' but app 'app1' doesn't provide model 'MyModel'
Mam nerwowe migracje ręcznego kodowania (zgodnie z wymogami odpowiedzi Ozana ), więc poniższe łączy strategie Ozana i Michaela, aby zminimalizować ilość wymaganego ręcznego kodowania:
makemigrations
.app1
doapp2
Zgodnie z zaleceniami @Michael, wskazujemy nowy model na starą tabelę bazy danych, używając
db_table
opcji Meta na „nowym” modelu:class Meta: db_table = 'app1_yourmodel'
Biegnij
makemigrations
. Spowoduje to wygenerowanieCreateModel
wapp2
iDeleteModel
wapp1
. Technicznie rzecz biorąc, te migracje odnoszą się dokładnie do tej samej tabeli i spowodowałyby usunięcie (w tym wszystkie dane) i ponowne utworzenie tabeli.W rzeczywistości nie chcemy (ani nie musimy) nic robić przy stole. Potrzebujemy tylko Django, aby uwierzyć, że zmiana została dokonana. Zgodnie z odpowiedzią @ Ozana,
state_operations
flaga wSeparateDatabaseAndState
to robi. Więc zawijamy wszystkiemigrations
wpisy W OBU PLIKACH MIGRACJI zSeparateDatabaseAndState(state_operations=[...])
. Na przykład,operations = [ ... migrations.DeleteModel( name='YourModel', ), ... ]
staje się
operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name='YourModel', ), ... ]) ]
Należy również upewnić się, że nowa
CreateModel
migracja „wirtualna” zależy od migracji, która faktycznie utworzyła lub zmieniła oryginalną tabelę . Na przykład, jeśli Twoje nowe migracje sąapp2.migrations.0004_auto_<date>
(dlaCreate
) iapp1.migrations.0007_auto_<date>
(dlaDelete
), najprostszą rzeczą do zrobienia jest:app1.migrations.0007_auto_<date>
i skopiuj jegoapp1
zależność (np('app1', '0006...'),
.). Jest to migracja „bezpośrednio poprzedzająca”app1
i powinna obejmować zależności od całej faktycznej logiki budowania modelu.app2.migrations.0004_auto_<date>
i dodaj właśnie skopiowaną zależność do jejdependencies
listy.Jeśli masz
ForeignKey
powiązania z modelem, który przenosisz, powyższe może nie działać. Dzieje się tak, ponieważ:ForeignKey
zmianForeignKey
zmian,state_operations
więc musimy upewnić się, że są one oddzielone od operacji na tabeli.UWAGA: Django 2.2 dodało ostrzeżenie (
models.E028
), które przerywa tę metodę. Możesz sobie z tym poradzić,managed=False
ale nie testowałem tego.„Minimalny” zestaw operacji różni się w zależności od sytuacji, ale następująca procedura powinna działać w przypadku większości / wszystkich
ForeignKey
migracji:app1
naapp2
, ustawdb_table
, ale NIE zmieniaj żadnych odniesień FK.makemigrations
i zawiń całąapp2
migracjęstate_operations
(patrz wyżej)app2
CreateTable
do ostatniejapp1
migracjimodels.py
(NIE usuwaj go), aby nie konkurował z importowaną klasą.Uruchom,
makemigrations
ale NIE zawijaj niczegostate_operations
(zmiany FK powinny faktycznie nastąpić). Dodaj zależność we wszystkichForeignKey
migracjach (tj.AlterField
) DoCreateTable
migracji wapp2
(będziesz potrzebować tej listy w następnym kroku, więc śledź je). Na przykład:CreateModel
np.app2.migrations.0002_auto_<date>
I skopiuj nazwę tej migracji.Znajdź wszystkie migracje, które mają ForeignKey do tego modelu (np. Wyszukując
app2.YourModel
migracje takie jak:class Migration(migrations.Migration): dependencies = [ ('otherapp', '0001_initial'), ] operations = [ migrations.AlterField( model_name='relatedmodel', name='fieldname', field=models.ForeignKey(... to='app2.YourModel'), ), ]
Dodaj
CreateModel
migrację jako zależność:class Migration(migrations.Migration): dependencies = [ ('otherapp', '0001_initial'), ('app2', '0002_auto_<date>'), ]
Usuń modele z
app1
makemigrations
i zawińapp1
migracjęstate_operations
.ForeignKey
migracji (tj.AlterField
) Z poprzedniego kroku (może obejmować migracje doapp1
iapp2
).DeleteTable
już zależało odAlterField
migracji, więc nie musiałem ich ręcznie wymuszać (tj.Alter
WcześniejDelete
).W tym momencie Django jest gotowe. Nowy model wskazuje na starą tabelę, a migracje Django przekonały go, że wszystko zostało odpowiednio przeniesione. Dużym zastrzeżeniem (z odpowiedzi @ Michael) jest to, że
ContentType
nowy model jest tworzony. Jeśli łączysz (np. PrzezForeignKey
) z typami zawartości, musisz utworzyć migrację, aby zaktualizowaćContentType
tabelę.Chciałem wyczyścić po sobie (opcje Meta i nazwy tabel), więc zastosowałem następującą procedurę (od @Michael):
db_table
wpis Metamakemigrations
ponownie, aby wygenerować zmianę nazwy bazy danychDeleteTable
migracji. Wydaje się, że nie powinno to być konieczne, ponieważDelete
powinno być czysto logiczne, ale napotykam błędy (np.app1_yourmodel
Nie istnieje), jeśli nie.źródło
table_name: (models.E028) db_table 'table_name' is used by multiple models: app1.Model, app2.Model.
managed=False
ale nie mam gdzie to sprawdzić.Inną hackerską alternatywą, jeśli dane nie są duże lub zbyt skomplikowane, ale nadal ważne do utrzymania, jest:
źródło
Skopiowano z mojej odpowiedzi na https://stackoverflow.com/a/47392970/8971048
W przypadku, gdy musisz przenieść model i nie masz już dostępu do aplikacji (lub nie chcesz dostępu), możesz utworzyć nową operację i rozważyć utworzenie nowego modelu tylko wtedy, gdy migrowany model nie ma istnieć.
W tym przykładzie przekazuję „MyModel” z old_app do 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)) ], ), ]
źródło
Jest to testowane z grubsza, więc nie zapomnij wykonać kopii zapasowej bazy danych !!!
Na przykład istnieją dwie aplikacje:
src_app
idst_app
, chcemy przenieść modelMoveMe
zsrc_app
dodst_app
.Utwórz puste migracje dla obu aplikacji:
python manage.py makemigrations --empty src_app python manage.py makemigrations --empty dst_app
Załóżmy, że nowe migracje to,
XXX1_src_app_new
aXXX1_dst_app_new
poprzednie najpopularniejsze migracje toXXX0_src_app_old
iXXX0_dst_app_old
.Dodaj operację, która zmienia nazwę tabeli dla
MoveMe
modelu i zmienia nazwę jej app_label w ProjectState naXXX1_dst_app_new
. Nie zapomnij dodać zależności odXXX0_src_app_old
migracji. WynikowaXXX1_dst_app_new
migracja to:# -*- 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'), ]
Dodaj zależność od
XXX1_dst_app_new
doXXX1_src_app_new
.XXX1_src_app_new
to migracja bez operacji, która jest potrzebna, aby upewnić się, że przyszłesrc_app
migracje zostaną wykonane późniejXXX1_dst_app_new
.Przejdź
MoveMe
zsrc_app/models.py
dodst_app/models.py
. Następnie uruchomić:python manage.py migrate
To wszystko!
źródło
Możesz spróbować następujących rzeczy (nieprzetestowane):
src_app
dodest_app
dest_app
; upewnij się, że migracja schematu zależy od najnowszejsrc_app
migracji ( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files )dest_app
, która kopiuje wszystkie dane zsrc_app
src_app
; upewnij się, że migracja schematu zależy od ostatniej migracji (danych)dest_app
- to znaczy migracji z kroku 3Pamiętaj, że będziesz kopiować zamiast przenosić całą tabelę, cały stół, ale w ten sposób obie aplikacje nie muszą dotykać tabeli należącej do drugiej aplikacji, co moim zdaniem jest ważniejsze.
źródło
Powiedzmy, że przenosisz model TheModel z app_a do app_b.
Alternatywnym rozwiązaniem jest ręczna zmiana istniejących migracji. Chodzi o to, że za każdym razem, gdy widzisz operację zmieniającą TheModel w migracjach app_a, kopiujesz tę operację na koniec początkowej migracji app_b. Za każdym razem, gdy zobaczysz odniesienie „app_a.TheModel” w migracjach app_a, zmieniasz je na „app_b.TheModel”.
Zrobiłem to tylko dla istniejącego projektu, w którym chciałem wyodrębnić określony model do aplikacji wielokrotnego użytku. Procedura przebiegła sprawnie. Myślę, że byłoby znacznie trudniej, gdyby istniały odniesienia od app_b do app_a. Miałem również ręcznie zdefiniowaną tabelę Meta.db_table dla mojego modelu, co mogło pomóc.
Warto zauważyć, że skończysz ze zmienioną historią migracji. Nie ma to znaczenia, nawet jeśli masz bazę danych z zastosowanymi oryginalnymi migracjami. Jeśli zarówno migracje pierwotne, jak i przepisane, kończą się tym samym schematem bazy danych, takie przepisanie powinno być OK.
źródło
Zrób to indywidualnie dla każdego modelu, który ma zostać przeniesiony. Nie sugerowałbym robienia tego, co mówi druga odpowiedź, zmieniając na liczby całkowite iz powrotem na klucze obce Istnieje szansa, że nowe klucze obce będą inne, a wiersze mogą mieć inne identyfikatory po migracji i nie chciałem ryzykować niezgodnych identyfikatorów podczas przełączania z powrotem na klucze obce.
źródło