Strategi migrasi Django untuk mengganti nama model dan bidang hubungan

152

Saya berencana untuk mengganti nama beberapa model dalam proyek Django yang ada di mana ada banyak model lain yang memiliki hubungan kunci asing dengan model yang ingin saya ganti namanya. Saya cukup yakin ini akan membutuhkan banyak migrasi, tetapi saya tidak yakin prosedurnya.

Katakanlah saya mulai dengan model-model berikut dalam aplikasi Django bernama 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()

Saya ingin mengganti nama Foomodel karena namanya tidak benar-benar masuk akal dan menyebabkan kebingungan dalam kode, dan Barakan membuat nama yang jauh lebih jelas.

Dari apa yang saya baca dalam dokumentasi pengembangan Django, saya mengasumsikan strategi migrasi berikut:

Langkah 1

Ubah 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()

Perhatikan AnotherModelnama bidang untuk footidak berubah, tetapi relasi diperbarui ke Barmodel. Alasan saya adalah bahwa saya tidak boleh mengubah terlalu banyak sekaligus dan bahwa jika saya mengubah nama bidang ini menjadi barrisiko kehilangan data di kolom itu.

Langkah 2

Buat migrasi kosong:

python manage.py makemigrations --empty myapp

Langkah 3

Edit Migrationkelas dalam file migrasi yang dibuat pada langkah 2 untuk menambahkan RenameModeloperasi ke daftar operasi:

class Migration(migrations.Migration):

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

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

Langkah 4

Terapkan migrasi:

python manage.py migrate

Langkah 5

Edit nama bidang terkait di 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()

Langkah 6

Buat migrasi kosong lain:

python manage.py makemigrations --empty myapp

Langkah 7

Edit Migrationkelas dalam file migrasi yang dibuat pada langkah 6 untuk menambahkan RenameFieldoperasi untuk nama bidang terkait ke daftar operasi:

class Migration(migrations.Migration):

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

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

Langkah 8

Terapkan migrasi ke-2:

python manage.py migrate

Selain memperbarui sisa kode (tampilan, formulir, dll.) Untuk mencerminkan nama variabel baru, apakah ini pada dasarnya bagaimana fungsi migrasi baru akan bekerja?

Juga, ini sepertinya banyak langkah. Bisakah operasi migrasi dikondensasi dengan cara tertentu?

Terima kasih!

Uang kertas lima dolar
sumber

Jawaban:

125

Jadi ketika saya mencoba ini, sepertinya Anda bisa memadatkan Langkah 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')
    ]

Anda mungkin mendapatkan beberapa kesalahan jika Anda tidak memperbarui nama yang diimpor misalnya admin.py dan bahkan file migrasi yang lebih lama (!).

Pembaruan : Seperti yang disebutkan dalam ceasaro , Django versi yang lebih baru biasanya dapat mendeteksi dan menanyakan apakah suatu model diganti namanya. Jadi coba manage.py makemigrationsdulu lalu periksa file migrasi.

wasabigeek
sumber
Terima kasih atas jawabannya. Saya sejak bermigrasi menggunakan langkah-langkah yang saya uraikan, tetapi saya ingin tahu apakah Anda mencoba ini dengan data yang ada atau hanya dengan database kosong?
Fiver
2
Sudah dicoba dengan data yang ada, meskipun hanya beberapa baris pada sqlite di lingkungan lokal saya (ketika saya pindah ke Produksi saya bermaksud untuk menghapus semuanya termasuk file migrasi)
wasabigeek
4
Anda tidak harus mengubah nama model dalam file migrasi jika Anda menggunakannya apps.get_model. Butuh banyak waktu untuk memikirkannya.
ahmed
9
Di Django 2.0 jika Anda mengubah nama model Anda, ./manage.py makemigrations myappperintah itu akan menanyakan apakah Anda mengganti nama model Anda. Misalnya: Apakah Anda mengganti nama model myapp.Foo ke Bar? [y / T] Jika Anda menjawab 'y' migrasi Anda akan berisi migration.RenameModel('Foo', 'Bar')jumlah yang sama untuk bidang yang diubah namanya :-)
ceasaro
1
manage.py makemigrations myappmungkin masih gagal: "Anda mungkin harus menambahkan ini secara manual jika Anda mengubah nama model dan beberapa bidangnya sekaligus; ke autodetector, ini akan terlihat seperti Anda menghapus model dengan nama lama dan menambahkan yang baru dengan nama yang berbeda, dan migrasi yang dibuatnya akan kehilangan data apa pun di tabel lama. " Django 2.1 Documents Bagi saya, itu sudah cukup untuk membuat migrasi kosong, tambahkan model ganti nama, lalu jalankan makemigrationsseperti biasa.
jauh
36

