将现有 auth.User 数据迁移到新的 Django 1.5 自定义用户模型?

Posted

技术标签:

【中文标题】将现有 auth.User 数据迁移到新的 Django 1.5 自定义用户模型?【英文标题】:Migrating existing auth.User data to new Django 1.5 custom user model? 【发布时间】:2013-01-31 23:30:42 【问题描述】:

我不想销毁我网站上的所有用户。但我想利用 Django 1.5 的自定义可插拔用户模型。这是我的新用户模型:

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

在新安装时,一切都适用于我的新模型(我还有其他代码,以及这样做的充分理由——所有这些在这里都无关紧要)。但是,如果我将它放在我的实时站点上并进行同步数据库和迁移,我将失去所有用户,或者至少他们将位于与为我的新模型创建的新表不同的孤立表中。

我对 South 很熟悉,但根据 this post 和我的一些试验,它的数据迁移目前似乎不适合这种特定的迁移。因此,我正在寻找某种方法来让 South 为此工作或进行一些非 South 迁移(原始 SQL、dumpdata/loaddata 或其他),我可以在我的每台服务器(Postgres 9.2)上运行以迁移用户一旦创建了新表,而旧的 auth.User 表仍在数据库中。

【问题讨论】:

【参考方案1】:

South 完全可以为您完成此迁移,但您需要聪明并分阶段进行。这是分步指南:(本指南假定您是 AbstractUser 的子类,而不是 AbstractBaseUser

    在进行切换之前,请确保在应用程序中启用了南向支持 包含您的自定义用户模型(为了指南,我们将其称为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.
    

    在帐户应用中创建一个新的空白用户迁移。

    $ ./manage.py schemamigration accounts --empty switch_to_custom_user
    Created 0002_switch_to_custom_user.py.
    

    accounts 应用程序中创建您的自定义User 模型,但确保将其定义为:

    class SiteUser(AbstractUser): pass
    

    使用以下代码填写空白迁移。

    # 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
    

    运行迁移

    $ ./manage.py migrate accounts
     - Migrating forwards to 0002_switch_to_custom_user.
     > accounts:0002_switch_to_custom_user
     - Loading initial data for accounts.
    

    现在对您的用户模型进行任何更改。

    # settings.py
    AUTH_USER_MODEL = 'accounts.User'
    
    # accounts/models.py
    class SiteUser(AbstractUser):
        site = models.ForeignKey(Site, null=True)
    

    为此更改创建并运行迁移

    $ ./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.
    

老实说,如果您已经很好地了解了您的设置并且已经使用了 south,那么应该像将以下迁移添加到您的帐户模块一样简单。

# 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 表添加了重命名。由于 db 约束,FK 将自动更新以指向正确的表,但 M2M 字段的表名是从 2 个结束表的名称生成的,需要以这种方式手动更新。

编辑 2:感谢 @Tuttle 和 @pix0r 的更正。

【讨论】:

享受你的赏金吧!我最终使用 dumpdata/loaddata(并在其间操作转储的数据)执行此操作,它工作起来很简单,但如果我早一点了解这一点,这肯定会奏效。 从 0002 冻结的 orm 是一个“问题”——它需要一个 'accounts.user' 条目,所以南不会认为该模型对于下一次迁移来说是新的。我做了schemamigration --auto,将'accounts.user'条目从0003复制到0002,删除0003并重新创建它,现在它要做的就是更新FK。 +1 auth_user_groups 和 auth_user_permissions 不是也要迁移吗? 我认为这个例子仍然错过了重命名auth_user_user_permissions 表。无论如何,这是迄今为止我发现的最好的 1.5 用户模型迁移指南。 @eternicode:提示,从 0003 创建的副本可以使用 --stdout 生成,因此不会创建真正的文件,也不需要删除。【参考方案2】:

我非常懒惰的做法:

    创建一个新模型(用户),扩展 AbstractUser。在新模型的 Meta 中,覆盖 db_table 并设置为 'auth_user'。

    使用 South 创建初始迁移。

    迁移,但伪造迁移,在运行迁移时使用--fake

    添加新字段,创建迁移,正常运行。

这不是懒惰,而是有效。您现在有一个 1.5 兼容的用户模型,它只使用旧的用户表。您也有适当的迁移历史记录。

您可以稍后通过手动迁移来重命名表来解决此问题。

【讨论】:

我喜欢这种方式!但是我从 ./manage.py migrate --fake 收到一条消息,指出以下内容类型已过时,需要删除:auth | user 通过外键与这些内容类型相关的任何对象也将被删除。您确定要删除这些内容类型吗?如果您不确定,请回答“否”。输入“yes”继续,或输入“no”取消:你输入了什么? 如果您有任何 GenericForeignKeys,或者如果您使用 django 的权限(或任何其他 FKs ContentType),您需要添加一个迁移,重命名相应的 ContentType 实例以指向新模型.【参考方案3】:

我认为您已经正确地确定了像 South 这样的迁移框架是进入这里的正确方法。假设您使用的是 South,您应该能够使用 Data Migrations 功能将旧用户移植到您的新模型。

具体来说,我会添加一个forwards 方法来将用户表中的所有行复制到新表中。大致如下:

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

您也可以使用bulk_create 方法来加快速度。

【讨论】:

一个重要提示:如果您在将 AUTH_USER_MODEL 设置为新用户模型后尝试访问 auth.models.User,您将得到: AttributeError: Manager is not available;用户已更换为“users.TenantSiteUser”。我将尝试进行迁移,然后设置 AUTH_USER_MODEL 看看是否可行。 @BenRoberts,回想起来,如果您有任何ForeignKeys 指向User 模型,则可能会有上面未解决的更深层次的问题。不过,您也许可以进行两次迁移——一次创建新模型并复制数据,然后将新模型设置为您的用户,然后更新外键。最后,在原始 SQL 中创建和填充新的数据库表实际上可能更容易,尽管使用 INSERT...SELECT 语句应该很容易。【参考方案4】:

我厌倦了与 South 的斗争,所以实际上我最终以不同的方式做这件事,并且非常适合我的特殊情况:

首先,我使用 ./manage.py 转储数据,修复转储,然后使用 ./manage.py 加载数据,这很有效。然后我意识到我可以用一个独立的脚本来做基本相同的事情,它只加载必要的 django 设置并直接进行序列化/反序列化。

独立的python脚本

## 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()

使用转储数据/加载数据

我做了以下事情:

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 加载数据文件名

4) 设置 AUTH_USER_MODEL

*旁注:无论您使用哪种技术(South、序列化/修改/反序列化或其他),进行此类迁移的一个关键部分是,只要您将 AUTH_USER_MODEL 设置为当前的自定义模型设置,django 会切断你与 auth.User 的联系,即使表仍然存在。*

【讨论】:

【参考方案5】:

我们决定在 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)

【讨论】:

以上是关于将现有 auth.User 数据迁移到新的 Django 1.5 自定义用户模型?的主要内容,如果未能解决你的问题,请参考以下文章

如何将现有应用程序部署到新的 EC2 服务器?

将应用数据迁移到新的 IOS 应用

通过迁移将核心数据实体及其数据移动到新的核心数据模型文件中

将 EF 迁移合并到新的 InitialCreate

迁移到新的 Macbook - IOS 应用推送通知和证书

Cassandra 将数据迁移到新服务器