34 votes

Migration des données auth.User existantes vers le nouveau modèle d'utilisateur personnalisé de Django 1.5 ?

Je préfère ne pas détruire tous les utilisateurs de mon site. Mais je veux profiter du modèle d'utilisateur personnalisable de Django 1.5. Voici mon nouveau modèle d'utilisateur :

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

Tout fonctionne avec mon nouveau modèle sur une nouvelle installation (j'ai d'autres codes, ainsi qu'une bonne raison de faire cela - tout cela n'est pas pertinent ici). Mais si je mets cela sur mon site réel et que je synchronise et migre, je perdrai tous mes utilisateurs ou du moins ils seront dans une table différente, orpheline, de la nouvelle table créée pour mon nouveau modèle.

Je connais bien le Sud, mais sur la base de ce poste et quelques essais de ma part, il semble que ses migrations de données ne soient pas actuellement adaptées à cette migration spécifique. Je cherche donc un moyen de faire fonctionner South pour cette migration ou pour une migration autre que South (SQL brut, dumpdata/loaddata, ou autre) que je peux exécuter sur chacun de mes serveurs (Postgres 9.2) pour migrer les utilisateurs une fois que la nouvelle table a été créée alors que l'ancienne table auth.User est toujours dans la base de données.

49voto

Thomas Points 7109

South est tout à fait capable d'effectuer cette migration pour vous, mais vous devez être intelligent et procéder par étapes. Voici le guide étape par étape : (Ce guide présuppose que vous avez sous-classé AbstractUser , pas AbstractBaseUser )

  1. Avant de procéder au changement, assurez-vous que la prise en charge du sud est activée dans l'application qui contient votre modèle d'utilisateur personnalisé (pour les besoins du guide, nous l'appellerons accounts et le modèle User ). À ce stade, vous devez pas encore ont un modèle d'utilisateur personnalisé.

    $ ./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. Créez une nouvelle migration d'utilisateur vierge dans l'application Comptes.

    $ ./manage.py schemamigration accounts --empty switch_to_custom_user
    Created 0002_switch_to_custom_user.py.
  3. Créez votre propre User dans le modèle accounts mais veillez à ce qu'il soit défini comme :

    class SiteUser(AbstractUser): pass
  4. Remplissez la migration en blanc avec le code suivant.

    # 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. Exécuter la migration

    $ ./manage.py migrate accounts
     - Migrating forwards to 0002_switch_to_custom_user.
     > accounts:0002_switch_to_custom_user
     - Loading initial data for accounts.
  6. Apportez maintenant les modifications nécessaires à votre modèle d'utilisateur.

    # settings.py
    AUTH_USER_MODEL = 'accounts.User'
    
    # accounts/models.py
    class SiteUser(AbstractUser):
        site = models.ForeignKey(Site, null=True)
  7. créer et exécuter des migrations pour ce changement

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

Honnêtement, si vous avez déjà une bonne connaissance de votre configuration et que vous utilisez déjà South, cela devrait être aussi simple que d'ajouter la migration suivante à votre module de comptes.

# 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 : ajout du renommage de la table auth_user_group. Les FKs seront automatiquement mis à jour pour pointer vers la bonne table en raison des contraintes de la base de données, mais les noms de table des champs M2M sont générés à partir des noms des 2 tables finales et devront être mis à jour manuellement de cette manière.

EDIT 2 : Merci à @Tuttle & @pix0r pour les corrections.

16voto

Jack Shedd Points 2081

Ma façon incroyablement paresseuse de procéder :

  1. Créer un nouveau modèle (User), étendant AbstractUser. A l'intérieur du nouveau modèle, dans son Meta, surchargez db_table et défini comme "auth_user".

  2. Créer une migration initiale en utilisant South.

  3. Migrer, mais simuler la migration, en utilisant la fonction --fake lors de l'exécution de migrate.

  4. Ajouter de nouveaux champs, créer une migration, l'exécuter normalement.

C'est plus que paresseux, mais ça marche. Vous avez maintenant un modèle d'utilisateur conforme à la version 1.5, qui utilise simplement l'ancienne table des utilisateurs. Vous avez également un historique de migration correct.

Vous pouvez y remédier ultérieurement en procédant à des migrations manuelles pour renommer la table.

4voto

Je pense que vous avez correctement identifié qu'un cadre de migration comme South est la bonne façon de procéder. En supposant que vous utilisiez South, vous devriez être en mesure d'utiliser la fonction Migrations de données pour transférer les anciens utilisateurs vers votre nouveau modèle.

Plus précisément, j'ajouterais un forwards pour copier toutes les lignes de votre table utilisateur dans la nouvelle table. Quelque chose comme :

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

Vous pouvez également utiliser la fonction bulk_create pour accélérer les choses.

4voto

Ben Roberts Points 8429

J'en ai eu assez de lutter contre le Sud, j'ai donc fini par faire les choses différemment et cela a bien fonctionné dans ma situation particulière :

Tout d'abord, j'ai fait fonctionner ./manage.py dumpdata, en corrigeant le dump, puis ./manage.py loaddata, ce qui a fonctionné. Puis j'ai réalisé que je pouvais faire à peu près la même chose avec un seul script autonome qui ne charge que les paramètres nécessaires de django et fait la sérialisation/désérialisation directement.

Script python autonome script.

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

Avec dumpdata/loaddata

J'ai procédé comme suit :

1) ./manage.py dumpdata auth.User

