web-dev-qa-db-ja.com

DjangoモデルフィールドのタイプをCharFieldからForeignKeyに変更します

Djangoモデルの1つのフィールドのタイプをCharFieldからForeignKeyに変更する必要があります。フィールドにはすでにデータが入力されているので、これを行うための最良または適切な方法は何かと思っていました。フィールドタイプを更新して移行することはできますか?それとも、知っておくべき「落とし穴」はありますか? N.B:南部ではなく、バニラDjango管理操作(makemigrationsおよびmigrate)のみを使用します。

23
ChrisM

これは、多段階の移行を実行する場合によく発生します。これに対する私の推奨は、次のようになります。

まず、これがdiscographyというアプリケーション内の初期モデルであると仮定します。

from Django.db import models

class Album(models.Model):
    name = models.CharField(max_length=255)
    artist = models.CharField(max_length=255)

ここで、代わりにアーティストにForeignKeyを使用することに気付きました。さて、述べたように、これはこれのための単純なプロセスではありません。これはいくつかのステップで実行する必要があります。

ステップ1、ForeignKeyの新しいフィールドを追加します。必ずnullとしてマークしてください。

from Django.db import models

class Album(models.Model):
    name = models.CharField(max_length=255)
    artist = models.CharField(max_length=255)
    artist_link = models.ForeignKey('Artist', null=True)

class Artist(models.Model):
    name = models.CharField(max_length=255)

...この変更の移行を作成します。

./manage.py makemigrations discography

ステップ2、新しいフィールドに入力します。これを行うには、空のマイグレーションを作成する必要があります。

./manage.py makemigrations --empty --name transfer_artists discography

この空の移行が完了したら、レコードをリンクするために、単一のRunPython操作をそれに追加する必要があります。この場合、次のようになります。

def link_artists(apps, schema_editor):
    Album = apps.get_model('discography', 'Album')
    Artist = apps.get_model('discography', 'Artist')
    for album in Album.objects.all():
        artist, created = Artist.objects.get_or_create(name=album.artist)
        album.artist_link = artist
        album.save()

データが新しいフィールドに転送されたので、新しいフィールドをすべてに使用して、実際に作業を終えてすべてをそのままにすることができます。または、少しクリーンアップする場合は、さらに2つのマイグレーションを作成します。

最初の移行では、元のフィールドartistを削除する必要があります。 2回目の移行では、新しいフィールドの名前をartist_linkからartistに変更します。

Djangoが操作を正しく認識できるようにするために、これは複数のステップで行われます。これを処理するために手動で移行を作成することもできますが、理解するためにそれを残します。

64
Joey Wilhelm

それはジョーイの素晴らしい答えの続きです。 新しいフィールドの名前を元の名前に変更するには?

フィールドにデータがある場合、それはおそらくプロジェクトの他の場所で使用していることを意味します。したがって、この解決策では別の名前のフィールドが残され、新しいフィールドを使用するようにプロジェクトをリファクタリングするか、古いフィールドを削除して、新しい名前を変更します。

このプロセスはコードのリファクタリングを妨げるものではないことに注意してください。 CHOICESでCharFieldを使用している場合、たとえばget_filename_display()を使用してコンテンツにアクセスしていました。

移行を行うためにフィールドを削除しようとした場合、他のフィールドの名前を変更してからもう一度移行すると、Djangoで問題が発生します。これは、事業。

Joeyが説明したように空のマイグレーションを作成し、これを操作に入れます。

operations = [
    migrations.RemoveField(
        model_name='app_name',
        name='old_field_name',
    ),
    migrations.RenameField(
        model_name='app_name',
        old_name='old_field_name_link',
        new_name='old_field_name',
    ),
]

次に、migrateを実行すると、データベースに変更が加えられますが、モデルには明らかにありません。今度は、古いフィールドを削除し、新しいForeignKeyフィールドの名前を元の名前に変更します。

これを行うことは特にハックだとは思わないが、それでも、何をいじっているのかを完全に理解している場合にのみ、この種のことを行う。

1
Pere Picornell

Joeyの回答に加えて、Django 2.2.11。

CompanyモデルとEmployeeモデルで構成される、私のユースケースのモデルを以下に示します。 designationを外部キーフィールドに変換する必要があります。アプリ名はcoreです

class Company(CommonFields):
    name = models.CharField(max_length=255, blank=True, null=True

class Employee(CommonFields):
    company = models.ForeignKey("Company", on_delete=models.CASCADE, blank=True, null=True)
    designation = models.CharField(max_length=100, blank=True, null=True)

ステップ1

外部キーを作成するdesignation_linkをEmployeeに追加し、null = Trueとしてマークします

class Designation(CommonFields):
    name = models.CharField(max_length=255)
    company = models.ForeignKey("Company", on_delete=models.CASCADE, blank=True, null=True)

class Employee(CommonFields):
    company = models.ForeignKey("Company", on_delete=models.CASCADE, blank=True, null=True)
    designation = models.CharField(max_length=100, blank=True, null=True)
    designation_link = models.ForeignKey("Designation", on_delete=models.CASCADE, blank=True, null=True)

ステップ2

空の移行を作成します。次のコマンドを使用します。

python app_code/manage.py makemigrations --empty --name transfer_designations core

これにより、migrationsディレクトリに次のファイルが作成されます。

# Generated by Django 2.2.11 on 2020-04-02 05:56

from Django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('core', '0006_auto_20200402_1119'),
    ]

    operations = [
    ]

ステップ

すべてのEmployeesをループし、Designationを作成してEmployeeにリンクする関数を空のマイグレーションに入力します。

私の使用例では、各DesignationCompanyにリンクされています。つまり、Designationには「マネージャー」の2つの行が含まれる可能性があります。1つは会社Aの行で、もう1つは会社Bの行です。

最終的な移行は次のようになります。

# core/migrations/0007_transfer_designations.py

# Generated by Django 2.2.11 on 2020-04-02 05:56

from Django.db import migrations

def link_designation(apps, schema_editor):
    Employee = apps.get_model('core', 'Employee')
    Designation = apps.get_model('core', 'Designation')
    for emp in Employee.objects.all():
        if(emp.designation is not None and emp.company is not None):
            desig, created = Designation.objects.get_or_create(name=emp.designation, company=emp.company)
            emp.designation_link = desig
            emp.save()

class Migration(migrations.Migration):

    dependencies = [
        ('core', '0006_auto_20200402_1119'),
    ]

    operations = [
        migrations.RunPython(link_designation),
    ]

ステップ4

最後に、次を使用してこの移行を実行します。

python app_code/manage.py migrate core 0007

0
jerrymouse