문제

Django와 함께 다음 마이그레이션이 가능한지 궁금합니다. 남쪽 그리고 여전히 데이터를 유지합니다.

전에:

현재 TV라는 두 개의 앱이 있습니다. 하나는 영화라고하며, 각각은 videofile 모델 (여기서 단순화)이 있습니다.

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)

후에:

두 개의 videofile 객체가 너무 비슷하기 때문에 복제를 제거하고 일반 Videofile 클래스가 포함 된 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)

영화/models.py :

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

제 질문은 Django-South로 어떻게 이것을 달성하고 기존 데이터를 유지할 수 있습니까?

이 세 가지 앱은 모두 South Migrations에 의해 이미 관리되고 있으며 South 문서에 따르면 스키마와 데이터 마이그레이션을 결합하는 것은 나쁜 실습이며 몇 단계로 수행해야합니다.

이와 같은 별도의 마이그레이션을 사용하여 수행 할 수 있다고 생각합니다 (미디어를 가정합니다. 이미 생성되었다고 가정).

  1. 스키마 마이그레이션 TV.Videofile 및 Movies.Videofile의 모든 필드 이름을 새 미디어로 이동할 수있는 Schema Migration.
  2. 스키마 TV.Videofile 및 movies.
  3. 이름으로 Old_name을 복사하는 데이터 마이그레이션, Old_size 크기 등
  4. Old_ 필드를 제거하기위한 체계 마이그레이션

그 모든 일을 끝내기 전에 그것이 효과가 있다고 생각하십니까? 더 좋은 방법이 있습니까?

관심이 있으시면 프로젝트가 여기에서 호스팅됩니다. http://code.google.com/p/medianav/

도움이 되었습니까?

해결책

최신 버전의 Django/South와의 호환성에 대한 일부 메모는 Paul의 아래 응답을 확인하십시오.


이것은 흥미로운 문제처럼 보였고, 나는 사우스의 열렬한 팬이되고 있기 때문에 나는 이것을 조금 조사하기로 결정했습니다. 위에서 설명한 내용에 대한 초록에 대한 테스트 프로젝트를 구축했으며 South를 성공적으로 사용하여 귀하가 요구하는 마이그레이션을 수행했습니다. 코드에 도달하기 전에 몇 가지 메모가 있습니다.

  • 사우스 문서는 스키마 마이그레이션 및 데이터 마이그레이션을 별도로 수행 할 것을 권장합니다. 나는 이것에서 정장을 따랐다.

  • 백엔드에서 Django는 상속 모델에서 OnetoOne 필드를 자동으로 작성하여 상속 테이블을 나타냅니다.

  • 이것을 이해하면, 우리의 사우스 마이그레이션은 onetoone 필드를 수동으로 올바르게 처리해야하지만,이를 실험 할 때 남쪽 (또는 아마도 Django 자체)은 동일한 이름의 여러 상속 테이블에 제출 된 onetoone을 만들 수없는 것으로 보입니다. 이로 인해 영화/TV 앱의 각 어린이 테이블 이름을 자체 앱 (예 : 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

우주를 위해, 모델이 항상 동일하게 보이기 때문에 '영화'앱으로 만 시연 할 것입니다.

영화/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')

영화/마이그레이션/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')

영화/마이그레이션/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 스키마를 테스트합니다. 무엇이든 시도하기 전에 항상 백업을 만들고 일반적으로 조심하십시오.

호환성 통지

원래 메시지를 그대로 유지하려고하지만 사우스는 명령을 변경했습니다. manage.py startmigration ~ 안으로 manage.py schemamigration.

다른 팁

나는 T Stone이 설명한 솔루션을 걸어 보려고 노력했으며 그것이 훌륭한 스타터라고 생각하고 일이 어떻게 해야하는지 설명하는 동안 몇 가지 문제를 해결했습니다.

나는 주로 당신을 생각합니다 ~하지 않다 더 이상 부모 수업에 대한 테이블 항목을 만들어야합니다. 즉, 필요하지 않습니다.

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

더 이상. Django는 이제 귀하를 위해 자동 으로이 작업을 수행합니다 (Null 필드가없는 경우 위의 것이 나에게 작동하지 않아 데이터베이스 오류가 발생했습니다).

아마도 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)

분명히 많은 겹치가 있으므로 공통점을 일반 게시물 모델하고 다른 모델 클래스의 차이점 만 유지하십시오.

새로운 설정 :

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

(나는 사용 중입니다 $ 쉘 프롬프트를 나타내려면 입력하지 마십시오.)

다음으로 새 클래스를 만듭니다 SimplePost 그리고 ExtPost post1/models.py 및 post2/models.py에서 각각 (아직 나머지 클래스를 삭제하지 마십시오). 그런 다음이 두 가지에 대한 스키미 이민을 만듭니다.

$./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에서 지금 중복되는 부품을 삭제 한 다음 스키미 이민을 만들어 테이블을 새 상태로 업데이트 할 수 있습니다.

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

그리고 그게되어야합니다! 바라건대 모든 것이 작동하고 모델을 리팩토링했습니다.

추상 모델

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

아마도 일반적인 관계 당신에게도 유용 할 것입니다.

나는 비슷한 마이그레이션을했고 여러 단계로 그것을 선택했습니다. 다중 마이그레이션을 만드는 것 외에도 문제가 발생하면 폴백을 제공하기 위해 후진 마이그레이션을 만들었습니다. 그런 다음 테스트 데이터를 가져 와서 앞으로 마이그레이션 할 때 올바르게 나올 것이라고 확신 할 때까지 앞뒤로 마이그레이션했습니다. 마지막으로 생산 현장을 마이그레이션했습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top