Strategia migracji Django do zmiany nazwy modelu i pól relacji

153

Planuję zmienić nazwy kilku modeli w istniejącym projekcie Django, w którym istnieje wiele innych modeli, które mają relacje klucza obcego z modelami, które chciałbym zmienić. Jestem prawie pewien, że będzie to wymagało wielu migracji, ale nie jestem pewien dokładnej procedury.

Powiedzmy, że zacznę od następujących modeli w aplikacji Django o nazwie myapp:

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Chcę zmienić nazwę Foomodelu, ponieważ nazwa tak naprawdę nie ma sensu i powoduje zamieszanie w kodzie i Barsprawiłaby, że nazwa byłaby znacznie jaśniejsza.

Z tego, co przeczytałem w dokumentacji deweloperskiej Django, przyjmuję następującą strategię migracji:

Krok 1

Modyfikuj models.py:

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)  # <-- changed relation, but not field name
    is_ridonkulous = models.BooleanField()

Zwróć uwagę, że AnotherModelnazwa pola foonie zmienia się, ale relacja jest aktualizowana doBar modelu. Moje rozumowanie jest takie, że nie powinienem zmieniać zbyt wiele na raz i że gdybym zmienił nazwę tego pola na bar, ryzykowałbym utratę danych w tej kolumnie.

Krok 2

Utwórz pustą migrację:

python manage.py makemigrations --empty myapp

Krok 3

Edytuj Migrationklasę w pliku migracji utworzonym w kroku 2, aby dodać RenameModeloperację do listy operacji:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Krok 4

Zastosuj migrację:

python manage.py migrate

Krok 5

Edytuj powiązane nazwy pól w models.py:

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Krok 6

Utwórz kolejną pustą migrację:

python manage.py makemigrations --empty myapp

Krok 7

Edytuj Migrationklasę w pliku migracji utworzonym w kroku 6, aby dodać RenameFieldoperację (e) dla wszystkich powiązanych nazw pól do listy operacji:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_rename_fields'),  # <-- is this okay?
    ]

    operations = [
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Krok 8

Zastosuj drugą migrację:

python manage.py migrate

Oprócz aktualizacji reszty kodu (widoków, formularzy itp.) W celu odzwierciedlenia nowych nazw zmiennych, czy w zasadzie tak będzie działać nowa funkcja migracji?

Wydaje się również, że jest to wiele kroków. Czy można w jakiś sposób skondensować operacje migracyjne?

Dzięki!

Piątka
źródło

Odpowiedzi:

127

Więc kiedy próbowałem tego, wydaje się, że możesz skondensować krok 3-7:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'), 
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar'),
        migrations.RenameField('AnotherModel', 'foo', 'bar'),
        migrations.RenameField('YetAnotherModel', 'foo', 'bar')
    ]

Mogą pojawić się błędy, jeśli nie zaktualizujesz nazw, do których jest zaimportowany, np. Admin.py, a nawet starsze pliki migracji (!).

Aktualizacja : Jak wspomina ceasaro , nowsze wersje Django są zwykle w stanie wykryć i zapytać, czy nazwa modelu została zmieniona. Więc spróbuj manage.py makemigrationsnajpierw, a następnie sprawdź plik migracji.

wasabigeek
źródło
Dziękuję za odpowiedź. Od tego czasu przeprowadziłem migrację, wykonując opisane przeze mnie kroki, ale jestem ciekawy, czy próbowałeś tego z istniejącymi danymi, czy tylko z pustą bazą danych?
Piątek
2
Wypróbowałem to z istniejącymi danymi, choć tylko kilka wierszy na sqlite w moim lokalnym środowisku env (kiedy przechodzę do produkcji, zamierzam wymazać wszystko, w tym pliki migracji)
wasabigeek
4
Nie musisz zmieniać nazwy modelu w plikach migracji, jeśli używasz apps.get_modelw nich. zajęło mi dużo czasu, żeby to rozgryźć.
ahmed
9
W django 2.0, jeśli zmienisz nazwę swojego modelu, ./manage.py makemigrations myapppolecenie zapyta Cię, czy zmieniłeś nazwę swojego modelu. Np .: Czy zmieniłeś nazwę modelu myapp.Foo na Bar? [t / N] Jeśli odpowiesz „t”, migracja będzie zawierała te migration.RenameModel('Foo', 'Bar')same liczby dla pól o zmienionych
nazwach
1
manage.py makemigrations myappmoże nadal się nie powieść: „Może być konieczne ręczne dodanie tego elementu, jeśli zmienisz nazwę modelu i kilka jego pól naraz; dla autodetektora będzie to wyglądać tak, jakbyś usunął model ze starą nazwą i dodał nowy z inną nazwę, a utworzona migracja spowoduje utratę wszystkich danych ze starej tabeli ”. Django 2.1 Docs Dla mnie wystarczyło stworzyć pustą migrację, dodać do niej przemianowania modelu, a następnie uruchomić makemigrationsjak zwykle.
hlongmore
37

