Why does Django's send_mail not work during testing? - python

I have an app that imports a number of user email addresses and creates accounts for them. To have them set their own password, I tried to use django's PasswordResetForm (in django.contrib.auth.forms). The password reset is called as soon as a user account has been created:
def reset_password(person):
form = PasswordResetForm({'email': person.email})
if form.is_valid():
form.save(from_email='myname#myserver.com')
I haven't gotten any further with testing than including a unit test that does this:
import password_reset_module
class TestPasswordReset(TestCase):
def setUp(self):
p = Person(email='test#test.com')
def test_send(self):
password_reset_module.reset_password(p)
No assertions, right now I just want to see if there is mail sent at all by monitoring the console in which I run:
python -m smtpd -n -c DebuggingServer localhost:1025
Saving the form calls django's send_mail. When running the testcase, the send_mail method returns 1. However, no mails show up in the console. The strange thing is that calling send_mail from django's interactive shell:
python manage.py shell
works fine. Mail shows up in the console. Clicking the forgot my password link in a browser also result in sent mails.
I have also tried the file based email backend to no avail.
Current settings.py email settings:
EMAIL_USE_TLS = False
EMAIL_HOST = 'localhost'
DEFAULT_FROM_EMAIL = 'myname#myserver.com'
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_PORT = 1025
Now wondering if I am missing something when calling the password reset, or is there a mailserver configuration issue at hands?

Your config is being overruled
In Section "Email Services", the
Django testing documentation says:
Django's test runner automatically redirects all
Django-sent email to a dummy outbox. [...]
During test running, each outgoing email is saved in
django.core.mail.outbox.
This is a simple list of all EmailMessage instances
that have been sent.
Huh?
The Django test runner will actually configure a different email backend for you (called locmem).
It is very convenient if you want to do unit-testing only
(without integration with an actual email server), but
very surprising if you don't know it.
(I am not using the Django test runner manage.py test,
but it happens anyway, presumably because I have
pytest-django installed which magically modifies my py.test.)
If you want to override the overriding and use the email configuration
given in your settings module, all you need to re-set is the
setting for the email backend, e.g. like this:
#django.test.utils.override_settings(
EMAIL_BACKEND='django.core.mail.backends.smtp.EmailBackend')
def test_send_email_with_real_SMTP(self):
...

It is probably worthwhile trying to turn this into a proper unit test, which of course you can then run as part of your automated test suite. For unit testing, probably the easiest way to check whether the mail was sent (and verify the contents of the mail if required) is to use Django's built-in in memory email backend - you can simply use the outbox attribute on this to get a list of sent mails:
https://docs.djangoproject.com/en/dev/topics/email/#in-memory-backend
This has the advantage of not requiring any infrastructure setup to support testing email sending, makes it very simple to assert the contents of your email, and this should also make the tests fast (when compared to actually sending the emails to an SMTP server).
Hope this helps.

From what I understand, you are running the following command while testing the unit.
python -m smtpd -n -c DebuggingServer localhost:1025
This command starts up a "dumb" SMTP server that recieves your emails and displays them on the terminal. Try running your site without this DebuggingServer set up and see if the mails are sent.
Here is the reference to the docs page

You can use the mailoutbox fixture of pytest-django:
A clean email outbox to which Django-generated emails are sent.
Example
from django.core import mail
def test_mail(mailoutbox):
mail.send_mail('subject', 'body', 'from#example.com', ['to#example.com'])
assert len(mailoutbox) == 1
m = mailoutbox[0]
assert m.subject == 'subject'
assert m.body == 'body'
assert m.from_email == 'from#example.com'
assert list(m.to) == ['to#example.com']

Related

Get Error 530 5.7.0 Authentication Required when sending email using Django Python

