web-dev-qa-db-ja.com

DjangoフィールドをManyToManyに変更するときのデータ移行

フィールドをForeignKeyからManyToManyFieldに変更したいDjangoアプリケーションがあります。古いデータを保持したいのですが、これを行うための最も簡単で最良のプロセスは何ですか?重要なのは、データベースのバックエンドとしてsqlite3を使用していることです。

問題の要約が明確でない場合は、ここに例を示します。 2つのモデルがあるとします。

class Author(models.Model):  
    author = models.CharField(max_length=100) 

class Book(models.Model):  
    author = models.ForeignKey(Author)  
    title = models.CharField(max_length=100)

データベースに大量のデータがあるとします。ここで、Bookモデルを次のように変更します。

class Book(models.Model):  
    author = models.ManyToManyField(Author)  
    title = models.CharField(max_length=100) 

以前のデータをすべて「失う」ことはしたくありません。

これを達成するための最良/最も簡単な方法は何ですか?

ケン

45
Ken H

この質問は古く、当時のデータ移行の最良の選択肢は南を使用することでした。現在、Djangoには独自のmigrateコマンドがあり、プロセスは少し異なります。

これらのモデルをbooksというアプリに追加しました。そうでない場合は、それに応じて調整してください。

まず、フィールドをBookに追加し、_related_name_を少なくとも1つ、または両方に追加します(または衝突します)。

_class Book(models.Model):  
    author = models.ForeignKey(Author, related_name='book')
    authors = models.ManyToManyField(Author, related_name='books')
    title = models.CharField(max_length=100) 
_

移行を生成します。

_$ ./manage.py makemigrations
Migrations for 'books':
  0002_auto_20151222_1457.py:
    - Add field authors to book
    - Alter field author on book
_

次に、データ自体の移行を保持するために空の移行を作成します。

_./manage.py makemigrations books --empty
    Migrations for 'books':
0003_auto_20151222_1459.py:
_

そして、それに次のコンテンツを追加します。これがどのように機能するかを正確に理解するには、 データ移行 のドキュメントを確認してください。移行の依存関係を上書きしないように注意してください。

_# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from Django.db import models, migrations


def make_many_authors(apps, schema_editor):
    """
        Adds the Author object in Book.author to the
        many-to-many relationship in Book.authors
    """
    Book = apps.get_model('books', 'Book')

    for book in Book.objects.all():
        book.authors.add(book.author)


class Migration(migrations.Migration):

    dependencies = [
        ('books', '0002_auto_20151222_1457'),
    ]

    operations = [
        migrations.RunPython(make_many_authors),
    ]
_

次に、モデルからauthorフィールドを削除します。次のようになります。

_class Book(models.Model):
    authors = models.ManyToManyField(Author, related_name='books')
    title = models.CharField(max_length=100)
_

そのための新しい移行を作成し、それらすべてを実行します。

_$ ./manage.py makemigrations
Migrations for 'books':
  0004_remove_book_author.py:
    - Remove field author from book

$ ./manage.py migrate
Operations to perform:
  Synchronize unmigrated apps: messages, staticfiles
  Apply all migrations: admin, auth, sessions, books, contenttypes
Synchronizing apps without migrations:
  Creating tables...
    Running deferred SQL...
  Installing custom SQL...
Running migrations:
  Rendering model states... DONE
  Applying books.0002_auto_20151222_1457... OK
  Applying books.0003_auto_20151222_1459... OK
  Applying books.0004_remove_book_author... OK
_

以上です。以前は_book.author_で利用可能だった作成者は、book.authors.all()から取得するクエリセットに含まれているはずです。

68
Rodrigo Deodoro

おそらく、あなたがすべき最善かつ最も簡単なことは次のとおりです。

別の名前で多対多フィールドを作成すると言う

authors = models.ManyToManyField(Author)

外部キー値をM2M値に変換する小さな関数を記述します。

def convert():
    books = Book.objects.all()
    for book in books:
        if book.author:
            li = [book.author.id]
            book.authors.append(li)
            book.save()

実行したら、作成者フィールドをテーブルから削除して、移行を再度実行できます。

1
sprksh