Początkowo myślałem, że metoda Piątka zadziałała, ponieważ migracja działała dobrze do kroku 4. Jednak niejawne zmiany „ForeignKeyField (Foo)” na „ForeignKeyField (Bar)” nie były powiązane z żadną migracją. Dlatego migracja nie powiodła się, gdy chciałem zmienić nazwy pól relacji (krok 5-8). Może to wynikać z faktu, że moje „AnotherModel” i „YetAnotherModel” są wysyłane w innych aplikacjach w moim przypadku.

Więc udało mi się zmienić nazwy moich modeli i pól relacji, wykonując poniższe czynności:

Zaadaptowałem metodę z tego, a szczególnie sztuczkę otranzera.

Więc tak jak Fiver, powiedzmy, że mamy w myapp :

class Foo(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

A w myotherapp :

class AnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Foo)
    is_ridonkulous = models.BooleanField()

Krok 1:

Przekształć każdy OneToOneField (Foo) lub ForeignKeyField (Foo) na IntegerField (). (To zachowa identyfikator powiązanego obiektu Foo jako wartość pola całkowitego).

class AnotherModel(models.Model):
    foo = models.IntegerField()
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.IntegerField()
    is_ridonkulous = models.BooleanField()

Następnie

python manage.py makemigrations

python manage.py migrate

Krok 2: (Podobnie jak krok 2-4 z Piątki)

Zmień nazwę modelu

class Bar(models.Model):  # <-- changed model name
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)

Utwórz pustą migrację:

python manage.py makemigrations --empty myapp

Następnie edytuj jak:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0001_initial'),
    ]

    operations = [
        migrations.RenameModel('Foo', 'Bar')
    ]

Ostatecznie

python manage.py migrate

Krok 3:

Przekształć swoje IntegerField () w ich poprzedni ForeignKeyField lub OneToOneField, ale z nowym modelem słupkowym. (Poprzednie pole całkowite przechowywało identyfikator, więc django to zrozumie i przywróć połączenie, co jest fajne.)

class AnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_awesome = models.BooleanField()

class YetAnotherModel(models.Model):
    foo = models.ForeignKey(Bar)
    is_ridonkulous = models.BooleanField()

Następnie wykonaj:

python manage.py makemigrations 

Co bardzo ważne, na tym etapie musisz zmodyfikować każdą nową migrację i dodać zależność od migracji RenameModel Foo-> Bar. Jeśli więc zarówno AnotherModel, jak i YetAnotherModel znajdują się w myotherapp, utworzona migracja w myotherapp musi wyglądać następująco:

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '00XX_the_migration_of_myapp_with_renamemodel_foo_bar'),
        ('myotherapp', '00xx_the_migration_of_myotherapp_with_integerfield'),
    ]

    operations = [
        migrations.AlterField(
            model_name='anothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar'),
        ),
        migrations.AlterField(
            model_name='yetanothermodel',
            name='foo',
            field=models.ForeignKey(to='myapp.Bar')
        ),
    ]

Następnie

python manage.py migrate

Krok 4:

Ostatecznie możesz zmienić nazwy swoich pól

class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar) <------- Renamed fields
    is_ridonkulous = models.BooleanField()

a następnie wykonaj automatyczną zmianę nazwy

python manage.py makemigrations

(django powinien zapytać, czy faktycznie zmieniłeś nazwę modelu, powiedz tak)

python manage.py migrate

I to wszystko!

Działa to na Django1.8

v.thorey
źródło
3
Dziękuję Ci! To było niezwykle pomocne. Ale uwaga - musiałem również ręcznie zmienić nazwę i / lub usunąć indeksy pól PostgreSQL, ponieważ po zmianie nazwy Foo na Bar utworzyłem nowy model o nazwie Bar.
Anatoly Scherbakov
Dziękuję Ci za to! Myślę, że kluczową częścią jest konwersja wszystkich kluczy obcych do lub z modelu, który ma zostać zmieniony, na IntegerField. To działało idealnie dla mnie i ma tę dodatkową zaletę, że są odtwarzane z poprawną nazwą. Oczywiście radziłbym przejrzeć wszystkie migracje przed ich uruchomieniem!
zelanix,
Dziękuję Ci! Wypróbowałem wiele różnych strategii, aby zmienić nazwę modelu, do którego inne modele mają klucze obce (kroki 1-3), i był to jedyny, który działał.
MSH
Zmiana ForeignKeys na IntegerFielduratowała mi dzisiaj dzień!
mehmet
8

