web-dev-qa-db-ja.com

Djangoモデルと関係フィールドの名前を変更するための移行戦略

私は、既存のDjangoプロジェクトのいくつかのモデルの名前を変更する予定です。このプロジェクトには、名前を変更したいモデルと外部キー関係を持つ他の多くのモデルがあります。これには複数の移行が必要になると確信していますが、正確な手順はわかりません。

myappname__というDjangoアプリ内の次のモデルから始めてみましょう。

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

Fooname__モデルの名前を変更したいのは、名前が実際には意味をなさず、コードの混乱を引き起こしているためです。また、Barname__はより明確な名前を作成します。

Django開発ドキュメントで読んだ内容から、次の移行戦略を想定しています。

ステップ1

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

AnotherModelname__のfooname__フィールド名は変更されませんが、リレーションはBarname__モデルに更新されます。私の理由は、一度にあまり変更しないで、このフィールド名をbarname__に変更すると、その列のデータが失われる危険性があるからです。

ステップ2

空の移行を作成します。

python manage.py makemigrations --empty myapp

ステップ3

手順2で作成した移行ファイルのMigrationname__クラスを編集して、RenameModelname__操作を操作リストに追加します。

class Migration(migrations.Migration):

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

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

ステップ4

移行を適用します。

python manage.py migrate

ステップ5

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

ステップ6

別の空の移行を作成します。

python manage.py makemigrations --empty myapp

ステップ7

手順6で作成した移行ファイルのMigrationname__クラスを編集して、関連するフィールド名のRenameFieldname__操作を操作リストに追加します。

class Migration(migrations.Migration):

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

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

ステップ8

2番目の移行を適用します。

python manage.py migrate

新しい変数名を反映するためにコードの残りの部分(ビュー、フォームなど)を更新する以外に、これは基本的に新しい移行機能がどのように機能するでしょうか?

また、これは多くの手順のようです。移行操作を何らかの方法で凝縮できますか?

ありがとう!

124
Fiver

私がこれを試したとき、ステップ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')
    ]

インポート先の名前を更新しないと、エラーが発生する場合があります。 admin.pyおよびさらに古い移行ファイル(!)。

Updateceasaro が言及しているように、Djangoの新しいバージョンは通常、モデルの名前が変更されました。最初にmanage.py makemigrationsを試してから、移行ファイルを確認してください。

103
wasabigeek

最初は、ステップ4まで移行がうまくいったため、Fiverの方法がうまくいくと思いました。ただし、「ForeignKeyField(Foo)」から「ForeignKeyField(Bar)」への暗黙的な変更は、どの移行にも関係しませんでした。これが、リレーションシップフィールドの名前を変更しようとしたときに移行が失敗した理由です(ステップ5-8)。これは、私の場合、「AnotherModel」と「YetAnotherModel」が他のアプリでディスパッチされているという事実による可能性があります。

そこで、以下の手順に従ってモデルと関係フィールドの名前を変更しました。

this のメソッド、特にotranzerのトリックを採用しました。

Fiverのようにmyappにあるとしましょう:

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

そして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()

ステップ1:

すべてのOneToOneField(Foo)またはForeignKeyField(Foo)をIntegerField()に変換します。 (これにより、関連するFooオブジェクトのidがintegerfieldの値として保持されます)。

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

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

それから

python manage.py makemigrations

python manage.py migrate

ステップ2:(Fiverのステップ2-4と同様)

モデル名を変更する

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

空の移行を作成します。

python manage.py makemigrations --empty myapp

次に、次のように編集します。

class Migration(migrations.Migration):

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

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

やがて

python manage.py migrate

ステップ3:

IntegerField()を以前のForeignKeyFieldまたはOneToOneFieldに変換しますが、新しいバーモデルを使用します。 (以前のintegerfieldはidを格納していたので、Djangoはそれを理解し、接続を再確立します。これは素晴らしいことです。)

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

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

それから:

python manage.py makemigrations 

非常に重要なこととして、このステップでは、すべての新しい移行を変更し、RenameModel Foo-> Bar移行への依存関係を追加する必要があります。したがって、AnotherModelとYetAnotherModelの両方がmyotherappにある場合、myotherappで作成された移行は次のようになります。

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

それから

python manage.py migrate

ステップ4:

最終的にはフィールドの名前を変更できます

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

その後、自動名前変更を行います

python manage.py makemigrations

(Djangoは、実際にモデル名を変更したかどうかを尋ねるはずです、yesと言います)

python manage.py migrate

以上です!

これはDjango1.8で動作します

29
v.thorey

同じことをする必要がありました。モデルを一度に変更しました(つまり、ステップ1とステップ5を一緒に変更しました)。次に、スキーマ移行を作成しましたが、次のように編集しました:

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

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

これは完全に機能しました。既存のデータがすべて表示され、他のすべてのテーブルはBarを参照しました。

ここから: https://hanmir.wordpress.com/2012/08/30/rename-model-Django-south-migration/

7
John Q

Django 1.10の場合、Makemigrationsを実行するだけで2つのモデルクラス名(ForeignKeyとデータを含む)を変更し、アプリを移行しました。 Makemigrationsステップでは、テーブル名を変更することを確認する必要がありました。移行により、テーブルの名前が問題なく変更されました。

