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 created\n")
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.
CodePudding user response:
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
CodePudding user response:
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.
CodePudding user response:
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.