And*_*ler 32 python migration django django-south
我想知道Django 南部是否可以进行以下迁移并仍保留数据.
我目前有两个应用程序,一个叫做tv,一个叫做电影,每个都有一个VideoFile模型(这里简化):
电视/ 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)
因为两个videofile对象是如此相似,所以我想摆脱重复并在一个名为media的单独应用程序中创建一个新模型,该应用程序包含一个通用的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)
电视/ 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实现这一目标并仍然维护现有数据?
所有这三个应用程序都已经由南迁移管理,根据南方文档,结合模式和数据迁移是不好的做法,他们建议应该只需几步即可完成.
我认为可以使用这样的单独迁移来完成(假设已经创建了media.VideoFile)
在我完成所有这些工作之前,你认为这会有用吗?有没有更好的办法?
如果您有兴趣,该项目将在此处托管:http://code.google.com/p/medianav/
T. *_*one 49
请查看以下Paul的回复,了解有关与较新版本Django/South的兼容性的一些注意事项.
这似乎是一个有趣的问题,我正在成为南方的忠实粉丝,所以我决定对此进行一些研究.我根据您上面描述的内容的摘要构建了一个测试项目,并成功使用South来执行您要求的迁移.在我们获得代码之前,这里有几个注意事项:
南文档建议单独执行模式迁移和数据迁移.我已经跟着这个了.
在后端,Django通过在继承模型上自动创建OneToOne字段来表示继承的表
理解这一点,我们的南迁移需要手动正确处理OneToOne字段,但是,在试验中,似乎South(或者Django本身)无法在具有相同名称的多个继承表上创建OneToOne.因此,我将电影/电视应用中的每个子表重命名为与其自己的应用程序(即MovieVideoFile/ShowVideoFile)相对应.
在使用实际的数据迁移代码时,似乎South更喜欢首先创建OneToOne字段,然后为其分配数据.在创建期间将数据分配给OneToOne字段会导致南方阻塞.(对所有酷的南方公平妥协).
所以说了这么多,我试着记录发出的控制台命令.我会在必要时插入评论.最终的代码位于底部.
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
为了空间,并且由于模型最终总是看起来一样,我只会用'电影'应用来演示.
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')
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')
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来测试你的架构.在尝试任何事情之前总是做一个备份,一般要小心.
兼容性通知
我将原始信息完好无损,但南方已将命令manage.py startmigration改为manage.py schemamigration.
我确实试图通过T Stone概述的解决方案,虽然我认为它是一个出色的入门者,并解释了应该如何做的事情我遇到了一些问题.
我认为大多数情况下你不需要为父类创建表条目,即你不需要
new_movie.videofile_ptr = orm['media.VideoFile'].objects.create()
了.Django现在将自动为您执行此操作(如果您有非空字段,那么上面的内容对我不起作用并且给了我一个数据库错误).
我想这可能是由于django和南方的变化,这是一个版本,对我来说在ubuntu 10.10上运行django 1.2.3和南0.7.1.模型有点不同,但你会得到要点:
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
怎么去呢?首先编写新的app genpost并在南方进行初始迁移:
$./manage.py schemamigration genpost --initial
(我$用来表示shell提示符,所以不要输入它.)
接下来分别在post1/models.py和post2/models.py中创建新类SimplePost和ExtPost(不要删除其余的类).然后为这两个创建schemamigrations:
$./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
这应该是它!希望一切正常,你已经重构了你的模型.
| 归档时间: | 
 | 
| 查看次数: | 8807 次 | 
| 最近记录: |