问题描述
我想知道使用 Django south 是否可以进行以下迁移并且仍然保留数据.
I was wondering if the following migration is possible with Django south and still retain data.
我目前有两个应用,一个叫 tv,一个叫电影,每个都有一个 VideoFile 模型(此处简化):
I currently have two apps, one called tv, one called movies, each with a VideoFile model (simplified here):
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)
movies/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 类并使用继承来扩展它:
After:
Because the two videofile objects are so similar I want to get rid of duplication and create a new model in a separate app called media that contains a generic VideoFile class and use inheritance to extend it:
media/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)
movies/models.py:
class VideoFile(media.models.VideoFile):
movie = models.ForeignKey(Movie, blank=True, null=True)
所以我的问题是,我如何使用 django-south 完成此任务并仍然维护现有数据?
So my question is, how can I accomplish this with django-south and still maintain existing data?
所有这三个应用程序都已由 south 迁移管理,根据 south 文档,将架构和数据迁移结合起来是一种不好的做法,他们建议应该分几步完成.
All three these apps are already managed by south migrations and according to the south documentation it is bad practice to combine a schema and data migration and they recommend it should be done in a few steps.
我认为可以使用像这样的单独迁移来完成(假设 media.VideoFile 已经创建)
I think it could be done using separate migrations like this (assuming media.VideoFile is already created)
- 架构迁移以重命名 tv.VideoFile 和 movies.VideoFile 中的所有字段,这些字段将移动到新的 media.VideoFile 模型,可能是 old_name、old_size 等
- 架构迁移到 tv.VideoFile 和 movies.VideoFile 以从 media.VideoFile 继承
- 将 old_name 复制到 name、old_size 到 size 等的数据迁移
- 计划迁移以移除 old_ 字段
在我完成所有这些工作之前,你认为这会奏效吗?有没有更好的办法?
Before I go through all that work, do you think that will work? Is there a better way?
如果您有兴趣,该项目在此处托管:http://code.google.com/p/medianav/
If you're interested, the project is hosted here: http://code.google.com/p/medianav/
推荐答案
查看下面 Paul 的回复,了解与新版本 Django/South 的兼容性的一些说明.
这似乎是一个有趣的问题,而且我正在成为 South 的忠实粉丝,所以我决定稍微研究一下.我根据您上面描述的摘要构建了一个测试项目,并已成功使用 South 执行您所询问的迁移.在我们进入代码之前,这里有几点说明:
This seemed like an interesting problem, and I'm becoming a big fan of South, so I decided to look into this a bit. I built a test project on the abstract of what you've described above, and have successfully used South to perform the migration you are asking about. Here's a couple of notes before we get to the code:
South 文档建议将架构迁移和数据迁移分开进行.我也照做了.
The South documentation recommends doing schema migrations and data migrations separate. I've followed suit in this.
在后端,Django 通过在继承模型上自动创建 OneToOne 字段来表示继承表
On the backend, Django represents an inherited table by automatically creating a OneToOne field on the inheriting model
了解这一点,我们的 South 迁移需要手动正确处理 OneToOne 字段,但是,在试验中,South(或者可能是 Django 本身)似乎无法在多个继承的同名表上创建 OneToOne.因此,我将电影/电视应用程序中的每个子表重命名为与其自己的应用程序相对应(即 MovieVideoFile/ShowVideoFile).
Understanding this, our South migration needs to properly handle the OneToOne field manually, however, in experimenting with this it seems that South (or perhaps Django itself) cannot create a OneToOne filed on multiple inherited tables with the same name. Because of this, I renamed each child-table in the movies/tv app to be respective to it's own app (ie. MovieVideoFile/ShowVideoFile).
在实际的数据迁移代码中,South 似乎更喜欢先创建 OneToOne 字段,然后将数据分配给它.在创建期间将数据分配给 OneToOne 字段会导致 South 阻塞.(对南方所有凉爽的公平妥协).
In playing with the actual data migration code, it seems South prefers to create the OneToOne field first, and then assign data to it. Assigning data to the OneToOne field during creation cause South to choke. (A fair compromise for all the coolness that is South).
说了这么多,我试着记录控制台命令的发布.我会在必要时插入评论.最终代码在底部.
So having said all that, I tried to keep a log of the console commands being issued. I'll interject commentary where necessary. The final code is at the bottom.
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
为了篇幅,由于模型最终看起来总是一样的,我将只使用电影"应用程序进行演示.
For space sake, and since the models invariably look the same in the end, I'm only going to demonstrate with 'movies' app.
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'
南方太棒了!
好的标准免责声明:您正在处理实时数据.我在这里给了你工作代码,但是请使用 --db-dry-run
来测试你的模式.在尝试任何事情之前务必进行备份,并且通常要小心.
South is awesome!
Ok standard disclaimer: You're dealing with live data. I've given you working code here, but please use the --db-dry-run
to test your schema. Always make a backup before trying anything, and generally be careful.
兼容性声明
我将保持原始消息不变,但 South 已将命令 manage.py startmigration
更改为 manage.py schemamigration
.
I'm going to keep my original message intact, but South has since changed the command manage.py startmigration
into manage.py schemamigration
.
这篇关于使用 south 重构具有继承的 Django 模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!