Блог Толика Вострякова

Живой опыт программирования, Python, Django, современные языки и немного фотографий
Блог - новые записи и ссылки (Atom)

South 0.7 и Django 1.2 -

Для тех кто не знает South - это система миграции структуры базы данных или самих данных для Django/Джанго. До версии 0.7 она была сыровата, но с недавним выходом версии 0.7 наконец South довели до ума и даже добавили поддержку нескольких баз, появившуюся в Django 1.2 и alfa-поддержку Oracle. В том числе поправлен досадный баг, делавший невозможной миграцию many-to-many fields.

Отмечу, что есть конечно и другие системы миграции для Django, типа deseb или django-evolution, но или проекты заброшены или нет поддержки распространенных баз данных. Так что South на данный момент определенно мэйнстрим. Итак, зачем и как использовать South?

Главное применение, которое я вижу - это миграция структуры базы данных и самих данных на боевой базе, то есть система находиться в эксплуатации и периодически, что то меняется в базе: добавили новое поле или удалили старое, может поменялись значения в редко меняемых справочных таблицах и т.д.

Начало

Начать применять South можно как в самом начале проекта, так и перед самым запуском в production. Давайте рассмотрим более предпочтительный вариант, когда мы начинаем применять South перед запуском в production (или в середине проекта). Последовательность действий будет следующая:

  1. Добавить 'south', в INSTALLED_APPS в файле настроек.
  2. Выполнить ./manage.py syncdb в корневой папке проекта, чтобы South создал таблицу в базе для своего внутреннего использования.
  3. Выполнить
    ./manage.py schemamigration "application_name" --initial
    , где "application_name" - название приложения в проекте. Следует заметить, что South применяется отдельно для каждого приложения в проекте. То есть одни приложения могуть быть под управлением South, а другие нет, если нужно. В результате этой команды в папке указанного приложения будет создана папка migrations с одним интересным файлом 0001_initial.py.
  4. Если у вас есть важные данные в fixtures, которые должны попасть в миграцию, тогда выполните еще команду: ./manage.py datamigration "application_name" "name_of_migration", где "name_of_migration" - произвольное название миграции для ваших fixtures, чтобы вам было понятно, что там храниться. В результате в папке migrations появиться еще один файл 0002_name_of_migration.py. В этом файле нужно будет руками добавить в функцию forwards класса Migration следующий код:
    from django.core.management import call_command
    call_command("loaddata", "name_of_file_with_fixtures.json")

Пунк 4 не всегда обязателен для выполнения, так как после миграции (см. ниже) South всегда накатит имеющиеся fixtures. Тут как вам будет удобно.

Так как предполагается, что ваша база разработчика отражает ваше текущее состояние моделей и миграция пока вам не требуется, то выполните мнимую миграцию командой ./manage.py migrate --fake. В результате South у себя пометит, что он применил все имеющиеся миграции, реально их не применяя.

На боевом же сервере, где уже создана пустая база, выполните команду ./manage.py migrate. В этом случае South применит все миграции из папок migrations всех приложений, которые еще не были применены. В последствии на боевом сервере нужно будет выполнять эту же команду.

Дополнение: В комментариях, Денис справедливо заметил про команду:

./manage.py convert_to_south "application_name"

Она сформирует первую миграцию и сразу фиктивно ее применить к базе. То есть два в одном. Правда такой фокус сработает только на первой машине разработчика, где ее выполняют. Если миграция уже создана и залита в репозитарий, то на последующих компьютерах такой фокус уже не пройдет.

Тонкости

Если вы используете нестандартные поля в модели (custom fields), то для работы South теперь придется сделать дополнительное действия. Например, вы используете в проекте свое поле для хранения денег:

class MoneyField(models.DecimalField):
    def __init__(self, min_value=None, max_value=None, **kwargs):
        self.min_value, self.max_value = min_value, max_value
        super(MoneyField, self).__init__(max_digits=15, decimal_places=4, **kwargs)

    def formfield(self, **kwargs):
        defaults = {'form_class': MoneyFormField, 'min_value': self.min_value, 'max_value': self.max_value}
        defaults.update(kwargs)
        return super(MoneyField, self).formfield(**defaults)

тогда, например в том же файле, где храняться модели, нужно будет написать:

from south.modelsinspector import add_introspection_rules

rules = [
            (
                (MoneyField, ), [],
                {
                    "min_value": ["min_value", {"default": None}],
                    "max_value": ["max_value", {"default": None}],
                    "null": ["null", {"default": False}],
                    "blank": ["blank", {"default": False}],
                    "max_digits": ["max_digits", {"default": 15}],
                    "decimal_places": ["decimal_places", {"default": 4}],
                }
            ),           
        ]

add_introspection_rules(rules, ["^data"])

Вторая тонкость связана с юнит-тестами. По умолчанию во время выполнения тестов будет создана тестовая база к которой будут применены все миграции. Если вы хотите старое поведение команды syndb, например по причине низкой скорости применения миграций, тогда в файле настроек проекта добавьте SOUTH_TESTS_MIGRATE = False. Можно так же пропустить собственные тесты South: SKIP_SOUTH_TESTS=True.

Применение

На данный момент мы не сделали ничего, чего бы не могла сделать команда ./manage.py syncdb. Давайте начнем использовать South по назначению и добавьте поле в любую модель или добавьте новую модель. После этого выполните команду

