抽象模型
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
也许 通用关系 对你也有用。
我做了类似的迁移,我选择了多个步骤。除了创建多个迁移之外,我还创建了向后迁移,以便在出现问题时提供回退。然后,我抓取了一些测试数据并向前和向后迁移,直到我确定它向前迁移时它正确地出来。最后,我迁移了生产站点。
我确实试图通过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)
import genpost.models as gp class SimplePost(gp.Post): class Meta: proxy = True
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提示符,所以不要输入。)
$
接下来创建新类 SimplePost 和 ExtPost 在post1 / models.py中 和post2 / models.py分别(不要删除其余的类)。 然后为这两个创建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.")
现在应用这些迁移:
接下来,您可以从post1 / models.py和post2 / models.py中删除现在冗余的部分,然后创建schemamigrations以将表更新为新状态:
$./manage.py schemamigration post1 --auto $./manage.py schemamigration post2 --auto $./manage.py migrate
这应该是它!希望一切正常,你已经重构了你的模型。
的 请查看以下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 测试您的架构。在尝试任何事情之前总是做一个备份,一般要小心。
--db-dry-run
的 兼容性通知 强>
我将原始信息完好无损,但南方已经改变了命令 manage.py startmigration 成 manage.py schemamigration 。
manage.py startmigration
manage.py schemamigration