Migracje w Django za pomocą South

Migracje struktury i danych baz danych w projektach Django

South to narzędzie do migracji struktury i danych zawartych w bazie danych dla aplikacji Django. South w praktycznie całkowicie zautomatyzowany sposób potrafi śledzić zmiany w modelach i tworzyć nowe migracje aktualizujące strukturę tabel w bazie danych. Ułatwia to rozwój aplikacji (nie trzeba tworzyć migracji ręcznie), jak i jej utrzymanie, zarządzanie (można cofnąć się do wybranej starszej migracji reprezentującej np. starszą wersję aplikacji).

Instalacja i konfiguracja South

Najprościej zainstalować South za pomocą pip:
pip install South
Następnie dodajemy south do INSTALLED_APPS naszego projektu i wykonujemy syncdb. South będzie w tym momencie gotowy do użycia. W bazie danych stworzy sobie tabelę do śledzenia stanu migracji w każdej obsługiwanej przez niego aplikacji.

Podstawy migracji

Tworząc nową aplikacje zamiast tworzyć tabele modeli za pomocą syncdb zaczynamy używać migracji south. By stworzyć pierwszą migrację wystarczy wykonać:
python manage.py schemamigration NAZWA_APLIKACJI --initial
W katalogu aplikacji stworzony zostanie katalog "migrations". W nim zawarte będą wszystkie migracje. Na początku będzie tylko początkowa migracja 0001_initial.py. Migracja została stworzona, ale nie wykonana. By wykonać migrację wystarczy wykonać polecenie:
python manage.py migrate NAZWA_APLIKACJI
Zakładając że mieliśmy np. taki prosty model:
from django.db import models

class MyModel(models.Model):
    name = models.CharField(max_length=100)
To pierwsza migracja stworzy tabelę dla modelu. Teraz np. wprowadzamy zmianę w modelu, dodajemy pole:
class MyModel(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
By stworzyć nową migrację aktualizującą strukturę bazy o to pole wystarczy polecenie:
python manage.py schemamigration NAZWA_APLIKACJI --auto

Zamiast "--initial" stosujemy "--auto", po czym wykonujemy stworzoną migrację. W tym prostym przykładzie stworzenie migracji będzie całkowicie automatyczne.

A gdyby w zmianie było pole, które nie może być puste?:

class MyModel(models.Model):
    name = models.CharField(max_length=100)
    description = models.TextField(blank=True)
    number = models.IntegerField()
Przy próbie stworzenia migracji South zada pytanie co zrobić z wartością dla nowego pola:
 ? The field 'MyModel.number' does not have a default specified, yet is NOT NULL.
 ? Since you are adding this field, you MUST specify a default
 ? value to use for existing rows. Would you like to:
 ?  1. Quit now, and add a default to the field in models.py
 ?  2. Specify a one-off value to use for existing columns now

Możemy wybrać opcję 1 co przerwie proces tworzenia migracji. Możemy wtedy zmienić coś w modelu, np. nadawać wartość wartość domyślną. Możemy także wybierając opcję 2 podać domyślną wartość na potrzeby migracji (tylko dla istniejących rekordów).

Migracje danych

Gdybyśmy chcieli dodać takie pole do istniejącego modelu z rekordami:
slug = models.SlugField(unique=True)
To mielibyśmy problem z unikalnością pusty pól. Jak nadać unikalne wartości wraz z migracją? Trzeba zrobić to etapami. Najpierw dodajemy kolumnę akceptującą wartości nullowskie:
slug = models.SlugField(unique=True, null=True)

Następnie migrujemy dane - generujemy unikalne wartości dla kolumny. Na koniec usuwamy null dla kolumny kolejną migracją.

By wygenerować pustą migrację danych wykonujemy polecenie:
python manage.py datamigration NAZWA_APLIKACJI NAZWA_MIGRACJI
W katalogu migrations zostanie stworzony nowy plik, w którym możemy napisać kod zajmujący się migracją danych. Będzie wyglądać mniej więcej tak:
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models

class Migration(DataMigration):

    def forwards(self, orm):
        # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..."

    def backwards(self, orm):
        "Write your backwards methods here."

Metoda forward dotyczy migracji jaką chcemy przeprowadzić. backwards dotyczy operacji wstecznej - gdybyśmy później chcieli cofnąć migrację (jeżeli zmiana jest "poważna" i nie ma możliwości jej cofnięcia to można wyłączyć opcję wstecznej migracji).

W tej migracji musimy ustawić unikalne wartości dla pola slug. Naprościej można zrobić to tak:
    def forwards(self, orm):
        # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..."
        for i in orm['first_app.MyModel'].objects.all():
            i.slug = i.id
            i.save()
Migrację danych wykonujemy tak samo jak migrację struktury.

Migracje wsteczne

Jeżeli chcemy cofnąć stan tabel do konkretnej migracji wystarczy wykonać:
python manage.py migrate NAZWA_APLIKACJI NUMER
np:
python manage.py migrate first_app 0005
By cofnąć się do stanu sprzed wszystkich migracji można użyć:
python manage.py migrate NAZWA_APLIKACJI zero

Zależności między migracjami

Migracje zawierające relacje mogą przy budowaniu struktury od zera wykonać się w niewłaściwej kolejności (migracja z relacją będzie chciała się wykonać przed migracją tworzącą tabelę wykorzystywaną w tej relacji). Problem ten może pojawić się w systemach testów, czy budowania aplikacji, gdzie migracje są wykonywane od zera.

Rozwiązanie jest proste. Wystarczy do klasy stworzonej migracji dodać atrybut "depends_on":
depends_on = (
        ("NAZWA_APLIKACJI", "NAZWA_MIGRACJI"),
    )
Więcej szczegółów w dokumentacji south.
blog comments powered by Disqus

Kategorie

Strony