I am trying to send emails with G-Suite account using python in Django. As Google stoped the less secure App option for the new applications, I have to use Oauth2.
But when I start to send emails via smtplib, the ERROR:
smtplib.SMTPSenderRefused: (530, b'5.7.0 Authentication Required. Learn more at\n5.7.0 https://support.google.com/mail/?p=WantAuthError q4sm8418287pfl.175 - gsmtp'
And after looking up the reference, it means "530, "5.7.0", Must issue a STARTTLS command first."
However, I have added "server.starttls()". Could someone help me? Many thanks.
server = smtplib.SMTP('smtp.gmail.com', port=587)
server.ehlo('test')
server.starttls()
server.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string.encode()).decode("utf-8"))
server.sendmail(from_addr, to_addr, msg.as_string())
server.quit()
Per this https://stackabuse.com/how-to-send-emails-with-gmail-using-python/#:~:text=As%20for%20the%20actual%20Python,com'%2C%20465)%20server., you probably have 2-step verification turned on.
To enable your web apps to send SMTP's from/to your gmail, you will need to create an app-specific password for less secure apps.
Go to your Google Account.
Select Security.
Under "Signing in to Google," select App Passwords. You may need to
sign in. If you don’t have this option, it might be because: 2-Step
Verification is not set up for your account. 2-Step Verification is
only set up for security keys. Your account is through work, school,
or other organization. You turned on Advanced Protection.
At the bottom, choose Select app and choose
the app you are using and then Select device and choose the device
you’re using and then Generate.
Take this 16-character code in the yellow bar and place it in your
environment variables as your "EMAIL_HOST_PASSWORD"
EMAIL_BACKEND="django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST_USER ='youremail#gmail.com'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_PASSWORD = 'yourpassword'
EMAIL_USE_TLS = True
Add this on your settings. After that :
python manage.py shell
You will enter to Python shell. Then:
from django.core.mail import send_mail
Write this command on shell and after that:
send_mail(
"django test mail",
"this is django test mail",
"your.email#gmail.com",
"recipient.email#example.com",
fail_silently=False
)
If output is equal to:
1
, then you send email through your Gmail.

django send_mail() function taking several minutes

