Як перенести модель з однієї програми django і в нову?


126

У мене є додаток джанго з чотирма моделями. Зараз я усвідомлюю, що одна з цих моделей повинна бути в окремому додатку. У мене встановлено південь для міграції, але я не думаю, що це щось, з чим він може працювати автоматично. Як можна перенести одну із моделей зі старої програми на нову?

Також майте на увазі, що мені це знадобиться для повторюваного процесу, щоб я міг мігрувати виробничу систему тощо.


6
Для Джанго 1.7 і вище см stackoverflow.com/questions/25648393 / ...
Рік Westera

Відповіді:


184

Як мігрувати за допомогою півдня.

Скажімо, у нас є дві програми: загальна та конкретна:

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

Тепер ми хочемо перемістити модель common.models.cat до конкретної програми (саме до Speci.models.cat). Спочатку внесіть зміни у вихідний код, а потім запустіть:

$ 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

Тепер нам потрібно відредагувати обидва файли міграції:

#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

Тепер обидві міграції додатків знають про зміни, і життя відсмоктується трохи менше :-) Встановлення цього взаємозв'язку між міграціями є ключовим для успіху. Тепер, якщо ви робите:

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

буде робити і міграцію, і

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

буде переміщувати речі вниз.

Зауважте, що для оновлення схеми я використовував загальну програму, а для пониження - використовував конкретну програму. Це тому, як тут працює залежність.


1
Нічого, дякую. Я дізнався на південь самостійно, коли задав це питання, але впевнений, що це дуже допоможе іншим.
Апрех

11
Можливо, вам також знадобиться зробити міграцію даних у таблиці django_content_type.
spookylukey

1
Дійсно чудовий путівник @Potr. Мені цікаво, чи не повинно бути orm['contenttypes.contenttype'].objects.filter рядка і в задній частині 0003_create_cat? Також хочу поділитися порадою. Якщо у вас є індекси, їх потрібно буде також змінити. У моєму випадку вони були унікальними індексами, тому мій вперед виглядає як ти: db.delete_unique('common_cat', ['col1']) db.rename_table('common_cat', 'specific_cat') db.delete_unique('specific_cat', ['col1'])
Бред Пітчер

2
Для доступу orm['contenttypes.contenttype']вам також потрібно додати --freeze contenttypesпараметр до своїх schemamigrationкоманд.
Гері

1
У моєму випадку (Django 1.5.7 та South 1.0) .. Мені довелося набрати python manage.py schemamigration specific create_cat --auto --freeze commonдля доступу до моделі котів із загальної програми.
geoom

35

Для того, щоб побудувати на Potr Czachur «s відповідь , ситуації, пов'язані з ForeignKeys більш складними і повинні бути оброблені трохи по- іншому.

(Наступний приклад ґрунтується на commonі specificпрограми згаданого до цього відповідь).

# common/models.py

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

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

Потім зміниться на

# common/models.py

from specific.models import Cat

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

# specific/models.py

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

Біг

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

призведе до таких міграцій (я навмисно ігнорую зміни Django ContentType - див. раніше згадану відповідь, як впоратися з цим):

# 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')

Як бачите, FK має бути змінено для посилання на нову таблицю. Нам потрібно додати залежність, щоб ми знали порядок застосування міграцій (і таким чином, що таблиця існуватиме до того, як ми спробуємо до неї додати FK), але нам також потрібно переконатися, що відкат назад також працює, тому що залежність застосовується у зворотному напрямку .

# 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

Згідно з документацією на Південь , depends_onгарантується, що вона буде 0004_auto__add_catзапущена раніше 0009_auto__del_cat при переміщенні вперед, але в зворотному порядку при переміщенні назад . Якщо ми залишимося db.rename_table('specific_cat', 'common_cat')при specificвідкаті, commonвідкат не вдасться при спробі перенести ForeignKey, оскільки таблиця, на яку посилається таблиця, не існувала б.

Сподіваємось, це ближче до ситуації "реального світу", ніж існуючі рішення, і хтось вважатиме це корисним. Ура!


Виправлені джерела у цій відповіді опускають рядки для оновлення типів контенту, які містяться у відповіді Потра Чачура. Це може ввести в оману.
Шай Бергер

@ShaiBerger я спеціально звернувся до цього: "Я навмисно ігнорую зміни Django ContentType - див. Раніше згадану відповідь, як з цим впоратися."
Метт Брайансон

9

Моделі не дуже щільно поєднані з додатками, тому переміщення досить просте. Django використовує ім'я програми у назві таблиці бази даних, тому, якщо ви хочете перемістити додаток, ви можете або перейменувати таблицю бази даних за допомогою оператора SQL ALTER TABLE, або - ще простіше - просто скористайтеся db_tableпараметром у Metaкласі вашої моделі для позначення стара назва.

