Unable to create a custom django-admin command

For my project, I really started simple. The only thing that is different is my database which is a postgresql. The project became quite large, so I fear if I change it drastically. Let me give you a quick background before stating the real problem. I created my django app using this command:

django-admin startproject config .
django-admin startapp core

and then, I built my first app named core, included it into my settings.py. Everything else is quite cliche and I think it would be best if I create a user model called Employee defined it as follows and make it handle all the auth functionality of my app:

"""
This is our data models used for our application.
"""

from django.contrib.auth.models import AbstractUser, PermissionsMixin
from django.core.validators import RegexValidator
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

# Introduced in Django 5, this feature allows you
# to use underlaying database functions to generate
# default values. For instance, the following code
# uses the database server’s current date and time
# as the default
# from django.db.models.functions import Now
# django's timezone or as before using
phone_validator = RegexValidator(
    r"^[7-9]{1}[0-9]{9}$", "Exactly 10 digits are required"
)


class Customer(models.Model):
    """
    It would be advisable to have a separate model for our customers,
    hence a separate table in our database
    """

    name = models.CharField(
        max_length=50,
        unique=True,
        null=False,
    )

    email = models.EmailField(
        unique=False,
        blank=True,
        null=True,
    )
    # null=True signifies that the given column is
    # allowed to store a null value, while blank=True
    # means that Django Admin will allow the field to
    # have an empty string as a valid value. In short,
    # null=True is a database constraint, while
    # blank=True is a Django application constraint.
    phonenumber = models.CharField(
        max_length=20,
        unique=False,
        blank=True,
        null=True,
    )
    created_at = models.DateTimeField(auto_now_add=True, null=True)
    # created_at = models.DateTimeField(auto_now_add=True, db_default=Now())
    # created_at = models.DateTimeField(default=timezone.now)
    updated_at = models.DateTimeField(auto_now=True, null=True)

    def __str__(self) -> str:
        """
        We make these models human-readable, so whenever some search
        query matches a User with some conditionals, we would see
        them with their full name
        """
        return str(self.name)

    def __repr__(self) -> str:
        """info"""
        return self.__str__()

    additional_info = models.CharField(max_length=200, null=True)

    class Meta:
        """info"""

        verbose_name = "Our Customer"
        verbose_name_plural = "Our Customers"


class JobType(models.Model):
    """Our app's defined job types"""

    name = models.CharField(max_length=50, null=False)

    def __str__(self):
        return str(self.name)


