Прийняття електронної адреси як імені користувача в Django


84

Чи є хороший спосіб зробити це в django без прокатки власної системи автентифікації? Я хочу, щоб ім’я користувача було електронною адресою користувача замість того, щоб вони створювали ім’я користувача.

Будь ласка, порадьте, дякую.


Відповіді:


35

Для всіх, хто хоче зробити це, я рекомендую поглянути на django-email-as-username, яке є досить комплексним рішенням, що включає виправлення адміністратора та createsuperuserкоманд управління, серед інших частин.

Редагувати : Починаючи з Django 1.5, ви повинні розглянути можливість користувацької моделі користувача замість django-email-as-username .


3
Якщо ви вирішите, що користувацька модель користувача є найкращим варіантом, можливо, ви захочете перевірити цей підручник у блозі групи caktus, він нагадує цей приклад, наведений у документах django, але дбає про деякі деталі, необхідні для виробничого середовища (наприклад, дозволи).
gaboroncancio

4
Для Django 1.5 та новіших версій програма django-custom-user містить спеціальну спеціальну модель користувача, яка реалізує це.
Джош Келлі,

29

Ось що ми робимо. Це не "повне" рішення, але воно робить більшу частину того, що ви шукаєте.

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)

Працює для мене. Хоча я бачу, що це бентежить майбутніх технічних працівників.
Нік Болтон

це, мабуть, найрозумніша відповідь, яку я коли-небудь бачив у SO.
Еммет Б

on admin Створити користувача це: exclude = ('email',) видає мені помилку про те, що ключ ['email'] відсутній у UserForm, це очевидно.
CristiC777

22

Ось один із способів зробити це, щоб прийняти як ім’я користувача, так і електронну адресу:

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

Не знаю, чи є якийсь параметр для встановлення форми автентифікації за замовчуванням, але ви також можете замінити URL-адресу в urls.py

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

Підвищення ValidationError дозволить запобігти 500 помилкам, коли надійде недійсний електронний лист. Використання визначення супер-параметра для "invalid_login" зберігає неоднозначне повідомлення про помилку (проти конкретного "жодного користувача за вказаною електронною поштою не знайдено"), яке потрібно для запобігання витоку інформації про те, чи зареєстрована електронна адреса для облікового запису у вашій службі. Якщо ця інформація не захищена у вашій архітектурі, можливо, буде більш дружнім повідомлення з більш інформативним повідомленням про помилку.


Я не використовую auth.views.login. Використовуючи спеціальний, це моя URL-адреса (r'accounts / login ',' login_view ',). Якщо я даю EmailAuthenticationForm, то помилка полягає в login_view () отримав несподіваний аргумент ключового слова 'authentication_form'
Раджа Саймон

Для мене це працювало чисто, я насправді використовував свій власний профіль користувача (оскільки в моїй архітектурі користувачеві не гарантовано прикріплену електронну адресу). Виглядає набагато приємніше, ніж налаштовувати модель користувача або робити ім’я користувача рівним електронному листу (оскільки це дозволяє зберігати електронну пошту друга приватною, наприклад, викриваючи ім’я користувача).
owenfi

8

Тепер Django пропонує повний приклад розширеної системи автентифікації з адміном та формою: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example

Ви можете в основному скопіювати / вставити його та адаптувати ( date_of_birthу моєму випадку це мені не потрібно ).

Він фактично доступний з Django 1.5 і досі доступний на сьогодні (django 1.7).


Я слідую підручнику djangoproject, і він чудово працює
Evhz,

7

Якщо ви збираєтеся розширити користувальницьку модель, вам все одно доведеться впровадити користувацьку модель користувача.

