将现有 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,回想起来,如果您有任何ForeignKey
s 指向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 自定义用户模型?的主要内容,如果未能解决你的问题,请参考以下文章