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


84

Есть ли хороший способ сделать это в django без развертывания моей собственной системы аутентификации? Я хочу, чтобы имя пользователя было адресом электронной почты пользователя, а не создавало имя пользователя.

Пожалуйста, посоветуйте, спасибо.


Ответы:


35

Для всех, кто хочет это сделать, я бы порекомендовал взглянуть на django-email-as-username, которое является довольно комплексным решением, которое включает исправление администратора и createsuperuserкоманд управления, среди прочего.

Изменить : начиная с Django 1.5 и далее вам следует рассмотреть возможность использования пользовательской модели вместо django-email-as-username .


3
Если вы решите, что настраиваемая модель пользователя - лучший вариант, вы можете проверить это руководство в блоге группы caktus, оно похоже на этот пример из документации django, но позаботится о некоторых деталях, необходимых для производственной среды (например, разрешениях).
gaboroncancio

4
Для Django 1.5 и выше приложение django-custom-user содержит стандартную пользовательскую модель, которая реализует это.
Джош Келли

29

Вот что мы делаем. Это не «полное» решение, но оно делает многое из того, что вы ищете.

from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        exclude = ('email',)
    username = forms.EmailField(max_length=64,
                                help_text="The person's email address.")
    def clean_email(self):
        email = self.cleaned_data['username']
        return email

class UserAdmin(UserAdmin):
    form = UserForm
    list_display = ('email', 'first_name', 'last_name', 'is_staff')
    list_filter = ('is_staff',)
    search_fields = ('email',)

admin.site.unregister(User)
admin.site.register(User, UserAdmin)

Работает на меня. Хотя я вижу, что это сбивает с толку будущих сопровождающих.
Ник Болтон,

это, наверное, самый умный ответ, который я когда-либо видел в SO.
Emmet B

на admin Создайте пользователя это: exclude = ('email',) выдает мне ошибку, что ключ ['email'] отсутствует в UserForm, это очевидно.
CristiC777,

22

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

from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.forms import ValidationError

class EmailAuthenticationForm(AuthenticationForm):
    def clean_username(self):
        username = self.data['username']
        if '@' in username:
            try:
                username = User.objects.get(email=username).username
            except ObjectDoesNotExist:
                raise ValidationError(
                    self.error_messages['invalid_login'],
                    code='invalid_login',
                    params={'username':self.username_field.verbose_name},
                )
        return username

Не знаю, есть ли какой-то параметр для установки формы аутентификации по умолчанию, но вы также можете переопределить URL-адрес в urls.py

url(r'^accounts/login/$', 'django.contrib.auth.views.login', { 'authentication_form': EmailAuthenticationForm }, name='login'),

Повышение ValidationError предотвратит 500 ошибок при отправке недопустимого электронного письма. Использование определения super для «invalid_login» делает сообщение об ошибке неоднозначным (по сравнению с конкретным «ни один пользователь по этому адресу электронной почты не обнаружен»), что необходимо для предотвращения утечки информации о том, зарегистрирован ли адрес электронной почты для учетной записи в вашей службе. Если эта информация небезопасна в вашей архитектуре, было бы удобнее иметь более информативное сообщение об ошибке.


Я не использую auth.views.login. Используя custom, это мой URL-адрес (r'accounts / login ',' login_view ',). Если я даю EmailAuthenticationForm, то ошибка в том, что login_view () получил неожиданный аргумент ключевого слова «authentication_form»
Раджа Саймон

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

8

Django теперь предоставляет полный пример расширенной системы аутентификации с администратором и формой: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example

Вы можете скопировать / вставить и адаптировать (мне не нужен date_of_birth в моем случае ).

Фактически он доступен с Django 1.5 и все еще доступен сейчас (django 1.7).


Я слежу за учебником djangoproject, и он отлично работает
Evhz,

7

Если вы собираетесь расширить модель пользователя, вам все равно придется реализовать собственную модель пользователя.

