【问题标题】:Migrating existing auth.User data to new Django 1.5 custom user model?将现有 auth.User 数据迁移到新的 Django 1.5 自定义用户模型?
【发布时间】: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 表仍在数据库中。

【问题讨论】:

    标签: django postgresql django-south django-1.5


    【解决方案1】:

    South 完全可以为您完成此迁移,但您需要聪明并分阶段进行。这是分步指南:(本指南假定您是 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. accounts 应用程序中创建您的自定义User 模型,但确保将其定义为:

      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.
      

    老实说,如果您已经很好地了解了您的设置并且已经使用了 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】:

    我非常懒惰的做法:

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

    2. 使用 South 创建初始迁移。

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

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

    这不是懒惰,而是有效。您现在有一个 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)
      

      【讨论】:

        猜你喜欢
        • 2013-06-08
        • 2015-09-12
        • 2015-08-20
        • 2017-08-05
        • 2015-10-10
        • 1970-01-01
        • 2015-03-30
        • 2012-12-26
        相关资源
        最近更新 更多