Làm cách nào để di chuyển một mô hình ra khỏi một ứng dụng django và sang một ứng dụng mới?


126

Tôi có một ứng dụng django với bốn mô hình trong đó. Bây giờ tôi nhận ra rằng một trong những mô hình này phải ở trong một ứng dụng riêng biệt. Tôi đã cài đặt phía nam để di chuyển, nhưng tôi không nghĩ đây là thứ nó có thể tự động xử lý. Làm cách nào tôi có thể di chuyển một trong các mô hình ra khỏi ứng dụng cũ sang một mô hình mới?

Ngoài ra, hãy nhớ rằng tôi sẽ cần điều này là một quá trình lặp lại, để tôi có thể di chuyển hệ thống sản xuất và như vậy.


6
Đối với django 1.7 trở lên, hãy xem stackoverflow.com/questions/25648393/
Kẻ

Câu trả lời:


184

Làm thế nào để di chuyển bằng cách sử dụng phía nam.

Hãy nói rằng chúng tôi có hai ứng dụng: phổ biến và cụ thể:

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   `-- 0002_create_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   `-- 0002_create_dog.py
    `-- models.py

Bây giờ chúng tôi muốn chuyển mô hình common.models.cat sang ứng dụng cụ thể (chính xác là cụ thể.models.cat). Đầu tiên thực hiện các thay đổi trong mã nguồn và sau đó chạy:

$ python manage.py schemamigration specific create_cat --auto
 + Added model 'specific.cat'
$ python manage.py schemamigration common drop_cat --auto
 - Deleted model 'common.cat'

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   |-- 0002_create_cat.py
|   |   `-- 0003_drop_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   |-- 0002_create_dog.py
    |   `-- 0003_create_cat.py
    `-- models.py

Bây giờ chúng tôi cần chỉnh sửa cả hai tệp di chuyển:

#0003_create_cat: replace existing forward and backward code
#to use just one sentence:

def forwards(self, orm):
    db.rename_table('common_cat', 'specific_cat') 

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='common',
            model='cat',
        ).update(app_label='specific')

def backwards(self, orm):
    db.rename_table('specific_cat', 'common_cat')

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='specific',
            model='cat',
        ).update(app_label='common')

#0003_drop_cat:replace existing forward and backward code
#to use just one sentence; add dependency:

depends_on = (
    ('specific', '0003_create_cat'),
)
def forwards(self, orm):
    pass
def backwards(self, orm):
    pass

Bây giờ cả hai ứng dụng di chuyển đều nhận thức được sự thay đổi và cuộc sống chỉ giảm đi một chút :-) Thiết lập mối quan hệ giữa các lần di chuyển này là chìa khóa thành công. Bây giờ nếu bạn làm:

python manage.py migrate common
 > specific: 0003_create_cat
 > common: 0003_drop_cat

sẽ thực hiện cả việc di chuyển và

python manage.py migrate specific 0002_create_dog
 < common: 0003_drop_cat
 < specific: 0003_create_cat

sẽ di chuyển mọi thứ xuống.

Lưu ý rằng để nâng cấp lược đồ tôi đã sử dụng ứng dụng chung và để hạ cấp, tôi đã sử dụng ứng dụng cụ thể. Đó là bởi vì cách thức phụ thuộc ở đây hoạt động.


1
Ồ cảm ơn nhé. Tôi đã tự học ở phía nam kể từ khi hỏi câu hỏi này, nhưng tôi chắc chắn rằng điều này sẽ giúp ích rất nhiều cho người khác.
Apreche

11
Bạn cũng có thể cần thực hiện di chuyển dữ liệu trong bảng django_content_type.
spookylukey

1
Hướng dẫn thực sự tuyệt vời @Potr. Tôi tò mò, không nên có một orm['contenttypes.contenttype'].objects.filter dòng trong phần ngược 0003_create_cat? Ngoài ra tôi muốn chia sẻ một mẹo. Nếu bạn có các chỉ mục, chúng cũng sẽ cần phải được sửa đổi. Trong trường hợp của tôi, chúng là các chỉ mục duy nhất, vì vậy, phía trước của tôi trông giống như thiS: db.delete_unique('common_cat', ['col1']) db.rename_table('common_cat', 'specific_cat') db.delete_unique('specific_cat', ['col1'])
Brad Pitcher

2
Để truy cập orm['contenttypes.contenttype'], bạn cũng cần thêm --freeze contenttypestùy chọn vào các schemamigrationlệnh của mình .
Gary

