How to programmatically trigger password reset email in django 1.7.6?
Question:
I’ve encountered a problem where I had to load more than 200 new users into my django app and right away send them a password reset email. This had to happen only once, done only by me and run quietly on backend. Surfing the internet brought me only to one more or less right answer: Trigger password reset email in django without browser?. The only problem was is that this post was about 4 years old and of course when I tried the solution, it didn’t work…
Answers:
Two most valuable points from the link I mentioned:
- In most recent version of Django, we need to call
form.is_valid()
- Sending of email is done upon
save()
.
Here is how I queried users that I needed and sent each of them a password reset link:
def find_users_and_send_email():
from django.http import HttpRequest
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.models import User
import logging
logger = logging.getLogger(__name__)
users = User.objects.filter(date_joined__gt = '2015-04-16')
for user in users:
try:
if user.email:
logger.info("Sending email for to this email:", user.email)
form = PasswordResetForm({'email': user.email})
assert form.is_valid()
request = HttpRequest()
request.META['SERVER_NAME'] = 'help.mydomain.com'
request.META['SERVER_PORT'] = '443'
form.save(
request= request,
use_https=True,
from_email="[email protected]",
email_template_name='registration/password_reset_email.html')
except Exception as e:
logger.warning(str(e))
continue
return 'done'
- Usually
PasswordResetForm
works with a "request" from the front-end, which I didn’t have. So I simply created one.
- When I followed the example in the link, it failed.. It couldn’t find server name in the request. (Makes sense because I instantiated my request out of nowhere)
- When I fixed server name, it asked for the server port. Since I used https, my port is 443, otherwise, use default port.
- If you use https, don’t forget to indicate it when you save the form
use_https=True
I found it useful to wrap @chabislav’s answer up in a custom management command which can then be reused as necessary. Note that I eliminated the user object filter, as I knew that I wanted to send password reset instructions to all users.
# myproject/myapp/management/commands/send_password_reset_emails.py
from django.core.management.base import BaseCommand, CommandError
# I use a custom user model, so importing that rather than the standard django.contrib.auth.models
from users.models import User
from django.http import HttpRequest
from django.contrib.auth.forms import PasswordResetForm
class Command(BaseCommand):
help = 'Emails password reset instructions to all users'
def handle(self, *args, **kwargs):
users = User.objects.all()
for user in users:
try:
if user.email:
print("Sending email for to this email:", user.email)
form = PasswordResetForm({'email': user.email})
assert form.is_valid()
request = HttpRequest()
request.META['SERVER_NAME'] = 'www.example.com'
request.META['SERVER_PORT'] = 443
form.save(
request= request,
use_https=True,
from_email="[email protected]",
email_template_name='registration/password_reset_email.html'
)
except Exception as e:
print(e)
continue
return 'done'
This can then be run easily with python manage.py send_password_reset_emails
.
Note that I got an error message (550, b'5.7.1 Relaying denied')
, which ended up being an issue with the way that my localhost email server was set up, but once that was cleared this worked quite well. Having it in a management command is also nice because it doesn’t clog up code that you use on a regular basis (views).
If you are reading this with django 3.x or 4.x AND are using django-allauth, this will work for you:
from allauth.account.forms import ResetPasswordForm # note the different form import
form = ResetPasswordForm({"email": "[email protected]"})
assert form.is_valid() # required because we need clean() executed
form.save(
request=None,
from_email="[email protected]",
)
Please note that this will take all values from the allauth config, using django.contrib.sites
.
I’ve encountered a problem where I had to load more than 200 new users into my django app and right away send them a password reset email. This had to happen only once, done only by me and run quietly on backend. Surfing the internet brought me only to one more or less right answer: Trigger password reset email in django without browser?. The only problem was is that this post was about 4 years old and of course when I tried the solution, it didn’t work…
Two most valuable points from the link I mentioned:
- In most recent version of Django, we need to call
form.is_valid()
- Sending of email is done upon
save()
.
Here is how I queried users that I needed and sent each of them a password reset link:
def find_users_and_send_email():
from django.http import HttpRequest
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.models import User
import logging
logger = logging.getLogger(__name__)
users = User.objects.filter(date_joined__gt = '2015-04-16')
for user in users:
try:
if user.email:
logger.info("Sending email for to this email:", user.email)
form = PasswordResetForm({'email': user.email})
assert form.is_valid()
request = HttpRequest()
request.META['SERVER_NAME'] = 'help.mydomain.com'
request.META['SERVER_PORT'] = '443'
form.save(
request= request,
use_https=True,
from_email="[email protected]",
email_template_name='registration/password_reset_email.html')
except Exception as e:
logger.warning(str(e))
continue
return 'done'
- Usually
PasswordResetForm
works with a "request" from the front-end, which I didn’t have. So I simply created one. - When I followed the example in the link, it failed.. It couldn’t find server name in the request. (Makes sense because I instantiated my request out of nowhere)
- When I fixed server name, it asked for the server port. Since I used https, my port is 443, otherwise, use default port.
- If you use https, don’t forget to indicate it when you save the form
use_https=True
I found it useful to wrap @chabislav’s answer up in a custom management command which can then be reused as necessary. Note that I eliminated the user object filter, as I knew that I wanted to send password reset instructions to all users.
# myproject/myapp/management/commands/send_password_reset_emails.py
from django.core.management.base import BaseCommand, CommandError
# I use a custom user model, so importing that rather than the standard django.contrib.auth.models
from users.models import User
from django.http import HttpRequest
from django.contrib.auth.forms import PasswordResetForm
class Command(BaseCommand):
help = 'Emails password reset instructions to all users'
def handle(self, *args, **kwargs):
users = User.objects.all()
for user in users:
try:
if user.email:
print("Sending email for to this email:", user.email)
form = PasswordResetForm({'email': user.email})
assert form.is_valid()
request = HttpRequest()
request.META['SERVER_NAME'] = 'www.example.com'
request.META['SERVER_PORT'] = 443
form.save(
request= request,
use_https=True,
from_email="[email protected]",
email_template_name='registration/password_reset_email.html'
)
except Exception as e:
print(e)
continue
return 'done'
This can then be run easily with python manage.py send_password_reset_emails
.
Note that I got an error message (550, b'5.7.1 Relaying denied')
, which ended up being an issue with the way that my localhost email server was set up, but once that was cleared this worked quite well. Having it in a management command is also nice because it doesn’t clog up code that you use on a regular basis (views).
If you are reading this with django 3.x or 4.x AND are using django-allauth, this will work for you:
from allauth.account.forms import ResetPasswordForm # note the different form import
form = ResetPasswordForm({"email": "[email protected]"})
assert form.is_valid() # required because we need clean() executed
form.save(
request=None,
from_email="[email protected]",
)
Please note that this will take all values from the allauth config, using django.contrib.sites
.