0

I have used django user model and extended it to add a phone field. I was able to use either username or email to login but cannot access phone number field from extended user model.

models.py

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    phonenumber = models.CharField(max_length=10)

backends.py

class AuthenticationBackend(backends.ModelBackend):
    def authenticate(self, request,  username=None, password=None, **kwargs):
        usermodel = get_user_model()
        print(usermodel)

        try:
            user = usermodel.objects.get(Q(username__iexact=username) | Q(
                email__iexact=username))
            if user.check_password(password):
                return user
        except user.DoesNotExist:
            pass

backend.py is what I used to implement login via username or email but I couldn't do it for phonenumber in extended user model.

Kshitish
  • 60
  • 10
  • Can you show the code for when you tried to get the user by phone_number? – Nico Griffioen Sep 05 '19 at 09:00
  • I haven't tried to get the user by phone number. I am stuck here. I don't know how to access phone number in extended user model – Kshitish Sep 05 '19 at 09:11
  • 1
    You can try `usermodel.objects.get(Q(profile__phonenumber=some_phone_number))`. The relationship from `User` to `Profile` is just `profile`. But you should make `phonenumber` unique on your model, otherwise there's the risk that you get more than one user with the same phone number and the exception won't be `usermodel.DoesNotExist` but `usermodel.MultipleObjectsReturned`. – dirkgroten Sep 05 '19 at 09:16
  • 1
    I think if you're wanting to use phone number as the authentication field, then you ideally want to extend the AbstractBaseUser class rather than mapping your profile to User like you have. See here for more info: https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html#abstractbaseuser – michjnich Sep 05 '19 at 09:19
  • But as @urbanespaceman mentions, in this case it's better to create your custom `User` model, as explained [here](https://docs.djangoproject.com/en/stable/topics/auth/customizing/#substituting-a-custom-user-model). – dirkgroten Sep 05 '19 at 09:20

2 Answers2

1

There are two ways to solve your problem.

  1. Extend the User model by subclassing it, and changing the default User model. I would recommend you go this route, since the Phone number is used for authentication.
  2. Set related_name on Profile, and query the User model from that name.

For the first one, I recommend you check out the Django documentation on creating custom users. There is also a pretty extensive tutorial on how to do this here.

If you want to go the easy route, you just need to set a reverse accessor on the Profile's phonenumber field.

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    phonenumber = models.CharField(max_length=10, related_name='profile')

You can then search users by their profile's phonenumbers:

class AuthenticationBackend(backends.ModelBackend):
    def authenticate(self, request,  username=None, password=None, **kwargs):
        usermodel = get_user_model()
        print(usermodel)

        try:
            user = usermodel.objects.get(Q(username__iexact=username) | Q(
                email__iexact=username) | Q(profile__phonenumber__iexact=username)
            if user.check_password(password):
                return user
        except user.DoesNotExist:
            pass

Although this is not within the scope of your question, I think you should be aware of the following issues with your code:

  • Phone numbers are often more complex than a 10 character string. +31 6 12345678, is a valid phone number, but it's more than 10 characters. Check out this question for pointers on how to store and validate phone numbers.
  • If you use a field for authentication, make sure it's unique. The phonenumber field should actually be phonenumber = models.CharField(max_length=10, related_name='profile', unique=True)
  • Your usermodel.objects.get(...) query will return more than one user if there is a user with phone number '0123456789' and a different user with username '0123456789'. You should constrain usernames to not contain phone numbers, which is only possible by extending the default User class.
Nico Griffioen
  • 5,143
  • 2
  • 27
  • 36
0

You can extend the AbstractUser and add the phonenumber on it.

class User(AbstractUser):
    phonenumber = models.CharField(max_length=10)

    USERNAME_FIELD = 'phonenumber'

Then you should specify the custom model as the default user model in settings.py AUTH_USER_MODEL = 'app_name.User'

Customizing authentication in Django