Войдите в систему, используя адрес электронной почты или имя пользователя в Django

Я пытаюсь создать бэкэнд auth, чтобы мои пользователи могли войти в систему, используя свой адрес электронной почты или свое имя пользователя в Django 1.6 с пользовательской моделью пользователя. Бэкэнд работает, когда я вхожу в систему с именем пользователя, но по какой-то причине не с электронной почтой. Я что-то забыл сделать?

from django.conf import settings
from django.contrib.auth.models import User

class EmailOrUsernameModelBackend(object):
    """
    This is a ModelBacked that allows authentication with either a username or an email address.

    """
    def authenticate(self, username=None, password=None):
        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, username):
        try:
            return User.objects.get(pk=username)
        except User.DoesNotExist:
            return None

Edit: как было предложено, я унаследовал от ModelBackend и установил его в своих настройках В моих настройках у меня есть это AUTHENTICATION_BACKENDS = ( - пользователи.бэкэнд', - Джанго.ВНО.автор.внутренний.ModelBackend', ) И я изменил бэкэнд на это:

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
class EmailOrUsernameModelBackend(ModelBackend):
    """
    This is a ModelBacked that allows authentication with either a username or an email address.

    """
    def authenticate(self, username=None, password=None):
        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, username):
        try:
            return User.objects.get(pk=username)
        except User.DoesNotExist:
            return None

Теперь я получаю Module "users" does not define a "backends" attribute/class ошибка.

7 ответов


еще одно решение:

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q


class DualModelBackend(ModelBackend):

    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()
        if username is None:
            username = kwargs.get(UserModel.USERNAME_FIELD)
        # `username` field does not restring using `@`, so technically email can be
        # as username and email, even with different users
        users = UserModel._default_manager.filter(
            Q(**{UserModel.USERNAME_FIELD: username}) | Q(email__iexact=username))
        # check for any password match
        for user in users:
            if user.check_password(password):
                return user
        if not users:
            # Run the default password hasher once to reduce the timing
            # difference between an existing and a non-existing user (#20760).
            UserModel().set_password(password)

устранение:

  • по умолчанию @ не запрещено в поле Имя пользователя, поэтому, если пользовательская модель пользователя не запрещает @ символ, он не может использоваться для различения имени пользователя и электронной почты.
  • технически, могут быть два пользователя, использующие одно и то же письмо, один в поле электронной почты, другой в имени пользователя. Если такая возможность не ограничена, это может привести к тому, что пользователь не сможет аутентифицироваться или необработанный MultipleObjectsReturned исключение, если есть.
  • ловить любое исключение с except: это вообще плохая практика

недостаток-если есть два пользователя, используя тот же адрес электронной почты, один в имени пользователя, другой в электронной почте, и они имеют тот же пароль, то он склонен к аутентификации первого матча. Думаю, шансы на это крайне малы.

обратите внимание: любой из подходов должен обеспечивать unique


после следования совету, данному мне выше, и изменения AUTHENTICATION_BACKENDS = ['yourapp.yourfile.EmailOrUsernameModelBackend'] Я получаю ошибку Manager isn't available; User has been swapped for 'users.User'. Это было вызвано тем, что я использовал пользовательскую модель по умолчанию вместо своей собственной. Вот рабочий код.

from django.conf import settings
from django.contrib.auth import get_user_model

class EmailOrUsernameModelBackend(object):
    """
    This is a ModelBacked that allows authentication with either a username or an email address.

    """
    def authenticate(self, username=None, password=None):
        if '@' in username:
            kwargs = {'email': username}
        else:
            kwargs = {'username': username}
        try:
            user = get_user_model().objects.get(**kwargs)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None

    def get_user(self, username):
        try:
            return get_user_model().objects.get(pk=username)
        except get_user_model().DoesNotExist:
            return None

Я думал, что брошу свой более простой подход для всех, кто столкнется с этим:

# -*- coding: utf-8 -*-
from django.contrib.auth import backends, get_user_model
from django.db.models import Q


class ModelBackend(backends.ModelBackend):
    def authenticate(self, username=None, password=None, **kwargs):
        UserModel = get_user_model()

        try:
            user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))

            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)

Примечание:

  • игнорирует USERNAME_FIELD, хотя вы могли бы добавить его в довольно легко
  • нечувствительный к регистру (вы можете просто удалить __iexactхотя бы сделать это не)

