Перенос существующих данных auth.User на новую пользовательскую модель Django 1.5?

Я бы предпочел не уничтожать всех пользователей на моем сайте. Но я хочу воспользоваться пользовательской моделью подключаемого модуля Django 1.5. Вот моя новая модель пользователя:

class SiteUser(AbstractUser):
    site = models.ForeignKey(Site, null=True)

Все работает с моей новой моделью в новой установке (у меня есть другой код, а также веская причина для этого - все это здесь не имеет значения). Но если я размещу это на своем живом сайте и произвожу синхронизацию и миграцию, я потеряю всех своих пользователей, или, по крайней мере, они будут в другой бесхозной таблице, чем новая таблица, созданная для моей новой модели.

Я знаком с Саутом, но, основываясь на этом посте и некоторых испытаниях с моей стороны, кажется, что его миграции данных в настоящее время не подходят для этой конкретной миграции. Поэтому я ищу какой-либо способ заставить South работать для этого или для какой-либо миграции не-South (необработанный SQL, dumpdata/loaddata или иным образом), которую я могу запустить на каждом из моих серверов (Postgres 9.2) для миграции пользователей после создания новой таблицы, когда старая таблица auth.User все еще находится в базе данных.

5 ответов

Решение

Юг более чем способен выполнить эту миграцию для вас, но вам нужно быть умным и делать это поэтапно. Вот пошаговое руководство: (Это руководство предполагало, что вы подкласс AbstractUserне AbstractBaseUser)

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

    $ ./manage.py schemamigration accounts --initial
    Creating migrations directory at 'accounts/migrations'...
    Creating __init__.py in 'accounts/migrations'...
    Created 0001_initial.py.
    
    $ ./manage.py migrate accounts [--fake if you've already syncdb'd this app]
     Running migrations for accounts:
     - Migrating forwards to 0001_initial.
     > accounts:0001_initial
     - Loading initial data for accounts.
    
  2. Создайте новую пустую миграцию пользователей в приложении учетных записей.

    $ ./manage.py schemamigration accounts --empty switch_to_custom_user
    Created 0002_switch_to_custom_user.py.
    
  3. Создайте свой кастом User модель в accounts приложение, но убедитесь, что оно определено как:

    class SiteUser(AbstractUser): pass
    
  4. Заполните пустую миграцию с помощью следующего кода.

    # encoding: utf-8
    from south.db import db
    from south.v2 import SchemaMigration
    
    class Migration(SchemaMigration):
    
        def forwards(self, orm):
            # Fill in the destination name with the table name of your model
            db.rename_table('auth_user', 'accounts_user')
            db.rename_table('auth_user_groups', 'accounts_user_groups')
            db.rename_table('auth_user_user_permissions', 'accounts_user_user_permissions')
    
        def backwards(self, orm):
            db.rename_table('accounts_user', 'auth_user')
            db.rename_table('accounts_user_groups', 'auth_user_groups')
            db.rename_table('accounts_user_user_permissions', 'auth_user_user_permissions')
    
        models = { ....... } # Leave this alone
    
  5. Запустите миграцию

    $ ./manage.py migrate accounts
     - Migrating forwards to 0002_switch_to_custom_user.
     > accounts:0002_switch_to_custom_user
     - Loading initial data for accounts.
    
  6. Внесите любые изменения в вашу модель пользователя сейчас.

    # settings.py
    AUTH_USER_MODEL = 'accounts.User'
    
    # accounts/models.py
    class SiteUser(AbstractUser):
        site = models.ForeignKey(Site, null=True)
    
  7. создать и запустить миграции для этого изменения

    $ ./manage.py schemamigration accounts --auto
     + Added field site on accounts.User
    Created 0003_auto__add_field_user_site.py.
    
    $ ./manage.py migrate accounts
     - Migrating forwards to 0003_auto__add_field_user_site.
     > accounts:0003_auto__add_field_user_site
     - Loading initial data for accounts.
    

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

# encoding: utf-8
from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        # Fill in the destination name with the table name of your model
        db.rename_table('auth_user', 'accounts_user')
        db.rename_table('auth_user_groups', 'accounts_user_groups')
        db.rename_table('auth_user_permissions', 'accounts_user_permissions')
        # == YOUR CUSTOM COLUMNS ==
        db.add_column('accounts_user', 'site_id',
            models.ForeignKey(orm['sites.Site'], null=True, blank=False)))

    def backwards(self, orm):
        db.rename_table('accounts_user', 'auth_user')
        db.rename_table('accounts_user_groups', 'auth_user_groups')
        db.rename_table('accounts_user_user_permissions', 'auth_user_user_permissions')
        # == YOUR CUSTOM COLUMNS ==
        db.remove_column('accounts_user', 'site_id')

    models = { ....... } # Leave this alone

РЕДАКТИРОВАНИЕ 2/5/13: добавлено переименование для таблицы auth_user_group. FK будут автоматически обновляться, чтобы указывать на правильную таблицу из-за ограничений в БД, но имена таблиц полей M2M генерируются из имен 2-х конечных таблиц и требуют ручного обновления таким образом.

РЕДАКТИРОВАТЬ 2: Спасибо @Tuttle & @pix0r за исправления.

Мой невероятно ленивый способ сделать это:

  1. Создайте новую модель (User), расширяющую AbstractUser. В новой модели, в ее Meta, переопределите db_table и установите "auth_user".

  2. Создайте начальную миграцию с помощью South.

  3. Мигрируйте, но имитируйте миграцию, используя --fake при запуске мигрировать.

  4. Добавьте новые поля, создайте миграцию, запустите ее как обычно.

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