Pada awalnya, saya berpikir bahwa metode Fiver bekerja untuk saya karena migrasi bekerja dengan baik hingga langkah 4. Namun, perubahan implisit 'ForeignKeyField (Foo)' menjadi 'ForeignKeyField (Bar)' tidak terkait dalam migrasi apa pun. Inilah sebabnya migrasi gagal ketika saya ingin mengganti nama bidang hubungan (langkah 5-8). Ini mungkin disebabkan oleh fakta bahwa 'AnotherModel' dan 'YetAnotherModel' saya dikirim ke aplikasi lain dalam kasus saya.

Jadi saya berhasil mengubah nama model dan bidang hubungan saya dengan mengikuti langkah-langkah di bawah ini:

Saya mengadaptasi metode dari ini dan khususnya trik otranzer.

Jadi seperti Fiver katakanlah yang kita miliki di aplikasi saya :

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

Dan di 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()

Langkah 1:

Ubah setiap OneToOneField (Foo) atau ForeignKeyField (Foo) menjadi IntegerField (). (Ini akan menjaga id dari objek Foo terkait sebagai nilai integerfield).

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

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

Kemudian

python manage.py makemigrations

python manage.py migrate

Langkah 2: (Seperti langkah 2-4 dari Fiver)

Ubah nama model

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

Buat migrasi kosong:

python manage.py makemigrations --empty myapp

Kemudian edit seperti:

class Migration(migrations.Migration):

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

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

Akhirnya

python manage.py migrate

Langkah 3:

Ubah Kembali IntegerField Anda () menjadi ForeignKeyField atau OneToOneField sebelumnya tetapi dengan Model Bar baru. (Integerfield sebelumnya menyimpan id, jadi Django mengerti itu dan membangun kembali koneksi, yang keren.)

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

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

Kemudian lakukan:

python manage.py makemigrations 

Sangat penting, pada langkah ini Anda harus memodifikasi setiap migrasi baru dan menambahkan ketergantungan pada migrasi Bar RenameModel Foo->. Jadi jika AnotherModel dan YetAnotherModel berada di myotherapp, migrasi yang dibuat di myotherapp harus terlihat seperti ini:

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

Kemudian

python manage.py migrate

Langkah 4:

Akhirnya, Anda dapat mengubah nama bidang Anda

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

dan kemudian melakukan penggantian nama otomatis

python manage.py makemigrations

(Django harus bertanya apakah Anda benar-benar mengganti nama modelnya, katakan ya)

python manage.py migrate

Dan itu dia!

Ini berfungsi pada Django1.8

v.thorey
sumber
3
Terima kasih! Itu sangat membantu. Tapi sebuah catatan - Saya juga harus mengganti nama dan / atau menghapus indeks bidang PostgreSQL dengan tangan karena, setelah mengganti nama Foo ke Bar, saya membuat model baru bernama Bar.
Anatoly Scherbakov
Terima kasih untuk ini! Saya pikir bagian kuncinya adalah mengubah semua kunci asing, masuk atau keluar dari model untuk diganti namanya, menjadi IntegerField. Ini bekerja dengan baik untuk saya, dan memiliki keuntungan tambahan bahwa mereka dapat diciptakan kembali dengan nama yang benar. Secara alami saya akan menyarankan untuk meninjau semua migrasi sebelum benar-benar menjalankannya!
zelanix
Terima kasih! Saya mencoba banyak strategi berbeda untuk mengganti nama model yang model lain memiliki kunci asing (langkah 1-3), dan ini adalah satu-satunya yang berhasil.
MSH
Mengubah ForeignKeys untuk IntegerFieldmenyelamatkan hari saya hari ini!
mehmet
8

Saya perlu melakukan hal yang sama dan mengikuti. Saya mengubah model sekaligus (Langkah 1 dan 5 bersamaan dari jawaban Fiver). Kemudian buat migrasi skema tetapi diedit menjadi ini:

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

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

