32

Django southで次の移行が可能であり、データを保持できるかどうか疑問に思っていました。

前:

私は現在、テレビと呼ばれる 1 つと映画と呼ばれる 2 つのアプリを持っています。

tv/models.py:

class VideoFile(models.Model):
    show = models.ForeignKey(Show, blank=True, null=True)
    name = models.CharField(max_length=1024, blank=True)
    size = models.IntegerField(blank=True, null=True)
    ctime = models.DateTimeField(blank=True, null=True)

ムービー/models.py:

class VideoFile(models.Model):
    movie = models.ForeignKey(Movie, blank=True, null=True)
    name = models.CharField(max_length=1024, blank=True)
    size = models.IntegerField(blank=True, null=True)
    ctime = models.DateTimeField(blank=True, null=True)

後:

2 つの videofile オブジェクトは非常に似ているため、重複を取り除き、メディアと呼ばれる別のアプリで新しいモデルを作成します。このアプリには、汎用の VideoFile クラスが含まれ、継承を使用して拡張されます。

メディア/models.py:

class VideoFile(models.Model):
    name = models.CharField(max_length=1024, blank=True)
    size = models.IntegerField(blank=True, null=True)
    ctime = models.DateTimeField(blank=True, null=True)

tv/models.py:

class VideoFile(media.models.VideoFile):
    show = models.ForeignKey(Show, blank=True, null=True)

ムービー/models.py:

class VideoFile(media.models.VideoFile):
    movie = models.ForeignKey(Movie, blank=True, null=True)

だから私の質問は、django-south でこれを達成し、既存のデータを維持するにはどうすればよいですか?

これら 3 つのアプリはすべて、すでに南方の移行によって管理されており、南方のドキュメントによると、スキーマとデータの移行を組み合わせることは推奨されておらず、いくつかの手順で行うことを推奨しています。

このような個別の移行を使用して実行できると思います(media.VideoFileが既に作成されていると仮定します)

  1. 新しい media.VideoFile モデルに移動する tv.VideoFile と movies.VideoFile のすべてのフィールドの名前を変更するためのスキーマの移行 (おそらく old_name、old_size など)
  2. media.VideoFile から継承するための tv.VideoFile および movies.VideoFile へのスキーマの移行
  3. old_name を name に、old_size を size にコピーするなどのデータ移行
  4. old_ フィールドを削除するスキーム移行

私がそのすべての作業を行う前に、それはうまくいくと思いますか? より良い方法はありますか?

興味があれば、プロジェクトはここでホストされています: http://code.google.com/p/medianav/

4

4 に答える 4

49

Django/South の新しいバージョンとの互換性に関するいくつかのメモについては、以下の Paul による応答を確認してください。


これは興味深い問題のように思えました。私は南部の大ファンになりつつあるので、これについて少し調べてみることにしました。上記で説明した内容の要約に基づいてテスト プロジェクトを作成し、South を使用して、質問している移行を正常に実行しました。コードに入る前に、いくつかの注意事項があります。

  • South のドキュメントでは、スキーマの移行とデータの移行を別々に行うことを推奨しています。私はこれに追随しました。

  • バックエンドでは、Django は、継承モデルで OneToOne フィールドを自動的に作成することにより、継承されたテーブルを表します。

  • これを理解した上で、South への移行では OneToOne フィールドを手動で適切に処理する必要がありますが、これを試してみると、South (またはおそらく Django 自体) は複数の継承されたテーブルに同じ名前の OneToOne フィールドを作成できないようです。このため、movies/tv アプリの各子テーブルの名前を、それ自体のアプリ (つまり、MovieVideoFile/ShowVideoFile) に対応するように変更しました。

  • 実際のデータ移行コードを試してみると、South は最初に OneToOne フィールドを作成し、次にそれにデータを割り当てることを好むようです。作成中に OneToOne フィールドにデータを割り当てると、South がチョークします。(南のすべての涼しさのための公正な妥協)。

そうは言っても、発行されたコンソールコマンドのログを保持しようとしました。必要に応じて解説を挿入します。最終的なコードは一番下にあります。

コマンド履歴

django-admin.py startproject southtest
manage.py startapp movies
manage.py startapp tv
manage.py syncdb
manage.py startmigration movies --initial
manage.py startmigration tv --initial
manage.py migrate
manage.py shell          # added some fake data...
manage.py startapp media
manage.py startmigration media --initial
manage.py migrate
# edited code, wrote new models, but left old ones intact
manage.py startmigration movies unified-videofile --auto
# create a new (blank) migration to hand-write data migration
manage.py startmigration movies videofile-to-movievideofile-data 
manage.py migrate
# edited code, wrote new models, but left old ones intact
manage.py startmigration tv unified-videofile --auto
# create a new (blank) migration to hand-write data migration
manage.py startmigration tv videofile-to-movievideofile-data
manage.py migrate
# removed old VideoFile model from apps
manage.py startmigration movies removed-videofile --auto
manage.py startmigration tv removed-videofile --auto
manage.py migrate