Вы можете исправить это позже с помощью миграции вручную, чтобы переименовать таблицу.

Я устал от борьбы с Югом, так что на самом деле я сделал это по-другому, и это хорошо сработало для моей конкретной ситуации:

Сначала я заставил его работать с./manage.py dumpdata, исправив дамп, а затем./manage.py loaddata, который сработал. Затем я понял, что могу сделать в основном то же самое с помощью одного автономного скрипта, который загружает только необходимые настройки django и выполняет сериализацию / десериализацию напрямую.

Автономный скрипт на питоне

## userconverter.py ##

import json
from django.conf import settings

settings.configure(
    DATABASES={ 
            # copy DATABASES configuration from your settings file here, or import it directly from your settings file (but not from django.conf.settings) or use dj_database_url
            },
    SITE_ID = 1, # because my custom user implicates contrib.sites (which is why it's in INSTALLED_APPS too)
    INSTALLED_APPS = ['django.contrib.sites', 'django.contrib.auth', 'myapp'])

# some things you have to import after you configure the settings
from django.core import serializers
from django.contrib.auth.models import User

# this isn't optimized for huge amounts of data -- use streaming techniques rather than loads/dumps if that is your case
old_users = json.loads(serializers.serialize('json', User.objects.all()))
for user in old_users:
    user['pk'] = None
    user['model'] = "myapp.siteuser"
    user['fields']["site"] = settings['SITE_ID']

for new_user in serializers.deserialize('json', json.dumps(old_users)):
    new_user.save()

С помощью dumpdata / loaddata

Я сделал следующее:

1)./manage.py dumpdata auth.User

2) Скрипт для преобразования данных auth.user новому пользователю. (или просто вручную ищите и заменяйте в вашем любимом текстовом редакторе или в grep) Моя выглядела примерно так:

def convert_user_dump(filename, site_id):
    file = open(filename, 'r')
    contents = file.read()
    file.close()
    user_list = json.loads(contents)
    for user in user_list:
        user['pk'] = None  # it will auto-increment
        user['model'] = "myapp.siteuser"
        user['fields']["site"] = side_id
    contents = json.dumps(user_list)
    file = open(filename, 'w')
    file.write(contents)
    file.close()

3)./manage.py имя файла loaddata

4) установить AUTH_USER_MODEL

* Примечание: одна из важнейших частей этого типа миграции, независимо от того, какую технику вы используете (Юг, сериализация / модификация / десериализация или иное), заключается в том, что как только вы установите AUTH_USER_MODEL для своей пользовательской модели в текущих настройках, django отключает вас от auth.User, даже если таблица все еще существует.*

Я думаю, что вы правильно определили, что миграционные рамки, такие как Юг, - верный путь. Предполагая, что вы используете Юг, вы сможете использовать функцию переноса данных для переноса старых пользователей на новую модель.

В частности, я бы добавил forwards способ скопировать все строки в вашей пользовательской таблице в новую таблицу. Что-то вроде:

def forwards(self, orm):
    for user in orm.User.objects.all():
        new_user = SiteUser(<initialize your properties here>)
        new_user.save()

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

Мы решили переключиться на пользовательскую модель в нашем проекте Django 1.6/Django-CMS 3, возможно, с небольшим опозданием, потому что в нашей базе данных были данные, которые мы не хотели потерять (некоторые страницы CMS и т. Д.).

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

Мы нашли быстрый и грязный способ перестроить все таблицы и отношения и скопировать наши старые данные (кроме пользователей):

  • сделать полную резервную копию вашей базы данных с mysqldump
  • сделать еще одну резервную копию без CREATE TABLE операторы и исключая несколько таблиц, которые не будут существовать после перестройки или будут заполнены syncdb --migrate на свежую базу данных:
    • south_migrationhistory
    • auth_user
    • auth_user_groups
    • auth_user_user_permissions
    • auth_permission
    • django_content_types
    • django_site
    • любые другие таблицы, которые принадлежат приложениям, которые вы удалили из своего проекта (вы можете узнать это, только экспериментируя)
  • сбросить базу данных
  • пересоздать базу данных (например, manage.py syncdb --migrate)
  • создайте дамп пустой базы данных (чтобы быстрее обойти этот цикл)
  • попытаться загрузить дамп данных, который вы создали выше
  • если его не удается загрузить из-за дублирования первичного ключа или отсутствующей таблицы, то:
    • редактировать дамп с помощью текстового редактора
    • удалить операторы, которые блокируют, сбрасывают и разблокируют эту таблицу
    • перезагрузите пустой дамп базы данных
    • попробуйте снова загрузить дамп данных
    • повторять до тех пор, пока дамп данных не загрузится без ошибок

Команды, которые мы запускали (для MySQL) были:

mysqldump <database> > ~/full-backup.sql
mysqldump <database> \
    --no-create-info \
    --ignore-table=<database>.south_migrationhistory \
    --ignore-table=<database>.auth_user \
    --ignore-table=<database>.auth_user_groups \
    --ignore-table=<database>.auth_user_user_permissions \
    --ignore-table=<database>.auth_permission \
    --ignore-table=<database>.django_content_types \
    --ignore-table=<database>.django_site \
> ~/data-backup.sql

./manage.py sqlclear
./manage.py syncdb --migrate
mysqldump <database> > ~/empty-database.sql

./manage.py dbshell < ~/data-backup.sql

(edit ~/data-backup.sql to remove data dumped from a table that no longer exists)

./manage.py dbshell < ~/empty-database.sql
./manage.py dbshell < ~/data-backup.sql

(repeat until clean)
Другие вопросы по тегам