Вот пример для Django 1.8. Django 1.7 потребует немного больше работы, в основном для изменения форм по умолчанию (просто взгляните на UserChangeForm& UserCreationFormin django.contrib.auth.forms- это то, что вам нужно в 1.7).

user_manager.py:

from django.contrib.auth.models import BaseUserManager
from django.utils import timezone

class SiteUserManager(BaseUserManager):
    def create_user(self, email, password=None, **extra_fields):
        today = timezone.now()

        if not email:
            raise ValueError('The given email address must be set')

        email = SiteUserManager.normalize_email(email)
        user  = self.model(email=email,
                          is_staff=False, is_active=True, **extra_fields)

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password, **extra_fields):
        u = self.create_user(email, password, **extra_fields)
        u.is_staff = True
        u.is_active = True
        u.is_superuser = True
        u.save(using=self._db)
        return u

models.py:

from mainsite.user_manager import SiteUserManager

from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin

class SiteUser(AbstractBaseUser, PermissionsMixin):
    email    = models.EmailField(unique=True, blank=False)

    is_active   = models.BooleanField(default=True)
    is_admin    = models.BooleanField(default=False)
    is_staff    = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'

    objects = SiteUserManager()

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

forms.py:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from mainsite.models import SiteUser

class MyUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = SiteUser
        fields = ("email",)


class MyUserChangeForm(UserChangeForm):
    class Meta(UserChangeForm.Meta):
        model = SiteUser


class MyUserAdmin(UserAdmin):
    form = MyUserChangeForm
    add_form = MyUserCreationForm

    fieldsets = (
        (None,              {'fields': ('email', 'password',)}),
        ('Permissions',     {'fields': ('is_active', 'is_staff', 'is_superuser',)}),  
        ('Groups',          {'fields': ('groups', 'user_permissions',)}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2')}
        ),
    )

    list_display = ('email', )       
    list_filter = ('is_active', )    
    search_fields = ('email',)       
    ordering = ('email',)


admin.site.register(SiteUser, MyUserAdmin)

settings.py:

AUTH_USER_MODEL = 'mainsite.SiteUser'

Я протестировал ваш подход и работает, но в моем случае мне пришлось добавить usernameполе в SiteUserмодель, потому что, когда я выполняю python manage.py makemigrations ...команду, я получаю следующий результат:ERRORS: <class 'accounts.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not an attribute of 'accounts.User'.
bgarcial

Я добавил usernameполе в свою Userмодель с их null=Trueатрибутом. В этой записи бункера для вставки я действительно хотел показать реализацию. pastebin.com/W1PgLrD9
bgarcial

2

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

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


1
     if user_form.is_valid():
        # Save the user's form data to a user object without committing.
        user = user_form.save(commit=False)
        user.set_password(user.password)
        #Set username of user as the email
        user.username = user.email
        #commit
        user.save()

отлично работает ... для django 1.11.4



0

Самый простой способ - найти имя пользователя по электронной почте в окне входа в систему. Таким образом, вы можете оставить все остальное в покое:

from django.contrib.auth import authenticate, login as auth_login

def _is_valid_email(email):
    from django.core.validators import validate_email
    from django.core.exceptions import ValidationError
    try:
        validate_email(email)
        return True
    except ValidationError:
        return False

def login(request):

    next = request.GET.get('next', '/')

    if request.method == 'POST':
        username = request.POST['username'].lower()  # case insensitivity
        password = request.POST['password']

    if _is_valid_email(username):
        try:
            username = User.objects.filter(email=username).values_list('username', flat=True)
        except User.DoesNotExist:
            username = None
    kwargs = {'username': username, 'password': password}
    user = authenticate(**kwargs)

        if user is not None:
            if user.is_active:
                auth_login(request, user)
                return redirect(next or '/')
            else:
                messages.info(request, "<stvrong>Error</strong> User account has not been activated..")
        else:
            messages.info(request, "<strong>Error</strong> Username or password was incorrect.")

    return render_to_response('accounts/login.html', {}, context_instance=RequestContext(request))

В вашем шаблоне установите следующую переменную соответственно, т.е.