Musiałem zrobić to samo i podążać. Od razu zmieniłem model (kroki 1 i 5 razem z odpowiedzi Piątka). Następnie utworzył migrację schematu, ale zmodyfikował go tak:

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('Foo','Bar')

    def backwards(self, orm):
        db.rename_table('Bar','Foo')

To działało doskonale. Wszystkie moje istniejące dane pokazały się, wszystkie inne tabele wskazywały na Bar w porządku.

stąd: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/

John Q
źródło
Świetnie, dzięki za udostępnienie. Nie zapomnij dać +1 wasibigeek, jeśli ta odpowiedź pomogła.
Piątek
7

W przypadku Django 1.10 udało mi się zmienić dwie nazwy klas modeli (w tym ForeignKey i dane), po prostu uruchamiając Makemigrations, a następnie Migrate dla aplikacji. Na etapie Makemigrations musiałem potwierdzić, że chcę zmienić nazwy tabel. Migracja bez problemu zmieniła nazwy tabel.

Następnie zmieniłem nazwę pola ForeignKey na zgodne i ponownie zostałem poproszony przez Makemigrations o potwierdzenie, że chcę zmienić nazwę. Przeprowadź migrację niż dokonaj zmiany.

Zrobiłem to w dwóch krokach bez specjalnej edycji plików. Na początku dostałem błędy, ponieważ zapomniałem zmienić plik admin.py, o czym wspomniał @wasibigeek.

excyberlabber
źródło
Wielkie dzięki! Idealny również dla Django 1.11
Francisco
6

Zmierzyłem się również z problemem, który opisał v.thorey i stwierdziłem, że jego podejście jest bardzo przydatne, ale można je skondensować na mniejszą liczbę kroków, które są w rzeczywistości krokami od 5 do 8, jak opisał Piątka, bez kroków od 1 do 4, z wyjątkiem tego, że krok 7 musi zostać zmieniony jako mój poniżej kroku 3. Ogólne kroki są następujące:

Krok 1: Edytuj powiązane nazwy pól w models.py

class Bar(models.Model):
    name = models.CharField(unique=True, max_length=32)
    description = models.TextField(null=True, blank=True)


class AnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_awesome = models.BooleanField()


class YetAnotherModel(models.Model):
    bar = models.ForeignKey(Bar)  # <-- changed field name
    is_ridonkulous = models.BooleanField()

Krok 2: Utwórz pustą migrację

python manage.py makemigrations --empty myapp

Krok 3: Edytuj klasę migracji w pliku migracji utworzonym w kroku 2

class Migration(migrations.Migration):

dependencies = [
    ('myapp', '0001_initial'), 
]

operations = [
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.IntegerField(),
    ),
    migrations.RenameModel('Foo', 'Bar'),
    migrations.AlterField(
        model_name='AnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.AlterField(
        model_name='YetAnotherModel',
        name='foo',
        field=models.ForeignKey(to='myapp.Bar'),
    ),
    migrations.RenameField('AnotherModel', 'foo', 'bar'),
    migrations.RenameField('YetAnotherModel', 'foo', 'bar')
]

Krok 4: Zastosuj migrację

python manage.py migrate

Gotowe

PS Próbowałem tego podejścia w Django 1.9

Curtis Lo
źródło
5

Używam Django w wersji 1.9.4

Wykonałem następujące kroki: -

Właśnie zmieniłem nazwę modelu oldName na NewName Run python manage.py makemigrations. Poprosi Cię o Did you rename the appname.oldName model to NewName? [y/N]wybranie Y

Biegnij python manage.py migratei zapyta cię o

Następujące typy zawartości są nieaktualne i należy je usunąć:

appname | oldName
appname | NewName

Wszelkie obiekty powiązane z tymi typami zawartości za pomocą klucza obcego również zostaną usunięte. Czy na pewno chcesz usunąć te typy zawartości? Jeśli nie masz pewności, odpowiedz „nie”.

Type 'yes' to continue, or 'no' to cancel: Select No

Zmienia nazwę i migruje wszystkie istniejące dane do nowej nazwanej tabeli.

Piyush S. Wanare
źródło
Dzięki koleś, byłem zdezorientowany, ponieważ nic się nie wydarzyło, gdy po
kliknięciu
3

Niestety napotkałem problemy (każdy django 1.x) z migracją zmiany nazwy, która pozostawia stare nazwy tabel w bazie danych.

Django nawet nie próbuje niczego na starym stole, po prostu zmień nazwę swojego modelu. Ten sam problem z kluczami obcymi i ogólnie indeksami - zmiany tam nie są poprawnie śledzone przez Django.

Najprostsze rozwiązanie (obejście):

class Foo(models.Model):
     name = models.CharField(unique=True, max_length=32)
     ...
Bar = Foo  # and use Bar only

Prawdziwe rozwiązanie (łatwy sposób na zmianę wszystkich indeksów, ograniczeń, wyzwalaczy, nazw itp. W 2 zatwierdzeniach, ale raczej w przypadku mniejszych tabel):