class Employee(AbstractUser, PermissionsMixin):
    """
    AbstractUser is an abstract built-in class
    implementing a fully featured User model
    with admin-compliant permissions.

    These are the main users of our application

    Email, first_name, last_name, job_type,
    username and password are required. Other
    fields are optional.
    """

    class GenderChoices(models.TextChoices):
        """choices for gender"""

        MALE = "M", "Mr. "
        FEMALE = "F", "Mrs. "
        NONE = "N", ""

    first_name = models.CharField(max_length=50, null=False)
    last_name = models.CharField(max_length=50, null=False)
    email = models.EmailField(
        blank=False,
        unique=True,
        verbose_name=_("Email Address"),
    )
    personal_code = models.CharField(max_length=40, null=True)
    phonenumber = models.CharField(max_length=40, null=True)
    gender = models.CharField(
        max_length=1,
        choices=GenderChoices.choices,
        default=GenderChoices.NONE,
        null=True,
    )
    job_type = models.ForeignKey(
        JobType,
        on_delete=models.PROTECT,
        related_name="core_jobtype",
        null=True,
        blank=True,
    )

    job_experience = models.TextField(null=True, blank=True)

    profile_picture = models.ImageField(
        upload_to="profile_pictures/", null=True, blank=True
    )
    city = models.CharField(max_length=40, null=True)
    bio = models.TextField(blank=True, null=True)

    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_(
            "Designates whether the user can log into this admin site.",
        ),
    )
    is_active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as active. "
            "Unselect this instead of deleting accounts."
        ),
    )
    is_superuser = models.BooleanField(default=False)
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
    updated_at = models.DateTimeField(auto_now=True)
    last_login = models.DateTimeField(null=True)
    username = models.CharField(
        _("username"),
        max_length=150,
        unique=True,
        help_text=_(
            "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."  # noqa: E501
        ),
        # validators=[UnicodeUsernameValidator()],
        error_messages={
            "unique": _("A user with that username already exists."),
        },
        blank=False,
        null=False,
    )
    password = models.CharField(_("password"), max_length=128, null=True)

    USERNAME_FIELD = "username"
    USERNAME_FIELDS = ["email", "username", "phonenumber"]
    REQUIRED_FIELDS = ["first_name", "last_name", "email"]

    # objects = EmployeeManager()

    # A TODO: BUG I COULD NOT RESOLVE
    groups = models.ManyToManyField(
        "auth.Group",
        related_name="employee_set",  # Updated related_name to avoid conflict
        blank=True,
        help_text=_(
            "The groups this user belongs to. A user will get all permissions "
            "granted to each of their groups."
        ),
        verbose_name=_("groups"),
    )
    user_permissions = models.ManyToManyField(
        "auth.Permission",
        related_name="employee_permissions",  # Updated related_name to avoid conflict # noqa: E501
        blank=True,
        help_text="Specific permissions for this user.",
        verbose_name="user permissions",
    )

    def __str__(self) -> str:
        """
        We make these models human-readable, so whenever some query matches
        a User with some conditionals, we would see them with their full name
        """
        # gender_map = {
        #     "M": "Mr. " + self.first_name + " " + self.last_name,
        #     "F": "Mrs. " + self.first_name + " " + self.last_name,
        #     "N": self.first_name + " " + self.last_name,
        #     None: "Mr./Mrs. " + self.first_name + " " + self.last_name,
        # }
        # return gender_map[self.gender]
        return self.first_name + " " + self.last_name

    def save(self, *args, **kwargs):

        # Ensure username is set to email if not provided
        if not self.username:
            self.username = self.email

        # Ensure the username is not set to an empty string when deactivating
        if not self.is_active and self.username == "":
            raise ValueError("Cannot deactivate user with an empty username.")

        if not self.username:
            self.username = self.email

        # if not self.is_active:
        #     self.set_unusable_password()

        super().save(*args, **kwargs)

    def clean(self):
        """info"""
        if self:
            pass
        super().clean()

    class Meta:
        """info"""

        # constraints = [
        #     models.UniqueConstraint(
        #         fields=["username"],
        #         name="unique_username_if_not_null",
        #         condition=Q(username__isnull=False),
        #     )
        # ]

        verbose_name = "Our Staff Member"
        verbose_name_plural = "Our Staff"

And this is the structure of the whole project:

β”œβ”€β”€ Dockerfile
β”œβ”€β”€ Makefile
β”œβ”€β”€ README.md
β”œβ”€β”€ config
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ asgi.py
β”‚   β”œβ”€β”€ constants.py
β”‚   β”œβ”€β”€ settings
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ base.py
β”‚   β”‚   β”œβ”€β”€ development.py
β”‚   β”‚   └── production.py
β”‚   β”œβ”€β”€ tree.txt
β”‚   β”œβ”€β”€ urls.py
β”‚   └── wsgi.py
β”œβ”€β”€ core
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ admin.py
β”‚   β”œβ”€β”€ apps.py
β”‚   β”œβ”€β”€ custom_versions.py
β”‚   β”œβ”€β”€ management
β”‚   β”‚   β”œβ”€β”€ __init__.py
β”‚   β”‚   β”œβ”€β”€ __pycache__
β”‚   β”‚   β”‚   └── __init__.cpython-310.pyc
β”‚   β”‚   └── commands
β”‚   β”‚       β”œβ”€β”€ __init__.py
β”‚   β”‚       └── createsuperuser.py
β”‚   β”œβ”€β”€ migrations
β”‚   β”‚   β”œβ”€β”€ 0001_initial.py
β”‚   β”‚   └── __init__.py
β”‚   β”œβ”€β”€ models.py
β”‚   β”œβ”€β”€ serializers.py
β”‚   β”œβ”€β”€ signals.py
β”‚   β”œβ”€β”€ tests.py
β”‚   β”œβ”€β”€ urls.py
β”‚   └── views.py
β”œβ”€β”€ manage.py
β”œβ”€β”€ requirements
    β”œβ”€β”€ dev.txt
    β”œβ”€β”€ linting.txt
    β”œβ”€β”€ prod.txt
    └── test.txt