обновленная версия того же фрагмента, с улучшенной безопасностью. Кроме того, это позволяет включить или отключить проверку подлинности с учетом регистра. Если вы предпочитаете, вы можете установите его непосредственно из pypi.

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.conf import settings

###################################
"""  DEFAULT SETTINGS + ALIAS   """
###################################


try:
    am = settings.AUTHENTICATION_METHOD
except:
    am = 'both'
try:
    cs = settings.AUTHENTICATION_CASE_SENSITIVE
except:
    cs = 'both'

#####################
"""   EXCEPTIONS  """
#####################


VALID_AM = ['username', 'email', 'both']
VALID_CS = ['username', 'email', 'both', 'none']

if (am not in VALID_AM):
    raise Exception("Invalid value for AUTHENTICATION_METHOD in project "
                    "settings. Use 'username','email', or 'both'.")

if (cs not in VALID_CS):
    raise Exception("Invalid value for AUTHENTICATION_CASE_SENSITIVE in project "
                    "settings. Use 'username','email', 'both' or 'none'.")

############################
"""  OVERRIDDEN METHODS  """
############################


class DualAuthentication(ModelBackend):
    """
    This is a ModelBacked that allows authentication
    with either a username or an email address.
    """

    def authenticate(self, username=None, password=None):
        UserModel = get_user_model()
        try:
            if ((am == 'email') or (am == 'both')):
                if ((cs == 'email') or cs == 'both'):
                    kwargs = {'email': username}
                else:
                    kwargs = {'email__iexact': username}

                user = UserModel.objects.get(**kwargs)
            else:
                raise
        except:
            if ((am == 'username') or (am == 'both')):
                if ((cs == 'username') or cs == 'both'):
                    kwargs = {'username': username}
                else:
                kwargs = {'username__iexact': username}

                user = UserModel.objects.get(**kwargs)
        finally:
            try:
                if user.check_password(password):
                    return user
            except:
                # Run the default password hasher once to reduce the timing
                # difference between an existing and a non-existing user.
                UserModel().set_password(password)
                return None

    def get_user(self, username):
        UserModel = get_user_model()
        try:
            return UserModel.objects.get(pk=username)
        except UserModel.DoesNotExist:
            return None

Я знаю, что на это уже ответили, однако я нашел очень аккуратный способ реализовать логин как с электронной почтой, так и с именем пользователя, используя представления Django auth. Я не видел, чтобы кто-то использовал этот метод, поэтому решил поделиться им для простоты.

from django.contrib.auth.models import User


class EmailAuthBackend():
    def authenticate(self, username=None, password=None):
        try:
            user = User.objects.get(email=username)
            if user.check_password(raw_password=password):
                return user
            return None
        except User.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

тогда в вашем settings.py добавьте это

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'myapp.authentication.EmailAuthBackend',
)

вот обходной путь, который не требует изменения бэкэнда аутентификации вообще.

во-первых, посмотрите на пример входа в систему от Джанго.

from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

Если аутентификация с именем пользователя не удается, мы можем проверить, есть ли совпадение по электронной почте, получить соответствующее имя пользователя и попытаться аутентифицировать снова.

from django.contrib.auth import authenticate, login, get_user_model

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is None:
        User = get_user_model()
        user_queryset = User.objects.all().filter(email__iexact=username)
        if user_queryset:
            username = user_queryset[0].username
            user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...

подобно примеру 1bit0fMe, электронная почта должна быть уникальным полем, и есть тот же (маловероятный) недостаток, что они упоминали.

Я бы рекомендовал этот подход, только если весь вход на ваш сайт обрабатывается одним представлением или формой. В противном случае было бы лучше изменить сам метод authenticate() в бэкэнде, чтобы избежать создания нескольких точек потенциального сбоя.


предполагая, что вы заблокировали / запретили имя пользователя, имеющее@, и вы хотите использовать модель пользователя django.

if request.method == 'POST':
    form = LoginForm(request.POST)
    if form.is_valid():
        cd=form.cleaned_data
        if '@' in cd['username']:
            username=User.objects.get(email=cd['username']).username
        else:
            username=cd['username']

        user = authenticate(username=username,
                                password=cd['password'])

        if user is not None and user.is_active:
            login(request,user)
            return redirect('loggedin')
        else:
            return render(request, 'login.html')