I have read similar questions but I cannot understand why this is not working. I am trying to send a reactivation email to user when they click on a link. The activation link is generated properly when the user signs up and the email is sent, but when I try to call the same function again to reactivate link, it is not working saying it is missing one parameter. Here's the function:
acounts/views.py
def sendactivationmail(request, user):
# Creating token and masing uid of user
token = default_token_generator.make_token(user)
uid = urlsafe_base64_encode(force_bytes(user.pk)).decode()
# Sending email for email verification
subject = 'Please Verify Your Email'
from_email = settings.DEFAULT_FROM_EMAIL
to_email = [user.email]
context = {
'url': settings.BASE_URL + reverse('user-activation-link', kwargs={
'uidb64': uid,
'token': token,
}),
}
contact_message = render_to_string('verify_email.html', context)
plain_message = strip_tags(contact_message)
send_mail(subject, plain_message, from_email, to_email, html_message=contact_message, fail_silently=True)
return redirect(reverse('login'))
accounts/urls.py
from django.conf.urls import url
from .views import *
from . import views
from urllib import request
from django.contrib.auth.models import User
urlpatterns = [
url(r'^reactivate/$', views.sendactivationmail(request, User), name='reactivate'),
Is this the right way to pass on the request and user parameters to the function?
EDIT: This is the link that is used to redirect user:
<p>Click here to resend the activation email.</p>
change your urlpattern to this
urlpatterns = [
url(r'^reactivate/$', views.sendactivationmail, name='reactivate'),
.....
]
and html to, but the user has to be logged in to use this link. remember that
<p>Click here to resend the activation email.</p>
Related
I'm building a flask application and am trying send post requests in the terminal using the requests library, but it is giving a 400 error, but I don't know what's malformed about the request.
Here's the signup page I'm trying to reach in my browser:
And the subsequent result after submitting the post:
However, when I try and recreate this post request and use the same info in the command line I get a 400 status code.
Here's the underlying code I use to generate it:
FORM:
class SignupForm(FlaskForm):
"""Main form for sign up page"""
email = EmailField(label = 'email', validators = [DataRequired(), Email()])
password = PasswordField(label = 'password',
validators = [DataRequired(), Length(min=9, max=20),
check_valid_password])
sec_question = SelectField(label = 'Security Question',
validators = [DataRequired()],
choices = ['What is your favorite band?',
'What is your favorite pets name?',
'What was the last name of your favorite childhoold teacher?',
'In what city were you born?',
'What is your best friends last name?',
'What is the country you would most like to visit?'])
sec_answer = TextField(label='your answer. not case sensitive', validators=[DataRequired(), Length(4, 200)])
Here's the flask code that handles the incoming request:
#user.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
user_exists = User.query.filter_by(email = request.form.get('email')).first()
if user_exists:
flash('User already exists with that e-mail. Please login instead', 'warning')
return redirect(url_for('user.signup', form = form))
user = User()
form.populate_obj(user)
user.password = User.encrypt_password(request.form.get('password'))
user.save()
# send confirmation e-mail using celery
from app.blueprints.main.tasks import send_email
token = generate_new_account_token(form.email.data)
send_email.delay(
subject = 'Welcome to CPM! Please confirm your account',
recipients = [form.email.data],
template = 'email/user_new_confirm.html',
confirm_url = url_for('user.confirm_new', token = token, _external=True)
)
flash(f'A confirmation e-mail has been sent to {form.email.data}', 'info')
redirect(url_for('user.signup', form=form))
return render_template('blueprints/user/signup.html', form=form)
I can't for the life of me seem to get this to work inside a post request. It's running on localhost:5000 on my computer.
Here are two examples that illustrate the issue. My successful GET request:
My unsuccessful POST request:
data = {'email': 'myemail#gmail.com', 'password': 'Passw0rd!', 'sec_question': 'What is your favorite band?', 'sec_answer': 'paul simon'}
requests.post('http://localhost:5000/signup', data = data)
As a security measure, forms need a CSRF token to be sent along with the POST request to prevent CSRF (Cross-Site Request Forgery, sometimes abbreviated XSRF) attacks. Browsers will handle that for you, but in cURL you will have to retrieve and use a CSRF token manually (which is usually a pain in the neck).
For development purposes, you can temporarily disable the CSRF protection. Here is an explanation on how to do that with Flask.
Essentially, just add the #csrf.exempt decorator to your signup method:
from flask_wtf import csrf
#user.route('/signup', methods=['GET', 'POST'])
#csrf.exempt
def signup():
# The rest of the method
Alternatively, you can quickly and easily disable CSRF for all your views by setting WTF_CSRF_CHECK_DEFAULT to False.
WARNING: Never push a form view to production with CSRF protection disabled.
My Django site sends out an email confirmation link, and everything seems to work fine on desktop. On mobile devices, however, the link text is highlighted blue and underlined (i.e. it looks like a link) but nothing happens when the user clicks it. It should open a browser tab and say, "you have confirmed your email, etc"
Thanks for any tips!
views.py:
def signup(request):
if request.method == 'POST':
#send signup form
email_address = request.POST['email']
if request.POST['password1'] == request.POST['password2']:
try:
user = User.objects.get(email=email_address)
return render(request, 'accounts/signup.html', {'error': "Email already in use."})
except User.DoesNotExist:
user = User.objects.create_user(request.POST['email'], password=request.POST['password1'])
#auth.login(request, user)
#send email confirmation link then redirect:
#user = User.objects.get(email=email_address)
current_site = get_current_site(request)
mail_subject = 'Welcome to My Site'
plain_msg = 'Thank you for signing up to My Site! Click this link to activate your account:\n\n'+current_site.domain+'/accounts/activated/'+urlsafe_base64_encode(force_bytes(user.pk)).decode()+'/'+account_activation_token.make_token(user)+'/'
msg = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>title</title></head><body>Confirm your email address to continue:<br/>Confirm my email address</body></html>'
print(msg)
send_mail(mail_subject, plain_msg, 'my_email#gmail.com', [email_address], html_message=msg)
return render(request, 'accounts/activation-link-sent.html', {'email': email_address})
#truncated for Stack Overflow post
You should use build_absolute_uri to create fully qualified links that include the current domain and protocol, then use this link in your email
link = request.build_absolute_uri('/accounts/activated/'+urlsafe_base64_encode(force_bytes(user.pk)).decode()+'/'+account_activation_token.make_token(user)+'/')
Do you have a url pattern that matches this URL? You should consider using reverse for building URLs
path = reverse('activate', kwargs={
'pk': user.pk,
'token': account_activation_token.make_token(user)
})
link = request.build_absolute_uri(path)
i am using Ngrok to run my website with https://, and when i send an activation email to myself, instead of seeing 'https://something.ngrok.io/...' i get 'http://localhost:8000/...'.
This is the code responsible for sending the Activation Email which - to my opinion - it should send Ngrok's domain not the development domain ...
...
current_site = get_current_site(request)
mail_subject = 'Activate your customer account.'
message = render_to_string('user_register_email/account_activation_email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
'token': user_token.make_token(user),
})
receiver = form.cleaned_data['email']
email = EmailMessage(
mail_subject, message, to=[receiver]
)
email.send()
messages.info(
request, f'An activation link has been sent to %s' % (receiver))
return redirect('accounts:login')
is it possible ?
current_site.domain returns the value set on the site instance, you can either change it from the admin panel, or you can use request.get_host() instead
I have an ActivationTokenGenerator that create a token that will be used for account verification that will be send by email. For example I configured it like this with parameters including timestamp, id, and user active status:
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
class ActivationTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active)
account_activation_token = ActivationTokenGenerator()
Then I use the account_activation_token for generating token in a verification email I sent with a send_mail.
#classmethod
def send_email(cls, request, user):
current_site = get_current_site(request)
mail_subject = 'Activate your Poros account.'
message = render_to_string('email_verification.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
'token': account_activation_token.make_token(user),
})
to_email = user.email
email = EmailMessage(
mail_subject, message, to=[to_email]
)
email.send()
Everything looks perfect email sent with a token that included inside a url with pattern like this:
url(r'activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
activate, name='activate'),
and looks like this in the email:
http://{{ domain }}{% url 'activate' uidb64=uid token=token %}
then when the link clicked it will call this activate view:
from django.http import HttpResponse
from django.contrib.auth import login
from django.utils.encoding import force_text
from django.utils.http import urlsafe_base64_decode
from accounts.utilities import account_activation_token
from accounts.models import User
def activate(request, uidb64, token):
try:
id = force_text(urlsafe_base64_decode(uidb64))
print(id)
user = User.objects.get(pk=id)
print(user)
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
print(token)
if user is not None and account_activation_token.check_token(user, token):
user.is_active = True
user.save()
login(request, user)
return HttpResponse('Thank you for your email confirmation. Now you can login your account.')
else:
return HttpResponse('Activation link is invalid!')
But the activate view always return activation link is invalid. I tried to track it down into the account_activation_token.check_token(user, token)
I tried to go deeper and debug Inside the Django PasswordResetTokenGenerator I found the check_token() has step to check the timestamp/uid with a line like this:
# Check that the timestamp/uid has not been tampered with
if not constant_time_compare(self._make_token_with_timestamp(user, ts), token):
return False
which called constant_time_compare:
def constant_time_compare(val1, val2):
"""Return True if the two strings are equal, False otherwise."""
return hmac.compare_digest(force_bytes(val1), force_bytes(val2))
I don't really understand what's going on in the lower level of this token_check. What is the better approach to solve this?
Change this line in :
return six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active)
to this line:
return six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.username)
And it works. I workaround with this method. But don't know the cause.
I have a bug like in subject of this question:
"to" argument must be a list or tuple
I want to create a email form and I can't find a error. Maybe someone help me?
My files are:
forms.py
from django import forms
class WyslijEmail(forms.Form):
name = forms.CharField(max_length=25)
subject = forms.CharField(max_length=255)
email = forms.EmailField()
message = forms.CharField(required=True, widget=forms.Textarea)
views.py
from django.shortcuts import render, redirect
from django.core.mail import send_mail, BadHeaderError
from .forms import WyslijEmail
def kontakt(request):
if request.method == 'GET':
form = WyslijEmail()
else:
form = WyslijEmail(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
subject = form.cleaned_data['subject']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
try:
send_mail(name,subject,email,message, ['admin#gmail.com',])
except BadHeaderError:
return render('Niewlasciwe cos tam')
return redirect('sukces')
return render(request,'bajki/kontakt.html', {'form':form})
def sukces(request):
return render('Sukces udało sie')
kontakt.html
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<p><input type="submit" value="Wyślij wiadomość"></p>
</form>
What I must define to fix this problem?
Os so I updated my files and now theyare looking like this:
views.py
from django.shortcuts import render, redirect
from django.core.mail import send_mail
from .forms import WyslijEmail
def kontakt(request):
sent = False
if request.method == 'POST':
form = WyslijEmail(request.POST)
if form.is_valid():
subject = form.cleaned_data['subject']
from_email = form.cleaned_data['from_email']
message = form.cleaned_data['message']
send_mail(subject, message, from_email,['login#gmail.com'])
sent = True
else:
form = WyslijEmail()
return render(request,'bajki/kontakt.html', {'form':form, 'sent': sent})
But its a second problem. When I trying to sent email from page the system responded me a error like this:
SMTPAuthenticationError at /kontakt/
(535, b'5.7.8 Username and Password not accepted. Learn more at\n5.7.8 https://support.google.com/mail/?p=BadCredentials h63sm1453929ljf.36 - gsmtp')
I change the settings in my google account to accept the all emails. I dont know how to fix it.
Here's how to structure send_mail from the Django Documentation:
send_mail(subject,
message,
from_email,
recipient_list,
fail_silently=False,
auth_user=None,
auth_password=None,
connection=None,
html_message=None)
You should change the arguments in your send_mail function accordingly.
Your send_mail method should have four arguments.
send_mail(subject, message, from_email, recipient_list)
EX:
subject == string
message == string
from_email == string
recipient_list == list
Looks like you have additional argument 'name'. And you have not mentioned the from_email argument.
I believe your problem is at the send_mail() function.
I would advise you save your form.clean_data in a variable for reusability
i.e cd = form.clean_data.
Then for the send_mail pass the parameter correctly, and wrap your cd['to'] in a list
send_mail(subject, message, 'admin#myblog.com', [cd['to']])
The issues in your updated code is that the to_email argument takes a list. Instead of writing ['login#gmail.com'], you need to write ['login#gmail.com',] with a comma to make it into a list.