2) script pour convertir les données auth.user en nouvel utilisateur. (ou recherchez et remplacez manuellement dans votre éditeur de texte favori ou grep) Le mien ressemblait à quelque chose comme :

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 nom du fichier loaddata

4) définir AUTH_USER_MODEL

*Note secondaire : Une partie critique de ce type de migration, quelle que soit la technique utilisée (Sud, sérialisation/modification/désérialisation, ou autre) est que dès que vous définissez AUTH_USER_MODEL à votre modèle personnalisé dans les paramètres actuels, django vous coupe de auth.User, même si la table existe toujours*.

2voto

qris Points 1649

Nous avons décidé de passer à un modèle utilisateur personnalisé dans notre projet Django 1.6/Django-CMS 3, peut-être un peu tard car nous avions des données dans notre base que nous ne voulions pas perdre (certaines pages du CMS, etc.).

Après avoir remplacé AUTH_USER_MODEL par notre modèle personnalisé, nous avons rencontré de nombreux problèmes que nous n'avions pas anticipés, car beaucoup d'autres tables avaient des clés étrangères vers l'ancien modèle AUTH_USER_MODEL. auth_user qui n'a pas été supprimée. Ainsi, bien que les choses semblent fonctionner en surface, beaucoup de choses se sont cassées en dessous : publication de pages, ajout d'images aux pages, ajout d'utilisateurs, etc. parce qu'ils ont essayé de créer une entrée dans une table qui avait encore une clé étrangère vers auth_user sans pour autant insérer un enregistrement correspondant dans auth_user .

Nous avons trouvé un moyen rapide et peu pratique de reconstruire toutes les tables et relations, et de copier nos anciennes données (à l'exception des utilisateurs) :

  • faire une sauvegarde complète de votre base de données avec mysqldump
  • faire une autre sauvegarde sans CREATE TABLE et en excluant quelques tables qui n'existeront pas après la reconstruction, ou qui seront alimentées par des données syncdb --migrate sur une nouvelle base de données :
    • south_migrationhistory
    • auth_user
    • auth_user_groups
    • auth_user_user_permissions
    • auth_permission
    • django_content_types
    • django_site
    • toutes les autres tables appartenant à des applications que vous avez supprimées de votre projet (vous ne le découvrirez qu'en expérimentant).
  • supprimer la base de données
  • recréer la base de données (par exemple manage.py syncdb --migrate )
  • créer un dump de la base de données vide (pour accélérer le processus)
  • tentez de charger le fichier de données que vous avez créé ci-dessus
  • si le chargement échoue en raison d'une clé primaire dupliquée ou d'une table manquante, alors :
    • éditer le dump avec un éditeur de texte
    • supprimer les instructions qui verrouillent, déchargent et déverrouillent cette table
    • recharger la base de données vide
    • essayez de charger à nouveau le fichier de données
    • répéter l'opération jusqu'à ce que les données soient chargées sans erreur

Les commandes que nous avons exécutées (pour MySQL) étaient les suivantes :

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)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X