I'm trying to send emails in a function within my views.py file. I've set up the email in my settings file in the same manner as here.
Python Django Gmail SMTP setup
Email sending does work but it takes several minutes to occur which my users have been complaining about. I am receiving a gethostbyaddress error in my var/log/mail.log file which I'll post here. I used to get nginx timeout errors but put "proxy_read_timeout 150;" into my /etc/nginx/sites-enabled/django file.
http://www.nginxtips.com/upstream-timed-out-110-connection-timed-out-while-reading-response-header-from-upstream/
This solved the timeout errors when interacting with the website but the emails still take several minutes to load. I'm using a digitalocean django droplet and this slow speed has occured on all my droplets.
Here's my view function
#login_required
def AnnouncementPostView(request, leaguepk):
league = League.objects.get(pk=leaguepk)
lblog = league.blog
if request.method == 'POST':
form = AnnouncementPostForm(request.POST)
if form.is_valid():
posttext = request.POST['text']
newAnnouncement = Announcement(text=posttext, poster=request.user)
newAnnouncement.save()
lblog.announce.add(newAnnouncement)
titleText = "%s Announcement" % (league.name)
send_mail(titleText, posttext, settings.EMAIL_HOST_USER, ['mytestemail#gmail.com'], fail_silently=False)
return HttpResponseRedirect(reverse('league-view', args=[league.pk]))
else:
form = AnnouncementPostForm()
return render(request, 'simposting/announcementpost.html', {'form': form, 'league': league})
This has worked, the announcement is posted to the desired page and is even emailed, it's just a time problem, people have come to expect nearly instant emailing processes which makes the 2-3 minutes unacceptable, especially when signing up also causes the 2-3 minute wait.
One issue may be the fact that while trying to solve this issue with the DigitalOcean support team I changed my droplet name and the hostname to be the domain that I set up.
My current hostname and droplet name is mydomain.com. I have it setup that way in my /etc/hostname file. My /etc/hosts file looks like this
127.0.0.1 localhost.localdomain localhost mydomain.com
127.0.1.1 mydomain.com
My var/log/mail.log file responds with this whenever I try to send mail
Oct 6 16:13:24 "oldDropletName" sm-mta[13660]: gethostbyaddr(10.xxx.xx.x) failed: 1
Oct 6 16:13:24 "oldDropletName" sm-mta[13662]: starting daemon (8.14.4): SMTP+queueing#00:10:00
I hope this is enough information to help, it's been troubling for several weeks and usually I can either solve my problems by looking up stuff here or working with the support team but it's got us stumped. Thank you for taking the time to help!
Sending an email is a network bound task and you don't know how long it will take to finish exactly like in your case. Although there might be a latency in your network but it's better to do such task in an async fashion so your main thread is free.
I am using the following code in one my projects.
utils.py
import threading
from django.core.mail import EmailMessage
class EmailThread(threading.Thread):
def __init__(self, subject, html_content, recipient_list, sender):
self.subject = subject
self.recipient_list = recipient_list
self.html_content = html_content
self.sender = sender
threading.Thread.__init__(self)
def run(self):
msg = EmailMessage(self.subject, self.html_content, self.sender, self.recipient_list)
msg.content_subtype = 'html'
msg.send()
def send_html_mail(subject, html_content, recipient_list, sender):
EmailThread(subject, html_content, recipient_list, sender).start()
just call send_html_mail from your view.
I am not particularly familiar with sendmail (I use postfix) but I would suspect this is almost certainly related to something with sendmail and probably not Django. The second log entry has "SMTP+queueing#00:10:00". and this link would indicate that sendmail takes a flag on startup to determine how often to process the mail queue. You may want to look around your init or wherever your startup scripts are and see how sendmail is configured. Also, if you are using Gmail you really can't control any delays on their end, so along with determining the configuration of your mail server, you'll need to check logs for when actions are actually occurring such as the mail being queued/sent. Is the time that line shows in your log from when the view was executed? If so, it is in the hands of sendmail.

Sending a mail from Flask-Mail (SMTPSenderRefused 530)

The app configuration used in a Flask Mail application (following Miguel Grinberg Flask developlemt book) :
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
The Mail Username and Password variables have been set correctly and rechecked. While trying to send a message using the following code,
from flask.ext.mail import Message
from hello import mail
msg = Message('test subject', sender='same as MAIL_USERNAME', recipients=['check#mail.com'])
msg.body = 'text body'
msg.html = '<b>HTML</b> body'
with app.app_context():
mail.send(msg)
While sending, the application is again and again resulting in the following error:
SMTPSenderRefused: (530, '5.5.1 Authentication Required. Learn more at\n5.5.1 http://support.google.com/mail/bin/answer.py?answer=14257 qb10sm6828974pbb.9 - gsmtp', u'configured MAIL_USERNAME')
Any workaround for the error?
While digging into the issues faced, I rechecked the SMTP settings for Google,
Changing the
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
to
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
did the trick.
Also make sure that the full username is used as Gmail SMTP username, i.e., example#gmail.com as shown in the image above.
Hope this helps!!!
I also followed this book and get the same problem, after some digging here and there, I found out the root cause of the problem. However, I am not sure whether it will be the same case for you or not.
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
As you can see your flask app gets the your email credentials through os.environ.get(), and if you set this environment variables temporarily in your system, in my case Mac OSX, after your terminal session they will be gone, so you need to set them again for the next time you enter the terminal, like below:
export MAIL_USERNAME=**YOUR EMAIL**
export PASSWORD=**YOUR PASSWORD**
I got this error because of this scenario, in order to set them permanently you need to include these variables into .bash_profile file in your home directory.
You need to change your Google account settings. On this page, turn on the option to "Allow less secure apps".
As that page says:
Some apps and devices use less secure sign-in technology, which makes your account more vulnerable. You can turn off access for these apps, which we recommend, or turn on access if you want to use them despite the risks. Learn more
Do these 2 things to resolve:
Use this link and turn on 'Allow less secure apps'- https://myaccount.google.com/lesssecureapps
Use hard-coded value for email and password and it worked fine.
Simply in the file 'init.py', edit the following section:
Don't use os.environ.get
app.config['MAIL_USERNAME'] = 'youremail#gmail.com'
app.config['MAIL_PASSWORD'] = 'yourpassword'

