Chấp nhận địa chỉ email làm tên người dùng trong Django


84

Có cách nào tốt để thực hiện việc này trong django mà không cần sử dụng hệ thống xác thực của riêng tôi không? Tôi muốn tên người dùng là địa chỉ email của người dùng thay vì họ tạo tên người dùng.

Xin hãy cho tôi lời khuyên. Cảm ơn.


Câu trả lời:


35

Đối với bất kỳ ai khác muốn làm điều này, tôi khuyên bạn nên xem qua django-email-as-username , đây là một giải pháp khá toàn diện, bao gồm vá lỗi quản trị và các createsuperuserlệnh quản lý, cùng với các bit và mảnh khác.

Chỉnh sửa : Kể từ Django 1.5 trở đi, bạn nên cân nhắc sử dụng mô hình người dùng tùy chỉnh thay vì django-email-as-username .


3
Nếu bạn quyết định rằng mô hình người dùng tùy chỉnh là lựa chọn tốt nhất, bạn có thể muốn xem hướng dẫn này trên blog của nhóm caktus, nó giống với ví dụ này được đưa ra trong tài liệu django nhưng quan tâm đến một số chi tiết cần thiết cho môi trường sản xuất (ví dụ: Quyền).
gaboroncancio

4
Đối với Django 1.5 trở lên, ứng dụng django-custom-user chứa một mô hình người dùng tùy chỉnh đóng hộp thực hiện điều này.
Josh Kelley

29

Đây là những gì chúng tôi làm. Nó không phải là một giải pháp "hoàn chỉnh", nhưng nó làm được nhiều điều bạn đang tìm kiếm.

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)

Làm việc cho tôi. Mặc dù tôi có thể thấy điều này gây nhầm lẫn cho những người bảo trì trong tương lai.
Nick Bolton

đó có lẽ là câu trả lời thông minh nhất mà tôi từng thấy trong SO.
Emmet B

trên quản trị Tạo Người dùng this :lude = ('email',) ném cho tôi một lỗi rằng khóa ['email'] bị thiếu trong UserForm, điều đó là hiển nhiên.
CristiC777

22

Đây là một cách để làm điều đó để cả tên người dùng và email đều được chấp nhận:

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

Không biết có cài đặt nào đó để đặt biểu mẫu Xác thực mặc định hay không nhưng bạn cũng có thể ghi đè url trong urls.py

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

Việc nâng ValidationError sẽ tránh được 500 lỗi khi gửi một email không hợp lệ. Việc sử dụng định nghĩa của cấp cao cho "invalid_login" khiến thông báo lỗi không rõ ràng (so với một thông báo lỗi cụ thể "không tìm thấy người dùng theo email đó") sẽ được yêu cầu để ngăn rò rỉ cho dù địa chỉ email có được đăng ký tài khoản trên dịch vụ của bạn hay không. Nếu thông tin đó không an toàn trong kiến ​​trúc của bạn, thì thông báo lỗi có nhiều thông tin hơn có thể hữu ích hơn.


Tôi không sử dụng auth.views.login. Sử dụng tùy chỉnh, đây là url url của tôi (r'accounts / login ',' login_view ',). Nếu tôi đang cung cấp EmailAuthenticationForm, thì lỗi là login_view () nhận được một đối số từ khóa không mong muốn là 'authentic_form'
Raja Simon

Điều này hoạt động rõ ràng đối với tôi, tôi thực sự đã sử dụng hồ sơ người dùng tùy chỉnh của mình (vì trong kiến ​​trúc của tôi Người dùng không được đảm bảo có địa chỉ email đính kèm). Có vẻ đẹp hơn nhiều so với việc tùy chỉnh mô hình người dùng hoặc làm cho tên người dùng ngang bằng với một email (vì điều đó cho phép giữ email của bạn bè ở chế độ riêng tư trong khi để lộ tên người dùng chẳng hạn).
owenfi

8

Django hiện cung cấp một ví dụ đầy đủ về hệ thống xác thực mở rộng với quản trị viên và biểu mẫu: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example

Về cơ bản, bạn có thể sao chép / dán nó và điều chỉnh (tôi không cần date_of_birthtrong trường hợp của mình).

Nó thực sự có sẵn kể từ Django 1.5 và vẫn có sẵn cho đến nay (django 1.7).


Tôi đi theo các djangoproject hướng dẫn và nó hoạt động hoàn hảo
Evhz

7

Nếu bạn định mở rộng mô hình người dùng, bạn sẽ phải triển khai mô hình người dùng tùy chỉnh.

