How Django-allauth signal/adapter flow works ? - django

I have implemented Django-allauth using Facebook as a social account provider as it gives lots of information about its users.
Below is my custom signup form:
ACCOUNT_SIGNUP_FORM_CLASS = 'profiles.forms.MySignupForm'
class MySignupForm(forms.ModelForm):
class Meta:
model = get_user_model()
fields = ['email', 'first_name', 'last_name']
def __init__(self, *args, **kwargs):
super(MySignupForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.fields["email"].widget.input_type = "email" # ugly hack
self.helper.form_method = "POST"
self.helper.form_action = "account_signup"
self.helper.form_id = "signup_form"
self.helper.form_class = "signup"
self.helper.layout = Layout(
Field('email', placeholder="Enter Email", autofocus=""),
Field('first_name', placeholder="Enter First Name"),
Field('last_name', placeholder="Enter Last Name"),
Field('password1', placeholder="Enter Password"),
Field('password2', placeholder="Re-enter Password"),
Submit('sign_up', 'Sign up', css_class="btn-warning"),
)
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.email = self.cleaned_data['email']
user.save()
*What else can be added here in signup form ??? or to do the processing on received input i need to write adapter ??*
I have below signal receiver implemented.
#receiver(user_signed_up)
def set_initial_user_names(request, user, sociallogin=None, **kwargs):
if sociallogin:
***grab the data***
email_verified = sociallogin.account.extra_data['verified']
profile = models.Profile(user=user, avatar_url=picture_url, email_verified=email_verified)
profile.save() <--- *saving custom user profile here*
from allauth.account.models import EmailAddress
emails = EmailAddress.objects.filter(user=user, email=user.email)
for email in emails:
email.verified = email_verified
email.save() <--- *saving allauth Email Address instance.*
user.guess_display_name()
user.save() <----- *saving custom user model based on email address.*
Now if you see i am verifying if email is verified by social account(Facebook) if so i am updating Email Address instance of allauth.
But it happens twice.
allauth already updates the email address instance (account_emailaddress) during the login/sign up process. So database is getting hit twice for account_emailaddress table.
I want to control this scenario myself so that it only does it once..
Answer is Adapter, but if i write adapter, what happens to the signal receiver ??
Can someone tell me the adapter/signal flow ?? how it should be implemented. ??
I have gone through the documentation but still it would be nice if i get some direction on the flow like at which stage what i can control !!

Don't use a ModelForm for your custom signup form, as allauth needs to be in charge of constructing a User instance and saving it. Make it a plain form, simply deriving from django.forms.Form. Only use it in cases when you need to add additional inputs during signup. Looking at your form it seems you are not adding any additional fields, so you probably don't need a custom form at all.
Use the following form to ask users for their first/last name:
class SignupForm(forms.Form):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=30)
def signup(self, request, user):
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
user.save()

Related

How to get a token passed to Django allauth Signup

I am using allauth and want a user to be able to invite people via email to sign up to an account. When the new user signs up, I want to set their 'school' field to be the same as the user who invited them. In other words, they are part of the same school and I want to store that info when saving the new user.
To achieve this, I have an invite button which sends an email with the original user's school passed as a token, like so:
class AddUsers(TemplateView):
template_name = 'schools/add-users.html'
def get(self, request, *args, **kwargs):
add_user_form = AddUserForm()
context = {
'add_user_form': add_user_form
}
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
if 'add_users' in request.POST:
add_user_form = AddUserForm(request.POST)
if add_user_form.is_valid():
to_email_address = add_user_form.cleaned_data.get('email_field')
user = request.user
school = request.user.school
mail_subject = 'Invitation to create an account'
url = request.build_absolute_uri(reverse('account_signup'))
uid = urlsafe_base64_encode(force_bytes(school.pk))
token = account_activation_token.make_token(school)
activation_link = "{0}?uid={1}&token{2}".format(url, uid, token)
message = 'Hi,\n\nYour colleague ' + user.first_name + ' has invited you to sign up.\n\n'
message += 'Click the activation link below\n\n'
message += activation_link
email = EmailMessage(mail_subject, message, to=[to_email_address])
email.send()
return HttpResponseRedirect(reverse('schools:add-users',))
return HttpResponseRedirect(reverse('settings', ))
I override the allauth Signup form like this for a regular new user, but when the user has been invited by another user (i.e. via the email activation link with the school token), I plan to hide the school field and save the object held in the token value instead:
class SignupForm(ModelForm):
first_name = forms.CharField(max_length=30)
last_name = forms.CharField(max_length=150)
class Meta:
model = School
fields = ['school_name', 'authority']
def __init__(self, *args, **kwargs):
super(SignupForm, self).__init__(*args, **kwargs)
self.fields['first_name'].widget.attrs['class'] = 'form-control'
self.fields['first_name'].widget.attrs['placeholder'] = 'First name'
self.fields['last_name'].widget.attrs['class'] = 'form-control'
self.fields['last_name'].widget.attrs['placeholder'] = 'Last name'
self.fields['school_name'].widget.attrs['class'] = 'form-control'
self.fields['school_name'].widget.attrs['placeholder'] = 'School name'
self.fields['authority'].queryset = Authority.objects.get_all_authorities()
self.fields['authority'].label = 'Local authority'
self.fields['authority'].widget.attrs['class'] = 'form-control'
self.fields['authority'].empty_label = 'No local authority'
def signup(self, request, user):
school_name = self.cleaned_data['school_name']
first_name = self.cleaned_data['first_name']
last_name = self.cleaned_data['last_name']
authority = self.cleaned_data['authority']
school = School.objects.new_school_account(school_name, authority, 28)
user.school = school
user.first_name = first_name
user.last_name = last_name
user.save()
This works and sends an email with the token which correctly redirects to the allauth account_signup page. I can see how I can use the code in this solution to convert the token back again, but I don't know how/where I can actually access the token using allauth Signup in order to save the school when saving the new user details.
So my question is - I am passing a token to the allauth account_signup page but how can I get the token so I can process it?
token = request.GET.get('token')
You can get it in de dispatch method in the SignupView like this:
class MySignupView(SignupView):
def dispatch(self, request, *args, **kwargs):
token = request.GET.get('token')
return super(MySignupView, self).dispatch(request, *args, **kwargs)