次に、ForeignKeyフィールドの名前を一致するように変更し、Makemigrationsから名前の変更を確認するように再度求められました。移行してから変更しました。

そのため、特別なファイル編集を行うことなく、2つのステップでこれを行いました。 @wasibigeekで述べたように、admin.pyファイルを変更するのを忘れたため、最初はエラーになりました。

6
excyberlabber

Djangoバージョン1.9.4を使用しています

私は次の手順に従っています:

モデルの名前をoldNameからNewName Run python manage.py makemigrationsに変更しました。 Did you rename the appname.oldName model to NewName? [y/N] select Yを要求します

python manage.py migrateを実行すると、

次のコンテンツタイプは古く、削除する必要があります。

appname | oldName
appname | NewName

外部キーによってこれらのコンテンツタイプに関連するオブジェクトも削除されます。これらのコンテンツタイプを削除してもよろしいですか?不明な場合は、「いいえ」と答えてください。

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

既存のすべてのデータの名前を変更し、新しい名前付きテーブルに移行します。

5

また、v.thoreyが説明したように問題に直面し、彼のアプローチは非常に有用ですが、ステップ1から4なしでファイバーが説明したように、ステップ7を変更する必要があることを除いて、実際にはステップ5から8のより少ないステップに凝縮できることを発見しました手順3の下。全体的な手順は次のとおりです。

ステップ1: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()

ステップ2:空の移行を作成する

python manage.py makemigrations --empty myapp

手順3:手順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')
]

ステップ4:移行を適用する

python manage.py migrate

完了

追伸Django 1.9でこのアプローチを試しました

4
Curtis Lo

残念ながら、データベース内に古いテーブル名を残す名前変更移行に関する問題(それぞれDjango 1.x)が見つかりました。

Djangoは古いテーブルでは何も試さず、自分のモデルの名前を変更するだけです。一般的な外部キーとインデックスに関する同じ問題-そこの変更はDjangoによって適切に追跡されません。

最も簡単な解決策(回避策):

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

実際の解決策(すべてのインデックス、制約、トリガー、名前などを2回のコミットで切り替える簡単な方法ですが、smallerテーブルの場合):

Aをコミット:

  1. 古いモデルと同じsameモデルを作成する
# deprecated - TODO: TO BE REMOVED
class Foo(model.Model):
    ...

class Bar(model.Model):
    ...
  1. 新しいモデルBarのみで動作するようにコードを切り替えます。 (スキーマ上のすべての関係を含む)

移行では、FooからBarにデータをコピーするRunPythonを準備します(Fooのidを含む)

  1. オプションの最適化(より大きなテーブルに必要な場合)

B:をコミット(ラッシュなし、チーム全体が移行されたときに実行)

  1. 古いモデルの安全なドロップFoo

さらにクリーンアップ:

  • 移行のスカッシュ

djangoのバグ:

2

いくつかのテーブルの名前を変更する必要がありました。しかし、Djangoが気づいたモデルの名前は1つだけです。これは、Djangoがモデルの追加、削除を繰り返したために発生しました。各ペアについて、それらが同じアプリのものであり、 同一フィールド であるかどうかをチェックします。名前を変更するテーブルへの外部キーを持たないテーブルは1つだけです(覚えているように、外部キーにはモデルクラス名が含まれています)。つまり、1つのテーブルのみにフィールドの変更はありませんでした。それが気づいた理由です。

そのため、解決策は、一度に1つのテーブルの名前を変更し、models.py(おそらくviews.py)のモデルクラス名を変更し、移行を行うことです。その後、他の参照(モデルクラス名、関連(クエリ)名、変数名)についてコードを調べます。必要に応じて移行を行います。次に、オプションでこれらすべての移行を1つに結合します(インポートも必ずコピーしてください)。

1
x-yuri

私は@ceasaroの言葉を作ります。これについての彼のコメント answer です。

Djangoの新しいバージョンは、変更を検出し、何が行われたかを尋ねることができます。また、Djangoがいくつかの移行コマンドの実行順序を混在させる可能性があることも追加します。

小さな変更を適用してmakemigrationsおよびmigrateを実行し、エラーが発生した場合は移行ファイルを編集できます。

一部の行の実行順序は、エラーを回避するために変更できます。

1
diogosimao

PyCharmのような優れたIDEを使用している場合は、モデル名を右クリックしてリファクタリング->名前変更を実行できます。これにより、モデルを参照するすべてのコードを調べる手間が省けます。次に、makemigrationsを実行して移行します。 Django 2+は単に名前の変更を確認します。

0
Josh

Ceasaroコメントを確認して追加したかっただけです。 Django 2.0は現在、これを自動的に行うようです。

私はJango 2.2.1を使用しています。モデルの名前を変更してmakemigrationを実行するために必要なことはすべてしました。

ここで、特定のクラスの名前をAからBに変更したかどうかを確認し、yesを選択してmigrateを実行しましたが、すべて機能しているようです。

注:project/migrationsフォルダー内のファイルの古いモデル名は変更しませんでした。

0
Peheje