1
Trong trường hợp của tôi (Django 1.5.7 và South 1.0) .. Tôi đã phải gõ python manage.py schemamigration specific create_cat --auto --freeze commonđể truy cập vào mô hình con mèo từ ứng dụng phổ biến.
geoom

35

Để xây dựng dựa trên câu trả lời của Potr Czachur , các tình huống liên quan đến ForeignKeys phức tạp hơn và nên được xử lý hơi khác nhau.

(Ví dụ sau đây được xây dựng trên commonspecificcác ứng dụng được đề cập trong câu trả lời hiện tại).

# common/models.py

class Cat(models.Model):
    # ...

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

sau đó sẽ đổi thành

# common/models.py

from specific.models import Cat

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

# specific/models.py

class Cat(models.Model):
    # ...

Đang chạy

./manage.py schemamigration common --auto
./manage.py schemamigration specific --auto # or --initial

sẽ tạo ra các lần di chuyển sau (Tôi cố tình bỏ qua Django ContentType thay đổi, xem câu trả lời được tham chiếu trước đó để biết cách xử lý điều đó):

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.delete_table('common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.create_table('common_cat', (
            # ...
        ))
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.create_table('specific_cat', (
            # ...
        ))

    def backwards(self, orm):
        db.delete_table('specific_cat')

Như bạn có thể thấy, FK phải được thay đổi để tham chiếu bảng mới. Chúng tôi cần phải thêm một phụ thuộc để chúng ta biết thứ tự mà di cư sẽ được áp dụng (và do đó mà bảng sẽ tồn tại trước khi chúng tôi cố gắng thêm một FK với nó) nhưng chúng ta cũng cần phải chắc chắn lăn ngược công trình quá vì các phụ thuộc áp dụng theo hướng ngược lại .

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):

    depends_on = (
        ('specific', '0004_auto__add_cat'),
    )

    def forwards(self, orm):
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat')

    def backwards(self, orm):
        pass

Theo tài liệu hướng Nam , depends_onsẽ đảm bảo 0004_auto__add_catchạy trước 0009_auto__del_cat khi di chuyển về phía trước nhưng theo thứ tự ngược lại khi di chuyển ngược . Nếu chúng ta để lại db.rename_table('specific_cat', 'common_cat')trong specificrollback, các commonrollback sẽ thất bại khi cố gắng di chuyển ForeignKey vì bảng tham chiếu bảng sẽ không tồn tại.

Hy vọng rằng điều này gần với một tình huống "thế giới thực" hơn các giải pháp hiện có và ai đó sẽ thấy điều này hữu ích. Chúc mừng!


Các nguồn cố định trong câu trả lời này bỏ qua các dòng để cập nhật nội dung, có trong câu trả lời của Potr Czachur. Điều này có thể gây hiểu nhầm.
Shai Berger

@ShaiBerger Tôi đã đề cập cụ thể: "Tôi cố tình bỏ qua Django ContentType thay đổi, xem câu trả lời được tham chiếu trước đó để biết cách xử lý điều đó."
Matt Briançon

9

Các mô hình không được kết hợp chặt chẽ với các ứng dụng, do đó việc di chuyển khá đơn giản. Django sử dụng tên ứng dụng trong tên của bảng cơ sở dữ liệu, vì vậy nếu bạn muốn di chuyển ứng dụng của mình, bạn có thể đổi tên bảng cơ sở dữ liệu thông qua câu lệnh SQL ALTER TABLEhoặc - thậm chí đơn giản hơn - chỉ cần sử dụng db_tabletham số trong Metalớp mô hình của bạn để tham khảo tên Cu.

Nếu bạn đã sử dụng ContentTypes hoặc quan hệ chung ở bất kỳ đâu trong mã của bạn cho đến nay, có lẽ bạn sẽ muốn đổi tên app_labelnội dung chỉ vào mô hình đang di chuyển, để các mối quan hệ hiện có được giữ nguyên.

Tất nhiên, nếu bạn không có bất kỳ dữ liệu nào để bảo tồn, việc dễ làm nhất là bỏ hoàn toàn các bảng cơ sở dữ liệu và chạy ./manage.py syncdblại.


2
Làm thế nào để tôi làm điều đó với một di cư phía nam?
Apreche

4

Đây là một sửa chữa nữa cho giải pháp tuyệt vời của Potr. Thêm phần sau vào cụ thể / 0003_create_cat

depends_on = (
    ('common', '0002_create_cat'),
)

