Django - migrating to a custom user model mid-project
2024-03-12 | python, django
This was quite painful. Please just follow the best practice of using a custom user model from the start.
Starting point in my app #
- Default user model
 - One model proxying the current user model, because I didn’t want to do this change before
 - Wanted to add a new app to contain the new custom user model (
shared) - Using 
django-tenants 
Process #
First stage #
- Create a new app, 
shared, to contain the new user modelUser. Just withdjango-admin startapp shared. Clean up files that won’t be used likeviews.pyetc. - Update 
settings.py: Add this app to all tenants - Before making any changes to models: Create a single, blank migration for this app, i.e. 
0001_initial.pythat does nothing (python manage.py makemigrations --empty shared) - Run 
python manage.py migrateto get this first initial app set up. 
Here, between the stages, you should push these changes and run the migrations against your production and any other DBs that you wish to persists.
Second stage #
Now update the
models.pyinsharedapp with your new model, like the one below. Do not make any other changes at this point preferably, if you want to add new fields, do it later once the change process is complete.from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): class Meta: db_table = 'auth_user'Edit the
shared/migrations/0001_initial.pymigration that you had created before to handle the creation of the entireUsermodel. Quoting below from one of the references on what to do:- This is because you want the initial migration to already have been executed in an instance with data (hence the first stage), but in the future you’ll want to recreate it completely in new environments (e.g. in testing)
 - (The exact code to use here will likely change over time with newer versions of Django. You can find the current code by creating a new app temporarily, add the User model to it and then look at the migration file 
./manage.py makemigrationsproduces.) 
Update the Django setting to use your new model
AUTH_USER_MODEL = "shared.User"Create a new migration with
manage.py makemigrations --empty sharedEdit that second migration, called something like
shared/migrations/0002_xx.py, adding a function that will convert the user model entries from the old model to the new one:def change_user_type(apps, schema_editor): ContentType = apps.get_model('contenttypes', 'ContentType') ct = ContentType.objects.filter( app_label='auth', model='user' ).first() if ct: ct.app_label = 'user' ct.save()Now run your second migration with
manage.py migrateAll done! Double check everything, including logging in and the various pages of your app.
Additional issues I encountered #
- Wrong default manager in the new 
Usermodel didn’t allow me to log in, due to previously screwing up with the managers of my proxy model. (default query was returning active, non-staff users only, hence my superuser logins were not working) - Postgres versions in different envs. 
dbbackupneeds a matching Postgres version in order to function correctly. - Previous references to 
ActiveTenantUservia a relationship fromVotingEventneeded to be manually changed. The table column name ofactivetenantuser_idwouldn’t change, so needed to do this manually in postgres in Prod