Django: Create a superuser in a data migration
Question:
Goal: automatically creating a superuser
I’m trying to create a default user, specifically a superuser, in an early data migration, so whenever my Django application is run in my Docker container, it already has a superuser with which I can access the admin site.
I had already tried different options for creating said superuser, and although I have some functioning ones (based on the command
parameter of my docker-compose file), I’ve seen when adding initial data to a Django project, the best practice is to do it through a Data Migration.
My custom user
In my Django project I’ve extended the AbstactBaseUser
so I can change the default username field requirement for the email field. My User
is as such:
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
# Implementation...
def create_superuser(self, email, password, **extra_fields):
# Implementation...
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email
Failed attempts
By following the Django documentation here I tried making my superuser in a data migration with the following code, located in a file called 0002_data_superuser in the migrations folder of my app:
def generate_superuser(apps, schema_editor):
User = apps.get_model("users.User")
User.objects.create_superuser(
email=settings.DJANGO_SUPERUSER_EMAIL,
password=settings.DJANGO_SUPERUSER_PASSWORD,
)
print("nInitial superuser createdn")
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.RunPython(generate_superuser)
]
When running my docker-compose, however, I run into this error:
AttributeError: 'Manager' object has no attribute 'create_superuser'
I’ve tried debugging by printing the Manager and, indeed, it does not have the create_superuser
necessary for this. My next thought was to try to reproduce what create_superuser
does myself, but I find it quite impossible given that a lot of methods for managing passwords, hashing, normalizing emails and stuff are not available to me.
I gather from all of this that the problem is that the Manager, for some reason, is not available during migrations, and I’d like to know if there’s a solution to this.
Answers:
Add use_in_migrations = True
to your custom manager to make it available during migrations
class UserManager(BaseUserManager):
use_in_migrations = True
...
Then you should be able to use User.objects.create_superuser
in your data migration
Be careful though, there are some implications for doing this here in the docs
Reproducing create_user
functionality manually
Checking out a bit more the source code of Django I found at there is a way to reproduce the functionality from create_superuser
. By importing BaseUserManager
and the classmethod make_password
from django.contrib.auth.hashers
, I came up with this:
from django.contrib.auth.models import BaseUserManager
from django.contrib.auth.hashers import make_password
def generate_superuser(apps, schema_editor):
User = apps.get_model("users.User")
email = settings.DJANGO_SUPERUSER_EMAIL
password = settings.DJANGO_SUPERUSER_PASSWORD
user = User()
user.email = BaseUserManager.normalize_email(email)
user.password = make_password(password)
user.is_staff = True
user.is_superuser = True
user.save()
which does the trick.
Still, I don’t like it much as a solution given that it requires reeimplementing an already existing Django method that just is not accessible at this point of the program.
The Django documentation provides clear guide on doing something similar Data Migrations.
First create a empty migration in your user app
python manage.py makemigrations --empty yourappname
This will create a empty migration file, then
import logging
import environ
from django.db import migrations
logger = logging.getLogger(__name__)
def generate_superuser(apps, schema_editor):
from django.contrib.auth import get_user_model
env = environ.Env()
USERNAME = env.str("ADMIN_USERNAME")
PASSWORD = env.str("ADMIN_PASSWORD")
EMAIL = env.str("ADMIN_EMAIL")
user = get_user_model()
if not user.objects.filter(username=USERNAME, email=EMAIL).exists():
logger.info("Creating new superuser")
admin = user.objects.create_superuser(
username=USERNAME, password=PASSWORD, email=EMAIL
)
admin.save()
else:
logger.info("Superuser already created!")
class Migration(migrations.Migration):
dependencies = [("users", "0009_user_full_name")]
operations = [migrations.RunPython(generate_superuser)]
This workflow works well with Docker, once python migrate gets run this will generate a new user.
Goal: automatically creating a superuser
I’m trying to create a default user, specifically a superuser, in an early data migration, so whenever my Django application is run in my Docker container, it already has a superuser with which I can access the admin site.
I had already tried different options for creating said superuser, and although I have some functioning ones (based on the command
parameter of my docker-compose file), I’ve seen when adding initial data to a Django project, the best practice is to do it through a Data Migration.
My custom user
In my Django project I’ve extended the AbstactBaseUser
so I can change the default username field requirement for the email field. My User
is as such:
class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields):
# Implementation...
def create_superuser(self, email, password, **extra_fields):
# Implementation...
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(
verbose_name="email address",
max_length=255,
unique=True,
)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = "email"
REQUIRED_FIELDS = []
def __str__(self):
return self.email
Failed attempts
By following the Django documentation here I tried making my superuser in a data migration with the following code, located in a file called 0002_data_superuser in the migrations folder of my app:
def generate_superuser(apps, schema_editor):
User = apps.get_model("users.User")
User.objects.create_superuser(
email=settings.DJANGO_SUPERUSER_EMAIL,
password=settings.DJANGO_SUPERUSER_PASSWORD,
)
print("nInitial superuser createdn")
class Migration(migrations.Migration):
dependencies = [
('users', '0001_initial'),
]
operations = [
migrations.RunPython(generate_superuser)
]
When running my docker-compose, however, I run into this error:
AttributeError: 'Manager' object has no attribute 'create_superuser'
I’ve tried debugging by printing the Manager and, indeed, it does not have the create_superuser
necessary for this. My next thought was to try to reproduce what create_superuser
does myself, but I find it quite impossible given that a lot of methods for managing passwords, hashing, normalizing emails and stuff are not available to me.
I gather from all of this that the problem is that the Manager, for some reason, is not available during migrations, and I’d like to know if there’s a solution to this.
Add use_in_migrations = True
to your custom manager to make it available during migrations
class UserManager(BaseUserManager):
use_in_migrations = True
...
Then you should be able to use User.objects.create_superuser
in your data migration
Be careful though, there are some implications for doing this here in the docs
Reproducing create_user
functionality manually
Checking out a bit more the source code of Django I found at there is a way to reproduce the functionality from create_superuser
. By importing BaseUserManager
and the classmethod make_password
from django.contrib.auth.hashers
, I came up with this:
from django.contrib.auth.models import BaseUserManager
from django.contrib.auth.hashers import make_password
def generate_superuser(apps, schema_editor):
User = apps.get_model("users.User")
email = settings.DJANGO_SUPERUSER_EMAIL
password = settings.DJANGO_SUPERUSER_PASSWORD
user = User()
user.email = BaseUserManager.normalize_email(email)
user.password = make_password(password)
user.is_staff = True
user.is_superuser = True
user.save()
which does the trick.
Still, I don’t like it much as a solution given that it requires reeimplementing an already existing Django method that just is not accessible at this point of the program.
The Django documentation provides clear guide on doing something similar Data Migrations.
First create a empty migration in your user app
python manage.py makemigrations --empty yourappname
This will create a empty migration file, then
import logging
import environ
from django.db import migrations
logger = logging.getLogger(__name__)
def generate_superuser(apps, schema_editor):
from django.contrib.auth import get_user_model
env = environ.Env()
USERNAME = env.str("ADMIN_USERNAME")
PASSWORD = env.str("ADMIN_PASSWORD")
EMAIL = env.str("ADMIN_EMAIL")
user = get_user_model()
if not user.objects.filter(username=USERNAME, email=EMAIL).exists():
logger.info("Creating new superuser")
admin = user.objects.create_superuser(
username=USERNAME, password=PASSWORD, email=EMAIL
)
admin.save()
else:
logger.info("Superuser already created!")
class Migration(migrations.Migration):
dependencies = [("users", "0009_user_full_name")]
operations = [migrations.RunPython(generate_superuser)]
This workflow works well with Docker, once python migrate gets run this will generate a new user.