Test UpdateView for useraccounts application

Test doesn't give status_code 302 in user profile UpdateView and so there's no updates occurs on the object
the model code
class User(AbstractBaseUser, PermissionsMixin):
'''
This a replaced user profile instead of the default django one
'''
language_choices=[('en',_('English')),('se',_('Swedish'))]
email=models.CharField(verbose_name=_('Email'), max_length=128, blank=False, unique=True)
first_name=models.CharField(verbose_name=_('First Name'), max_length=128)
last_name=models.CharField(verbose_name=_('Last Name'), max_length=128)
joined_at=models.DateField(
verbose_name=_('Joined at'),
auto_now_add=True,
blank=False
)
language=models.CharField(
verbose_name=_('Language'),
max_length=2,
choices=language_choices,
default=language_choices[0][0]
)
active=models.BooleanField(verbose_name=_('Active'), default=False)
is_superuser=models.BooleanField(verbose_name=_('Is Superuser'), default=False)
is_active=models.BooleanField(verbose_name=_('Is Active'), default=True)
is_staff=models.BooleanField(verbose_name=_('Is Staff'), default=False)
The form code
class EditUserForm(UserChangeForm):
'''
Profile form to update existing user information
'''
# error message for email matches
error_messages = {
'email_mismatch': _("The two email fields didn't match."),
}
# create field for email
email1 = forms.EmailField(
label=_("Email"),
widget=forms.EmailInput,
help_text=_("If you change your email your account will be inactive untill your reactivate by email link."),
)
# get the email from confirmed email field
email2 = forms.EmailField(
label=_("Confirm Email"),
widget=forms.EmailInput,
help_text=_("Enter the same email as before, for verification."),
)
# hide password field
password = ReadOnlyPasswordHashField(label="Password")
class Meta:
'''
Initial fields and model for the form
'''
model = models.User
fields = ('first_name','last_name','email1','email2', 'language')
def clean_email2(self):
'''
Method for if email and confirmed email are the same
This method works when confirmed email cleared
'''
# get the email from email field
email1 = self.cleaned_data.get("email1")
# get the email from confirmed email field
email2 = self.cleaned_data.get("email2")
# check if both emails are equal
if email1 and email2 and BaseUserManager.normalize_email(email1) != BaseUserManager.normalize_email(email2):
# give an error message if emails not matches
raise forms.ValidationError(
self.error_messages['email_mismatch'],
code='email_mismatch')
# return the confirmed email
return BaseUserManager.normalize_email(email2)
def save(self, commit=True):
'''
Method tosave the edited user data
'''
# get the initial method
user = super().save(commit=False)
# set the email on the model field
user.email = self.cleaned_data["email1"]
# save edited user data
if commit:
user.save()
return user
def __init__(self, *args, **kwargs):
'''
Method for initial values and functions for the SignUp form class
'''
# get user data from User model
user = get_user_model().objects.get(email=kwargs['instance'])
# get the initial form class values
super(EditUserForm, self).__init__(*args, **kwargs)
# Add the current email as the inital email
self.fields['email1'].initial = user.email
# Add the current email as the intial confirmed email
self.fields['email2'].initial = user.email
# Add help text in the password field for change
self.fields['password'].help_text=(
_("Raw passwords are not stored, so there is no way to see "
"this user's password, but you can change the password "
"using this form.")
.format(reverse(
'core:ChangePassword',
kwargs={'pk':user.pk})))
and the view code
class EditUser(UserPassesTestMixin, UpdateView):
'''
Class view to update user details
'''
# used template
template_name = 'core/edit.html'
# View model
model = models.User
# View form
form_class = forms.EditUserForm
def test_func(self):
return self.request.user == get_user_model().objects.get(pk=self.kwargs['pk'])
def get_success_url(self):
'''
Metho to redirect after a valid form
'''
# check if the email is verified
if self.request.user.active:
# get the user key
pk=self.request.user.pk
# redirect to profile details
return reverse_lazy('core:details', kwargs={'pk':pk})
else:
# send a verification email
return SendActivationEmail(self.request, self.request.user)
the test code
self.viewuser_url = reverse('core:details', kwargs={'pk':self.user.pk})
self.edituser_url = reverse('core:edit', kwargs={'pk':self.user.pk})
def test_edit_user_post(self):
first_name = 'Osama'
response = self.client.post(self.edituser_url,
data={
'first_name': first_name,
'last_name': self.last_name,
'email': self.email,
})
self.assertRedirects(response, self.viewuser_url)
self.user.refresh_from_db()
self.assertEqual(self.user.first_name, first_name)
I tried to get assertEqual for the status code and it gitves me 200 instead of 302
also I tried to enter the form details instead of model details and it gives me an error
The get test works fine and also permission test works great.. all the models, forms and urls test works perfect.
I don't know how I can test this..
If the form isn't valid, then the form will be re-rendered with errors and you'll get a 200 response.
To debug the problem, check response.context['form'].errors in your test to see what the problem is.
response = self.client.post(self.edituser_url,
data={
'first_name': first_name,
'last_name': self.last_name,
'email': self.email,
})
print(response.context['form'].errors
Your view uses EditUserForm, but you are not posting any values for email1 or email2, so there is probably something in the errors about missing data.

Stripe with Django - make form clean() method return value that isn't a form field

I am integrating Stripe payment processing into my Django app, and I can't figure out the 'correct' way to verify the customer's card information and insert a row into my Users table that contains the user's Stripe Customer ID.
Ideally, I'd love to do something along the lines of the following, in which my CheckoutForm verifies card details and raises a form ValidationError if they are incorrect. However, using this solution, I can't figure a way to get the customer.id that's generated out of the clean() function.
forms.py
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
// I can now get a customer.id from this 'customer' variable, which I want to insert into my database
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
views.py
# If the form is valid...
if form.is_valid():
# Create a new user
user = get_user_model().objects.create_user(email=form.cleaned_data['email'], stripe_customer_id=<<<I want the customer.id generated in my form's clean() method to go here>>>)
user.save()
The only other solution I can think of is to run the stripe.Customer.create() function in views.py after the form is validated. That'll work, but it doesn't seem like the 'right' way to code things, since as I understand it all validation of form fields is supposed to be done within forms.py.
What's the proper Django coding practice in this situation? Should I just move my card validation code to views.py, or is there a cleaner way to keep the card validation code within forms.py and get the customer.id out of it?
I don't think that proper Django coding practice is any different from Python coding practice in this situation. Since Django form is just a class, you can define property for customer. Something like this:
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
_customer = None
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
self.customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
#property
def customer(self):
return self._customer
#customer.setter
def customer(self, value):
self._customer = value
Then it the views.py after form.is_valid(), you'd call this property.
if form.is_valid():
customer = form.customer
Or maybe #property is an overkill and you could simply do it like this:
class CheckoutForm(forms.Form):
email = forms.EmailField(label='E-mail address', max_length=128, widget=forms.EmailInput(attrs={'class': 'form-control'}))
stripe_token = forms.CharField(label='Stripe token', widget=forms.HiddenInput)
customer = None
def clean(self):
cleaned_data = super().clean()
stripe_token = cleaned_data.get('stripe_token')
email = cleaned_data.get('email')
try:
self.customer = stripe.Customer.create(
email=email,
source=stripe_token,
)
except:
raise forms.ValidationError("It looks like your card details are incorrect!")
... and still form.customer in views.py.
I guess both should work, but I haven't tested the code.

Django Contact Emails Not Working

I have a contact form on my Django site and when submitted it goes to the success url but the email is not sent, and the logging set up in the form_valid function is never called.
Here is the code for the view:
class ContactView(FormView):
form_class = ContactForm
template_name = "contact.html"
success_url = "/contact-sent/"
def form_valid(self, form):
message = "{name} / {email} said: ".format(
name=form.cleaned_data.get('name'),
email=form.cleaned_data.get('email'))
message += "\n\n{0}".format(form.cleaned_data.get('message'))
recipients = [recipient for recipient in settings.LIST_OF_EMAIL_RECIPIENTS]
try:
send_mail(
subject=form.cleaned_data.get('subject').strip(),
message=message,
from_email='XXX#XXX.com'
recipient_list=recipients,
)
logger = logging.getLogger(__name__)
logger.info("Contact Email sent successfully")
except Exception as e:
logger = logging.getLogger(__name__)
logger.warning("Contact Email failed to send\nInfo: %s" % e)
return super(ContactView, self).form_valid(form)
and the form, which is a model form using floppyforms and crispyforms:
class ContactForm(ffuture.ModelForm):
def __init__(self, *args, **kwargs):
super(ContactForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-contactForm'
self.helper.form_class = 'contact-form'
self.helper.form_method = 'post'
self.helper.form_action = 'submit-feedback'
self.helper.form_tag = True
self.helper.layout = Layout(
Fieldset(
_('Contact Us'),
Field('name', placeholder=_('Name'), css_class='input-medium'),
Field('email', placeholder=_('Email'), css_class='input-xlarge'),
Field('subject', placeholder=_('Subject'), css_class='input-xlarge'),
Field('message', placeholder=_('Add a message'), rows='5', css_class='input-xlarge'),
),
)
self.helper.add_input(Submit('submit', _('Submit')))
class Meta:
model = Feedback
fields = ('name', 'email', 'subject', 'message')
and the model:
#python_2_unicode_compatible
class Feedback(models.Model):
subject = models.CharField(max_length=100)
message = models.TextField(max_length=500)
name = models.CharField(max_length=100)
email = models.EmailField()
creation_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.topic
class Meta:
verbose_name_plural = _("Feedback")
verbose_name = _("Feedback")
The emails are never sent, and the Feedback model is never updated in the admin.
Anyone have any ideas as to why this would be happening? I've been pouring over the code and looking at other examples and none of them seem much different from what I have. I am stumped as to why it is not sending the emails, nor calling any of the logging in the form_valid method.
Ideally I want it to send the contact email to the recipients, and also save the information entered into the Feedback model.
Two other things that may be relevant:
The site is currently running on Apache and for the from_email set in the view I never configured any credentials for it. I am unsure of where to do this. But even if that's the reason the email is not being sent, I don't see why the Feedback model would not be updated.
Thanks for any help you guys can provide, I've been stuck on this for a bit now.
EDIT:
I was thinking it could be the send_mail function that is the issue, but I added logging above the try block and that wasn't called either, so I am now sure that the form_valid method is never being called.
As for the Feedback model not being saved, I realized this is probably because I am never actually saving the form.
I am a bit confused here, because I am using a model form for the contact so the user submitting the form is not logged in. The objective was to both send the email, and store the results in the database. But I can't seem to figure out how I should go about saving the modelform without a valid user.
Would it be enough to just do
feedback = form.save()
inside my form_valid method in the ContactView? Or do I want a save method inside my model form?
The solution was to just call
form.save()
and store the model. The user being logged in did not matter as the fields on the model don't reference a logged in user at all.

django allauth custom signup form to assign different groups

I have two types of users in the system, I want to assign the appropriate group at the time of signing up. Referring to How to customize user profile when using django-allauth, I thought I can override the Signup form and do something like:
class CustomSignupForm(forms.Form):
login_widget = forms.TextInput(attrs={'type': 'email',
'placeholder': _('email'),
'autofocus': 'autofocus',
'class': 'form-control'
})
email = forms.EmailField(label='Email', widget=login_widget)
password = PasswordField(label='Password', widget=forms.PasswordInput(attrs={'class': 'form-control'}))
password2 = PasswordField(label='Re-type Password', widget=forms.PasswordInput(attrs={'class': 'form-control'}))
def save(self, request, user):
role = request.GET.get('type')
print(role)
group = role or "group1"
g = Group.objects.get(name=group)
user.groups.add(g)
user.save()
But I keep getting the error below:
save() missing 1 required positional argument: 'user'
Also, I have configured allauth to use email to login.
Thanks for your help.
signup is the method to override not save.
class LocalSignupForm(forms.Form):
pass
def signup(self, request, user):
role = request.session.get('user_type')
group = role or "Default"
g = Group.objects.get(name=group)
user.groups.add(g)
user.save()
Also the settings has to be
ACCOUNT_SIGNUP_FORM_CLASS = 'useraccount.forms.LocalSignupForm'