Also I added the following to the django settings.py:

AUTH_USER_MODEL = "core.Employee"

If you’re curious about knowing anything else, I would be glad to provide additional info. Now here’s the thing, everything works as intended, I use simple-jwt library as well in order to handle my JWT authentication, when I type this:

django-admin createsuperuser

I get this:

Traceback (most recent call last):
  File "/home/couzhei/code/backend-liara/venv/bin/django-admin", line 8, in <module>
    sys.exit(execute_from_command_line())
  File "/home/couzhei/code/backend-liara/venv/lib/python3.10/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/couzhei/code/backend-liara/venv/lib/python3.10/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/couzhei/code/backend-liara/venv/lib/python3.10/site-packages/django/core/management/__init__.py", line 262, in fetch_command
    settings.INSTALLED_APPS
  File "/home/couzhei/code/backend-liara/venv/lib/python3.10/site-packages/django/conf/__init__.py", line 89, in __getattr__
    self._setup(name)
  File "/home/couzhei/code/backend-liara/venv/lib/python3.10/site-packages/django/conf/__init__.py", line 76, in _setup
    self._wrapped = Settings(settings_module)
  File "/home/couzhei/code/backend-liara/venv/lib/python3.10/site-packages/django/conf/__init__.py", line 190, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
  File "/home/couzhei/.pyenv/versions/3.10.14/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'config'

As you can see I created a custom createsuperuser but it only works with manage.py which shows a successful message regarding the creation of super user, but when I try to login in the admin panel it doesn’t let me in. What am I doing wrong?
Here’s my custom core/management/commands/createsuperuser.py

# from django.contrib.auth.hashers import make_password
from django.core.management.base import BaseCommand, CommandError

from core.models import Employee


class Command(BaseCommand):
    """info"""

    help = "Creates a superuser account"

    def add_arguments(self, parser):
        parser.add_argument("--username", required=True)
        parser.add_argument("--email", required=True)
        parser.add_argument("--password", required=True)

    def handle(self, *args, **options):
        username = options["username"]
        email = options["email"]
        password = options["password"]

        print("args:", *args, "\noptions:", **options)

        if Employee.objects.filter(username=username).exists():
            raise CommandError(
                f'User with username "{username}" already exists.',
            )

        user = Employee.objects.create(username=username, email=email)

        user.set_password(password)  # Use set_password to hash and assign password
        user.is_active = True
        user.is_staff = True
        user.is_superuser = True
        user.save()

        self.stdout.write(
            self.style.SUCCESS(f'Superuser "{user.username}" created successfully.')
        )

If it helps, here’s my core/apps.py:

"""info"""

# pylint: disable=C0415

from django.apps import AppConfig

# from config.constants import get_logger

# logger = get_logger("core.apps")


class CoreConfig(AppConfig):
    """info"""

    default_auto_field = "django.db.models.BigAutoField"
    name = "core"

    def ready(self):
        # logger.info("I have no idea what! 95387")
        import core.management.commands.createsuperuser  # noqa
        import core.signals

Again, I would be happy to provide you with any other information. Thanks in advance.

have you added config to your installed apps? Or Are you importing a package called config?

In the general case, when you want to run a custom admin command that is part of a project, you run it using the manage command, not django-admin. So in this case, the command to use this would be:

python manage.py createsuperuser

(It’s the same way you would use runserver or makemigrations or migrate, etc.)

1 Like

It’s not an app, it’s just (somehow) the name of the django project itself, hence you don’t need to add it to your INSTALLED_APPS.

It magically works, now! So I guess you better not use django-admin any further and limit it to 2 commands only startproject and startapp, since I guess other commands are almost useless.

See the docs at django-admin and manage.py | Django documentation | Django for a better understanding of the relationship between these two commands.

1 Like

Thanks @KenWhitesell for all your efforts. I’ve noticed that you’ve been helping people for many years non-stop, and that’s truly remarkable. IMHO, The willingness to serve others is one of the greatest qualities (if not the greatest) a person can have. I wish you all the best and want to acknowledge your beautiful, helpful nature.

Mark the post that helped you as a solution in order to help others.

It has already been marked - Unable to create a custom django-admin command - #3 by KenWhitesell

1 Like