<form method="post" class="form-login" action="{% url 'login' %}?next={{ request.GET.next }}" accept-charset="UTF-8">

И дайте вашему логину / паролю входы правильные имена, то есть имя пользователя, пароль.

ОБНОВЛЕНИЕ :

В качестве альтернативы вызов if _is_valid_email (email): можно заменить на if '@' в имени пользователя. Таким образом вы можете отказаться от функции _is_valid_email. Это действительно зависит от того, как вы определяете свое имя пользователя. Это не сработает, если вы разрешите использовать символ «@» в своих именах пользователей.


1
В этом коде есть ошибки, потому что имя пользователя может также иметь символ '@', поэтому, если присутствует '@', электронная почта не требуется.
MrKsn 06

на самом деле зависит от вас, я не позволяю имени пользователя содержать символ @. Если вы это сделаете, вы можете добавить еще один фильтрующий запрос для поиска через объект User по имени пользователя. PS. имя пользователя также может быть адресом электронной почты, поэтому вы должны быть осторожны с тем, как вы проектируете управление пользователями.
radtek 06

Также проверьте, из django.core.validators import validate_email. Вы можете попробовать, кроме блока ValidationError, с помощью validate_email ('your@email.com '). В зависимости от вашего приложения он все еще может быть ошибочным.
radtek 06

Конечно, вы правы, это зависит от того, как настроена логика входа в систему. Я упоминал об этом, потому что возможность использования «@» в имени пользователя является значением по умолчанию для django. Кто-то может скопировать ваш код и столкнуться с проблемами, когда пользователь с именем пользователя Gladi @ tor не может войти в систему, потому что код входа думает, что это адрес электронной почты.
MrKsn 07

Хороший звонок. Я надеюсь, люди понимают, что они копируют. Я добавлю подтверждение электронной почты и закомментирую текущую проверку, оставив ее как альтернативу. Я предполагаю, что единственная другая причина, по которой я не использовал проверку, кроме моих имен пользователей, не имеющих @, состоит в том, что это делает код менее зависимым от django. Вещи имеют тенденцию переходить от версии к версии. Ура!
radtek 07

0

Я думаю, что самый быстрый способ - создать форму, унаследованную от UserCreateForm, а затем переопределить usernameполе с помощьюforms.EmailField . Затем для каждого нового зарегистрированного пользователя им необходимо войти в систему со своим адресом электронной почты.

Например:

urls.py

...
urlpatterns += url(r'^signon/$', SignonView.as_view(), name="signon")

views.py

from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django import forms

class UserSignonForm(UserCreationForm):
    username = forms.EmailField()


class SignonView(CreateView):
    template_name = "registration/signon.html"
    model = User
    form_class = UserSignonForm

signon.html

...
<form action="#" method="post">
    ...
    <input type="email" name="username" />
    ...
</form>
...

Проголосовали против по нескольким причинам. Этот ответ - лучший способ сделать то же самое. И зачем создавать подкласс, если вы можете просто определить, какой виджет использовать непосредственно в UserCreationFormклассе? И, пожалуйста, не рекомендую выписывать, <input …когда, конечно, {{form.username}}лучше.
Jonas G. Drange

0

Не уверен, что люди пытаются это сделать, но я нашел хороший (и чистый) способ только запросить электронную почту, а затем установить имя пользователя в качестве адреса электронной почты в представлении перед сохранением.

My UserForm требует только адрес электронной почты и пароль:

class UserForm(forms.ModelForm):
    password = forms.CharField(widget=forms.PasswordInput())

    class Meta:
        model = User
        fields = ('email', 'password')

Затем, на мой взгляд, я добавляю следующую логику:

if user_form.is_valid():
            # Save the user's form data to a user object without committing.
            user = user_form.save(commit=False)

            user.set_password(user.password)
            #Set username of user as the email
            user.username = user.email
            #commit
            user.save()

1
Не удалось сохранить электронную почту в имени пользователя, потому что имя пользователя составляет 30 символов, а электронное письмо - 75 символов?
user2233706
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.