I have successfully setup django-allauth along with a custom user model which let's users sign in directly using email and password or through Facebook, in which case email is taken from Facebook and saved in the Email field of the custom user model. I have also created a mobile field which stays empty as of now.
I want to allow users to log in using their Facebook, Email or MOBILE. Unfortunately that field is not unique=True in the model. I have thought of capturing the mobile and fetching the associated email address and then use that along with the password to log in any user.
However, I don't know how to extend the SIGN IN form that comes with django-allauth or the block of code that signs a user in where I can change it to meet my need.
As of now I don't find any of my current code relevant to this problem, but if it helps I am willing to provide it upon mention.
The following solution worked for me. I put the code in the forms.py file.
from allauth.account.forms import LoginForm
from auth_project import settings
class CustomLoginForm(LoginForm):
def __init__(self, *args, **kwargs):
super(LoginForm, self).__init__(*args, **kwargs)
if settings.ACCOUNT_AUTHENTICATION_METHOD == "email":
login_widget = forms.TextInput(attrs={'type': 'text',
'placeholder':
('Mobile Number'),
'autofocus': 'autofocus'})
login_field = forms.CharField(label=("Mobile"),
widget=login_widget)
self.fields["login"] = login_field
set_form_field_order(self, ["login", "password", "remember"])
def user_credentials(self):
credentials = {}
mobile = self.cleaned_data["login"]
login = CustomUser.objects.filter(mobile=mobile).values('email')[0]['email']
if settings.ACCOUNT_AUTHENTICATION_METHOD == "email":
credentials["email"] = login
credentials["password"] = self.cleaned_data["password"]
return credentials
# this is to set the form field in order
# careful with the indentation
def set_form_field_order(form, fields_order):
if hasattr(form.fields, 'keyOrder'):
form.fields.keyOrder = fields_order
else:
# Python 2.7+
from collections import OrderedDict
assert isinstance(form.fields, OrderedDict)
form.fields = OrderedDict((f, form.fields[f])
for f in fields_order)
Thanks.
Related
i am trying to use django allauth to send confirmation emails on sign-up,after successfully sending the email i need top have a way to identify if the user has confirmed his email,dajngo allauth provides somthing for that
from allauth.account.signals import email_confirmed
#receiver(email_confirmed)
def email_confirmed_(request, email_address, **kwargs):
user = email_address.user
user.email_verified = True
user.save()
what i can't figure out how to do is access the user.email_verified that i saved
#api_view(['GET'])
def current_user(request,user):
current_user = request.user
return Response({
'id': current_user.id,
'verified': user.email_verified #???????
})
Usually you doing something like this to access a model instance:
(considering the name of your model is User) - User.objects.filter(id=current_user.id)
you can define a variable like this:
user_obj = User.objects.filter(id=current_user.id)
and than you accessing it's field as:
user_obj.email_verified
is this what you need?
Is it possible to use Django allauth with the authentication method set to 'email' when using it on multiple sites?
I'm aiming to allow a user with the email address bob#example.com to create an account at site1.com and a separate account at site2.com.
In order to use email authentication, I need to leave UNIQUE_EMAIL set to True in the settings but this prevents users who already have accounts in one site from creating accounts in the other site.
I am assuming you'd like to allow the same email to be registered separately for each of the sites in your Django setup.
Looking at the allauth code; it appears that it is infeasible to do so at the moment, likely because allauth does not take into account site ID as part of the User signup process.
class AppSettings(object):
class AuthenticationMethod:
USERNAME = 'username'
EMAIL = 'email'
USERNAME_EMAIL = 'username_email'
class EmailVerificationMethod:
# After signing up, keep the user account inactive until the email
# address is verified
MANDATORY = 'mandatory'
# Allow login with unverified e-mail (e-mail verification is
# still sent)
OPTIONAL = 'optional'
# Don't send e-mail verification mails during signup
NONE = 'none'
def __init__(self, prefix):
self.prefix = prefix
# If login is by email, email must be required
assert (not self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.EMAIL) or self.EMAIL_REQUIRED
# If login includes email, login must be unique
assert (self.AUTHENTICATION_METHOD ==
self.AuthenticationMethod.USERNAME) or self.UNIQUE_EMAIL
One way to do this would be as follows:
- Keep allauth AUTHENTICATION_METHOD as Username
- Store the site alongside the User information, perhaps in a UserProfile or by overriding the User Model.
- Make the combination of Email and Site unique.
- Override the LoginView such that the user enters email; you can translate the combination of Email, Site to a Unique User account and username; which you can pass on to allauth to perform login.
Assuming you use the Sites framework; your code would look something like this:
from allauth.account.views import LoginView
from django.core.exceptions import ObjectDoesNotExist
class CustomLoginView(LoginView):
def get_user():
email = request.POST.get('email')
current_site = Site.objects.get_current()
try:
user = User.objects.get(email=email, site=current_site)
except ObjectDoesNotExist:
pass # Handle Error: Perhaps redirect to signup
return user
def dispatch(self, request, *args, **kwargs):
user = self.get_user()
request.POST = request.POST.copy()
request.POST['username'] = user.username
return super(CustomLoginView, self).dispatch(request, *args, **kwargs)
Then monkey-patch the LoginView with the custom login view:
allauth.account.views.LoginView = CustomLoginView
Related Reading on setting up a Site FK, and custom auth backends:
How to get unique users across multiple Django sites powered by the "sites" framework?
https://docs.djangoproject.com/en/dev/topics/auth/#writing-an-authentication-backend
I'm using django allauth for social login/signup. Also, I've my own signup form as an alternate login/signup. Following are the fields that I'm fetching from the user in the alternate form.
class Profile(models.Model):
col1 = models.CharField(max_length=50, blank=True, null=True)
col2 = models.CharField(max_length=50, blank=True, null=True)
user = models.OneToOneField(User)
So, when the user signs up, it asks for additional fields as well(col1, col2), apart from username, email and password.
Following is the signup view.
user = User.objects.create_user(username, user_email, user_pass)
Profile.objects.create(user=user, col1=col1, col2=col2)
return
So, whenever the user signs up via the alternate form, the above view is called up.
Now, in contrast, when the user signs up from social account FB, it does not ask for extra info, ie col1/col2. It directly signs up without asking for extra info, neither I want it to ask.
I then create a row in Profile model post signup using signals.
#receiver(user_signed_up)
def create_profile(request, user, sociallogin=None, **kwargs):
if sociallogin:
if sociallogin.account.provider == 'facebook':
data = sociallogin.account.extra_data
col1 = data.get('col1')
col2 = data.get('col2')
Profile.objects.create(user=user, col1=col1, col2=col2)
So, (1) my problem is when creating a user using alternate form, no record is inserted in allauth tables, which i find weird.
(2) Consider, I signed up using alternate form using E1 as email id. Now I signup via allauth(FB) with the same id, it throws an error.
(3) How do I send confirmation mail to the users who signed up in alternate form using all_auth.
I played around with the library a bit and finally found the solution to my question. I'm pasting it over here for other's to review.
Add a signal pre_social_login that'll check for conditions.
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin=None, **kwargs):
if sociallogin:
user = User.objects.filter(email=email).first()
# If user already exists in custom local form, then login directly.
# Save/Update his details in social login tables.
if user:
# create or update social login tables to maintain the uniformity in the code.
token = sociallogin.token.token
socialaccount = SocialAccount.objects.filter(user=user).first()
if socialaccount: # If it exists, then update social tables.
# updating account.
socialaccount.extra_data = extra_data
socialaccount.save()
# updating token.
SocialToken.objects.filter(account=socialaccount) \
.update(token=token)
else: # else create.
# saving Social EmailAddress.
EmailAddress.objects.create(email=email, user=user)
# saving social account.
provider = 'facebook'
kwargs = {
'uid': extra_data.get('id'),
'provider': provider,
'extra_data': extra_data,
'user': user
}
socialaccount = SocialAccount.objects.create(**kwargs)
# saving social token.
app = SocialApp.objects.get(provider=provider)
kwargs = {
'token': token,
'account': socialaccount,
'app': app
}
SocialToken.objects.create(**kwargs)
# finally login.
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
raise ImmediateHttpResponse(redirect(reverse('index')))
I want social users are only able to login without password. Therefore, I want to disable password reset at /accounts/password/reset/ for social users.
I know that I need to add a conditional in this code at allauth.accounts.forms.py
class ResetPasswordForm(forms.Form):
email = forms.EmailField(
label=_("E-mail"),
required=True,
widget=forms.TextInput(attrs={"type": "email", "size": "30"}))
def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email)
if not self.users.exists():
raise forms.ValidationError(_("The e-mail address is not assigned"
" to any user account"))
return self.cleaned_data["email"]
I'm thinking about this solution, but I don't know how to do it properly:
elif SocialAccount.objects.filter(provider='facebook'):
raise forms.ValidationError(_("You are using a social account.")
Allauth extendability is not a problem. self.users contain all users with provided email but if you have setting ACCOUNT_UNIQUE_EMAIL=True or just didn't change it (so it's True by default) then you can take just first user. Every user contain socialaccount_set related manager. So we just filter that set by provider name and if there is any item then this user has related socialaccount. So you can inherit allauth form and put additional check there:
# forms.py
class MyResetPasswordForm(ResetPasswordForm):
def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email)
if not self.users.exists():
raise forms.ValidationError(_("The e-mail address is not assigned"
" to any user account"))
# custom code start
elif self.users.first().socialaccount_set.filter(provider='facebook').exists():
raise forms.ValidationError(_("You are using a social account.")
# custom code end
return self.cleaned_data["email"]
And then override allauth settings to use your form in their reset password view:
# your settings
ACCOUNT_FORMS = {
'reset_password': 'your_app.forms.MyResetPasswordForm'
}
I have not tested this code so it can contain typos so feel free to post any further questions.
#bellum answer is kind off right but now Allauth return a list on filter_users_by_email(email)
So this worked for me
from allauth.account.forms import SignupForm, LoginForm, ResetPasswordForm
from allauth.account.adapter import get_adapter
from django.utils.translation import ugettext_lazy as _
from allauth.account.utils import filter_users_by_email
class MyResetPasswordForm(ResetPasswordForm):
def clean_email(self):
email = self.cleaned_data["email"]
email = get_adapter().clean_email(email)
self.users = filter_users_by_email(email)
if not self.users:
raise forms.ValidationError(_("Unfortunately no account with that"
" email was found"))
# custom code start
elif self.users[0].socialaccount_set.filter(provider='google').exists():
raise forms.ValidationError(_("Looks like the email/password combination was not used. Maybe try Social?"))
# custom code end
return self.cleaned_data["email"]
Note: I am using google social authentication if you are using Facebook then change it to facebook.
I have run into the following error trying to create a user in django:
>>> email = 'verylongemail#verylongemail.com'
>>> user_object = User.objects.create_user(username=email, email=email, password='password')
Data truncated for column 'username' at row 1
It seems Django has a limit on the number of chars allowed in a username. How would I get around this?
I've had to modify the auth_user table by hand to make the field longer and then convert emails into a username by removing the # symbol and the period (maybe other characters too, it's really not a great solution). Then, you have to write a custom auth backend that authenticates a user based on their email, not the username, since you just need to store the username to appease django.
In other words, don't use the username field for auth anymore, use the email field and just store the username as a version of the email to make Django happy.
Their official response on this topic is that many sites prefer usernames for auth. It really depends if you are making a social site or just a private site for users.
If you override the form for Django users you can actually pull this off pretty gracefully.
class CustomUserCreationForm(UserCreationForm):
"""
The form that handles our custom user creation
Currently this is only used by the admin, but it
would make sense to allow users to register on their own later
"""
email = forms.EmailField(required=True)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
class Meta:
model = User
fields = ('first_name','last_name','email')
and then in your backends.py you could put
class EmailAsUsernameBackend(ModelBackend):
"""
Try to log the user in treating given username as email.
We do not want superusers here as well
"""
def authenticate(self, username, password):
try:
user = User.objects.get(email=username)
if user.check_password(password):
if user.is_superuser():
pass
else: return user
except User.DoesNotExist: return None
then in your admin.py you could override with
class UserCreationForm(CustomUserCreationForm):
"""
This overrides django's requirements on creating a user
We only need email, first_name, last_name
We're going to email the password
"""
def __init__(self, *args, **kwargs):
super(UserCreationForm, self).__init__(*args, **kwargs)
# let's require these fields
self.fields['email'].required = True
self.fields['first_name'].required = True
self.fields['last_name'].required = True
# let's not require these since we're going to send a reset email to start their account
self.fields['username'].required = False
self.fields['password1'].required = False
self.fields['password2'].required = False
Mine has a few other modifications, but this should get you on the right track.
You have to modify the username length field so that syncdb will create the proper length varchar and you also have to modify the AuthenticationForm to allow greater values as well or else your users won't be able to log in.
from django.contrib.auth.forms import AuthenticationForm
AuthenticationForm.base_fields['username'].max_length = 150
AuthenticationForm.base_fields['username'].widget.attrs['maxlength'] = 150
AuthenticationForm.base_fields['username'].validators[0].limit_value = 150