4๐
Finally got it :
With so much experimenting and minimum effect on User
model, finally achieved it.
[ Thanks to Mr. @freakish for a different thought ]
Here it is :
############ username case-insensitivity ############
class iunicode(unicode):
def __init__(self, value):
super(iunicode, self).__init__(value)
self.value = value
def __eq__(self, other):
if isinstance(other, str) or isinstance(other, unicode):
return self.value.lower() == other.lower()
if isinstance(other, self.__class__):
return other == self.value
def custom_getattribute(self, name):
val = object.__getattribute__(self, name)
if name == "username":
val = iunicode(val)
return val
def auth_user_save(self, *args, **kwargs): # Ensures lowercase usernames
username = self.username
if username and type(username) in [unicode, str, iunicode]:
self.username = username.lower() # Only lower case allowed
super(User, self).save(*args, **kwargs)
User.__getattribute__ = custom_getattribute
User.save = MethodType(auth_user_save, None, User)
#####################################################
I tested it and it worked as expected. ๐
So, here are the testcases :
from django.test.testcases import TestCase
def create_user(data='testuser'):
email = '%s@%s.com' % (data, data)
user = G(User, username=data, email=email, is_active=True)
user.set_password(data)
user.save()
return user
class UsernameCaseInsensitiveTests(TestCase):
def test_user_create(self):
testuser = 'testuser'
user = create_user(testuser)
# Lowercase
self.assertEqual(testuser, user.username)
# Uppercase
user.username = testuser.upper()
user.save()
self.assertEqual(testuser, user.username)
def test_username_eq(self):
testuser = 'testuser'
user = create_user(testuser)
self.assertTrue(isinstance(user.username, iunicode))
self.assertEqual(user.username, testuser)
self.assertEqual(user.username, testuser.upper())
self.assertTrue(user.username == testuser.upper())
self.assertTrue(testuser.upper() == user.username)
self.assertTrue(user.username == iunicode(testuser.upper()))
Implicit Case-insensitive queries for database
###################### QuerySet #############################
def _filter_or_exclude(self, negate, *args, **kwargs):
if 'username' in kwargs:
kwargs['username__iexact'] = kwargs['username']
del kwargs['username']
if args or kwargs:
assert self.query.can_filter(),\
"Cannot filter a query once a slice has been taken."
from django.db.models import Q
clone = self._clone()
if negate:
clone.query.add_q(~Q(*args, **kwargs))
else:
clone.query.add_q(Q(*args, **kwargs))
return clone
from django.db.models.query import QuerySet
QuerySet._filter_or_exclude = _filter_or_exclude
#############################################################
This will allow, User.objects.get(username='yugal')
& User.objects.get(username='YUGAl')
yield the same user.
43๐
As of Django 1.5, making usernames case insensitive is straightforward:
class MyUserManager(BaseUserManager):
def get_by_natural_key(self, username):
return self.get(username__iexact=username)
- [Django]-How to fix " AttributeError at /api/doc 'AutoSchema' object has no attribute 'get_link' " error in Django
- [Django]-Page not found 404 on Django site?
- [Django]-How do I use a dictionary to update fields in Django models?
7๐
I modified few lines in my registration and login process that seems to work for me. With my solution usernames will still be displayed like the user wrote them when registering, but it will not allow others to use the same username written differently. It also allows users to login without worrying about writing the case sensitive username.
I modified the registration form to search for case insensitive usernames.
This is line from my validation of username, it searches for user with this username.
User._default_manager.get(username__iexact=username)
Then I needed to allow users to login with case insensitive usernames.
From my login view:
username = request.POST['username']
password = request.POST['password']
caseSensitiveUsername = username
try:
findUser = User._default_manager.get(username__iexact=username)
except User.DoesNotExist:
findUser = None
if findUser is not None:
caseSensitiveUsername = findUser.get_username
user = auth.authenticate(username=caseSensitiveUsername, password=password)
- [Django]-Django: Catching Integrity Error and showing a customized message using template
- [Django]-Django: How to check if the user left all fields blank (or to initial values)?
- [Django]-Mocking default=timezone.now for unit tests
2๐
The simplest way to use case insensitive username is to inherit from default ModelBackend
and override authenticate
method.
Please note that inside the except
block we are executing UserModel().set_password(password)
, by doing this we decreasing hasher work time. Fixed bug report
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from users.models import User
class CaseInsensitiveModelBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
d = {'%s__iexact'%UserModel.USERNAME_FIELD: username}
user = UserModel.objects.get(**d)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
return None
And add this backend to AUTHENTICATION_BACKENDS
in settings.py
AUTHENTICATION_BACKENDS = (
'sdcpy.backends.CaseInsensitiveModelBackend', # inherits from 'django.contrib.auth.backends.ModelBackend'
)
- [Django]-Django debug display all variables of a page
- [Django]-Django's forms.Form vs forms.ModelForm
- [Django]-How to access my 127.0.0.1:8000 from Android tablet
0๐
This monkey patching looks like a bad idea. You will definetly hit some problems in future ( Django does a lot of stuff behind the scene ). I highly recommend redesigning your app.
However hereโs what you can try ( using your iUnicode
class ):
def new_getattribute( self, name ):
val = object.__getattribute__( self, name )
if name == "username":
val = iUnicode( val )
return val
User.__getattribute__ = new_getattr
Now, Iโm not 100% that this will work, and it is a bit hacky, so use it with caution. ๐
- [Django]-Django : Can't import 'module'. Check that module AppConfig.name is correct
- [Django]-Uploading large files with Python/Django
- [Django]-Get SQL query count during a Django shell session
0๐
There is a relatively clean way to do this:
# Case-insensitive django authentication, modified from
# http://justcramer.com/2008/08/23/logging-in-with-email-addresses-in-django/
# See also https://github.com/dabapps/django-email-as-username
# And https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user
from django.contrib.auth.models import User
class EmailOrUsernameModelBackend(object):
def authenticate(self, username=None, password=None):
username = username.lower() # Force all usernames & email to all lower case
if '@' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
user = User.objects.get(**kwargs)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
def my_password_reset(request, **kwargs):
# Override django.contrib.auth.views.password_reset not needed because django does
# SELECT FROM "auth_user" WHERE UPPER("auth_user"."email"::text) = UPPER(E'xxx@emaple.com')
# But note you may want to manually create an UPPER index in the database for speed.
return password_reset(request, **kwargs)
Then set
AUTHENTICATION_BACKENDS = (
'obviously.backends.EmailOrUsernameModelBackend',
'django.contrib.auth.backends.ModelBackend',
)
Youโll also have to force usernames to lower case in your registration workflow
This all works OK, but does not preserve the case given by the user, nor is it efficient in looking up in the database. The default django behavior is by design, see https://code.djangoproject.com/ticket/2273
- [Django]-Django-rest-framework limit the allowed_methods to GET
- [Django]-Failing to install psycopg2-binary on new docker container
- [Django]-Django limit_choices_to for multiple fields with "or" condition
0๐
Using UserManager
is one of the easiest way of achieving case-insensitive usernames without messing with other things.
Sample Code (Models.py):
from django.contrib.auth.models import AbstractUser, UserManager
class CustomUserManager(UserManager):
def get_by_natural_key(self, username):
case_insensitive_username_field = '{}__iexact'.format(self.model.USERNAME_FIELD)
return self.get(**{case_insensitive_username_field: username})
class CustomUser(AbstractUser):
objects = CustomUserManager()
And you are good to go! Source
- [Django]-How to allow users to change their own passwords in Django?
- [Django]-Django equivalent of SQL not in
- [Django]-Reducing Django Memory Usage. Low hanging fruit?