Django - Changing Auth username to Autofield breaks the code
Asked Answered
M

1

0

I've an application where I need to change username field to Autofield.

Use-case - I'am building an application where when the users will be created, usernames should be like CustomerID(integers) in banking applications.

There are 2 ways of doing it:-

1) Have an id (PK), the default implementation of it. Copy the id, of the object and assign it to the username column, to allow users to sign-in via the id. Trade-offs

  • Two duplicate columns.
  • How would I call create_user function as it requires username.

2) Allow login via the IDs, update AuthBackend and allow login via the IDs too. TradeOff

  • What will be the value of the username. Shall I keep it as email?
  • Not sure, whether allowing login via the IDs is a good idea.

Pheww, enough of discussion. Now comes the implementation(problematic) part.:-

Here's my model.

 class User(AbstractUser):
    username = models.BigAutoField(primary_key=True)
    user_id = models.UUIDField(default=uuid.uuid4, unique=True,
                               editable=False)

A model can't have more than one AutoField, as a result, username is PK.

I migrations worked perfectly well. And the table was created.

However, when I create a user using

User.objects.create_user(email=email, password=password)  #expecting the username to be auto-generated.

It says username is mandatory.

I checked Django code UserManager function create_user, and updated argument username=None

class CustomUserManager(UserManager):
    use_in_migrations = True

    def create_user(self, username=None, email=None, password=None,
                    **extra_fields):
        return super(CustomUserManager, self).create_user(username=username,
                                                          email=email,
                                                          password=password,
                                                          extra_fields=extra_fields)

It still throws an exception:-

In [4]: User.objects.create_user(email='[email protected]', password='abcd1234@')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-de9c49dd635d> in <module>()
----> 1 User.objects.create_user(email='[email protected]', password='abcd1234@')

/Users/praful/Desktop/code/seynse/authentication/app/modules/users/models.py in create_user(self, username, email, password, **extra_fields)
     17                                                           email=email,
     18                                                           password=password,
---> 19                                                           extra_fields=extra_fields)
     20
     21

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/django/contrib/auth/models.py in create_user(self, username, email, password, **extra_fields)
    157         extra_fields.setdefault('is_staff', False)
    158         extra_fields.setdefault('is_superuser', False)
--> 159         return self._create_user(username, email, password, **extra_fields)
    160
    161     def create_superuser(self, username, email, password, **extra_fields):

/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/django/contrib/auth/models.py in _create_user(self, username, email, password, **extra_fields)
    146         """
    147         if not username:
--> 148             raise ValueError('The given username must be set')
    149         email = self.normalize_email(email)
    150         username = self.model.normalize_username(username)

ValueError: The given username must be set

What can be done in this case? How can I change username to AutoField.

Manis answered 26/6, 2018 at 11:51 Comment(5)
you still call super method with username=NoneAquiver
but then how would username will be auto-generated?Manis
Also, Over-riding doesn't make any difference. But the main question is how would username will be auto-generated?Manis
i think you need override more than only call create_user, you need to override all manager github.com/django/django/blob/master/django/contrib/auth/…Aquiver
I did. I over-ride _create_user too. But the question is how do I allot auto-incremented value to the user.?Manis
N
0

You must create a custom manager for your user model. It can inherit from BaseManager, but not from the default UserManager:

class CustomUserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """
        Create and save a user with the given email, and password.
        """
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email=None, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractUser):
    username = models.BigAutoField(primary_key=True)
    user_id = models.UUIDField(default=uuid.uuid4, unique=True,
                               editable=False)

    objects = CustomUserManager()

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

Note that I've completely removed username, as it doesn't make sense to pass an explicit value for an AutoField.

Neff answered 26/6, 2018 at 14:50 Comment(2)
It almost works. The object is getting created, but it breaks up while returning the object. Throws an error. TypeError: __str__ returned non-string (type int)Manis
That's because the AbstractUser.__str__ directly returns the username without casting it to a string. I'll edit my answerNeff

© 2022 - 2024 — McMap. All rights reserved.