Якщо ви до цього часу використовували ContentTypes або загальні відносини в коді, ви, ймовірно, захочете перейменувати app_labelтип контенту, що вказує на модель, що рухається, щоб зберегти існуючі відносини.

Звичайно, якщо у вас взагалі немає даних для збереження, найпростіше зробити це повністю скинути таблиці баз даних і запустити ./manage.py syncdbзнову.


2
Як це зробити з міграцією на південь?
Апрех

4

Ось ще одне виправлення відмінного рішення Потра. Додайте наступне до конкретного / 0003_create_cat

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

Якщо ця залежність не встановлена, Південь не гарантуватиме існування common_catтаблиці в момент запуску конкретного / 0003_create_cat, видаючиdjango.db.utils.OperationalError: no such table: common_cat на вас помилку.

Південь здійснює міграцію в лексикографічному порядку, якщо чітко не встановлено залежність. Оскільки commonвідбувається перед тим, як specificвсі commonміграції 's запустяться до перейменування таблиці, тому, ймовірно, вона не відтвориться в оригінальному прикладі, показаному Potr. Але якщо ви перейменовуєтесь commonу app2та specificдо app1вас, ви зіткнетесь із цією проблемою.


Це насправді не є проблемою на прикладі Потра. Він використовує специфічну міграцію для перейменування, а загальну міграцію залежить від конкретної. Якщо конкретний запускається першим, ви все в порядку. Якщо спільний запускається першим, залежність зробить конкретний запуск перед ним. Це означає, що я змінював порядок, виконуючи це, тож перейменування сталося спільним, а залежність - конкретною, і тоді вам потрібно змінити залежність, як описано вище.
Еміль Стенстрем

1
Я не можу погодитися з тобою. З моєї точки зору, рішення повинно бути надійним і працювати просто, не намагаючись догодити. Оригінальне рішення не працює, якщо ви починаєте зі свіжого db та syncdb / migrate. Моя пропозиція це виправляє. Так чи інакше, відповідь Порта врятувала мені багато часу, кудо йому :)
Ihor Kaharlichenko

Якщо цього не зробити, тести теж можуть збити (вони завжди створюють повну міграцію на південь при створенні своєї тестової бази даних). Я робив щось подібне раніше. Хороший улов Ігор :)
odinho - Велмонт

4

Процес, на якому я врегулювався, оскільки я кілька разів повертався сюди і вирішив його формалізувати.

Це був спочатку побудований на відповідь Potr Czachur в і відповідь Метт Бріансон в , використовуючи Південний 0.8.4

Крок 1. Розкрийте зовнішні ключові стосунки дитини

# 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>]

Тож у цьому розширеному випадку ми виявили ще одну споріднену модель на зразок:

# 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)

    ...

Крок 2. Створіть міграцію

# 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

Крок 3. Контроль джерела: ввести зміни досі.

Це робить більш повторюваний процес, якщо ви стикаєтеся з конфліктами злиття, як товариші по команді, які пишуть міграції в оновлених додатках.

Крок 4. Додайте залежності між міграціями.

В основному create_kittycatзалежить від поточного стану всього, і все далі залежить від цього 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'),
    )
    ...

Крок 5. Зміна перейменування таблиці, яку ми хочемо внести.

# 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

Крок 6. Тільки якщо вам потрібно назад () для роботи І отримати KeyError, який працює назад.

# 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']})
        },
        ...
    }

Крок 7. Перевірте - що для мене може бути недостатньо для вашої реальної життєвої ситуації :)

python manage.py migrate

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

3

Тож використання оригінальної відповіді від @Potr вище для мене не працювало на South 0.8.1 та Django 1.5.1. Я публікую те, що робилося для мене нижче, сподіваючись, що це корисно для інших.

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

Я збираюся дати більш чітку версію однієї з речей, яку запропонував Даніел Роузман у своїй відповіді ...

Якщо ви просто змінили db_tableатрибут Meta моделі, яку ви перемістили, щоб вказати на існуючу назву таблиці (замість нового імені Django дав би це, якщо ви відмовились і зробили syncdb), то ви можете уникнути складних міграцій на південь. наприклад:

Оригінал:

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

Після переміщення:

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

Тепер вам просто потрібно зробити міграцію даних , щоб оновити app_labelдля MyModelв django_content_typeтаблиці , і ви повинні бути добре йти ...

Запустіть ./manage.py datamigration django update_content_typeпотім відредагуйте файл, який створює South для вас:

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.