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