Accettazione dell'indirizzo e-mail come nome utente in Django


Risposte:


35

Per chiunque desideri farlo, consiglierei di dare un'occhiata a django-email-as-username, che è una soluzione piuttosto completa, che include la patch dell'amministratore e dei createsuperusercomandi di gestione, tra le altre cose.

Modifica : da Django 1.5 in poi dovresti considerare l'utilizzo di un modello utente personalizzato invece di django-email-as-username .


3
Se decidi che un modello utente personalizzato è l'opzione migliore, potresti voler controllare questo tutorial sul blog del gruppo caktus, assomiglia a questo esempio fornito nei documenti di django ma si prende cura di alcuni dettagli necessari per un ambiente di produzione (ad es. Autorizzazioni).
gaboroncancio

4
Per Django 1.5 e versioni successive, l' app django-custom-user contiene un modello utente personalizzato predefinito che lo implementa.
Josh Kelley

29

Ecco cosa facciamo. Non è una soluzione "completa", ma fa molto di quello che stai cercando.

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)

Per me va bene. Anche se posso vedere che questo crea confusione per i futuri manutentori.
Nick Bolton

questa è probabilmente la risposta più intelligente che abbia mai visto in SO.
Emmet B

su admin Crea utente this: exclude = ('email',) mi genera un errore che la chiave ['email'] non è presente in UserForm, questo è ovvio.
CristiC777

22

Ecco un modo per farlo in modo che sia il nome utente che l'email siano accettati:

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

Non so se ci sono delle impostazioni per impostare il modulo di autenticazione predefinito ma puoi anche sovrascrivere l'URL in urls.py

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

L'aumento dell'eccezione ValidationError eviterà 500 errori quando viene inviata un'e-mail non valida. L'utilizzo della definizione del super per "invalid_login" mantiene il messaggio di errore ambiguo (rispetto a uno specifico "nessun utente da quell'email trovato") che sarebbe richiesto per evitare che trapelino se un indirizzo email è registrato per un account sul tuo servizio. Se tali informazioni non sono protette nella tua architettura, potrebbe essere più amichevole avere un messaggio di errore più informativo.


Non sto usando auth.views.login. Usando personalizzato questo è il mio URL url (r'accounts / login ',' login_view ',). Se sto fornendo EmailAuthenticationForm, l'errore è login_view () ha ricevuto un argomento di parola chiave imprevisto 'authentication_form'
Raja Simon

Questo ha funzionato in modo pulito per me, ho effettivamente utilizzato il mio profilo utente personalizzato (poiché nella mia architettura non è garantito che l'utente abbia allegato l'indirizzo e-mail). Sembra molto più bello che personalizzare il modello utente o rendere il nome utente uguale a un'e-mail (poiché ciò consente di mantenere l'email di un amico privata mentre si espone il nome utente, ad esempio).
owenfi


7

Se hai intenzione di estendere il modello utente, dovrai comunque implementare il modello utente personalizzato.

Ecco un esempio per Django 1.8. Django 1.7 richiederebbe un po 'più di lavoro, per lo più cambiando i moduli predefiniti (basta dare un'occhiata a UserChangeForm& UserCreationFormin django.contrib.auth.forms- questo è ciò di cui hai bisogno in 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'

Ho testato il tuo approccio e funziona, ma nel mio caso ho dovuto aggiungere il usernamecampo al SiteUsermodello, perché quando 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'.
eseguo

Ho aggiunto il usernamecampo al mio Usermodello con il loro null=Trueattributo. In questa voce del cestino della pasta volevo mostrare l'implementazione. pastebin.com/W1PgLrD9
bgarcial

2

Altre alternative sembrano troppo complesse per me, quindi ho scritto uno snippet che consente di autenticarsi utilizzando nome utente, e-mail o entrambi e anche abilitare o disabilitare la distinzione tra maiuscole e minuscole. L'ho caricato su pip come 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()

perfettamente funzionante ... per django 1.11.4



0

Il modo più semplice è cercare il nome utente in base all'e-mail nella vista di accesso. In questo modo puoi lasciare tutto il resto da solo:

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

Nel tuo modello imposta la variabile successiva di conseguenza, ad es

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

E dai al tuo nome utente / password i nomi corretti, cioè nome utente, password.

AGGIORNAMENTO :

In alternativa, la chiamata if _is_valid_email (email): può essere sostituita con if '@' nel nome utente. In questo modo puoi eliminare la funzione _is_valid_email. Dipende da come definisci il tuo nome utente. Non funzionerà se consenti il ​​carattere "@" nei tuoi nomi utente.


1
Questo codice è bacato, perché il nome utente può anche avere un simbolo "@", quindi se è presente "@" non è necessaria un'e-mail.
MrKsn

dipende davvero da te, non permetto al nome utente di avere il simbolo @. In tal caso, è possibile aggiungere un'altra query di filtro per eseguire la ricerca nell'oggetto utente in base al nome utente. PS. username può anche essere un'e-mail, quindi devi stare attento a come progetti la tua gestione utenti.
radtek

Controlla anche da django.core.validators import validate_email. Puoi fare un tentativo tranne il blocco ValidationError con validate_email ('tua@email.com '). Potrebbe ancora essere difettoso a seconda della tua app.
radtek

Certo, hai ragione, dipende da come è impostata la logica di accesso. L'ho detto perché la possibilità di "@" nel nome utente è l'impostazione predefinita di django. Qualcuno può copiare il tuo codice e avere problemi quando l'utente con il nome utente "Gladi @ tor" non riesce ad accedere perché il codice di accesso pensa che sia un'e-mail.
MrKsn

Ottima scelta. Spero che le persone capiscano cosa stanno copiando. Aggiungerò la convalida dell'email e commenterò la convalida corrente, lasciandola come alternativa. Immagino che l'unico altro motivo per cui non ho usato la convalida a parte i miei nomi utente che non hanno @, è che rende il codice meno dipendente da django. Le cose tendono a spostarsi da una versione all'altra. Saluti!
radtek

0

Penso che il modo più rapido sia creare un modulo da cui ereditare UserCreateForme quindi sovrascrivere il usernamecampo con forms.EmailField. Quindi, per ogni nuovo utente registrato, deve accedere con il proprio indirizzo e-mail.

Per esempio:

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

Downvoted per un paio di motivi. Questa risposta è un modo migliore per fare la stessa cosa. E perché sottoclasse quando puoi semplicemente definire quale widget usare direttamente nella UserCreationFormclasse? E per favore sconsiglia di scrivere <input …quando, sicuramente, {{form.username}}è meglio.
Jonas G. Drange,

0

Non sono sicuro che le persone stiano cercando di farlo, ma ho trovato un modo carino (e pulito) per chiedere solo l'email e quindi impostare il nome utente come email nella vista prima di salvare.

Il mio form utente richiede solo l'email e la password:

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

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

Quindi a mio avviso aggiungo la seguente logica:

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
Non è stato possibile memorizzare l'email nel nome utente causa problemi perché il nome utente è di 30 caratteri mentre l'email è di 75 caratteri?
user2233706
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.