Trừ khi phụ thuộc này được đặt Nam sẽ không đảm bảo rằng common_catbảng tồn tại vào thời điểm khi / 0003_create_cat cụ thể được chạy, django.db.utils.OperationalError: no such table: common_catgây ra lỗi cho bạn.

Nam chạy di chuyển theo thứ tự từ điển trừ khi phụ thuộc được thiết lập rõ ràng. Vì commonxuất hiện trước khi specifictất cả các lần commondi chuyển sẽ được chạy trước khi đổi tên bảng, nên có thể nó sẽ không sao chép trong ví dụ ban đầu được hiển thị bởi Potr. Nhưng nếu bạn đổi tên commonđể app2specificđể app1bạn sẽ chạy vào vấn đề này.


Đây thực sự không phải là một vấn đề với ví dụ của Potr. Nó sử dụng di chuyển cụ thể để đổi tên và di chuyển chung để phụ thuộc vào di chuyển cụ thể. Nếu cụ thể được chạy trước, bạn ổn. Nếu phổ biến được chạy trước, phụ thuộc sẽ thực hiện chạy cụ thể trước nó. Điều đó nói rằng, tôi đã trao đổi thứ tự khi thực hiện điều này, vì vậy việc đổi tên xảy ra chung và sự phụ thuộc cụ thể, và sau đó bạn cần thay đổi sự phụ thuộc như bạn mô tả ở trên.
Emil Stenström

1
Tôi không thể đồng ý với bạn. Theo quan điểm của tôi, giải pháp nên mạnh mẽ và hoạt động mà không cần cố gắng làm hài lòng nó. Giải pháp ban đầu không hoạt động nếu bạn bắt đầu từ db mới và syncdb / di chuyển. Đề nghị của tôi sửa nó. Dù bằng cách này hay cách khác, câu trả lời của Port đã tiết kiệm cho tôi rất nhiều thời gian, danh tiếng cho anh ấy :)
Ihor Kaharlichenko

Không làm điều này có thể làm cho các thử nghiệm thất bại quá (dường như chúng luôn chạy toàn bộ di chuyển về phía nam khi tạo cơ sở dữ liệu thử nghiệm của chúng). Tôi đã làm một cái gì đó tương tự trước đây. Bắt tốt Ihor :)
odinho - Velmont

4

Quá trình tôi hiện đang giải quyết kể từ khi tôi trở lại đây một vài lần và quyết định chính thức hóa nó.

Điều này ban đầu được xây dựng dựa trên câu trả lời của Potr Czachurcâu trả lời của Matt Briançon , sử dụng South 0.8.4

Bước 1. Khám phá các mối quan hệ khóa ngoại

# Caution: This finds OneToOneField and ForeignKey.
# I don't know if this finds all the ways of specifying ManyToManyField.
# Hopefully Django or South throw errors if you have a situation like that.
>>> Cat._meta.get_all_related_objects()
[<RelatedObject: common:toy related to cat>,
 <RelatedObject: identity:microchip related to cat>]

Vì vậy, trong trường hợp mở rộng này, chúng tôi đã phát hiện ra một mô hình liên quan khác như:

# Inside the "identity" app...
class Microchip(models.Model):

    # In reality we'd probably want a ForeignKey, but to show the OneToOneField
    identifies = models.OneToOneField(Cat)

    ...

Bước 2. Tạo di chuyển

# Create the "new"-ly renamed model
# Yes I'm changing the model name in my refactoring too.
python manage.py schemamigration specific create_kittycat --auto

# Drop the old model
python manage.py schemamigration common drop_cat --auto

# Update downstream apps, so South thinks their ForeignKey(s) are correct.
# Can skip models like Toy if the app is already covered
python manage.py schemamigration identity update_microchip_fk --auto

Bước 3. Kiểm soát nguồn: Cam kết thay đổi cho đến nay.

Làm cho nó trở thành một quy trình lặp lại nhiều hơn nếu bạn gặp phải các xung đột hợp nhất như các đồng đội viết di chuyển trên các ứng dụng được cập nhật.

Bước 4. Thêm phụ thuộc giữa các lần di chuyển.

Về cơ bản create_kittycatphụ thuộc vào trạng thái hiện tại của mọi thứ, và mọi thứ sau đó phụ thuộc vào create_kittycat.

# create_kittycat
class Migration(SchemaMigration):

    depends_on = (
        # Original model location
        ('common', 'the_one_before_drop_cat'),

        # Foreign keys to models not in original location
        ('identity', 'the_one_before_update_microchip_fk'),
    )
    ...


