Aceptar la dirección de correo electrónico como nombre de usuario en Django

84

¿Hay alguna buena manera de hacer esto en django sin tener mi propio sistema de autenticación? Quiero que el nombre de usuario sea la dirección de correo electrónico del usuario en lugar de que ellos creen un nombre de usuario.

Por favor avise, gracias.

demonio
fuente

Respuestas:

35

Para cualquier otra persona que desee hacer esto, recomiendo echar un vistazo a django-email-as-username, que es una solución bastante completa, que incluye parchear los createsuperusercomandos de administración y administración, entre otras partes.

Editar : A partir de Django 1.5 en adelante, debería considerar usar un modelo de usuario personalizado en lugar de django-email-as-username .

Tom Christie
fuente
3
Si decide que un modelo de usuario personalizado es la mejor opción, es posible que desee consultar este tutorial en el blog del grupo caktus, se parece a este ejemplo dado en los documentos de django, pero tenga cuidado con algunos detalles necesarios para un entorno de producción (por ejemplo, permisos).
gaboroncancio
4
Para Django 1.5 y superior, la aplicación django-custom-user contiene un modelo de usuario personalizado que lo implementa.
Josh Kelley
29

Esto es lo que hacemos. No es una solución "completa", pero hace mucho de lo que está buscando.

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. Lot
fuente
Funciona para mi. Aunque puedo ver que esto es confuso para futuros mantenedores.
Nick Bolton
esa es probablemente la respuesta más inteligente que he visto en SO.
Emmet B
en el administrador Crear usuario esto: exclude = ('correo electrónico',) me arroja un error de que falta la clave ['correo electrónico'] en UserForm, eso es obvio.
CristiC777
22

Aquí hay una forma de hacerlo para que se acepten tanto el nombre de usuario como el correo electrónico:

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

No sé si hay alguna configuración para establecer el formulario de autenticación predeterminado, pero también puede anular la URL en urls.py

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

Al aumentar el ValidationError, se evitarán 500 errores cuando se envíe un correo electrónico no válido. El uso de la definición del super para "invalid_login" mantiene el mensaje de error ambiguo (frente a un "no se encontró ningún usuario por ese correo electrónico" específico) que sería necesario para evitar filtrar si una dirección de correo electrónico está registrada para una cuenta en su servicio. Si esa información no está segura en su arquitectura, podría ser más amigable tener un mensaje de error más informativo.

Juho Rutila
fuente
No estoy usando auth.views.login. Usando personalizado, esta es mi URL URL (r'accounts / login ',' login_view ',). Si estoy dando EmailAuthenticationForm, entonces el error es login_view () obtuvo un argumento de palabra clave inesperado 'authentication_form'
Raja Simon
Esto funcionó perfectamente para mí, de hecho usé mi perfil de usuario personalizado (ya que en mi arquitectura no se garantiza que el usuario tenga una dirección de correo electrónico adjunta). Parece mucho mejor que personalizar el modelo de usuario o hacer que el nombre de usuario sea igual a un correo electrónico (ya que eso permite mantener privado el correo electrónico de un amigo mientras se expone el nombre de usuario, por ejemplo).
owenfi
7

Si va a ampliar el modelo de usuario, tendrá que implementar un modelo de usuario personalizado de todos modos.

Aquí hay un ejemplo para Django 1.8. Django 1.7 requeriría un poco más de trabajo, principalmente cambiando los formularios predeterminados (solo eche un vistazo a UserChangeForm& UserCreationFormin django.contrib.auth.forms, eso es lo que necesita en 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

modelos.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

formularios.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
fuente
Probé su enfoque y funciona, pero en mi caso usernameSiteUserpython 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'.
agregué
Agregué el usernamecampo a mi Usermodelo con su null=Trueatributo. En esta entrada de la papelera de pegado, quería mostrar la implementación. pastebin.com/W1PgLrD9
bgarcial
2

Otras alternativas me parecen demasiado complejas, así que escribí un fragmento que me permite autenticarme usando un nombre de usuario, correo electrónico o ambos, y también habilitar o deshabilitar la distinción entre mayúsculas y minúsculas. Lo subí a 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
Adrián López
fuente
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 perfectamente ... para django 1.11.4

syed
fuente
0

La forma más sencilla es buscar el nombre de usuario en función del correo electrónico en la vista de inicio de sesión. De esa manera puedes dejar todo lo demás en paz:

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

En su plantilla, establezca la siguiente variable en consecuencia, es decir

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

Y proporcione a sus entradas de nombre de usuario / contraseña los nombres correctos, es decir, nombre de usuario, contraseña.

ACTUALIZAR :

Alternativamente, la llamada if _is_valid_email (email): se puede reemplazar con if '@' en el nombre de usuario. De esa forma, puede eliminar la función _is_valid_email. Esto realmente depende de cómo defina su nombre de usuario. No funcionará si permite el carácter '@' en sus nombres de usuario.

radtek
fuente
1
Este código tiene errores, porque el nombre de usuario también puede tener un símbolo '@', por lo que si '@' está presente, no es necesario un correo electrónico.
MrKsn
Depende de usted realmente, no permito que el nombre de usuario tenga el símbolo @. Si lo hace, puede agregar otra consulta de filtro para buscar a través del objeto Usuario por nombre de usuario. PD. El nombre de usuario también puede ser un correo electrónico, por lo que debe tener cuidado con la forma en que diseña su administración de usuarios.
radtek
También echa un vistazo, desde django.core.validators import validate_email. Puede intentarlo, excepto el bloque ValidationError con validate_email ('[email protected] '). Es posible que aún tenga errores dependiendo de su aplicación.
radtek
Por supuesto, tiene razón, depende de cómo esté configurada la lógica de inicio de sesión. Mencioné eso porque la posibilidad de '@' en el nombre de usuario es la predeterminada de django. Alguien puede copiar su código y tener problemas cuando el usuario con el nombre de usuario 'Gladi @ tor' no puede iniciar sesión porque el código de inicio de sesión cree que es un correo electrónico.
MrKsn
Buena llamada. Espero que la gente entienda qué es lo que están copiando. Agregaré la validación por correo electrónico y comentaré la validación actual, la dejaré como una alternativa. Supongo que la única otra razón por la que no utilicé la validación aparte de que mis nombres de usuario no tienen @, es que hace que el código sea menos dependiente de django. Las cosas tienden a moverse de una versión a otra. ¡Salud!
radtek
0

Creo que la forma más rápida es crear un formulario heredado UserCreateFormy luego anular el usernamecampo con forms.EmailField. Luego, para cada nuevo usuario registrado, debe iniciar sesión con su dirección de correo electrónico.

Por ejemplo:

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
fuente
Votó en contra por un par de razones. Esta respuesta es una mejor forma de hacer lo mismo. ¿Y por qué subclase cuando simplemente puede definir qué widget usar directamente en la UserCreationFormclase? Y por favor no recomiende escribir <input …cuando, seguramente, {{form.username}}es mejor.
Jonas G. Drange
0

No estoy seguro de si las personas están tratando de lograr esto, pero encontré una manera agradable (y limpia) de solo pedir el correo electrónico y luego establecer el nombre de usuario como el correo electrónico en la vista antes de guardar.

Mi formulario de usuario solo requiere el correo electrónico y la contraseña:

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

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

Luego, en mi opinión, agrego la siguiente 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.
fuente
1
¿No se pueden producir problemas al almacenar el correo electrónico en el nombre de usuario porque el nombre de usuario tiene 30 caracteres mientras que el correo electrónico tiene 75 caracteres?
user2233706