Akceptowanie adresu e-mail jako nazwy użytkownika w Django

84

Czy jest dobry sposób na zrobienie tego w django bez konieczności zmiany własnego systemu uwierzytelniania? Chcę, aby nazwa użytkownika była adresem e-mail użytkownika, zamiast tworzyć nazwę użytkownika.

Proszę o poradę, dziękuję.

damon
źródło

Odpowiedzi:

35

Każdemu, kto chciałby to zrobić, polecam przyjrzenie się django-email-as-username, które jest dość kompleksowym rozwiązaniem, które obejmuje łatanie administratora i createsuperuserpoleceń zarządzania, wśród innych drobiazgów.

Edycja : Począwszy od Django 1.5, powinieneś rozważyć użycie niestandardowego modelu użytkownika zamiast django-email-as-username .

Tom Christie
źródło
3
Jeśli zdecydujesz, że niestandardowy model użytkownika jest najlepszą opcją, możesz chcieć sprawdzić ten samouczek na blogu grupy caktus, przypomina ten przykład podany w dokumentacji django, ale zwróć uwagę na pewne szczegóły potrzebne w środowisku produkcyjnym (np. Uprawnienia).
gaboroncancio
4
W przypadku Django 1.5 i nowszych, aplikacja django-custom-user zawiera niestandardowy model użytkownika, który to implementuje.
Josh Kelley,
29

Oto, co robimy. To nie jest „kompletne” rozwiązanie, ale robi wiele z tego, czego szukasz.

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
źródło
Pracuje dla mnie. Chociaż widzę, że jest to mylące dla przyszłych opiekunów.
Nick Bolton
to jest prawdopodobnie najmądrzejsza odpowiedź, jaką kiedykolwiek widziałem w SO.
Emmet B
na admin Utwórz użytkownika this: exclude = ('email',) wyślij mi błąd, że brakuje klucza ['email'] w UserForm, to oczywiste.
CristiC777
22

Oto jeden sposób, aby to zrobić, aby zaakceptować zarówno nazwę użytkownika, jak i adres e-mail:

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

Nie wiem, czy jest jakieś ustawienie, aby ustawić domyślny formularz uwierzytelniania, ale możesz również zastąpić adres URL w urls.py

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

Podniesienie wartości ValidationError zapobiegnie 500 błędom w przypadku przesłania nieprawidłowego e-maila. Korzystanie z definicji super dla „invalid_login” powoduje, że komunikat o błędzie jest niejednoznaczny (w porównaniu z konkretnym „brakiem użytkownika według tego adresu e-mail”), co byłoby wymagane, aby zapobiec wyciekowi informacji, czy adres e-mail jest zarejestrowany dla konta w Twojej usłudze. Jeśli te informacje nie są bezpieczne w Twojej architekturze, może być bardziej przyjazne wyświetlenie bardziej informacyjnego komunikatu o błędzie.

Juho Rutila
źródło
Nie używam auth.views.login. Używając niestandardowego, jest to mój adres URL (r'accounts / login ',' login_view ',). Jeśli podam EmailAuthenticationForm, błąd to login_view () otrzymał nieoczekiwany argument słowa kluczowego „authentication_form”
Raja Simon
To zadziałało dla mnie czysto, faktycznie użyłem mojego niestandardowego profilu użytkownika (ponieważ w mojej architekturze Użytkownik nie ma gwarancji, że ma dołączony adres e-mail). Wydaje się o wiele przyjemniejsze niż dostosowywanie modelu użytkownika lub ustawianie nazwy użytkownika na taką samą, jak adres e-mail (ponieważ pozwala to zachować prywatność wiadomości e-mail znajomego, na przykład ujawniając nazwę użytkownika).
owenfi
8

Django udostępnia teraz pełny przykład rozszerzonego systemu uwierzytelniania z administratorem i formularzem: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example

Możesz go w zasadzie skopiować / wkleić i dostosować (nie potrzebowałem date_of_birthw moim przypadku).

W rzeczywistości jest dostępny od Django 1.5 i jest nadal dostępny od teraz (django 1.7).

Vinyll
źródło
I wyznawcę djangoproject samouczek i działa idealnie
Evhz
7

Jeśli zamierzasz rozszerzyć model użytkownika, i tak będziesz musiał zaimplementować niestandardowy model użytkownika.

