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.