#25313: Document how to migrate from a built-in User model to a custom User
model
-------------------------------+------------------------------------
Reporter: Carl Meyer | Owner: nobody
Type: New feature | Status: new
Component: Documentation | Version: 1.8
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------+------------------------------------
Comment (by johanneswilm):
The following approach has not been tested with all database backends
(only sqlite), but it seems to work by only using migrations, no raw SQL
and not destroying any existing migrations. This is based of Tobias
McNulty's walk-through [1], with some modifications to avoid doing
anything "nasty".
----
0. Assumptions:
* You have an existing project without a custom user model.
* You're using Django's migrations, and all migrations are up-to-date (and
have been applied to the production database).
* You have an existing set of users that you need to keep, and any number
of models that point to Django's built-in User model.
* **You have an existing django app with existing migrations to which you
want to add the custom user model.**
* **You need the same code to run both for new installations of your code
base and for existing ones.
1. First, assess any third party apps to make sure they either don't have
any references to the Django's User model, or if they do, that they use
Django's generic methods for referencing the user model.
2. Next, do the same thing for your own project. Go through the code
looking for any references you might have to the User model, and replace
them with the same generic references. In short, you can use the
get_user_model() method to get the model directly, or if you need to
create a ForeignKey or other database relationship to the user model, use
settings.AUTH_USER_MODEL (which is simply a string corresponding to the
appname.ModelName path to the user model).
Note that get_user_model() cannot be called at the module level in any
models.py file (and by extension any file that a models.py imports), since
you'll end up with a circular import. Generally, it's easier to keep calls
to get_user_model() inside a method whenever possible (so it's called at
run time rather than load time), and use settings.AUTH_USER_MODEL in all
other cases. This isn't always possible (e.g., when creating a ModelForm),
but the less you use it at the module level, the fewer circular imports
you'll have to stumble your way through.
3. In the existing app in which you want the custom User model to live (we
will call it "user" in this example), add the following to the models.py
file:
{{{
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
class Meta:
db_table = 'auth_user'
}}}
4. Edit the migration with a name starting with "0001" of this app.
Under "operations" paste the following as the first item:
{{{
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True,
primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128,
verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True,
verbose_name='last login')),
('is_superuser', models.BooleanField(default=False,
help_text='Designates that this user has all permissions without
explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A
user with that username already exists.'}, help_text='Required. 150
characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name='username')),
('first_name', models.CharField(blank=True,
max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150,
verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254,
verbose_name='email address')),
('is_staff', models.BooleanField(default=False,
help_text='Designates whether the user can log into this admin site.',
verbose_name='staff status')),
('is_active', models.BooleanField(default=True,
help_text='Designates whether this user should be treated as active.
Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined',
models.DateTimeField(default=django.utils.timezone.now, verbose_name='date
joined')),
('groups', models.ManyToManyField(blank=True,
help_text='The groups this user belongs to. A user will get all
permissions granted to each of their groups.', related_name='user_set',
related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True,
help_text='Specific permissions for this user.', related_name='user_set',
related_query_name='user', to='auth.Permission', verbose_name='user
permissions')),
],
options={
'db_table': 'auth_user',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
}}}
Under "dependencies" add:
{{{
('auth', '0012_alter_user_first_name_max_length'),
}}}
and, if present, remove
{{{
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
}}}
(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 makemigrations` produces.)
5. Create a new data migration to the user app by typing: `./manage.py
makemigrations --empty user`
6. Edit the newly created migration:
Under "operations" add `migrations.RunPython(change_user_type),`
Add this function to the top of the file:
{{{
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()
}}}
7. You can now migrate to the custom user model by running `./manage.py
migrate`. This should always work, however, it will do so in two different
ways depending on whether it is run on an existing or a new instance:
* On a new instance, the new user model will be created in migration 0001.
The last migration will have no effect.
* On an existing instance, the migration 0001 will be ignored as the
system has already previously applied migration 0001 and will therefore
now ignore it. Instead the last migration will have the effect of change
the app of the user model.
8. Now, you should be able to make changes to your users.User model and
run makemigrations / migrate as needed. For example, as a first step, you
may wish to rename the auth_user table to something in your users app's
namespace. You can do so by removing db_table from your User model, so it
looks like this:
{{{
class User(AbstractUser):
pass
}}}
You'll also need to create and run a new migration to make this change in
the database:
`./manage.py makemigrations --name rename_user_table`
`./manage.py migrate`
----
Please let me know if this works for you - especially with other
databases.
[1] https://www.caktusgroup.com/blog/2019/04/26/how-switch-custom-django-
user-model-mid-project/ with mo
--
Ticket URL: <https://code.djangoproject.com/ticket/25313#comment:24>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/064.826ea1eb9bf0c94108eecc66cdfa6193%40djangoproject.com.