./manage.py schemamigration "application_name" "name_of_migration_2" --auto

В папке migrations будет создан новый файл 0003_name_of_migration_2.py. Давайте посмотрим, что там хранится. Там объявлен один класс Migration с двумя методами forwards и backwards. Дело в том, что South может работать в двух направлениях: применять миграцию или откатывать миграцию обратно. В forwards код для применения в базе тех изменений, которые мы внесли в модели по отношению к предыдущей миграции, в backwards код для отката изменений этой миграции. Как видите South все сделал автоматически, но вы можете добавить свой код в эти функции, если необходимо.

Остается выполнить команду ./manage.py migrate у себя и на боевом сервере. На боевом сервере конечно можно применять за раз сразу несколько миграций.

Дабы оправдать название поста :), упомяну, что можно выбрать, к какой базе будут примены изменения в миграции (работает только в Django 1.2):

./manage.py migrate --database="db_alias_from_settings"

Или можно указать базу в самой миграции, например так :

from south.db import dbs
dbs['db_alias_from_settings'].create_table(...)

Таким образом мы создадим таблицу в базе db_alias_from_settings.

Тем не менее в South еще будут изменения в этом вопросе, чтобы реализовать более тесную интеграцию с Django 1.2: Ticket 370

Заключение

Вот в общем-то и все, что я хотел рассказать. Основную суть South я надеюсь раскрыл, а более подробно все возможности South вы можете прочитать в документации.

Комментарии: 12

presidentua - 23.03.2010 Когда первый раз начал юзать South, то что-то у меня не получилось. И я жил долго без него, вернее сказать мучался: иногда вместо добавления поля в БД, делал разные костыли, иногда поля как-то неправильно использовал - из-за того что делать миграции было не так легко на всех сервера. Одним словом жизнь без South - ужОс ).

Спасибо за статью!
nuklea - 23.03.2010 И давно south стал версии 0.7r1? На http://south.aeracode.org/ вижу 0.7rc1.
Толик Востряков - 23.03.2010 Спасибо, поправил на 0.7rc1. Но релиз уже скоро :)
Денис - 24.03.2010 Толик, а в этой команде пробел перед --auto не нужен?
./manage.py schemamigration "applicationname" "nameofmigration2"--auto

А ещё есть такая команда, сокращающая время миграции на South:
./manage.py converttosouth myapp
Артём Сапегин - 24.03.2010 У South какие-то проблемы с SQLite (теряются типы полей при миграции, например). А так хорошая штука, конечно.
Толик Востряков - 24.03.2010 Денис, спасибо! Сейчас дополню описание
cheelohidge - 10.09.2010 Просто супер!!!
iceone - 08.10.2010 Меня интересует такой вопрос: в документации сказано, что если хотите data migration - используйте ORM в стиле:

for obj in orm.MyModel.objects.all():
# действия с экземляром модели
obj.save()


А что если записей в таблице 100 тысяч?
Есть ли у South возможность делать миграции данных с использованием конструкции UPDATE ... SET field = ... WHERE ... ?

Или нормальные люди просто пишут raw sql?
Толик Востряков - 09.10.2010

Есть ли у South возможность делать миграции данных с использованием конструкции UPDATE ... SET field = ... >WHERE ... ?


Да, конечно есть. Есть команда db.execute. Например:

db.execute('update companies_company set company_type_temp=%s where company_type=1', ['production'])

Вставляйте ее в существующую миграцию в метод forwards или backwards. Но там есть одна тонкость, изменение структуры базы и изменение данных south рекомендует разносить в две разные миграции. Если все же хотите все сделать в одной миграции придется после измнения данных еще закрыть и открыть транзакцию. Полный пример конвертирования данных в поле company_type:

db.add_column('companies_company', 'company_type_temp', self.gf('django.db.models.fields.SmallIntegerField')(null=True, blank=True), keep_default=False)
db.execute('update companies_company set company_type_temp=1 where company_type=%s', ['production'])
db.commit_transaction()

db.start_transaction()
db.delete_column('companies_company', 'company_type')
db.rename_column('companies_company', 'company_type_temp', 'company_type')

krvss - 30.10.2010 Несколько штук, выявленных по ходу:


  1. До того, как накатывать изменения можно протестировать их используя опцию --db-dry-run

  2. Миграция данных производится командой datamigrate, --auto указывать не нужно. Я все же решил их разделять.

  3. Откат на любую версию делается migrate 000x, откат на zero (migrate myapp zero) делать не рекомедую - таблица пропадет :-О



Хороший пример есть еще вот тут: http://stackoverflow.com/questions/1600129/using-south-to-refactor-a-django-model-with-inheritence
медик - 05.07.2011 Я всё ещё пользуюсь системой deseb. А теперь вижу, что в South огромное число преимуществ. Большое спасибо за стать. Отличный блог. Будем переходить на South.
Толик Востряков - 05.07.2011 Рад, что было полезно!

Добавить комментарий:


Буду использовать, только для связи с тобой

Чтобы и другие могли узнать о тебе

Вы можете использовать markdown разметку.

Например:
**жирный**
*курсив*
## Заголовок

Не используйте html-тэги. Все ссылки станут активными, все переводы строк будут заменены на <br>
captcha