Najłatwiejszy sposób na zmianę nazwy modelu przy użyciu Django / South?

141

Szukałem odpowiedzi na to pytanie w witrynie South, Google i SO, ale nie mogłem znaleźć prostego sposobu, aby to zrobić.

Chcę zmienić nazwę modelu Django przy użyciu South. Załóżmy, że masz:

class Foo(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Foo)

i chcesz przekonwertować Foo na Bar, a mianowicie

class Bar(models.Model):
    name = models.CharField()

class FooTwo(models.Model):
    name = models.CharField()
    foo = models.ForeignKey(Bar)

Aby to uprościć, po prostu próbuję zmienić nazwę z Foona Bar, ale na razie zignoruję fooczłonka FooTwo.

Jaki jest najłatwiejszy sposób na zrobienie tego za pomocą South?

  1. Prawdopodobnie mógłbym przeprowadzić migrację danych, ale wydaje się to dość skomplikowane.
  2. Napisz niestandardową migrację, np. db.rename_table('city_citystate', 'geo_citystate')Ale nie jestem pewien, jak naprawić klucz obcy w tym przypadku.
  3. Łatwiejszy sposób, który znasz?
vaughnkoch
źródło
5
Zobacz także stackoverflow.com/questions/3235995/… w celu zmiany nazwy pola modelu zamiast modelu .
Mechaniczny ślimak,
Zoptymalizowane rozwiązanie dla Django> = 1,8 stackoverflow.com/questions/25091130/…
programista chemiczny

Odpowiedzi:

130

Odpowiadając na pierwsze pytanie, prosta zmiana nazwy modelu / tabeli jest dość prosta. Uruchom polecenie:

./manage.py schemamigration yourapp rename_foo_to_bar --empty

(Aktualizacja 2: spróbuj --autozamiast --emptyuniknąć poniższego ostrzeżenia. Dzięki @KFB za wskazówkę).

Jeśli używasz starszej wersji South, będziesz potrzebować startmigrationzamiast schemamigration.

Następnie ręcznie edytuj plik migracji, aby wyglądał następująco:

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('yourapp_foo', 'yourapp_bar')


    def backwards(self, orm):
        db.rename_table('yourapp_bar','yourapp_foo')   

Możesz to zrobić w prostszy sposób, używając db_tableopcji Meta w swojej klasie modelu. Ale za każdym razem, gdy to robisz, zwiększasz wagę swojej bazy kodu - posiadanie nazw klas różniących się od nazw tabel sprawia, że ​​kod jest trudniejszy do zrozumienia i utrzymania. W pełni popieram robienie prostych refaktoryzacji, takich jak ta ze względu na przejrzystość.

(aktualizacja) Właśnie wypróbowałem to w środowisku produkcyjnym i otrzymałem dziwne ostrzeżenie, gdy poszedłem zastosować migrację. Powiedziało:

The following content types are stale and need to be deleted:

    yourapp | foo

Any objects related to these content types by a foreign key will also
be deleted. Are you sure you want to delete these content types?
If you're unsure, answer 'no'.

Odpowiedziałem „nie” i wszystko wydawało się być w porządku.

Leopd
źródło
3
Udało mi się uniknąć komunikatu o błędzie wyświetlanego przez Leopd, tworząc migrację schematu za pomocą --auto zamiast --empty. Następnie wyedytowałem plik migracji, zmieniając usuwanie / tworzenie tabel na wywołanie db.rename_table (). Wygląda na to, że zadziałało to bardzo dobrze.
KFB
4
Użyłem tej techniki w dniu 02.09.2011 bez żadnych błędów. Może nowsza wersja South rozwiązała problem z błędami.
Chip Tol,
1
Dzięki za aktualizowanie tego! Odpowiedź Jiana poniżej mówi, że ważne jest, aby zachować wywołania „send_create_signal”. Czy masz jakąś wiedzę na ten temat? Jeśli się zgadzasz, byłoby wspaniale zaktualizować przykładową migrację.
mrooney,
5
Uważaj, nie spowoduje to zmiany nazw indeksów w tej tabeli. Jeśli w przyszłości utworzysz nową tabelę o tej samej nazwie co stara tabela, możesz otrzymać błędy z powodu kolizji nazw indeksów. Używaliśmy tej techniki, ale od teraz zamierzamy jawnie utworzyć nową tabelę, przeprowadzić migrację danych, a następnie usunąć starą tabelę.
Jeremy Banks
3
Nazwy kolumn w automatycznie generowanych tabelach, takich jak tabele M2M do oryginalnego modelu, również nie są migrowane tą metodą.
spookylukey
66

