views:

366

answers:

2

Django now provides a BigIntegerField for use in django models (available in svn trunk and also 1.2 alpha-1 release).

I need my django.contrib.auth.user model to have a BigIntegerField as its auto-incrementing primary key, whereas it currently uses an auto-incrementing IntegerField as its primary key. Also, wherever contrib.auth.user is used as a ForeginKey, it would need to be BigIntegerField as well.

What is the best and safest way to go about achieving this?

+1  A: 

While I'm not sure why you need a BigIntegerField on User (you must have a whole lotta users) its pretty easy to implement. First you'll need to get a database migration system like South. We'll use this to do a handful of migrations of your current data. If you don't have anything in your database then just ignore this part and skip to the end.

I would start by making a custom user class which inherits from the contrib.auth version like so:

from django.contrib.auth.models import User, UserManager
from django.db import models

class BigUser(User):
    id = models.BigIntegerField(pk = True)

    objects = UserManager() 
    #this lets you transperantly use any 
    #query methods that you could on User

Then use South's data-migration capability to make a copy of all of you User.objects.all() into your new BigUser model.

Then go through and ADD a foriegnkey in each model where its needed. DO NOT delete the original FK yet, otherwise you're links will be lost. After adding the new keys do another schema migration.

Then make another data migration which copies the FK's from the old User model to the new BigUser model. Migrate that data.

Then its safe to delete the old FK to the User model.

If you want to avoid changing the rest of your code to use the new field-name for the BigUser you can use the South rename-field utility (South can't auto-detect field renames so make sure to read the docs).

If you don't have any data in the database then you can simply implement the class above and drop it into your current models.

If you need help writing data-migrations you'll have to post a model or two.

Since you need something that's a "drop-in" replacement for User you'll need two more steps: First we need to create a custom authentication back-end, this makes sure that any authentication requests go to your new model and that request.user returns BigUser and not User. Just cut and paste this snippet into a file called auth_backend.py in the same directory as settings.py:

from django.conf import settings
from django.contrib.auth.backends import ModelBackend
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model

class CustomUserModelBackend(ModelBackend):
    def authenticate(self, username=None, password=None):
        try:
            user = self.user_class.objects.get(username=username)
            if user.check_password(password):
                return user
        except self.user_class.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return self.user_class.objects.get(pk=user_id)
        except self.user_class.DoesNotExist:
            return None

    @property
    def user_class(self):
        if not hasattr(self, '_user_class'):
            self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
            if not self._user_class:
                raise ImproperlyConfigured('Could not get custom user model')
        return self._user_class

Then in your settings.py file you need to add this back-end and set the custom user model setting ... like so:

AUTHENTICATION_BACKENDS = (
    'auth_backends.CustomUserModelBackend',
)
...

CUSTOM_USER_MODEL = 'your-app-name.BigUser'

This last section of code comes from another website describing subclassing the User model.

Now all you need to do to "drop-in" in the rest of your code is to replace all of the from django.contrib.auth.models import User with from your-app-name import BigUser as User. By doing this you wont have to update any references of User with BigUser

JudoWill
That's clear enough, but will this work transparently? I mean will authentication, login, logout, groups etc all work as expected? What do I need to do to continue using django.contrib.auth seamlessly?
gbsmith
@gbsmith I updated the answer to allow for a better drop-in replacement, hope that helps.
JudoWill
@JudoWill Reading your code, together with the website link you provided and also the Django docs, I have to say this looks good. I'm holding off marking this as the right answer until I try it out in my app, and I want to try out how the contrib.auth.group behaves
gbsmith
this seems to have repercussions for contrib.admin
gbsmith
A: 

I am weighing the option of changing the code of django.contrib.auth.models.user to include an id field as BigIntegerField primary key.

Seems to me to be the best way to go.

(I am ready to migrate the data manually via sql)

gbsmith
Not as easy as I thought. Django's AutoField is an IntegerField and not a BigIntegerField. Specifying primary=True solves one problem, not both.
gbsmith