Для тех кто не знает 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 (или в середине проекта). Последовательность действий будет следующая:
'south', в INSTALLED_APPS в файле настроек. ./manage.py syncdb в корневой папке проекта, чтобы South создал таблицу в базе для своего внутреннего использования../manage.py schemamigration "application_name" --initial, где "application_name" - название приложения в проекте. Следует заметить, что South применяется отдельно для каждого приложения в проекте. То есть одни приложения могуть быть под управлением South, а другие нет, если нужно. В результате этой команды в папке указанного приложения будет создана папка migrations с одним интересным файлом 0001_initial.py../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 вы можете прочитать в документации.
| 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 в стиле:А что если записей в таблице 100 тысяч? Есть ли у South возможность делать миграции данных с использованием конструкции UPDATE ... SET field = ... WHERE ... ? Или нормальные люди просто пишут raw sql? |
| Толик Востряков - 09.10.2010 |
Да, конечно есть. Есть команда 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)
|
| krvss - 30.10.2010 |
Несколько штук, выявленных по ходу:
Хороший пример есть еще вот тут: http://stackoverflow.com/questions/1600129/using-south-to-refactor-a-django-model-with-inheritence |
| медик - 05.07.2011 |
Я всё ещё пользуюсь системой deseb. А теперь вижу, что в South огромное число преимуществ. Большое спасибо за стать. Отличный блог. Будем переходить на South. |
| Толик Востряков - 05.07.2011 |
Рад, что было полезно! |
Спасибо за статью!