Akzeptieren der E-Mail-Adresse als Benutzername in Django

84

Gibt es eine gute Möglichkeit, dies in Django zu tun, ohne mein eigenes Authentifizierungssystem zu rollen? Ich möchte, dass der Benutzername die E-Mail-Adresse des Benutzers ist, anstatt dass er einen Benutzernamen erstellt.

Bitte raten Sie, danke.

Damon
quelle

Antworten:

35

Für alle anderen, die dies tun möchten, würde ich empfehlen, einen Blick auf django-email-as-username zu werfen. Dies ist eine ziemlich umfassende Lösung, die unter anderem das Patchen des Admin- und des createsuperuserVerwaltungsbefehls umfasst .

Bearbeiten : Ab Django 1.5 sollten Sie ein benutzerdefiniertes Benutzermodell anstelle von django-email-as-username verwenden .

Tom Christie
quelle
3
Wenn Sie der Meinung sind , dass ein benutzerdefiniertes Benutzermodell die beste Option ist, sollten Sie dieses Lernprogramm im Blog der Caktus-Gruppe lesen. Es ähnelt dem in Django-Dokumenten angegebenen Beispiel , berücksichtigt jedoch einige Details, die für eine Produktionsumgebung erforderlich sind (z. B. Berechtigungen).
Gaboroncancio
4
Für Django 1.5 und höher enthält die App django-custom-user ein vordefiniertes benutzerdefiniertes Benutzermodell, das dies implementiert.
Josh Kelley
29

Folgendes machen wir. Es ist keine "vollständige" Lösung, aber es macht viel von dem, was Sie suchen.

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
quelle
Funktioniert bei mir. Obwohl ich sehe, dass dies für zukünftige Betreuer verwirrend ist.
Nick Bolton
Das ist wahrscheinlich die klügste Antwort, die ich je in SO gesehen habe.
Emmet B
22

Hier ist eine Möglichkeit, damit sowohl der Benutzername als auch die E-Mail-Adresse akzeptiert werden:

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

Sie wissen nicht, ob es eine Einstellung zum Festlegen des Standardauthentifizierungsformulars gibt, aber Sie können die URL auch in urls.py überschreiben

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

Durch Auslösen des ValidationError werden 500 Fehler verhindert, wenn eine ungültige E-Mail gesendet wird. Durch die Verwendung der Super-Definition für "invalid_login" bleibt die Fehlermeldung mehrdeutig (im Vergleich zu einem bestimmten "Kein Benutzer durch diese E-Mail gefunden"), die erforderlich wäre, um zu verhindern, dass bekannt wird, ob eine E-Mail-Adresse für ein Konto in Ihrem Dienst angemeldet ist. Wenn diese Informationen in Ihrer Architektur nicht sicher sind, ist es möglicherweise freundlicher, eine informativere Fehlermeldung zu erhalten.

Juho Rutila
quelle
Ich verwende nicht auth.views.login. Mit benutzerdefinierten ist dies meine URL-URL (r'accounts / login ',' login_view ',). Wenn ich EmailAuthenticationForm gebe, dann ist der Fehler login_view () hat ein unerwartetes Schlüsselwortargument 'authentication_form'
Raja Simon
Dies hat bei mir sauber funktioniert. Ich habe tatsächlich mein benutzerdefiniertes Benutzerprofil verwendet (da in meiner Architektur nicht garantiert ist, dass dem Benutzer eine E-Mail-Adresse zugeordnet ist). Scheint viel schöner zu sein, als das Benutzermodell anzupassen oder den Benutzernamen einer E-Mail gleichzusetzen (da dies ermöglicht, die E-Mail eines Freundes privat zu halten, während beispielsweise der Benutzername verfügbar gemacht wird).
Owenfi
8

Django bietet jetzt ein vollständiges Beispiel für ein erweitertes Authentifizierungssystem mit Administrator und Formular: https://docs.djangoproject.com/de/stable/topics/auth/customizing/#a-full-example

Sie können es grundsätzlich kopieren / einfügen und anpassen (das brauchte ich date_of_birthin meinem Fall nicht).

Es ist tatsächlich seit Django 1.5 verfügbar und ist ab sofort noch verfügbar (Django 1.7).

vinyll
quelle
Ich folge dem Djangoprojekt- Tutorial und es funktioniert perfekt
Evhz
7

Wenn Sie das Benutzermodell erweitern möchten, müssen Sie trotzdem ein benutzerdefiniertes Benutzermodell implementieren.

Hier ist ein Beispiel für Django 1.8. Django 1.7 würde ein wenig mehr Arbeit erfordern und meistens die Standardformulare ändern (werfen Sie einen Blick auf UserChangeForm& UserCreationFormin django.contrib.auth.forms- das ist es, was Sie in 1.7 brauchen).

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
quelle
Ich habe Ihren Ansatz getestet und funktioniert, aber in meinem Fall musste ich das usernameFeld zum SiteUserModell hinzufügen , da ich beim Ausführen des python manage.py makemigrations ...Befehls die folgende Ausgabe ERRORS: <class 'accounts.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not an attribute of 'accounts.User'.
erhalte
Ich habe das usernameFeld Usermit ihrem null=TrueAttribut zu meinem Modell hinzugefügt . In diesem Einfügefacheintrag wollte ich die Implementierung zeigen. pastebin.com/W1PgLrD9
bgarcial
2

