代码之家  ›  专栏  ›  技术社区  ›  Andre Miller

使用south重构具有继承的Django模型

  •  32
  • Andre Miller  · 技术社区  · 16 年前

    south

    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)
    

    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)
    

    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)
    

    电影/模型.py:

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

    我认为可以使用这样的单独迁移来完成(假设media.VideoFile已经创建)

    1. 数据迁移,将old_name复制到name,将old_size复制到size等
    2. 方案迁移以删除旧字段

    在我完成所有这些工作之前,你认为这会奏效吗?有更好的办法吗?

    如果您感兴趣,该项目托管在这里: http://code.google.com/p/medianav/

    4 回复  |  直到 10 年前
        1
  •  49
  •   Jon Cage    14 年前

    查看Paul下面的回复,了解与Django/South新版本兼容性的一些注意事项。


    这似乎是一个有趣的问题,我越来越喜欢South,所以我决定对此进行一些研究。我根据你上面描述的摘要构建了一个测试项目,并成功地使用South来执行你所要求的迁移。在我们开始代码之前,有几点注意事项:

    • 在后端,Django通过在继承模型上自动创建OneToOne字段来表示继承的表

    • 理解这一点后,我们的South迁移需要手动正确处理OneToOne字段,然而,在实验中,South(或者Django本身)似乎无法在多个同名继承表上创建OneToOne文件。因此,我将电影/电视应用程序中的每个子表重命名为其自己的应用程序(即MovieVideoFile/ShowVideoFile)。

    命令历史

    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'
    

    南方太棒了!

    --db-dry-run 测试你的模式。在尝试任何事情之前,一定要做好备份,通常要小心。

    兼容性通知

    我会保持我最初的信息不变,但南方已经改变了指挥权 manage.py startmigration 进入 manage.py schemamigration .

        2
  •  9
  •   Paul    15 年前

    我想主要是你 需要为父类创建表条目,即您不需要

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

    不再。Django现在会自动为你做这件事(如果你有非空字段,那么上面的方法对我不起作用,给了我一个数据库错误)。

    我认为这可能是由于django和south的变化,这是一个在ubuntu 10.10上使用django 1.2.3和south 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)
    

    普通邮件

    新设置:

    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
    

    向南迁徙:

    $./manage.py schemamigration genpost --initial
    

    (我正在使用 $ 要表示shell提示符,请不要键入。)

    然后为这两个也创建schemamigration:

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

    现在我们可以应用所有这些迁移:

    $./manage.py migrate
    

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

    然后编辑genpost/migrations/0002_post1_和_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迁移
    

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

        3
  •  3
  •   Oduvan    16 年前

    Abstract Model

    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
    

    多半 generic relation

        4
  •  1
  •   Edward Dale    16 年前

    我做了一个类似的迁移,我选择分多个步骤进行。除了创建多次迁移外,我还创建了反向迁移,以便在出现问题时提供回退。然后,我抓取了一些测试数据,并前后迁移,直到我确信在向前迁移时它是正确的。最后,我迁移了生产站点。