Ось приклад для Django 1.8. Django 1.7 зажадає трохи більше роботи, переважно зміна форм за замовчуванням (просто подивіться на UserChangeForm& UserCreationFormin django.contrib.auth.forms- це те, що вам потрібно в 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'

Я перевірив ваш підхід і працює, але в моєму випадку я мав додати usernameполе до SiteUserмоделі, тому що, коли я виконую 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'.
bgarcial

Я додав usernameполе до своєї Userмоделі з їх null=Trueатрибутом. У цьому записі в коду вставки я хотів показати реалізацію. pastebin.com/W1PgLrD9
bgarcial

2

Інші альтернативи виглядають для мене занадто складними, тому я написав фрагмент, який дозволяє автентифікувати за допомогою імені користувача, електронної пошти або того й іншого, а також увімкнути або вимкнути регістр. Я завантажив його в pip як django-подвійну автентифікацію .

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

працює ідеально ... для django 1.11.4



0

Найпростіший спосіб - це пошук імені користувача на основі електронної пошти у поданні для входу. Таким чином ви можете залишити все інше в спокої:

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

У своєму шаблоні встановіть наступну змінну відповідно, тобто

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

І дайте своїм ім’ям користувача / паролю правильні імена, тобто ім’я користувача, пароль.

ОНОВЛЕННЯ :

В якості альтернативи виклик if _is_valid_email (електронна пошта): можна замінити на if в імені користувача. Таким чином ви можете скинути функцію _is_valid_email. Це насправді залежить від того, як ви визначите своє ім’я користувача. Це не буде працювати, якщо ви дозволите в своїх іменах символу '@'.


1
Цей код є помилковим, оскільки ім'я користувача також може мати символ "@", тому, якщо "@" присутній, це не є електронною поштою.
MrKsn

залежить від вас насправді, я не дозволяю імені користувача мати символ @. Якщо ви це зробите, ви можете додати інший запит фільтра для пошуку в об'єкті користувача за іменем користувача. PS. ім'я користувача також може бути електронною поштою, тому ви повинні бути обережними з тим, як ви розробляєте управління користувачами.
radtek

Також ознайомтесь із імпортом django.core.validators validate_email. Ви можете спробувати, крім блоку ValidationError, з validate_email ('yourour@email.com'). Залежно від вашого додатка, він все ще може бути помилковим.
radtek

Звичайно, ви маєте рацію, це залежить від того, як налаштована логіка входу. Я згадав це, оскільки можливість "@" в імені користувача є типовою для django. Хтось може скопіювати ваш код і мати проблеми, коли користувач з іменем користувача "Gladi @ tor" не може увійти, оскільки код входу вважає це електронною поштою.
MrKsn

Гарний дзвінок. Сподіваюся, люди розуміють, що саме вони копіюють. Я додаю до перевірки електронної пошти та прокоментую поточну перевірку, залишивши це як альтернативу. Я думаю, єдиною причиною, чому я не використовував перевірку, крім моїх імен користувачів, які не мають @, є те, що це робить код менш залежним від django. Речі, як правило, змінюються від версії до версії. На здоров’я!
radtek

0

Я думаю, що найшвидший спосіб - це створити форму, успадковувану з UserCreateForm, а потім замінити usernameполе наforms.EmailField . Тоді для кожного нового користувача, який реєструється, їм потрібно підписати свою електронну адресу.

Наприклад:

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

Проголосував за кількома причинами. Ця відповідь - кращий спосіб зробити те саме. І навіщо підклас, коли ви можете просто визначити, який віджет використовувати безпосередньо в UserCreationFormкласі? І, будь ласка, не рекомендуйте виписувати, <input …коли, звичайно, {{form.username}}це краще.
Jonas G. Drange

0

Не впевнений, чи намагаються люди це зробити, але я знайшов приємний (і чистий) спосіб запитати лише електронну пошту, а потім встановити ім’я користувача як електронну пошту в поданні перед збереженням.

My UserForm вимагає лише електронної пошти та пароля:

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

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

Тоді, на мій погляд, я додаю таку логіку:

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
Не вдалося зберегти електронне повідомлення в імені користувача, оскільки це ім’я користувача становить 30 символів, а електронне повідомлення - 75 символів?
user2233706
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.