Change Flask-Babel locale outside of request context for scheduled tasks

I run a job every hour that can send an email to users. When the email is sent, it needs to be in the language set by the user (saved in the db).
I can't figure out a way to set a different locale outside of a request context.
Here is what I would like to do:
def scheduled_task():
for user in users:
set_locale(user.locale)
print lazy_gettext(u"This text should be in your language")
You can also use method force_locale from package flask.ext.babel:
from flask.ext.babel import force_locale as babel_force_locale
english_version = _('Translate me')
with babel_force_locale('fr'):
french_version = _("Translate me")
Here's what its docstring say:
"""Temporarily overrides the currently selected locale.
Sometimes it is useful to switch the current locale to different one, do
some tasks and then revert back to the original one. For example, if the
user uses German on the web site, but you want to send them an email in
English, you can use this function as a context manager::
with force_locale('en_US'):
send_email(gettext('Hello!'), ...)
:param locale: The locale to temporary switch to (ex: 'en_US').
"""
One way to do it is to set up dummy request context:
with app.request_context({'wsgi.url_scheme': "", 'SERVER_PORT': "", 'SERVER_NAME': "", 'REQUEST_METHOD': ""}):
from flask import g
from flask_babel import refresh
# set your user class with locale info to Flask proxy
g.user = user
# refreshing the locale and timezeone
refresh()
print lazy_gettext(u"This text should be in your language")
Flask-Babel gets its locale settings by calling #babel.localeselector.
My localeselector looks something like this:
#babel.localeselector
def get_locale():
user = getattr(g, 'user', None)
if user is not None and user.locale:
return user.locale
return en_GB
Now, every time you change your g.user you should call refresh() to refresh Flask-Babel locale settings
#ZeWaren 's answer is great if you are using Flask-Babel, but if you are using Flask-BabelEx, there is no force_locale method.
This is a solution for Flask-BabelEx:
app = Flask(__name__.split('.')[0]) # See http://flask.pocoo.org/docs/0.11/api/#application-object
with app.test_request_context() as ctx:
ctx.babel_locale = Locale.parse(lang)
print _("Hello world")
Note the .split() is important if you are using blueprints. I struggled for a couple of hours because the app object was created with a root_path of 'app.main', which would make Babel look for translation files in 'app.main.translations' while they were in 'app.translations'. And it would silently fall back to NullTranslations i.e. not translating.
Assuming that Flask-Babel uses a request context scoped locale setting, you could try running your code with a temporary request context:
with app.request_context(environ):
do_something_with(request)
See http://flask.pocoo.org/docs/0.10/api/#flask.Flask.request_context

Google App Engine 1.7.6 Remote API error

Prior to the 1.7.6 dev server update, I was able to use /_ah/remote_api to upload test data to my dev server having to go through the authentication process by not entering a username and password (hitting return twice). Since the update, this now continuously asks for a username and password, regardless of what I put in - often says error incorrect username or password. I am currently targeting localhost:8080,
def auth_func():
return (raw_input('Username:'), getpass.getpass('Password:'))
remote_api_stub.ConfigureRemoteApi(None, '/_ah/remote_api', auth_func,
'localhost:8080')
though there are two new servers including the API server and the admin server. Has anyone else encountered this? if so, how did you fix it?
Thanks!
Jon
Apparently thanks to Tim - If you use the new dev_appserver then you need to sepecify a email like looking username and a single character as a password on the local development server in order for it to accept and move past the login stage.

Categories

Resources