Django Multi-tenancy with custom authentication

Hello, before telling the problem let me explain my project architecture a little bit. I try to build an app that supports multitenancy in django. The app uses postgresql database. I do not want to use different schema for each tenant. So I will store all tenants data into same schema and same table but with different tenant.id . Well, I designed the all database architecture of project and created all models. Now lets talk about my project’s accounts model. In that model I’ve defined all attributes of user table. Of course I added tenant.id attribute as well.
And I’ve build a custom authentication model to allow users login with email. When I migrate the changes it’s okay. But once I try to createsuperuser in the terminal it gives an error such as
value too long for type character varying(50) . I’ve researched and tried some answers but did not solve the problem.
And another thing that I want to understand is if I try to create a superuser does django want me a tenant id too just because of my multitenant user model ? Thanks in advance

My first question would be, how have you altered the standard User model?

The built-in createsuperuser command is fairly rigid with what it expects in terms of the user model, and if you’ve made significant changes to it, it’s no longer going to work. (As you’ve seen…)

Thanks for your care, in my accounts app’s model.py file I’ve created 2 class. First one is Account class that is inherited from AbstractBaseUser and I’ve defined all the attributes with tenant.id attribute inside this class. And the second class is MyAccountManager and this is inherited from BaseUserManager. I’ve used second class to set creating user options.
in the accounts admin.py file I created a class called AccountAdmin and this class was inherited from UserAdmin which is from django.contrib.auth.admin
Finally I created a file called backends.py into accounts app. There is a class called MyModelBackend that has been inherited from ModelBackend class.
I’ve done this before in a project but without multitenancy support. It worked like a charm
But now in this multitenant supported project there is a weird reason that I could not find yet.
As for your question I’ve changed the standart user model and created a custom one. And I’ve changed the authentication options too to make my custom user model work. But as I said creating superuser gives error.
Btw I do not use any 3rd part library such as django-multitenant etc.

That’s the key part.

The createsuperuser command is tied to the standard user object. You can examine the createsuperuser.py file to see how changes you have made may affect it.

I got it but when I type :
python manage.py createsuperuser it shows me email and password fill to sign up. It doesn’t want username field anymore. Doesn’t that mean my custom model work as its purpose.
I really appreciate for your help

At this point, I think I’d need to see your custom user model and the complete traceback from the error message you’re receiving.

Well there it is. Model.py file

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager

# Create your models here.
def validateEmail(e_mail):
    from django.core.validators import validate_email
    from django.core.exceptions import ValidationError
    try:
        validate_email(e_mail)
        return True
    except ValidationError:
        return False

class MyAccountManager(BaseUserManager):  # These class will be use for creating user options
    def create_user(self, e_mail, username, password=None):
        if (not e_mail) or (not validateEmail(e_mail)):
            raise ValueError("Kullanicinin bir email adresi olmali")
        if not username:
            raise ValueError("Kullanicinin bir kullanici adi olmali")
        user = self.model(
            e_mail=self.normalize_email(e_mail),
            username=username,)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, e_mail, username, password):
        user = self.create_user(e_mail=self.normalize_email(e_mail),
                                username=username,
                                password=password, )
        user.is_admin = True
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user

def get_profile_image_filepath(self):
    return f'profile_images/{self.pk}/{"profile_img.png"}'
def get_default_profile_image():
    return "images/default_profile_image.png"

class Account(AbstractBaseUser):
    customer = models.ForeignKey('customers.Customer', null=False, blank=False, verbose_name='Customer',
                                 on_delete=models.CASCADE, related_name='customeraccount')
    name = models.CharField(max_length=50, verbose_name='Name')
    surname = models.CharField(max_length=50, verbose_name='Surname')
    address = models.CharField(max_length=300,blank=True, verbose_name='Address')
    birth_date = models.DateTimeField(verbose_name='UserBirthDate', auto_now_add=False)
    diseases = models.CharField(max_length=500,blank=True, verbose_name='Diseases')
    duty = models.CharField(max_length=300,blank=True,default='yok', verbose_name='Duty')

    username = models.CharField(max_length=50, verbose_name='Username',unique=True, null=False, blank=False)
    password = models.CharField(max_length=50, verbose_name='Password', null=False, blank=False)
    e_mail = models.CharField(max_length=60,blank=True,unique=True, verbose_name='Email')
    registered_date = models.DateTimeField(verbose_name='Registered Date', auto_now_add=True)
    profile_image = models.ImageField(max_length=255,upload_to=get_profile_image_filepath, null=True, blank=True, default=get_default_profile_image)

    is_admin = models.BooleanField(default=False) #These four properties are require because we use abstractbaseuser we must overwrite these properties
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    hide_email = models.BooleanField(default=True)
    is_manager = models.BooleanField(default=False)
    is_assistant = models.BooleanField(default=False)
    is_deleted = models.BooleanField(default=False)

    objects = MyAccountManager()  # user registiration options defining here
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['e_mail']

    def __str__(self):
        return self.name + ' ' + self.surname
    ################################## the below two functions must be overwritten to be able to use custom user login
    def has_perm(self,perm,obj=None):
        return self.is_admin
    def has_module_perms(self, app_label):
        return True

admin.py file :

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from accounts.models import Account
# Register your models here.

class AccountAdmin(UserAdmin):
    list_display=('e_mail','username','name','surname','registered_date','is_admin')
    search_field=('e_mail','username')
    readonly_fields = ('id','registered_date')
    filter_horizontal = ()
    list_filter = ()
    fieldsets = ()
    add_fieldsets=(
        (None, {
            'classes':('wide'),
            'fields':('e_mail','username','password1','password2'),
        }),  
    )
