[英]Django : Case insensitive matching of username from auth user?
Django 默認實現用戶名區分大小寫,現在為了身份驗證,我編寫了自己的Authentication Backend
來處理Authentication Backend
不區分大小寫的用戶名。
如圖: http : //blog.shopfiber.com/?p=220
現在,問題是:
我有各種視圖和 util 方法,可以將username
與一些刺進行比較。
IE
request.user.username == username_from_some_other_system_as_str
現在,如果用戶名是yugal
則:
request.user.username == 'Yugal' # Returns False
現在,它應該返回True
[我想要實現的目標]
為此,我記得在C++
時代, Operator Overloading
。 但我不認為簡單地為 django 的auth user
做這件事是個好主意,因為auth user
與django
緊密綁定。 此外,重載==
將使整個類不區分大小寫,而不僅僅是username
段。
那么,即使在整個過程中進行比較,我應該如何處理這個username
不區分大小寫的問題。
筆記:
創建一個始終返回小寫用戶名的get_username
方法是不可能的,因為它需要重構所有代碼才能使用它。 您可以為您的代碼執行一次,但如果您使用的是 3rd 方 django 應用程序,則不可能。
我知道user.username.lower() = something.lower()
是可能的,但容易出錯,而不是在多開發人員設置中經常使用的東西的編寫解決方案。
我盡可能使用SomeModel.objects.filter(username__iexact=username)
。 但這仍然使系統容易受到任何不知情的開發人員的錯誤影響。
======================================
從概念上找出解決方案,但無法使其工作(幫助):
####### Custom CharField for username case-insensitivity #######
from django.db.models.fields import CharField
class iUnicode:
def __init__(self, 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 __unicode__(self):
return unicode(self.value)
def __str__(self):
return self.__unicode__()
class UsernameCharField(CharField):
def to_python(self, value): # Its not getting called
unicode_val = super(CharField, self).to_python(value)
return iUnicode(unicode_val)
if User._meta.local_fields[1].name == 'username':
User._meta.local_fields[1] = UsernameCharField(max_length=30)
User._meta.local_fields[1].model = User
################################################################
我假設to_python
用於將從數據庫接收到的值轉換為 python 中的unicode
。 但是,我想我的to_python
沒有被調用。
這也將確保 3rd 方應用程序不區分大小寫,並且不需要任何重構。 它將在其核心修補
User
。 我將把它添加到我的第一個INSTALLED_APP
__init__.py
我究竟做錯了什么 ?
我在注冊和登錄過程中修改了幾行似乎對我有用。 使用我的解決方案,用戶名仍然會像用戶在注冊時寫的那樣顯示,但它不會允許其他人使用不同寫法的相同用戶名。 它還允許用戶登錄而無需擔心編寫區分大小寫的用戶名。
我修改了注冊表以搜索不區分大小寫的用戶名。
這是我驗證用戶名的行,它搜索具有此用戶名的用戶。
User._default_manager.get(username__iexact=username)
然后我需要允許用戶使用不區分大小寫的用戶名登錄。
從我的登錄視圖:
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)
終於明白了:
經過如此多的實驗和對User
模型的最小影響,終於實現了。 [感謝@freakish 先生的不同想法]
這里是 :
############ 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)
#####################################################
我測試了它,它按預期工作。 :D
所以,這里是測試用例:
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()))
數據庫的隱式不區分大小寫查詢
###################### 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 #############################################################
這將允許User.objects.get(username='yugal')
& User.objects.get(username='YUGAl')
產生相同的用戶。
使用不區分大小寫的用戶名的最簡單方法是從默認ModelBackend
繼承並覆蓋authenticate
方法。
請注意,在except
塊中,我們正在執行UserModel().set_password(password)
,通過這樣做我們減少了哈希器的工作時間。 修復了錯誤報告
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
並將此后端添加到 settings.py 中的AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = (
'sdcpy.backends.CaseInsensitiveModelBackend', # inherits from 'django.contrib.auth.backends.ModelBackend'
)
這種猴子補丁看起來是個壞主意。 將來你肯定會遇到一些問題(Django 在幕后做了很多事情)。 我強烈建議重新設計您的應用程序。
但是,您可以嘗試以下方法(使用您的iUnicode
類):
def new_getattribute( self, name ):
val = object.__getattribute__( self, name )
if name == "username":
val = iUnicode( val )
return val
User.__getattribute__ = new_getattr
現在,我不是 100% 認為這會起作用,而且它有點 hacky,因此請謹慎使用。 :)
有一種相對干凈的方法可以做到這一點:
# 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)
然后設置
AUTHENTICATION_BACKENDS = (
'obviously.backends.EmailOrUsernameModelBackend',
'django.contrib.auth.backends.ModelBackend',
)
您還必須在注冊工作流程中強制用戶名小寫
這一切正常,但不保留用戶給出的大小寫,在數據庫中查找也效率不高。 默認 django 行為是設計使然,請參閱https://code.djangoproject.com/ticket/2273
使用UserManager
是實現不區分大小寫的用戶名而不與其他事情混淆的最簡單方法之一。
示例代碼(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()
你很高興去! 來源
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.