Aceitando endereço de e-mail como nome de usuário no Django

84

Existe uma boa maneira de fazer isso no Django sem lançar meu próprio sistema de autenticação? Quero que o nome de usuário seja o endereço de e-mail do usuário em vez de criar um nome de usuário.

Por favor, avise, obrigado.

Damon
fonte

Respostas:

35

Para qualquer pessoa que queira fazer isso, eu recomendo dar uma olhada em django-email-as-username que é uma solução bastante abrangente, que inclui remendar o administrador e os createsuperusercomandos de gerenciamento, entre outras partes.

Edit : A partir do Django 1.5 em diante, você deve considerar o uso de um modelo de usuário personalizado ao invés de django-email-as-username .

Tom Christie
fonte
3
Se você decidir que um modelo de usuário customizado é a melhor opção, você pode querer verificar este tutorial no blog do grupo caktus, ele se assemelha a este exemplo dado na documentação django, mas tome cuidado com alguns detalhes necessários para um ambiente de produção (por exemplo, Permissões).
gaboroncancio
4
Para Django 1.5 e superior, o aplicativo django-custom-user contém um modelo de usuário customizado enlatado que implementa isso.
Josh Kelley,
29

Aqui está o que fazemos. Não é uma solução "completa", mas faz muito do que você está procurando.

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)
S.Lott
fonte
Funciona para mim. Embora eu possa ver isso sendo confuso para futuros mantenedores.
Nick Bolton
essa é provavelmente a resposta mais inteligente que já vi no SO.
Emmet B
no admin Criar usuário isto: exclude = ('email',) me lance um erro de que a chave ['email'] está faltando no formulário de usuário, isso é óbvio.
CristiC777
22

Esta é uma maneira de fazer isso para que o nome de usuário e o e-mail sejam aceitos:

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

Não sei se há alguma configuração para definir o formulário de autenticação padrão, mas você também pode substituir o url em urls.py

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

Aumentar o ValidationError evitará 500 erros quando um e-mail inválido for enviado. Usar a definição do super para "invalid_login" mantém a mensagem de erro ambígua (em vez de um específico "nenhum usuário por esse e-mail encontrado"), que seria necessário para evitar vazar se um endereço de e-mail está inscrito em uma conta em seu serviço. Se essas informações não estiverem seguras em sua arquitetura, pode ser mais fácil ter uma mensagem de erro mais informativa.

Juho Rutila
fonte
Não estou usando auth.views.login. Usando custom, este é o meu url url (r'accounts / login ',' login_view ',). Se estou fornecendo EmailAuthenticationForm, o erro é login_view () obteve um argumento de palavra-chave inesperado 'authentication_form'
Raja Simon
Isso funcionou perfeitamente para mim. Na verdade, usei meu perfil de usuário personalizado (já que em minha arquitetura o usuário não tem garantia de ter um endereço de e-mail anexado). Parece muito mais agradável do que personalizar o modelo do usuário ou tornar o nome de usuário igual a um e-mail (já que isso permite manter o e-mail de um amigo privado enquanto expõe o nome de usuário, por exemplo).
owenfi
8

Django agora fornece um exemplo completo de um sistema de autenticação estendido com admin e formulário: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example

Você pode basicamente copiar / colar e adaptar (eu não precisava do date_of_birthno meu caso).

Na verdade, ele está disponível desde o Django 1.5 e ainda está disponível a partir de agora (django 1.7).

vinil
fonte
Eu sigo o tutorial do djangoproject e funciona perfeitamente
Evhz
7

Se você for estender o modelo de usuário, terá que implementar o modelo de usuário personalizado de qualquer maneira.