Ini bekerja dengan sempurna. Semua data saya yang ada muncul, semua tabel lain direferensikan Bar baik.

dari sini: https://hanmir.wordpress.com/2012/08/30/rename-model-django-south-migration/

John Q
sumber
Bagus, terima kasih sudah berbagi. Pastikan memberi +1 wasibigeek jika jawaban itu membantu.
Fiver
7

Untuk Django 1.10, saya berhasil mengubah dua nama kelas model (termasuk ForeignKey, dan dengan data) dengan hanya menjalankan Makemigrations, dan kemudian Migrasikan untuk aplikasi. Untuk langkah Makemigrations, saya harus mengkonfirmasi bahwa saya ingin mengubah nama tabel. Migrasi mengubah nama tabel tanpa masalah.

Kemudian saya mengubah nama bidang ForeignKey agar cocok, dan sekali lagi diminta oleh Makemigrations untuk mengkonfirmasi bahwa saya ingin mengubah nama. Bermigrasi daripada melakukan perubahan.

Jadi saya mengambil ini dalam dua langkah tanpa mengedit file khusus. Saya memang mendapatkan kesalahan pada awalnya karena saya lupa mengubah file admin.py, seperti yang disebutkan oleh @wasibigeek.

excyberlabber
sumber
Terima kasih banyak! Sempurna untuk Django 1.11 juga
Francisco
5

Saya juga menghadapi masalah seperti yang dijelaskan oleh v.thorey dan menemukan bahwa pendekatannya sangat berguna tetapi dapat diringkas menjadi langkah-langkah yang lebih sedikit yang sebenarnya adalah langkah 5 hingga 8 seperti yang dijelaskan oleh Fiver tanpa langkah 1 hingga 4 kecuali bahwa langkah 7 perlu diubah seperti yang saya lakukan. di bawah langkah 3. Langkah-langkah keseluruhan adalah sebagai berikut:

Langkah 1: Edit nama bidang terkait di 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()

Langkah 2: Buat migrasi kosong

python manage.py makemigrations --empty myapp

Langkah 3: Edit kelas Migrasi dalam file migrasi yang dibuat pada Langkah 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')
]

Langkah 4: Terapkan migrasi

python manage.py migrate

Selesai

PS Saya sudah mencoba pendekatan ini pada Django 1.9

Curtis Lo
sumber
5

Saya menggunakan Django versi 1.9.4

Saya telah mengikuti langkah-langkah berikut: -

Saya baru saja mengganti nama model oldName ke NewName Run python manage.py makemigrations. Ini akan meminta Anda untuk Did you rename the appname.oldName model to NewName? [y/N]memilih Y

Jalankan python manage.py migratedan itu akan meminta Anda

Jenis konten berikut ini sudah basi dan perlu dihapus:

appname | oldName
appname | NewName

Objek apa pun yang terkait dengan tipe konten ini dengan kunci asing juga akan dihapus. Anda yakin ingin menghapus jenis konten ini? Jika Anda tidak yakin, jawab 'tidak'.

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

Itu mengubah nama dan memigrasikan semua data yang ada ke tabel baru bernama untuk saya.

Piyush S. Wanare
sumber
Terima kasih bung, saya bingung karena tidak ada yang terjadi ketika setelah memukul "tidak"
farhawa
3

Sayangnya, saya menemukan masalah (masing-masing Django 1.x) dengan mengganti nama migrasi yang meninggalkan nama tabel lama dalam database.

Django bahkan tidak mencoba apa pun di atas meja lama, hanya mengganti nama modelnya sendiri. Masalah yang sama dengan kunci asing, dan indeks secara umum - perubahan di sana tidak dilacak dengan benar oleh Django.

Solusi paling sederhana (solusi):

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

Solusi nyata (cara mudah untuk mengganti semua indeks, batasan, pemicu, nama, dll dalam 2 komit, tetapi untuk tabel yang lebih kecil ):

komit A:

  1. buat model yang sama dengan yang lama
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. beralih kode untuk bekerja dengan model baru Barsaja. (termasuk semua hubungan pada skema)

Dalam persiapan migrasi RunPython, yang menyalin data dari Foo ke Bar (termasuk iddari Foo)

  1. optimasi opsional (jika diperlukan untuk tabel yang lebih besar)

melakukan B: (jangan terburu-buru, lakukan ketika seluruh tim dimigrasikan)

  1. setetes aman dari model lama Foo