admin.site.register(Account,AccountAdmin)

backends.py file:

from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend

# this class has been created for ignoring the case sensitivity on email during login
class CaseInsensitiveModelBackend(ModelBackend):
    def authenticate(self,request,username=None,password=None,**kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
            try:
                case_insensitive_username_field = '{}__iexact'.format(UserModel.USERNAME_FIELD)
                user = UserModel._default_manager.get(**{case_insensitive_username_field:username})
            except UserModel.DoesNotExist:
                UserModel().set_password(password)
            else:
                if (user.check_password(password) and self.user_can_authenticate(user)):
                    return user

in the settings.py file I also added :

AUTH_USER_MODEL = "accounts.Account"
AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.AllowAllUsersModelBackend',
    'accounts.backends.CaseInsensitiveModelBackend'
)

Additionally this is customer model.py :
from django.db import models


# Create your models here.
class Customer(models.Model):
    name = models.CharField(max_length=60, verbose_name='Name', unique=True)
    registration_date = models.DateTimeField(verbose_name='Registration Date', auto_now_add=True)
    city = models.ForeignKey('cities.Cities', null=False,blank=False,default=1, verbose_name='City', on_delete=models.CASCADE,related_name='customercity')
    def __str__(self):
        return self.name

And this is the full traceback :

Traceback (most recent call last):
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
psycopg2.errors.StringDataRightTruncation: value too long for type character varying(50)


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    main()
  File "manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 419, in execute_from_command_line
    utility.execute()
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/core/management/__init__.py", line 413, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/core/management/base.py", line 354, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/contrib/auth/management/commands/createsuperuser.py", line 79, in execute
    return super().execute(*args, **options)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/core/management/base.py", line 398, in execute
    output = self.handle(*args, **options)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/contrib/auth/management/commands/createsuperuser.py", line 189, in handle
    self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
  File "/home/vedat/Desktop/CRECHEPROJECT/accounts/models.py", line 28, in create_superuser
    user = self.create_user(e_mail=self.normalize_email(e_mail),
  File "/home/vedat/Desktop/CRECHEPROJECT/accounts/models.py", line 24, in create_user
    user.save(using=self._db)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/contrib/auth/base_user.py", line 67, in save
    super().save(*args, **kwargs)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/models/base.py", line 726, in save
    self.save_base(using=using, force_insert=force_insert,
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/models/base.py", line 763, in save_base
    updated = self._save_table(
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/models/base.py", line 868, in _save_table
    results = self._do_insert(cls._base_manager, using, fields, returning_fields, raw)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/models/base.py", line 906, in _do_insert
    return manager._insert(
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/models/query.py", line 1270, in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1416, in execute_sql
    cursor.execute(sql, params)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/backends/utils.py", line 98, in execute
    return super().execute(sql, params)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/backends/utils.py", line 66, in execute
    return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/backends/utils.py", line 75, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/utils.py", line 90, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "/home/vedat/Desktop/CRECHEPROJECT/venv/lib/python3.8/site-packages/django/db/backends/utils.py", line 84, in _execute
    return self.cursor.execute(sql, params)
django.db.utils.DataError: value too long for type character varying(50)

I have no idea whether or not this is a “problem”, but the calling sequence for create_superuser identifies the first positional parameter as being the username - which you’ve defined as username. But your implementation of create_superuser has e_mail first and username second.

If I were trying to diagnose this, I’d be looking at what parameters are being passed around at different points to verify what values are being supplied. (Either using the debugger or by adding print calls at key locations.)

Got it sir. I’ve a question that I really want to know.
When I type python manage.py createsuperuser it shows me username email password1 and password2 fields. if I fill this fields will django save these fields into my Account table that I have just created or into another table that specific for admin accounts that django uses. I’m asking this because in the Account table there is customer field needed to be fill as well. When I create superuser there will be no value to fill customer field right ? it cause an logical error.

It saves username, email, and the hashed password into your Account table.

Correct. Unless customer is listed in REQUIRED_FIELDS, you won’t be prompted for it - which would throw an error unless your save method created an instance of Customer and assigned that pk to the customer field. (However, I don’t believe listing customer in REQUIRED_FIELDS is much of an improvement, since my guess would be that the Customer instance would already need to exist and you’d need to know its pk for assignment.)

Side note - I generally recommend that no foreign table relationships ever be placed in the User model. Our principle here is that a User is a fundamental entity, and any other tables needing to be related to a User define the relationship. We do have exceptions to that, but they are very well-defined. The idea is that you generally need to have one or more users who are “outside” the normal user structure and should not need to have “fake relationships” created just to satisfy a database constraint. In this case, you may have one or more superusers who really aren’t customers and shouldn’t have Customer data created for them. You prevent needing to do this by defining the OneToOne relationship in Customer rather than in Account.

I think I spotted it - the create_superuser is going to pass the password as the second parameter, which you have assigned to username. So it’s trying to assign the hashed password (longer than 50 characters) to a 50 character field. That is what is throwing the actual error.

(And that’s another issue as well - you have password assigned as a 50 character field, which isn’t sufficient. If you look at AbstractBaseUser, it defines password as a 128-character field. Since you’re inheriting from AbstractBaseUser, you don’t need to define a password field at all.)

I understood very well. I will keep your advice in mind so thank you so much