Aqui está um exemplo para Django 1.8. Django 1.7 exigiria um pouco mais de trabalho, principalmente mudando as formas padrão (apenas dê uma olhada em UserChangeForm& UserCreationFormin django.contrib.auth.forms- é isso que você precisa no 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'
Max Malysh
fonte
Testei sua abordagem e funciona, mas no meu caso adicionei o usernamecampo para SiteUsermodelar, porque, quando executo o python manage.py makemigrations ...comando, recebo esta saída:ERRORS: <class 'accounts.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not an attribute of 'accounts.User'.
bgarcial
Eu adicionei o usernamecampo ao meu Usermodelo com seu null=Trueatributo. Nesta entrada de pasta de colar eu queria mostrar a implementação. pastebin.com/W1PgLrD9
bgarcial
2

Outras alternativas parecem muito complexas para mim, então escrevi um snippet que permite autenticar usando nome de usuário, e-mail ou ambos, e também habilitar ou desabilitar a distinção entre maiúsculas e minúsculas. Eu enviei para o pip como 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
Adrian Lopez
fonte
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()

funcionando perfeitamente ... para django 1.11.4

Syed
fonte
0

A maneira mais fácil é consultar o nome de usuário com base no e-mail na visualização de login. Dessa forma, você pode deixar todo o resto sozinho:

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))

Em seu modelo, defina a próxima variável de acordo, ou seja,

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

E forneça às entradas de nome de usuário / senha os nomes corretos, ou seja, nome de usuário e senha.

ATUALIZAÇÃO :

Alternativamente, o if _is_valid_email (email): call pode ser substituído por if '@' no nome de usuário. Dessa forma, você pode descartar a função _is_valid_email. Isso realmente depende de como você define seu nome de usuário. Não funcionará se você permitir o caractere '@' em seus nomes de usuário.

Radtek
fonte
1
Este código é bugado, pois o nome de usuário também pode ter um símbolo '@', então se '@' estiver presente, não é necessário um e-mail.
MrKsn
depende de você realmente, eu não permito que o nome de usuário tenha o símbolo @. Se você fizer isso, poderá adicionar outra consulta de filtro para pesquisar o objeto Usuário por nome de usuário. PS. O nome de usuário também pode ser um e-mail, portanto, você deve ter cuidado com a forma como projeta o gerenciamento de usuários.
radtek
Também verifique, em django.core.validators import validate_email. Você pode fazer uma tentativa, exceto o bloco ValidationError com validate_email ('[email protected] '). Ainda pode haver bugs, dependendo do seu aplicativo.
radtek
Claro, você está certo, depende de como a lógica de login está configurada. Eu mencionei isso porque a possibilidade de '@' no nome de usuário é o padrão do django. Alguém pode copiar seu código e ter problemas quando o usuário com o nome de usuário 'Gladi @ tor' não consegue fazer o login porque o código de login pensa que é um e-mail.
MrKsn
Boa decisão. Espero que as pessoas entendam o que estão copiando. Vou adicionar a validação do email e comentar a validação atual, deixando como alternativa. Eu acho que a única outra razão pela qual eu não usei a validação além de meus nomes de usuário não terem o @, é que isso torna o código menos dependente do django. As coisas tendem a se mover de uma versão para outra. Felicidades!
radtek
0

Acho que a maneira mais rápida é criar um formulário herdar de UserCreateForme, em seguida, substituir o usernamecampo por forms.EmailField. Então, para cada novo usuário de registro, eles precisam se conectar com seu endereço de e-mail.

Por exemplo:

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>
...
Enix
fonte
Votação negativa por alguns motivos. Essa resposta é a melhor maneira de fazer a mesma coisa. E por que criar uma subclasse quando você pode simplesmente definir qual widget usar diretamente na UserCreationFormclasse? E, por favor, não recomendo escrever <input …quando, com certeza, {{form.username}}é melhor.
Jonas G. Drange
0

Não tenho certeza se as pessoas estão tentando fazer isso, mas descobri uma maneira legal (e limpa) de apenas pedir o e-mail e definir o nome de usuário como o e-mail na visualização antes de salvar.

Meu UserForm requer apenas o e-mail e a senha:

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

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

Então, na minha opinião, adiciono a seguinte lógica:

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()
Nick S.
fonte
1
Não foi possível armazenar o e-mail no nome de usuário causar problemas porque o nome de usuário tem 30 caracteres enquanto o e-mail tem 75 caracteres?
user2233706