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?
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.
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.
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')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With