Wprowadź zmiany, models.pya następnie uruchom

./manage.py schemamigration --auto myapp

Po sprawdzeniu pliku migracji zobaczysz, że usuwa on tabelę i tworzy nową

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Deleting model 'Foo'                                                                                                                      
        db.delete_table('myapp_foo')

        # Adding model 'Bar'                                                                                                                        
        db.create_table('myapp_bar', (
        ...
        ))
        db.send_create_signal('myapp', ['Bar'])

    def backwards(self, orm):
        ...

Nie do końca tego chcesz. Zamiast tego edytuj migrację tak, aby wyglądała następująco:

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Renaming model from 'Foo' to 'Bar'                                                                                                                      
        db.rename_table('myapp_foo', 'myapp_bar')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(
                app_label='myapp', model='foo').update(model='bar')

    def backwards(self, orm):
        # Renaming model from 'Bar' to 'Foo'                                                                                                                      
        db.rename_table('myapp_bar', 'myapp_foo')                                                                                                                        
        if not db.dry_run:
            orm['contenttypes.contenttype'].objects.filter(app_label='myapp', model='bar').update(model='foo')

W przypadku braku updateoświadczenia db.send_create_signalwywołanie utworzy nowy ContentTypez nową nazwą modelu. Ale lepiej jest po prostu updateto, ContentTypeco już masz, na wypadek, gdyby wskazały na niego obiekty bazy danych (np. Poprzez a GenericForeignKey).

Ponadto, jeśli zmieniono nazwy niektórych kolumn, które są kluczami obcymi, na model o zmienionej nazwie, nie zapomnij o tym

db.rename_column(myapp_model, foo_id, bar_id)
Jian
źródło
2
Pojawia się błąd, KeyError: „Model„ contenttype ”z aplikacji„ contenttypes ”nie jest dostępny podczas tej migracji." Mam również tabelę django_content_type, ale nie mam tabeli contenttypes. (Django 1.6)
Seth
2
@Seth Obejrzałem to, wykonując aktualizację modeli ContentType w oddzielnej migracji danych i dodając contenttypes.ContentTypemodel do zamrożonych modeli za pomocą --frozenflagi do ./manage.py datamigration. Na przykład: ./manage.py datamigration --frozen contenttypes myapp update_contenttypes. Następnie edytuj myapp_migrations / NNNN_update_contenttypes.py z kodem aktualizującym typ zawartości, jak określono powyżej.
Geoffrey Hing
@GeoffreyHing Myślę, że parametr jest zamrożony nie zamrożony. south.readthedocs.io/en/latest/ormfreezing.html Ale dziękuję bardzo za pomoc, była naprawdę pomocna.
ccsakuweb
5

Południe nie może tego zrobić samo - skąd wie, że Barreprezentuje to, co Fookiedyś? To jest rodzaj rzeczy, dla której napisałbym niestandardową migrację. Możesz zmienić swój ForeignKeykod tak, jak zrobiłeś to powyżej, a wtedy wystarczy zmienić nazwy odpowiednich pól i tabel, co możesz zrobić w dowolny sposób.

Wreszcie, czy naprawdę musisz to zrobić? Nie muszę jeszcze zmieniać nazw modeli - nazwy modeli to tylko szczegół implementacji - szczególnie biorąc pod uwagę dostępność verbose_nameopcji Meta.

Dominic Rodger
źródło
7
Alternatywnie zmień nazwę modelu w kodzie, ale użyj db_tableopcji Meta, aby zachować tę samą nazwę tabeli bazy danych.
Daniel Roseman
@Daniel - czy wiesz, czy db_tablejest używany do wyprowadzania nazw kluczy obcych?
Dominic Rodger
wierzę, że tak. Jeśli zmienisz nazwę modelu i ustawisz db_table, wszystko powinno nadal działać zgodnie z oczekiwaniami.
Davor Lucic
1
@DanielRoseman to najlepsze rozwiązanie w całym wątku!
joerick,
-1

Postępowałem zgodnie z powyższym rozwiązaniem Leopda. Ale to nie zmieniło nazw modeli. Zmieniłem to ręcznie w kodzie (również w powiązanych modelach, gdzie jest to określane jako FK). I przeprowadziłem kolejną migrację na południe, ale z opcją --fake. Dzięki temu nazwy modeli i nazwy tabel są takie same.

Właśnie sobie uświadomiłem, że można najpierw zacząć od zmiany nazw modeli, a następnie edytować plik migracji przed ich zastosowaniem. Dużo czystsze.

gowthaman
źródło