Oto przykład dla Django 1.8. Django 1.7 wymagałoby trochę więcej pracy, głównie zmiany domyślnych formularzy (wystarczy spojrzeć na UserChangeForm& UserCreationFormin django.contrib.auth.forms- to jest to, czego potrzebujesz w 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
źródło
Przetestowałem Twoje podejście i działa, ale w moim przypadku musiałem dodać usernamepole do SiteUsermodelu, ponieważ wykonując python manage.py makemigrations ...polecenie otrzymuję takie wyjście:ERRORS: <class 'accounts.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not an attribute of 'accounts.User'.
bgarcial
Dodałem usernamepole do mojego Usermodelu z ich null=Trueatrybutem. W tym wpisie z wklejania chciałem pokazać implementację. pastebin.com/W1PgLrD9
bgarcial
2

Inne alternatywy wydają mi się zbyt skomplikowane, więc napisałem fragment, który pozwala na uwierzytelnianie przy użyciu nazwy użytkownika, adresu e-mail lub obu, a także włączanie lub wyłączanie rozróżniania wielkości liter. Wgrałem go do pip jako 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
źródło
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()

działa idealnie ... dla django 1.11.4

syed
źródło
0

Najłatwiejszym sposobem jest wyszukanie nazwy użytkownika na podstawie adresu e-mail w widoku logowania. W ten sposób możesz zostawić wszystko inne w spokoju:

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

W swoim szablonie ustaw odpowiednio następną zmienną, tj

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

I podaj swoją nazwę użytkownika / hasło, wprowadzając odpowiednie nazwy, tj. Nazwę użytkownika, hasło.

AKTUALIZACJA :

Alternatywnie, if _is_valid_email (email): call można zastąpić znakiem „@” w nazwie użytkownika. W ten sposób możesz porzucić funkcję _is_valid_email. To naprawdę zależy od tego, jak zdefiniujesz swoją nazwę użytkownika. Nie zadziała, jeśli pozwolisz na znak „@” w swoich nazwach użytkowników.

radtek
źródło
1
Ten kod jest błędny, ponieważ nazwa użytkownika może mieć również symbol „@”, więc jeśli obecny jest znak „@”, nie jest potrzebny e-mail.
MrKsn
naprawdę zależy od Ciebie, nie pozwalam, aby nazwa użytkownika miała symbol @. Jeśli tak, możesz dodać kolejne zapytanie filtrujące, aby przeszukiwać obiekt użytkownika według nazwy użytkownika. PS. nazwa użytkownika może być również adresem e-mail, więc musisz zachować ostrożność podczas projektowania zarządzania użytkownikami.
radtek
Zobacz także, z importu django.core.validators validate_email. Możesz spróbować z wyjątkiem bloku ValidationError z validate_email ('[email protected] '). W zależności od aplikacji może nadal zawierać błędy.
radtek
Oczywiście masz rację, zależy to od konfiguracji logiki logowania. Wspomniałem o tym, ponieważ możliwość '@' w nazwie użytkownika jest domyślna w django. Ktoś może skopiować Twój kod i mieć problemy, gdy użytkownik o nazwie użytkownika „Gladi @ tor” nie może się zalogować, ponieważ kod logowania uważa, że ​​jest to e-mail.
MrKsn,
Dobra decyzja. Mam nadzieję, że ludzie rozumieją, co kopiują. Dodam weryfikację adresu e-mail i zakomentuję bieżącą weryfikację, zostawiając ją jako alternatywę. Wydaje mi się, że jedynym innym powodem, dla którego nie użyłem walidacji oprócz moich nazw użytkowników, które nie mają znaku @, jest to, że sprawia, że ​​kod jest mniej zależny od django. Rzeczy mają tendencję do przenoszenia się z wersji na wersję. Twoje zdrowie!
radtek
0

Myślę, że najszybszym sposobem jest utworzenie formularza dziedziczącego z UserCreateForm, a następnie nadpisanie usernamepola za pomocą forms.EmailField. Następnie dla każdego nowego użytkownika rejestrującego się musi zalogować się przy użyciu swojego adresu e-mail.

Na przykład:

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
źródło
Głosowanie odrzucone z kilku powodów. Ta odpowiedź jest lepszym sposobem na zrobienie tego samego. I po co tworzyć podklasy, skoro można po prostu zdefiniować, jakiego widżetu użyć bezpośrednio w UserCreationFormklasie? I proszę nie polecać pisania, <input …kiedy z pewnością {{form.username}}jest lepiej.
Jonas G. Drange
0

Nie jestem pewien, czy ludzie próbują to osiągnąć, ale znalazłem dobry (i czysty) sposób, aby poprosić tylko o e-mail, a następnie ustawić nazwę użytkownika jako e-mail w widoku przed zapisaniem.

My UserForm wymaga tylko adresu e-mail i hasła:

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

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

Następnie, moim zdaniem, dodaję następującą logikę:

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.
źródło
1
Czy przechowywanie wiadomości e-mail w nazwie użytkownika nie powodowało problemów, ponieważ nazwa użytkownika ma 30 znaków, a e-mail 75 znaków?
user2233706