スペースの都合上、モデルは最終的に常に同じように見えるため、'movies' アプリでのみデモを行います。

ムービー/models.py

from django.db import models
from media.models import VideoFile as BaseVideoFile

# This model remains until the last migration, which deletes 
# it from the schema.  Note the name conflict with media.models
class VideoFile(models.Model):
    movie = models.ForeignKey(Movie, blank=True, null=True)
    name = models.CharField(max_length=1024, blank=True)
    size = models.IntegerField(blank=True, null=True)
    ctime = models.DateTimeField(blank=True, null=True)

class MovieVideoFile(BaseVideoFile):
    movie = models.ForeignKey(Movie, blank=True, null=True, related_name='shows')

movies/migrations/0002_unified-videofile.py (スキーマの移行)

from south.db import db
from django.db import models
from movies.models import *

class Migration:

    def forwards(self, orm):

        # Adding model 'MovieVideoFile'
        db.create_table('movies_movievideofile', (
            ('videofile_ptr', orm['movies.movievideofile:videofile_ptr']),
            ('movie', orm['movies.movievideofile:movie']),
        ))
        db.send_create_signal('movies', ['MovieVideoFile'])

    def backwards(self, orm):

        # Deleting model 'MovieVideoFile'
        db.delete_table('movies_movievideofile')

movies/migration/0003_videofile-to-movievideofile-data.py (データ移行)

from south.db import db
from django.db import models
from movies.models import *

class Migration:

    def forwards(self, orm):
        for movie in orm['movies.videofile'].objects.all():
            new_movie = orm.MovieVideoFile.objects.create(movie = movie.movie,)
            new_movie.videofile_ptr = orm['media.VideoFile'].objects.create()

            # videofile_ptr must be created first before values can be assigned
            new_movie.videofile_ptr.name = movie.name
            new_movie.videofile_ptr.size = movie.size
            new_movie.videofile_ptr.ctime = movie.ctime
            new_movie.videofile_ptr.save()

    def backwards(self, orm):
        print 'No Backwards'

南国最高です!

OK 標準免責事項: ライブ データを扱っています。ここで動作するコードを提供しましたが、 を使用し--db-dry-runてスキーマをテストしてください。何かを試す前に必ずバックアップを作成し、通常は注意してください。

互換性通知

元のメッセージはそのままにしておきますが、South はコマンドmanage.py startmigrationを に変更しましたmanage.py schemamigration

于 2009-10-21T20:56:57.010 に答える
9

私は T Stone によって概説された解決策を試してみましたが、それは素晴らしいスターターであり、物事をどのように行うべきかを説明していると思いますが、いくつかの問題に遭遇しました.

ほとんどの場合、親クラスのテーブル エントリをもう作成する必要はないと思います。つまり、必要ありません。

new_movie.videofile_ptr = orm['media.VideoFile'].objects.create()

もう。Djangoはこれを自動的に行うようになりました(null以外のフィールドがある場合、上記は機能せず、データベースエラーが発生しました)。

おそらくdjangoとsouthの変更が原因だと思います。これは、django 1.2.3とsouth 0.7.1を使用したubuntu 10.10で機能したバージョンです。モデルは少し異なりますが、要点は次のとおりです。

初期設定

post1/models.py:

class Author(models.Model):
    first = models.CharField(max_length=30)
    last = models.CharField(max_length=30)

class Tag(models.Model):
    name = models.CharField(max_length=30, primary_key=True)

class Post(models.Model):
    created_on = models.DateTimeField()
    author = models.ForeignKey(Author)
    tags = models.ManyToManyField(Tag)
    title = models.CharField(max_length=128, blank=True)
    content = models.TextField(blank=True)

post2/models.py:

class Author(models.Model):
    first = models.CharField(max_length=30)
    middle = models.CharField(max_length=30)
    last = models.CharField(max_length=30)

class Tag(models.Model):
    name = models.CharField(max_length=30)

class Category(models.Model):
    name = models.CharField(max_length=30)

class Post(models.Model):
    created_on = models.DateTimeField()
    author = models.ForeignKey(Author)
    tags = models.ManyToManyField(Tag)
    title = models.CharField(max_length=128, blank=True)
    content = models.TextField(blank=True)
    extra_content = models.TextField(blank=True)
    category = models.ForeignKey(Category)

明らかに多くの重複があるため、共通点を一般的な投稿モデルに分解し、他のモデル クラスの違いのみを保持したいと考えました。

新しいセットアップ:

genpost/models.py:

class Author(models.Model):
    first = models.CharField(max_length=30)
    middle = models.CharField(max_length=30, blank=True)
    last = models.CharField(max_length=30)

class Tag(models.Model):
    name = models.CharField(max_length=30, primary_key=True)

class Post(models.Model):
    created_on = models.DateTimeField()
    author = models.ForeignKey(Author)
    tags = models.ManyToManyField(Tag)
    title = models.CharField(max_length=128, blank=True)
    content = models.TextField(blank=True)

post1/models.py:

import genpost.models as gp

