ยอมรับที่อยู่อีเมลเป็นชื่อผู้ใช้ใน Django


84

มีวิธีที่ดีในการทำสิ่งนี้ใน django โดยไม่ต้องใช้ระบบการตรวจสอบสิทธิ์ของฉันเอง ฉันต้องการให้ชื่อผู้ใช้เป็นที่อยู่อีเมลของผู้ใช้แทนการสร้างชื่อผู้ใช้

กรุณาแนะนำขอบคุณ


คำตอบ:


35

สำหรับใครก็ตามที่ต้องการทำเช่นนี้ฉันขอแนะนำให้ดูdjango-email-as-usernameซึ่งเป็นวิธีแก้ปัญหาที่ค่อนข้างครอบคลุมซึ่งรวมถึงการแก้ไขผู้ดูแลระบบและcreatesuperuserคำสั่งการจัดการรวมถึงบิตและส่วนอื่น ๆ

แก้ไข : ณ Django 1.5 เป็นต้นไปคุณควรพิจารณาการใช้รูปแบบการใช้ที่กำหนดเองแทนDjango อีเมล as-ชื่อผู้ใช้


3
หากคุณตัดสินใจว่าโมเดลผู้ใช้แบบกำหนดเองเป็นตัวเลือกที่ดีที่สุดคุณอาจต้องการตรวจสอบบทช่วยสอนนี้ในบล็อกของกลุ่ม caktus ซึ่งจะคล้ายกับตัวอย่างนี้ที่ให้ไว้ใน django docs แต่ให้ความสำคัญกับรายละเอียดบางอย่างที่จำเป็นสำหรับสภาพแวดล้อมการผลิต (เช่นสิทธิ์)
gaboroncancio

4
สำหรับ Django 1.5 ขึ้นไปแอปผู้ใช้แบบกำหนดเองของdjangoมีโมเดลผู้ใช้แบบกำหนดเองสำเร็จรูปที่ใช้สิ่งนี้
Josh Kelley

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)

เหมาะสำหรับฉัน แม้ว่าฉันจะเห็นว่าสิ่งนี้สับสนสำหรับผู้ดูแลในอนาคต
Nick Bolton

นั่นอาจเป็นคำตอบที่ฉลาดที่สุดที่ฉันเคยเห็นใน SO
Emmet B

ในผู้ดูแลระบบสร้างผู้ใช้สิ่งนี้: 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 รายการเมื่อมีการส่งอีเมลที่ไม่ถูกต้อง การใช้คำจำกัดความของ super สำหรับ "invalid_login" ทำให้ข้อความแสดงข้อผิดพลาดไม่ชัดเจน (เทียบกับ "ไม่พบผู้ใช้โดยอีเมลนั้น") ซึ่งจะต้องมีเพื่อป้องกันการรั่วไหลไม่ว่าจะมีการลงชื่อสมัครใช้ที่อยู่อีเมลสำหรับบัญชีในบริการของคุณหรือไม่ หากข้อมูลนั้นไม่ปลอดภัยในสถาปัตยกรรมของคุณอาจเป็นมิตรกว่าหากมีข้อความแสดงข้อผิดพลาดที่ให้ข้อมูลเพิ่มเติม


ฉันไม่ได้ใช้ auth.views.login ใช้กำหนดเองนี่คือ url url ของฉัน (r'accounts / login ',' login_view ',) ถ้าฉันให้ EmailAuthenticationForm ข้อผิดพลาดคือ login_view () ได้รับอาร์กิวเมนต์คำหลักที่ไม่คาดคิด 'authentication_form'
Raja Simon

สิ่งนี้ใช้ได้ผลสำหรับฉันจริงๆฉันใช้โปรไฟล์ผู้ใช้ที่กำหนดเองของฉัน (เนื่องจากในสถาปัตยกรรมของฉันผู้ใช้ไม่รับประกันว่าจะแนบที่อยู่อีเมล) ดูเหมือนจะดีกว่าการกำหนดรูปแบบผู้ใช้เองหรือทำให้ชื่อผู้ใช้เท่ากับอีเมล (เนื่องจากจะทำให้อีเมลของเพื่อนเป็นส่วนตัวในขณะที่เปิดเผยชื่อผู้ใช้เป็นต้น)
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& UserCreationFormใน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

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'

ฉันได้ทดสอบแนวทางของคุณแล้ว แต่ในกรณีของฉันฉันได้เพิ่ม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-dual-รับรองความถูกต้อง

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 (email): call สามารถแทนที่ด้วย if "@" ในชื่อผู้ใช้ ด้วยวิธีนี้คุณสามารถวางฟังก์ชัน _is_valid_email สิ่งนี้ขึ้นอยู่กับว่าคุณกำหนดชื่อผู้ใช้ของคุณอย่างไร จะใช้ไม่ได้หากคุณอนุญาตให้ใช้อักขระ "@" ในชื่อผู้ใช้ของคุณ


1
รหัสนี้มีข้อบกพร่องเนื่องจากชื่อผู้ใช้สามารถมีสัญลักษณ์ "@" ได้ดังนั้นหากมี "@" แสดงว่าไม่จำเป็นต้องมีอีเมล
MrKsn

ขึ้นอยู่กับคุณจริงๆฉันไม่อนุญาตให้ชื่อผู้ใช้มีสัญลักษณ์ @ หากคุณทำได้คุณสามารถเพิ่มคำค้นหาตัวกรองอื่นเพื่อค้นหาผ่านวัตถุผู้ใช้ด้วยชื่อผู้ใช้ ปล. ชื่อผู้ใช้อาจเป็นอีเมลได้ดังนั้นคุณต้องระมัดระวังในการออกแบบการจัดการผู้ใช้ของคุณ
radtek

ตรวจสอบด้วยจาก django.core.validators นำเข้า validate_email คุณสามารถลองทำได้ยกเว้นบล็อก ValidationError ด้วย validate_email ('your@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

ไม่แน่ใจว่ามีคนพยายามทำสิ่งนี้หรือไม่ แต่ฉันพบวิธีที่ดี (และสะอาด) ในการขออีเมลเท่านั้นจากนั้นตั้งชื่อผู้ใช้เป็นอีเมลในมุมมองก่อนบันทึก

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.