# drop_cat
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...


# update_microchip_fk
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...

Bước 5. Thay đổi tên bảng mà chúng tôi muốn thực hiện.

# create_kittycat
class Migration(SchemaMigration):

    ...

    # Hopefully for create_kittycat you only need to change the following
    # 4 strings to go forward cleanly... backwards will need a bit more work.
    old_app = 'common'
    old_model = 'cat'
    new_app = 'specific'
    new_model = 'kittycat'

    # You may also wish to update the ContentType.name,
    # personally, I don't know what its for and
    # haven't seen any side effects from skipping it.

    def forwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.old_app, self.old_model),
            '%s_%s' % (self.new_app, self.new_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.old_app,
                model=self.old_model,
            ).update(
                app_label=self.new_app,
                model=self.new_model,
            )

        # Going forwards, should be no problem just updating child foreign keys
        # with the --auto in the other new South migrations

    def backwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.new_app, self.new_model),
            '%s_%s' % (self.old_app, self.old_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.new_app,
                model=self.new_model,
            ).update(
                app_label=self.old_app,
                model=self.old_model,
            )

        # Going backwards, you probably should copy the ForeignKey
        # db.alter_column() changes from the other new migrations in here
        # so they run in the correct order.
        #
        # Test it! See Step 6 for more details if you need to go backwards.
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['common.Cat']))


# drop_cat
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Remove the db.delete_table(), if you don't at Step 7 you'll likely get
        # "django.db.utils.ProgrammingError: table "common_cat" does not exist"

        # Leave existing db.alter_column() statements here
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass


# update_microchip_fk
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Leave existing db.alter_column() statements here
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass

Bước 6. Chỉ khi bạn cần backwards () để hoạt động VÀ nhận KeyError chạy ngược.

# the_one_before_create_kittycat
class Migration(SchemaMigration):

    # You many also need to add more models to South's FakeORM if you run into
    # more KeyErrors, the trade-off chosen was to make going forward as easy as
    # possible, as that's what you'll probably want to do once in QA and once in
    # production, rather than running the following many times:
    #
    # python manage.py migrate specific <the_one_before_create_kittycat>

    models = {
        ...
        # Copied from 'identity' app, 'update_microchip_fk' migration
        u'identity.microchip': {
            'Meta': {'object_name': 'Microchip'},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
            'identifies': ('django.db.models.fields.related.OneToOneField', [], {to=orm['specific.KittyCat']})
        },
        ...
    }

Bước 7. Kiểm tra nó - những gì làm việc cho tôi có thể không đủ cho tình huống thực tế của bạn :)

python manage.py migrate

# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>

3

Vì vậy, sử dụng phản hồi ban đầu từ @Potr ở trên không hoạt động với tôi trên South 0.8.1 và Django 1.5.1. Tôi đang đăng những gì đã làm cho tôi dưới đây với hy vọng rằng nó hữu ích cho người khác.

from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat') 

        if not db.dry_run:
             db.execute(
                "update django_content_type set app_label = 'specific' where "
                " app_label = 'common' and model = 'cat';")

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
            db.execute(
                "update django_content_type set app_label = 'common' where "
                " app_label = 'specific' and model = 'cat';")

1

Tôi sẽ đưa ra một phiên bản rõ ràng hơn về một trong những điều Daniel Roseman gợi ý trong câu trả lời của anh ấy ...

Nếu bạn chỉ thay đổi db_tablethuộc tính Meta của mô hình mà bạn đã di chuyển để trỏ đến tên bảng hiện có (thay vì tên mới Django sẽ đặt nó nếu bạn bỏ và thực hiện a syncdb) thì bạn có thể tránh di chuyển miền Nam phức tạp. ví dụ:

Nguyên:

# app1/models.py
class MyModel(models.Model):
    ...

Sau khi di chuyển:

# app2/models.py
class MyModel(models.Model):
    class Meta:
        db_table = "app1_mymodel"

Bây giờ bạn chỉ cần thực hiện di chuyển dữ liệu để cập nhật app_labelcho MyModeltrong django_content_typebảng và bạn nên đi ...

Chạy ./manage.py datamigration django update_content_typerồi chỉnh sửa tệp mà Nam tạo cho bạn:

def forwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app1', model='mymodel')
    moved.app_label = 'app2'
    moved.save()

def backwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app2', model='mymodel')
    moved.app_label = 'app1'
    moved.save()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.