class SimplePost(gp.Post):
    class Meta:
        proxy = True

post2/models.py:

import genpost.models as gp

class Category(models.Model):
    name = models.CharField(max_length=30)

class ExtPost(gp.Post):
    extra_content = models.TextField(blank=True)
    category = models.ForeignKey(Category)

先に進みたい場合は、まずこれらのモデルを南に移動する必要があります。

$./manage.py schemamigration post1 --initial
$./manage.py schemamigration post2 --initial
$./manage.py migrate

データの移行

それについてどうやって行くのですか?最初に新しいアプリ genpost を作成し、south で初期移行を行います。

$./manage.py schemamigration genpost --initial

($シェルプロンプトを表すために使用しているので、入力しないでください。)

次に、新しいクラスSimplePostExtPostをそれぞれ post1/models.py と post2/models.py に作成します (残りのクラスはまだ削除しないでください)。次に、これら 2 つのスキーマ移行も作成します。

$./manage.py schemamigration post1 --auto
$./manage.py schemamigration post2 --auto

これで、これらすべての移行を適用できます。

$./manage.py migrate

post1 と post2 から genpost にデータを移行して、問題の核心に取り掛かりましょう。

$./manage.py datamigration genpost post1_and_post2_to_genpost --freeze post1 --freeze post2

次に、genpost/migrations/0002_post1_and_post2_to_genpost.py を編集します。

class Migration(DataMigration):

    def forwards(self, orm):

        # 
        # Migrate common data into the new genpost models
        #
        for auth1 in orm['post1.author'].objects.all():
            new_auth = orm.Author()
            new_auth.first = auth1.first
            new_auth.last = auth1.last
            new_auth.save()

        for auth2 in orm['post2.author'].objects.all():
            new_auth = orm.Author()
            new_auth.first = auth2.first
            new_auth.middle = auth2.middle
            new_auth.last = auth2.last
            new_auth.save()

        for tag in orm['post1.tag'].objects.all():
            new_tag = orm.Tag()
            new_tag.name = tag.name
            new_tag.save()

        for tag in orm['post2.tag'].objects.all():
            new_tag = orm.Tag()
            new_tag.name = tag.name
            new_tag.save()

        for post1 in orm['post1.post'].objects.all():
            new_genpost = orm.Post()

            # Content
            new_genpost.created_on = post1.created_on
            new_genpost.title = post1.title
            new_genpost.content = post1.content

            # Foreign keys
            new_genpost.author = orm['genpost.author'].objects.filter(\
                    first=post1.author.first,last=post1.author.last)[0]

            new_genpost.save() # Needed for M2M updates
            for tag in post1.tags.all():
                new_genpost.tags.add(\
                        orm['genpost.tag'].objects.get(name=tag.name))

            new_genpost.save()
            post1.delete()

        for post2 in orm['post2.post'].objects.all():
            new_extpost = p2.ExtPost() 
            new_extpost.created_on = post2.created_on
            new_extpost.title = post2.title
            new_extpost.content = post2.content

            # Foreign keys
            new_extpost.author_id = orm['genpost.author'].objects.filter(\
                    first=post2.author.first,\
                    middle=post2.author.middle,\
                    last=post2.author.last)[0].id

            new_extpost.extra_content = post2.extra_content
            new_extpost.category_id = post2.category_id

            # M2M fields
            new_extpost.save()
            for tag in post2.tags.all():
                new_extpost.tags.add(tag.name) # name is primary key

            new_extpost.save()
            post2.delete()

        # Get rid of author and tags in post1 and post2
        orm['post1.author'].objects.all().delete()
        orm['post1.tag'].objects.all().delete()
        orm['post2.author'].objects.all().delete()
        orm['post2.tag'].objects.all().delete()


    def backwards(self, orm):
        raise RuntimeError("No backwards.")

これらの移行を適用します。

$./manage.py migrate

次に、冗長になった部分を post1/models.py と post2/models.py から削除し、schemamigrations を作成してテーブルを新しい状態に更新します。

$./manage.py schemamigration post1 --auto
$./manage.py schemamigration post2 --auto
$./manage.py migrate

そして、それだけです!すべてが機能し、モデルがリファクタリングされたことを願っています。

于 2011-01-26T14:42:52.403 に答える
3

抽象モデル

class VideoFile(models.Model):
    name = models.CharField(max_length=1024, blank=True)
    size = models.IntegerField(blank=True, null=True)
    ctime = models.DateTimeField(blank=True, null=True)
    class Meta:
        abstract = True

一般的な関係もあなたに役立つかもしれません。

于 2009-10-21T11:12:57.413 に答える
1

同様の移行を行いましたが、複数のステップで行うことにしました。複数の移行を作成するだけでなく、問題が発生した場合のフォールバックを提供する後方移行も作成しました。次に、いくつかのテスト データを取得し、順方向に移行したときに正しく出力されることを確認するまで、順方向および逆方向に移行しました。最後に、本番サイトを移行しました。

于 2009-10-21T13:25:09.550 に答える