pembersihan lebih lanjut:

  • squash pada migrasi

bug di Django:

Sławomir Lenart
sumber
3

Hanya ingin mengkonfirmasi dan menambahkan komentar ceasaro. Django 2.0 tampaknya melakukan ini secara otomatis sekarang.

Saya menggunakan Django 2.2.1, yang harus saya lakukan adalah mengubah nama model dan menjalankannya makemigrations.

Di sini ia bertanya apakah saya telah mengubah nama kelas tertentu dari Amenjadi B, saya memilih ya dan berlari bermigrasi dan semua tampaknya berfungsi.

Catatan saya tidak mengganti nama nama model lama di file apa pun di dalam folder proyek / migrasi.

Peheje
sumber
1

Saya perlu mengganti nama beberapa tabel. Tapi hanya satu model yang diganti nama yang diperhatikan oleh Django. Itu terjadi karena Django mengulangi penambahan, lalu menghapus model. Untuk setiap pasangan memeriksa apakah mereka memiliki aplikasi yang sama dan memiliki bidang yang sama . Hanya satu tabel yang tidak memiliki kunci asing untuk diubah namanya (kunci asing berisi nama kelas model, seperti yang Anda ingat). Dengan kata lain, hanya satu tabel yang tidak memiliki perubahan bidang. Karena itulah diperhatikan.

Jadi, solusinya adalah mengubah nama satu tabel pada satu waktu, mengubah nama kelas model di models.py, mungkin views.py, dan melakukan migrasi. Setelah itu periksa kode Anda untuk referensi lain (nama kelas model, nama terkait (permintaan), nama variabel). Buat migrasi, jika perlu. Lalu, secara opsional gabungkan semua migrasi ini menjadi satu (pastikan untuk menyalin impor juga).

x-yuri
sumber
1

Saya akan membuat kata-kata @ceasaro, milik saya di komentarnya pada jawaban ini .

Versi baru Django dapat mendeteksi perubahan dan bertanya tentang apa yang telah dilakukan. Saya juga akan menambahkan bahwa Django mungkin mencampur urutan pelaksanaan beberapa perintah migrasi.

Akan lebih bijaksana untuk menerapkan perubahan kecil dan menjalankan makemigrationsdan migratedan jika kesalahan terjadi file migrasi dapat diedit.

Beberapa baris urutan eksekusi dapat diubah untuk menghindari kesalahan.

diogosimao
sumber
Baik untuk dicatat bahwa ini tidak berfungsi jika Anda mengubah nama model dan ada kunci asing yang ditentukan, dll ...
Dean Kayton
Memperluas komentar sebelumnya: Jika semua yang saya lakukan adalah mengubah nama model dan menjalankan makemigrasi saya mendapatkan 'NameError: name' <oldmodel> 'tidak didefinisikan' dalam bahasa asing, dll. Jika saya mengubahnya dan menjalankan makemigrations, saya mendapatkan kesalahan impor di admin.py ... jika saya memperbaikinya dan menjalankan migrasi kembali, saya mendapatkan konfirmasi 'Apakah Anda mengganti nama model <app.oldmodel> menjadi <newmodel>' Tetapi kemudian dalam menerapkan migrasi, saya mendapatkan 'ValueError: Field <app .newmodel.field1> dideklarasikan dengan referensi malas ke '<app.oldmodel>', tetapi aplikasi '<app>' tidak menyediakan model '<oldmodel>', dll ... '
Dean Kayton
Kesalahan ini sepertinya Anda perlu mengganti nama referensi dalam migrasi historis Anda.
mhatch
@DeanKayton akan mengatakan itu migrations.SeparateDatabaseAndStatebisa membantu?
diogosimao
1

Jika Anda menggunakan IDE yang bagus seperti PyCharm, Anda dapat mengklik kanan pada nama model dan melakukan refactor -> rename. Ini menghemat masalah Anda melalui semua kode Anda yang referensi model. Kemudian jalankan macemigrations dan bermigrasi. Django 2+ hanya akan mengonfirmasi perubahan nama.

Josh
sumber
-10

Saya memutakhirkan Django dari versi 10 ke versi 11:

sudo pip install -U Django

( -Uuntuk "upgrade") dan itu memecahkan masalah.

Muhammad Hafid
sumber