Information Security and Network Awareness

Hurricane Labs

Subscribe to Hurricane Labs: eMailAlertsEmail Alerts
Get Hurricane Labs: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn

Blog Feed Post

Migrating from Django User Profiles to Custom User Models

The custom User model is a new feature in Django 1.5. Before this version, applications that wanted to use Django’s

  framework had to use Django’s provided User model and a custom Profile model to extend it. In this new version you are able to write your own user model. This could simply be an extension of the existing User model or you may want to completely replace the User with one more customized.

I’ve worked up a full solution for migrating from Django <1.5′s User + Profile models to 1.5′s custom User models without destroying all the users on my site. This article will explain how to do the migration while preserving all your users, group memberships, and permissions.

You will first need to create your new model. The

  function and
  property allow your new model to simulate the old behavior, while logging any function that still uses the old profile code. This assumes that the logger is configured and that your profile model had a user property.

from django.contrib.auth.models import AbstractUser

class MyUser(AbstractUser):

    def get_profile(self):
        import inspect
        s = inspect.stack()'location="MyUser.get_profile" '
        'message="Deprecated function" '
        'file="{1}" line="{2}" function="{3}"'.format(*s[1]))
        #No more profiles, user model supports all old profile properties
        return self

    def user(self):
        # Support for self as profile. Use of this is deprecated
        return self

South can be used to create an empty migration. We’ll use this to rename all the old user tables into tables for the new model:

./ schemamigration --empty myapp move_user_tables_to_myapp_myuser

And here are the forwards and backwards migrations. You’ll need to use replace “myapp” with the real name of your application. If your user model isn’t called myuser, you’ll need to change “myuser_id” as well. This will rename the contenttype model so that existing generic relations will continue to work:

def forwards(self, orm):
    db.rename_table('auth_user', 'myapp_myuser')
    db.rename_table('auth_user_groups', 'myapp_myuser_groups')
    db.rename_column('myapp_customer_users', 'user_id', 'myuser_id')
    db.rename_column('myapp_myuser_groups', 'user_id', 'myuser_id')
    if not db.dry_run:
        # For permissions to work properly after migrating
        model='user').update(app_label='myapp', model='myuser')

def backwards(self, orm):
    db.rename_table('myapp_myuser', 'auth_user')
    db.rename_table('myapp_myuser_groups', 'auth_user_groups')
    db.rename_column('myapp_customer_users', 'myuser_id', 'user_id')
    db.rename_column('myapp_myuser_groups', 'myuser_id', 'user_id')
    if not db.dry_run:
        # For permissions to work properly after migrating
        model='myuser').update(app_label='auth', model='user')

So, now we need to get those old profiles copied into the new user model. Start by copying the field definitions:

class UserProfile(models.Model):
    user = models.OneToOneField(User)
    parrot_count = models.IntegerField(null=True, blank=True)
    #More fields

    #  |
    #  |
    # \_/

class MyUser(AbstractUser):
    parrot_count = models.IntegerField(null=True, blank=True)
    #the same fields

    def get_profile(self):

And tell South about them:

./ schemamigration myapp --auto

Also, create a data migration, this is where we’ll copy the old profile information to the new fields

./ datamigration myapp copy_profile_data_to_myapp_myuser

Something like this ought to do you for the data-migration. Be sure to include all fields you want to copy:

def forwards(self, orm):
    # Write your forwards methods here.
    # Note: Remember to use orm['appname.ModelName'] rather than
    "from appname.models..."
    for u in orm['myapp.MyUser'].objects.all():
        p = orm['myapp.Profile'].get(user=u)
        u.parrot_count = p.parrot_count

def backwards(self, orm):
    "Write your backwards methods here."
    for u in orm['myapp.MyUser'].objects.all():
        p, created = orm['myapp.Profile'].get_or_create(user=u)
        p.parrot_count = u.parrot_count

Now that we’ve moved the database tables and copied the data, it’s time to throw the switch. In your you’ll set

AUTH_USER_MODEL = 'myapp.MyUser'
and remove
AUTH_PROFILE_MODEL = 'myapp.Profile'

Watch your logs for calls to

, and grep your code for instances of
so you can update them to just use MyUser.

One last thing, you can remove the Profile class from your code, and do one more schemamigration to drop it from the database.

Read the original blog entry...

More Stories By Hurricane Labs

Christina O’Neill has been working in the information security field for 3 years. She is a board member for the Northern Ohio InfraGard Members Alliance and a committee member for the Information Security Summit, a conference held once a year for information security and physical security professionals.