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

时间:2013-02-15 21:59:33

标签: django postgresql django-south django-1.5

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

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

在新安装中我的新模型一切正常(我还有其他代码,还有一个很好的理由 - 所有这些都与此无关)。但是,如果我把它放在我的实时网站和syncdb&迁移,我将丢失所有用户,或者至少他们将处于与为我的新模型创建的新表不同的孤立表中。

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

5 个答案:

答案 0 :(得分:49)

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. 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.
    
  8. 老实说,如果您已经熟悉设置并且已经使用了南方,那么应该像将以下迁移添加到您的帐户模块一样简单。

    # 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
    

    EDIT 2/5/13:为auth_user_group表添加了重命名。由于db约束,FK将自动更新以指向正确的表,但是M2M字段的表名是从2个结束表的名称生成的,并且需要以这种方式手动更新。

    编辑2:感谢@Tuttle& @ pix0r进行更正。

答案 1 :(得分:16)

我非常懒惰地这样做:

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

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

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

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

  5. 这超出了懒惰,但有效。您现在拥有符合1.5的用户模型,它只使用旧的用户表。您还有适当的迁移历史记录。

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

答案 2 :(得分:4)

我认为您已经正确地确定了像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方法加快速度。

答案 3 :(得分:4)

我厌倦了与南方的挣扎,所以我实际上最终以不同的方式做到这一点,并且它很好地适应了我的特殊情况:

首先,我使用./manage.py dumpdata,修复转储,然后运行./manage.py loaddata。然后我意识到我可以用一个单独的,自包含的脚本做同样的事情,它只加载必要的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()

使用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 filename

4)设置AUTH_USER_MODEL

*旁注:执行此类迁移的一个关键部分是,无论您使用哪种技术(南,序列化/修改/反序列化或其他技术),只要您在当前设置AUTH_USER_MODEL到自定义模型设置,即使表仍然存在,django也会将你从auth.User中删除。*

答案 4 :(得分:2)

我们决定在我们的Django 1.6 / Django-CMS 3项目中切换到自定义用户模型,可能有点迟,因为我们的数据库中有数据我们不想丢失(某些CMS页面等)

在我们将AUTH_USER_MODEL切换到我们的自定义模型之后,我们遇到了许多我们没有预料到的问题,因为很多其他表都有旧的auth_user表的外键,但没有删除。因此,尽管事情似乎在表面上起作用,但很多事情都在下面发生:发布页面,向页面添加图像,添加用户等等,因为他们试图在仍然具有{{1的外键的表的表中创建条目没有实际将匹配记录插入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
  • 创建一个空数据库的转储(以便更快地绕过此循环)
  • 尝试加载您在上面创建的数据转储
  • 如果由于重复的主键或缺少的表而无法加载,则:
    • 使用文本编辑器编辑转储
    • 删除锁定,转储和解锁该表的语句
    • 重新加载空数据库转储
    • 尝试再次加载数据转储
    • 重复,直到数据转储加载没有错误

我们运行的命令(对于MySQL)是:

manage.py syncdb --migrate