Andere Alternativen scheinen mir zu komplex zu sein, deshalb habe ich ein Snippet geschrieben, mit dem ich mich mit Benutzername, E-Mail oder beidem authentifizieren und die Groß- und Kleinschreibung aktivieren oder deaktivieren kann. Ich habe es als Django-Dual-Authentifizierung auf pip hochgeladen .

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

funktioniert perfekt ... für django 1.11.4

syed
quelle
0

Am einfachsten ist es, den Benutzernamen anhand der E-Mail-Adresse in der Anmeldeansicht zu suchen. Auf diese Weise können Sie alles andere in Ruhe lassen:

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

Stellen Sie in Ihrer Vorlage die nächste Variable entsprechend ein, dh

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

Und geben Sie Ihrem Benutzernamen / Passwort die richtigen Namen, dh Benutzername, Passwort.

UPDATE :

Alternativ kann der Aufruf if _is_valid_email (email): durch if '@' im Benutzernamen ersetzt werden. Auf diese Weise können Sie die Funktion _is_valid_email löschen. Dies hängt wirklich davon ab, wie Sie Ihren Benutzernamen definieren. Es funktioniert nicht, wenn Sie das Zeichen '@' in Ihren Benutzernamen zulassen.

radtek
quelle
1
Dieser Code ist fehlerhaft, da der Benutzername auch ein '@' Symbol haben kann. Wenn also '@' vorhanden ist, ist keine E-Mail erforderlich.
MrKsn
hängt wirklich von dir ab, ich erlaube dem Benutzernamen nicht, das @ -Symbol zu haben. In diesem Fall können Sie eine weitere Filterabfrage hinzufügen, um das Benutzerobjekt nach Benutzername zu durchsuchen. PS. Der Benutzername kann auch eine E-Mail-Adresse sein. Daher müssen Sie bei der Gestaltung Ihrer Benutzerverwaltung vorsichtig sein.
Radtek
Überprüfen Sie auch, aus django.core.validators importate validate_email. Sie können einen Versuch mit Ausnahme des ValidationError-Blocks mit validate_email ('[email protected] ') ausführen. Abhängig von Ihrer App ist es möglicherweise immer noch fehlerhaft.
Radtek
Natürlich haben Sie Recht, es hängt davon ab, wie die Anmeldelogik eingerichtet ist. Ich erwähnte das, weil die Möglichkeit von '@' im Benutzernamen die Standardeinstellung von Django ist. Jemand kann Ihren Code kopieren und Probleme haben, wenn sich Benutzer mit dem Benutzernamen 'Gladi @ tor' nicht anmelden können, weil der Anmeldecode denkt, dass es sich um eine E-Mail handelt.
MrKsn
Guter Anruf. Ich hoffe, die Leute verstehen, was sie kopieren. Ich werde die E-Mail-Validierung hinzufügen und die aktuelle Validierung auskommentieren, als Alternative lassen. Ich denke, der einzige andere Grund, warum ich die Validierung nicht verwendet habe, abgesehen davon, dass meine Benutzernamen kein @ haben, ist, dass der Code dadurch weniger von Django abhängig ist. Die Dinge bewegen sich in der Regel von Version zu Version. Prost!
Radtek
0

Ich denke, der schnellste Weg ist, ein Formular zu erstellen, von dem geerbt wird UserCreateForm, und dann das usernameFeld mit zu überschreiben forms.EmailField. Dann muss sich jeder neue Registrierungsbenutzer mit seiner E-Mail-Adresse anmelden.

Beispielsweise:

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
quelle
Aus mehreren Gründen abgelehnt. Diese Antwort ist eine bessere Möglichkeit, dasselbe zu tun. Und warum Unterklasse, wenn Sie lediglich definieren können, welches Widget direkt in der UserCreationFormKlasse verwendet werden soll? Und bitte empfehlen Sie nicht zu schreiben, <input …wann es sicher {{form.username}}besser ist.
Jonas G. Drange
0

Ich bin mir nicht sicher, ob die Leute versuchen, dies zu erreichen, aber ich habe eine gute (und saubere) Möglichkeit gefunden, nur nach der E-Mail zu fragen und dann vor dem Speichern den Benutzernamen als E-Mail in der Ansicht festzulegen.

Mein UserForm benötigt nur die E-Mail-Adresse und das Passwort:

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

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

Dann füge ich meiner Ansicht nach die folgende Logik hinzu:

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.
quelle
1
Konnte das Speichern der E-Mail im Benutzernamen nicht zu Problemen führen, da der Benutzername 30 Zeichen und die E-Mail 75 Zeichen beträgt?
user2233706