Django South — заполняем таблицы

В этой заметке я напишу очередную шпаргалку (я сам ими пользуюсь :)
На этот раз — по миграции данных с помощью Django South.

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

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

from django.db import models

class User(models.Model):

    username = models.CharField(max_length=255)
    password = models.CharField(max_length=60)
    name = models.TextField()

И мы недовольны тем, что пароли в ней хранятся в открытом виде.

Для решения этого вопроса мы хотим заменить поле password двумя хэш-полями:

    password_salt = models.CharField(max_length=8)
    password_hash = models.CharField(max_length=40)

И здесь мы будем иметь две проблемы:

  1. Поля пароля не могут быть пустыми — определены как NOT NULL;
  2. Перед удалением поля password нам необходимо заполнить поля password_salt и password_hash на его основе.

Django South предлагает такие преобразования делать в три этапа:

1. Добавить новые поля, разрешив им быть пустыми

    password_salt = models.CharField(max_length=8, null=True)
    password_hash = models.CharField(max_length=40, null=True)

Обратите внимание на использование null=True.

Создадим миграцию для добавления полей:

$ ./manage.py schemamigration myapp --auto

2. Подготовить миграцию данных

$ ./manage.py datamigration myapp hash_passwords
Created 0003_hash_passwords.py.

Открыв файл myapp/migrations/0003_hash_passwords.py, мы увидим в нем пустые функции forwards() и backwards(). Названия говорят сами за себя, поэтому давайте просто посмотрим, что туда можно записать:

def forwards(self, orm):
    import random, sha, string
    for user in orm.User.objects.all():
        user.password_salt = "".join(
            [random.choice(string.letters) for i in range(8)]
        )
        user.password_hash = sha.sha(
            user.password_salt + user.password
        ).hexdigest()
        user.save()

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

По умолчанию orm содержит не всю схему данных, а только мигрирующую модель и модели, связанные с ней отношениями Foreign Key и Many To Many.

Для добавления в orm других моделей используйте опцию «— -freeze appname» команды datamigration.

 

Увы, после задуманной миграции откат с восстановлением plain-text пароля будет невозможен…

def backwards(self, orm):
    raise RuntimeError("Cannot reverse this migration.")

3. Привести модель данных к конечному виду

Удаляем старое поле password и указываем, что новые поля не могут быть пустыми (убираем из описания null=True).

Создаем финальную миграцию

$ ./manage.py schemamigration myapp --auto
 ? The field 'User.password' does not have a default specified, yet is NOT NULL.
 ? Since you are adding or removing 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
 ? Please select a choice: 2
 ? Please enter Python code for your one-off default value.
 ? The datetime module is available, so you can do e.g. datetime.date.today()
 >>> ""
 - Deleted field password on myapp.User
Created 0004_auto__del_field_user_password.py.
 You can now apply this migration with: ./manage.py migrate myapp

Скрипт задаст вопрос про значение по умолчанию для удаляемого поля (на случай отката миграции) — укажем ему «».

Теперь мы можем применить все три миграции одновременно

$ ./manage.py migrate myapp

Пример взят из стандартной документации South.

Python/Django

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

captcha

Please enter the CAPTCHA text