Đây là một ví dụ cho Django 1.8. Django 1.7 sẽ yêu cầu nhiều công việc hơn một chút, chủ yếu là thay đổi các biểu mẫu mặc định (chỉ cần xem UserChangeForm& UserCreationFormin django.contrib.auth.forms- đó là những gì bạn cần trong 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

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

Tôi đã kiểm tra cách tiếp cận và hoạt động của bạn, nhưng trong trường hợp của tôi, tôi đã thêm usernametrường vào SiteUsermô hình, bởi vì, khi tôi thực hiện python manage.py makemigrations ...lệnh, tôi nhận được kết quả này:ERRORS: <class 'accounts.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not an attribute of 'accounts.User'.
bgarcial

Tôi đã thêm usernametrường vào Usermô hình của mình với null=Truethuộc tính của chúng . Trong mục nhập thùng dán này, tôi muốn hiển thị việc triển khai. pastebin.com/W1PgLrD9
bgarcial

2

Các lựa chọn thay thế khác trông quá phức tạp đối với tôi, vì vậy tôi đã viết một đoạn mã cho phép xác thực bằng tên người dùng, email hoặc cả hai và cũng có thể bật hoặc tắt phân biệt chữ hoa chữ thường. Tôi đã tải nó lên pip với tư cách là xác thực kép 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()

hoạt động hoàn hảo ... cho django 1.11.4



0

Cách dễ nhất là tra cứu tên người dùng dựa trên email trong giao diện đăng nhập. Bằng cách đó, bạn có thể để mọi thứ khác yên:

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

Trong mẫu của bạn, hãy đặt biến tiếp theo cho phù hợp, tức là

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

Và nhập tên người dùng / mật khẩu của bạn đúng tên, tức là tên người dùng, mật khẩu.

CẬP NHẬT :

Ngoài ra, lệnh if _is_valid_email (email): có thể được thay thế bằng if '@' trong tên người dùng. Bằng cách đó, bạn có thể bỏ hàm _is_valid_email. Điều này thực sự phụ thuộc vào cách bạn xác định tên người dùng của mình. Nó sẽ không hoạt động nếu bạn cho phép ký tự '@' trong tên người dùng của mình.


1
Mã này có lỗi, vì tên người dùng cũng có thể có ký hiệu '@', vì vậy nếu có '@' thì không cần email.
MrKsn

thực sự phụ thuộc vào bạn, tôi không cho phép tên người dùng có ký hiệu @. Nếu có, bạn có thể thêm một truy vấn lọc khác để tìm kiếm thông qua đối tượng Người dùng theo tên người dùng. Tái bút. tên người dùng cũng có thể là một email, vì vậy bạn phải cẩn thận với cách bạn thiết kế quản lý người dùng của mình.
radtek

Ngoài ra, hãy kiểm tra, từ django.core.validators, nhập validate_email. Bạn có thể thử ngoại trừ khối ValidationError với validate_email ('your@email.com '). Nó vẫn có thể gặp lỗi tùy thuộc vào ứng dụng của bạn.
radtek

Tất nhiên, bạn nói đúng, nó phụ thuộc vào cách thiết lập logic đăng nhập. Tôi đã đề cập điều đó vì khả năng '@' trong tên người dùng là mặc định của django. Ai đó có thể sao chép mã của bạn và gặp sự cố khi người dùng có tên người dùng 'Gladi @ tor' không thể đăng nhập vì mã đăng nhập cho rằng đó là email.
MrKsn

Tốt cuộc gọi. Tôi hy vọng mọi người hiểu những gì họ đang sao chép. Tôi sẽ thêm xác thực email và nhận xét xác thực hiện tại, hãy để nó như một sự thay thế. Tôi đoán lý do duy nhất khác khiến tôi không sử dụng xác thực ngoài việc tên người dùng của tôi không có @, đó là nó làm cho mã ít phụ thuộc vào django hơn. Mọi thứ có xu hướng thay đổi từ phiên bản này sang phiên bản khác. Chúc mừng!
radtek

0

Tôi nghĩ cách nhanh nhất là tạo một biểu mẫu kế thừa từ UserCreateForm, sau đó ghi đè usernametrường với forms.EmailField. Sau đó, đối với mỗi người dùng đăng ký mới, họ cần đăng nhập bằng địa chỉ email của họ.

Ví dụ:

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

Bị phản đối vì một số lý do. Câu trả lời này là một cách tốt hơn để làm điều tương tự. Và tại sao lại phân lớp khi bạn chỉ có thể xác định widget nào sẽ sử dụng trực tiếp trong UserCreationFormlớp? Và xin đừng khuyên bạn nên viết ra <input …khi, chắc chắn, {{form.username}}là tốt hơn.
Jonas G. Drange

0

Không chắc liệu mọi người có đang cố gắng thực hiện điều này hay không, nhưng tôi thấy một cách hay (và rõ ràng) là chỉ yêu cầu email và sau đó đặt tên người dùng làm email trong chế độ xem trước khi lưu.

UserForm của tôi chỉ yêu cầu email và mật khẩu:

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

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

Sau đó, theo quan điểm của tôi, tôi thêm logic sau:

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
Không thể lưu trữ email trong tên người dùng gây ra sự cố vì tên người dùng là 30 ký tự trong khi email là 75 ký tự?
user2233706
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.