How To: Use Improved User in Data Migrations
Creating users in data migrations is discouraged as doing so represents a potential security risk, as passwords are stored in plaintext in the migration. However, doing so in proof-of-concepts or in special cases may be necessary, and the steps below will demonstrate how to create and remove new users in a Django data migration.
The django-improved-user
package intentionally disallows use of
UserManager
in data migrations (we
forgo the use of model managers in migrations). The
create_user()
and
create_superuser()
methods
are thus both unavailable when using data migrations. Both of these
methods rely on User
model methods
which are unavailable in Historical models, so we could
not use them even if we wanted to (short of refactoring large parts of
code currently inherited by Django).
We therefore rely on the standard
Manager
, and supplement the
password creation behavior.
In an existing Django project, you will start by creating a new and
empty migration file. Replace APP_NAME
in the command below with the
name of the app for which you wish to create a migration.
$ python manage.py makemigrations --empty --name=add_user APP_NAME
We start by importing the necessary tools
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.db import migrations
We will use RunPython
to
run our code. RunPython
expects two functions with specific parameters. Our first function
creates a new user.
def add_user(apps, schema_editor):
User = apps.get_model(*settings.AUTH_USER_MODEL.split("."))
User.objects.create(
email="migrated@jambonsw.com",
password=make_password("s3cr3tp4ssw0rd!"),
short_name="Migrated",
full_name="Migrated Improved User",
)
NB: Due to the lack of
UserManager
or
User
methods, the email
field
is not validated or normalized. What’s more, the password
field
is not validated against the project’s password validators. It is up
to the developer coding the migration file to provide proper values.
The second function is technically optional, but providing one makes our lives easier and is considered best-practice. This function undoes the first, and deletes the user we created.
def remove_user(apps, schema_editor):
User = apps.get_model(*settings.AUTH_USER_MODEL.split("."))
User.objects.get(email="migrated@jambonsw.com").delete()
Finally, we use our migration functions via
RunPython
in a
django.db.migrations.Migration
subclass. Please note the addition
of the dependency below. If your file already had a dependency, please
add the tuple below, but do not remove the existing tuple(s).
class Migration(migrations.Migration):
dependencies = [
("improved_user", "0001_initial"),
]
operations = [
migrations.RunPython(add_user, remove_user),
]
The final migration file is printed in totality below.
1from django.conf import settings
2from django.contrib.auth.hashers import make_password
3from django.db import migrations
4
5
6def add_user(apps, schema_editor):
7 User = apps.get_model(*settings.AUTH_USER_MODEL.split("."))
8 User.objects.create(
9 email="migrated@jambonsw.com",
10 password=make_password("s3cr3tp4ssw0rd!"),
11 short_name="Migrated",
12 full_name="Migrated Improved User",
13 )
14
15
16def remove_user(apps, schema_editor):
17 User = apps.get_model(*settings.AUTH_USER_MODEL.split("."))
18 User.objects.get(email="migrated@jambonsw.com").delete()
19
20
21class Migration(migrations.Migration):
22
23 dependencies = [
24 ("improved_user", "0001_initial"),
25 ]
26
27 operations = [
28 migrations.RunPython(add_user, remove_user),
29 ]
You may wish to read more about Django Data Migrations and
RunPython
.