zatwierdzenie A:

  1. stwórz ten sam model co stary
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. przełącz kod, aby działał tylko z nowym modelem Bar. (w tym wszystkie relacje na schemacie)

W migracji przygotuj RunPython, które skopiują dane z Foo do Bar (w tym idz Foo)

  1. opcjonalna optymalizacja (w razie potrzeby dla większych tabel)

zatwierdzenie B: (bez pośpiechu, zrób to, gdy cały zespół jest migrowany)

  1. bezpieczny spadek starego modelu Foo

dalsze porządki:

  • squash na migracje

błąd w Django:

Sławomir Lenart
źródło
3

Chciałem tylko potwierdzić i dodać komentarz ceasaro. Wydaje się, że Django 2.0 robi to teraz automatycznie.

Jestem na Django 2.2.1, wszystko co musiałem zrobić, żeby zmienić nazwę modelu i uruchomić makemigrations .

Tutaj pyta, czy zmieniłem nazwę określonej klasy z Ana B, wybrałem tak i uruchomiłem migrację i wszystko wydaje się działać.

Uwaga: Nie zmieniłem nazwy starej nazwy modelu w żadnym pliku w folderze projektu / migracji.

Peheje
źródło
1

Musiałem zmienić nazwy kilku tabel. Ale tylko jedna zmiana nazwy modelu została zauważona przez Django. Stało się tak, ponieważ Django iteruje nad dodanymi, a następnie usuniętymi modelami. Dla każdej pary sprawdza, czy należą do tej samej aplikacji i mają identyczne pola . Tylko jedna tabela nie miała kluczy obcych do tabel do zmiany nazwy (klucze obce zawierają nazwę klasy modelu, jak pamiętasz). Innymi słowy, tylko jedna tabela nie miała żadnych zmian pól. Dlatego został zauważony.

Tak więc rozwiązaniem jest zmiana nazwy jednej tabeli na raz models.py, prawdopodobnie zmiana nazwy klasy modelu w views.pyi wykonanie migracji. Następnie sprawdź kod pod kątem innych odniesień (nazw klas modeli, nazw pokrewnych (zapytań), nazw zmiennych). W razie potrzeby przeprowadź migrację. Następnie opcjonalnie połącz wszystkie te migracje w jedną (pamiętaj również o skopiowaniu importu).

x-yuri
źródło
1

Zrobiłbym @ceasaro słowa, moje w jego komentarzu do tej odpowiedzi .

Nowsze wersje Django mogą wykrywać zmiany i pytać o to, co zostało zrobione. Dodałbym też, że Django może mieszać kolejność wykonywania niektórych poleceń migracji.

To byłoby mądre, aby zastosować niewielkie zmiany i uruchom makemigrationsi migratejeśli wystąpi błąd w pliku migracji mogą być edytowane.

Aby uniknąć błędów, można zmienić kolejność wykonywania niektórych wierszy.

diogosimao
źródło
Warto zauważyć, że to nie działa, jeśli zmienisz nazwy modeli i są zdefiniowane klucze obce itp ...
Dean Kayton,
Rozwinięcie poprzedniego komentarza: Jeśli wszystko, co robię, to zmieniam nazwy modeli i uruchamiam makemigracje, pojawia się komunikat „NameError: name” <oldmodel> ”is not defined” w obcych kluczach itp. ... Jeśli zmienię to i uruchomię makemigrations, otrzymuję błędy importu w admin.py ... jeśli naprawię to i ponownie uruchomię makemigracje, pojawi się komunikat „Czy zmieniłeś nazwę modelu <app.oldmodel> na <newmodel>” Ale potem podczas stosowania migracji otrzymuję „ValueError: The field <app .newmodel.field1> został zadeklarowany z leniwym odwołaniem do „<app.oldmodel>”, ale aplikacja „<app>” nie dostarcza modelu „<oldmodel>”, itd ... ”
Dean Kayton
Wygląda na to, że ten błąd wymaga zmiany nazw odniesień w historycznych migracjach.
mhatch
@DeanKayton powiedziałby, że to migrations.SeparateDatabaseAndStatemoże pomóc?
diogosimao
1

Jeśli używasz dobrego IDE, takiego jak PyCharm, możesz kliknąć prawym przyciskiem myszy nazwę modelu i wykonać refaktoryzację -> zmień nazwę. Oszczędza to kłopotów z przejściem przez cały kod, który odwołuje się do modelu. Następnie uruchom makemigrations i dokonaj migracji. Django 2+ po prostu potwierdzi zmianę nazwy.

Josh
źródło
-10

Zaktualizowałem Django z wersji 10 do wersji 11:

sudo pip install -U Django

( -Udla „upgrade”) i rozwiązało problem.

Muhammad Hafid
źródło