Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Renaming auth_user breaks migrations on fresh setup

Following what seems like good advice, I migrated from Django's built-in auth.User to my own app.User by doing a migration that renames auth_user to app_user. So far so good, this works fine. The problem comes when I set up a new machine.

In my settings.py I have AUTH_USER_MODEL = 'app.User'. Because of this, when I run syncdb, the auth_user table is not created, so when I migrate, that migration fails.

The only way around this I've found is to modify AUTH_USER_MODEL to point to auth.User, run syncdb and migrations up until the rename migration, then change AUTH_USER_MODEL back, then run the rest of the migrations.

Is there a way around this problem?

like image 901
fredley Avatar asked Jul 24 '14 08:07

fredley


3 Answers

Based on the issues you have mentioned having, the approach I would first try is to modify the migration that performs the table rename to check whether the rename should be performed. Unfortunately South does not readily cooperate with this kind of check. Most higher-level operations completely abort a migration if they fail. However, you can use db.execute and it will raise an exception if it fails. Something like:

from django.db.utils import ProgrammingError
from south.db import db

exists = False
db.start_transaction()
try:
    # Will fail if the destination table does not exist. 
    # Any typo here will yield incorrect results. Be careful.
    db.execute("select count(*) from auth_user")
    # If we get here, the table exists
    exists = True
except ProgrammingError:
    pass

# Always end the transaction we started, rollback or commit shouldn't matter.
db.rollback_transaction()

if exists:
    db.rename_table...
else:
    # The table does not exist, create new one.
    db.create_table...

My tests show that it is always possible to catch the errors raised by South's database calls. However, South does not clean up after an SQL error. (This is what I initially missed in the first version of this answer.) So even if the exception is caught, the next SQL operation that will start will find that there the connection is in an error state. In other words, the operation that occurs after the operation that failed will fail because the previous operation fails. This is the reason for the db.start_transaction() and db.rollback_transaction() calls. This makes the operation end cleanly even if there was an SQL error.

like image 165
Louis Avatar answered Nov 17 '22 03:11

Louis


I also had problems trying to follow the same instructions like you did, but I chose to fix it another way. I created my model (called UserProfile) like this:

class UserProfile(AbstractUser):
    # Fields
    ...
    class Meta:
        swappable = 'AUTH_USER_MODEL'
        db_table = 'auth_user'

This way, running syncdb will no longer cause problems, because your table is named properly. However, I don't remember exactly all the steps I took when I did this, so it might need a bit more setup.

like image 33
AdelaN Avatar answered Nov 17 '22 02:11

AdelaN


With the ideas from the other answers presented here, this is a solution that works:

def forwards(self, orm):
    if 'auth_user' not in db.execute('SHOW TABLES'):
        db.create_table('app_user', (
            (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
            ('password', self.gf('django.db.models.fields.CharField')(max_length=128)),
            ('last_login', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
            ('is_superuser', self.gf('django.db.models.fields.BooleanField')(default=False)),
            ('username', self.gf('django.db.models.fields.CharField')(unique=True, max_length=30)),
            ('first_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)),
            ('last_name', self.gf('django.db.models.fields.CharField')(max_length=30, blank=True)),
            ('email', self.gf('django.db.models.fields.EmailField')(max_length=75, blank=True)),
            ('is_staff', self.gf('django.db.models.fields.BooleanField')(default=False)),
            ('is_active', self.gf('django.db.models.fields.BooleanField')(default=True)),
            ('date_joined', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
        ))
        db.send_create_signal(app', ['User'])
    else:
        db.rename_table('auth_user', 'app_user')
like image 1
fredley Avatar answered Nov 17 '22 03:11

fredley