Existe uma boa maneira de fazer isso no Django sem lançar meu próprio sistema de autenticação? Quero que o nome de usuário seja o endereço de e-mail do usuário em vez de criar um nome de usuário.
Por favor, avise, obrigado.
Respostas:
Para qualquer pessoa que queira fazer isso, eu recomendo dar uma olhada em django-email-as-username que é uma solução bastante abrangente, que inclui remendar o administrador e os createsuperuser
comandos de gerenciamento, entre outras partes.
Edit : A partir do Django 1.5 em diante, você deve considerar o uso de um modelo de usuário personalizado ao invés de django-email-as-username .
Aqui está o que fazemos. Não é uma solução "completa", mas faz muito do que você está procurando.
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)
Esta é uma maneira de fazer isso para que o nome de usuário e o e-mail sejam aceitos:
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
Não sei se há alguma configuração para definir o formulário de autenticação padrão, mas você também pode substituir o url em urls.py
url(r'^accounts/login/$', 'django.contrib.auth.views.login', { 'authentication_form': EmailAuthenticationForm }, name='login'),
Aumentar o ValidationError evitará 500 erros quando um e-mail inválido for enviado. Usar a definição do super para "invalid_login" mantém a mensagem de erro ambígua (em vez de um específico "nenhum usuário por esse e-mail encontrado"), que seria necessário para evitar vazar se um endereço de e-mail está inscrito em uma conta em seu serviço. Se essas informações não estiverem seguras em sua arquitetura, pode ser mais fácil ter uma mensagem de erro mais informativa.
Django agora fornece um exemplo completo de um sistema de autenticação estendido com admin e formulário: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#a-full-example
Você pode basicamente copiar / colar e adaptar (eu não precisava do date_of_birth
no meu caso).
Na verdade, ele está disponível desde o Django 1.5 e ainda está disponível a partir de agora (django 1.7).
Se você for estender o modelo de usuário, terá que implementar o modelo de usuário personalizado de qualquer maneira.
Aqui está um exemplo para Django 1.8. Django 1.7 exigiria um pouco mais de trabalho, principalmente mudando as formas padrão (apenas dê uma olhada em UserChangeForm
& UserCreationForm
in django.contrib.auth.forms
- é isso que você precisa no 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
campo para SiteUser
modelar, porque, quando executo o python manage.py makemigrations ...
comando, recebo esta saída:ERRORS: <class 'accounts.admin.UserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not an attribute of 'accounts.User'.
username
campo ao meu User
modelo com seu null=True
atributo. Nesta entrada de pasta de colar eu queria mostrar a implementação. pastebin.com/W1PgLrD9
Outras alternativas parecem muito complexas para mim, então escrevi um snippet que permite autenticar usando nome de usuário, e-mail ou ambos, e também habilitar ou desabilitar a distinção entre maiúsculas e minúsculas. Eu enviei para o pip como 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
A última versão do django-registration permite uma boa personalização e pode fazer o trabalho - docs aqui https://bitbucket.org/ubernostrum/django-registration/src/fad7080fe769/docs/backend-api.rst
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()
funcionando perfeitamente ... para django 1.11.4
você também pode encontrar uma discussão interessante sobre este tópico no link abaixo:
A maneira mais fácil é consultar o nome de usuário com base no e-mail na visualização de login. Dessa forma, você pode deixar todo o resto sozinho:
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))
Em seu modelo, defina a próxima variável de acordo, ou seja,
<form method="post" class="form-login" action="{% url 'login' %}?next={{ request.GET.next }}" accept-charset="UTF-8">
E forneça às entradas de nome de usuário / senha os nomes corretos, ou seja, nome de usuário e senha.
ATUALIZAÇÃO :
Alternativamente, o if _is_valid_email (email): call pode ser substituído por if '@' no nome de usuário. Dessa forma, você pode descartar a função _is_valid_email. Isso realmente depende de como você define seu nome de usuário. Não funcionará se você permitir o caractere '@' em seus nomes de usuário.
Acho que a maneira mais rápida é criar um formulário herdar de UserCreateForm
e, em seguida, substituir o username
campo por forms.EmailField
. Então, para cada novo usuário de registro, eles precisam se conectar com seu endereço de e-mail.
Por exemplo:
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
classe? E, por favor, não recomendo escrever <input …
quando, com certeza, {{form.username}}
é melhor.
Não tenho certeza se as pessoas estão tentando fazer isso, mas descobri uma maneira legal (e limpa) de apenas pedir o e-mail e definir o nome de usuário como o e-mail na visualização antes de salvar.
Meu UserForm requer apenas o e-mail e a senha:
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
class Meta:
model = User
fields = ('email', 'password')
Então, na minha opinião, adiciono a seguinte lógica:
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()