¿Migración de datos de usuario de autenticación existentes al nuevo modelo de usuario personalizado de Django 1.5?
postgresql django-south (5)
Creo que ha identificado correctamente que un marco de migración como South es el camino correcto para ir aquí. Suponiendo que está utilizando South, debería poder usar la funcionalidad Data Migrations para transferir los usuarios antiguos a su nuevo modelo.
Específicamente, agregaría un método forwards
para copiar todas las filas en su tabla de usuario a la nueva tabla. Algo como:
def forwards(self, orm):
for user in orm.User.objects.all():
new_user = SiteUser(<initialize your properties here>)
new_user.save()
También puede usar el método bulk_create
para acelerar las cosas.
Prefiero no destruir a todos los usuarios de mi sitio. Pero quiero aprovechar el modelo de usuario conectable personalizado de Django 1.5. Aquí está mi nuevo modelo de usuario:
class SiteUser(AbstractUser):
site = models.ForeignKey(Site, null=True)
Todo funciona con mi nuevo modelo en una nueva instalación (tengo otro código, junto con una buena razón para hacerlo, todos los cuales son irrelevantes aquí). Pero si pongo esto en mi sitio en vivo y sincronizo y migro, perderé todos mis usuarios o al menos estarán en una mesa huérfana diferente a la nueva tabla creada para mi nuevo modelo.
Estoy familiarizado con South, pero en base a esta publicación y algunas pruebas de mi parte, parece que sus migraciones de datos actualmente no son aptas para esta migración específica. Así que estoy buscando la forma de hacer que South funcione para esto o para alguna migración no sur (SQL en bruto, dumpdata / datos de carga, u otros) que pueda ejecutar en cada uno de mis servidores (Postgres 9.2) para migrar a los usuarios una vez que se ha creado la nueva tabla, mientras que la vieja tabla de autenticación de usuario todavía está en la base de datos.
Decidimos cambiar a un modelo de usuario personalizado en nuestro proyecto Django 1.6 / Django-CMS 3, quizás un poco tarde porque teníamos datos en nuestra base de datos que no queríamos perder (algunas páginas de CMS, etc.).
Después de cambiar AUTH_USER_MODEL a nuestro modelo personalizado, tuvimos muchos problemas que no habíamos previsto, porque muchas otras tablas tenían claves externas a la vieja tabla auth_user
, que no se eliminó. Así que, aunque las cosas parecían funcionar en la superficie, muchas cosas se rompieron debajo: páginas de publicación, agregar imágenes a las páginas, agregar usuarios, etc. porque intentaron crear una entrada en una tabla que aún tenía una clave externa para auth_user
, sin de hecho insertando un registro coincidente en auth_user
.
Encontramos una forma rápida y sucia de reconstruir todas las tablas y relaciones, y copiar nuestros datos antiguos (excepto para los usuarios):
- hacer una copia de seguridad completa de su base de datos con
mysqldump
- haga otra copia de seguridad sin declaraciones
CREATE TABLE
, y excluya algunas tablas que no existirán después de la reconstrucción, o se completará consyncdb --migrate
en una nueva base de datos:-
south_migrationhistory
-
auth_user
-
auth_user_groups
-
auth_user_user_permissions
-
auth_permission
-
django_content_types
-
django_site
- cualquier otra tabla que pertenezca a las aplicaciones que eliminó de su proyecto (puede que solo descubra esto mediante la experimentación)
-
- soltar la base de datos
- recrear la base de datos (ej.
manage.py syncdb --migrate
) - crear un volcado de la base de datos vacía (para que sea más rápido dar la vuelta a este ciclo nuevamente)
- intentar cargar el volcado de datos que creó anteriormente
- si no se carga debido a una clave principal duplicada o una tabla faltante, entonces:
- edita el volcado con un editor de texto
- eliminar las instrucciones que bloquean, vuelcan y desbloquean esa tabla
- recargar el volcado de la base de datos vacía
- intenta cargar el volcado de datos de nuevo
- repita hasta que el volcado de datos se cargue sin errores
Los comandos que ejecutamos (para MySQL) fueron:
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)
Me cansé de luchar con South así que en realidad terminé haciendo esto de manera diferente y funcionó muy bien para mi situación particular:
Primero, lo hice funcionar con ./manage.py dumpdata, arreglando el volcado, y luego ./manage.py loaddata, que funcionó. Luego me di cuenta de que podía hacer básicamente lo mismo con un script único e independiente que solo cargaba la configuración necesaria de django y realiza la serialización / deserialización directamente.
Script python autónomo
## 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()
Con dumpdata / loaddata
Hice lo siguiente:
1) ./manage.py dumpdata auth.User
2) Script para convertir datos de auth.user a nuevo usuario. (O simplemente busque y reemplace manualmente en su editor de texto favorito o grep) El mío se veía algo así como:
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 nombre de archivo
4) establecer AUTH_USER_MODEL
* Nota al margen: una parte fundamental de este tipo de migración, independientemente de la técnica que utilice (sur, serialización / modificación / deserialización u otra), es que tan pronto como configure AUTH_USER_MODEL para su modelo personalizado en la configuración actual, django te desconecta de auth.User, incluso si la tabla aún existe. *
Mi forma increíblemente perezosa de hacer esto:
Crea un nuevo modelo (Usuario), extendiendo AbstractUser. Dentro del nuevo modelo, en Meta, anule db_table y establezca ''auth_user''.
Crea una migración inicial usando South.
Migre, pero
--fake
la migración, usando--fake
when running--fake
.Agregue nuevos campos, cree la migración, ejecútelo normalmente.
Esto es más que perezoso, pero funciona. Ahora tiene un modelo de Usuario compatible con 1.5, que solo usa la vieja tabla de usuarios. También tiene un historial de migración adecuado.
Puede solucionar esto más adelante con migraciones manuales para cambiar el nombre de la tabla.
South es más que capaz de hacer esta migración por usted, pero necesita ser inteligente y hacerlo en etapas. Aquí está la guía paso a paso: (Esta guía presupone la subclase AbstractUser
, no AbstractBaseUser
)
Antes de realizar el cambio, asegúrese de que la compatibilidad sur esté habilitada en la aplicación que contiene su modelo de usuario personalizado (por el bien de la guía, lo llamaremos
accounts
y elUser
modelo). En este momento, aún no deberías tener un modelo de usuario personalizado.$ ./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.
Cree una nueva migración de usuario en blanco en la aplicación de cuentas.
$ ./manage.py schemamigration accounts --empty switch_to_custom_user Created 0002_switch_to_custom_user.py.
Cree su modelo de
User
personalizado en la aplicación deaccounts
, pero asegúrese de que esté definido como:class SiteUser(AbstractUser): pass
Complete la migración en blanco con el siguiente código.
# 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
Ejecute la migración
$ ./manage.py migrate accounts - Migrating forwards to 0002_switch_to_custom_user. > accounts:0002_switch_to_custom_user - Loading initial data for accounts.
Realice cualquier cambio en su modelo de usuario ahora.
# settings.py AUTH_USER_MODEL = ''accounts.User'' # accounts/models.py class SiteUser(AbstractUser): site = models.ForeignKey(Site, null=True)
crear y ejecutar migraciones para este cambio
$ ./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.
Honestamente, si ya tiene un buen conocimiento de su configuración y ya utiliza el sur, debe ser tan simple como agregar la siguiente migración a su módulo de cuentas.
# 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
EDITAR 5/5/13: cambio de nombre agregado para la tabla auth_user_group. Los FK se actualizarán automáticamente para apuntar a la tabla correcta debido a las restricciones de db, pero los nombres de las tablas de los campos de M2M se generan a partir de los nombres de las 2 tablas de los extremos y necesitarán una actualización manual de esta manera.
EDIT 2: Gracias a @Tuttle & @ pix0r por las correcciones.