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.

Asked By: JanaL

||

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

Answered By: Iain Shelvington

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.

Answered By: JanaL

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.

Answered By: Jermaine