Email not sending on create_user - python

I am trying to use flask-security and have become helplessly stuck trying to send an email when create_user is called.
The user is being created. The roles are working, however, I can not find any documentation explaining how to send an email to the user when they sign up.
To keep things simple I am just using the code from the doc's to create a user before first request.
# Create a user to test with
#app.before_first_request
def create_user():
db.create_all()
# Create the Roles "admin" and "office_owner" -- unless they already exist
user_datastore.find_or_create_role(name='admin', description='Administrator')
user_datastore.find_or_create_role(name='office_owner', description='Office owner')
if not user_datastore.get_user('test#gmail.com'):
user_datastore.create_user(email='test#gmail.com', password=flask_security.utils.hash_password('password'))
# Commit any database changes; the User and Roles must exist before we can add a Role to the User
db.session.commit()
db.session.commit()
Here is my flask-mail settings
from flask import Flask
from flask_mail import Mail, Message
import os
mail_keys = {
'password_key': os.environ['EMAIL_PASSWORD'],
}
app =Flask(__name__)
mail=Mail(app)
app.config['MAIL_SERVER']='smtp.sendgrid.net'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = 'apikey'
app.config['MAIL_PASSWORD'] = mail_keys['password_key']
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail=Mail(app)
config.py is
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SQLALCHEMY_DATABASE_URI='postgresql://postgres///'
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECURITY_PASSWORD_SALT = 'hjdsafjkhalkj'
SECURITY_PASSWORD_HASH='bcrypt'
SECURITY_CONFIRMABLE=True
SECURITY_REGISTERABLE=True
SECURITY_RECOVERABLE=True
SECURITY_CHANGEABLE=True
settings.py
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from config import Config
from flask_mail import Mail, Message
from flask_migrate import Migrate
app=Flask(__name__)
app.config.from_object(Config)
mail=Mail(app)
db=SQLAlchemy(app)
migrate = Migrate(app, db)

The simplest answer is the create_user is an admin function. Flask-Security doesn't know anything about this since it isn't associated with a view.
Out of the box - as documented in the configuration.rst it can send mail for:
SEND_REGISTER_EMAIL, SEND_PASSWORD_CHANGE_EMAIL, SEND_PASSWORD_RESET_EMAIL, SEND_PASSWORD_RESET_NOTICE_EMAIL
that is all that is built in. Most sites would enable SECURITY_REGISTERABLE and allow users to sign up - at which point they will get an email.

It's been a while, but you can not send emails while TESTING = True in app.config :)

In your flask-mail settings file. Define a function for sending mail like this:
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = text_body
msg.html = html_body
mail.send(msg)
Call this method where ever you want to trigger a mail action. For example:
#app.before_first_request
def create_user():
db.create_all() # Create the Roles "admin" and "office_owner" -- unless they already exist
user_datastore.find_or_create_role(name='admin', description='Administrator')
user_datastore.find_or_create_role(name='office_owner', description='Office owner')
if not user_datastore.get_user('test#gmail.com'):
user_datastore.create_user(email='test#gmail.com', password=flask_security.utils.hash_password('password')) # Commit any database changes; the User and Roles must exist before we can add a Role to the User
db.session.commit()
send_mail(subject="AccountCreation",Sender="somesender",recepient="somerecepient",text_body="congratulations account created ",text_html=None)
db.session.commit()

Related

Problem with Flask Blueprints, canĀ“t remove the /home route, or app crashes

Hi there I'm creating a Flask web app and now I have to create more cruds, so I decided to modularize the app using Blueprints.
I have a Login function on main.py that allows me to enter the app interface:
app.route('/', methods=['GET', 'POST'])
def login():
# Output message if something goes wrong...
msg = ''
# Check if "username" and "password" POST requests exist (user submitted form)
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
# Create variables for easy access
username = request.form['username']
password = request.form['password']
# Check if account exists using MySQL
cursor = mysql.connection.cursor(MySQLdb.cursors.DictCursor)
cursor.execute(
'SELECT * FROM accounts WHERE username = %s AND password = %s', (username, password,))
# Fetch one record and return result
account = cursor.fetchone()
# If account exists in accounts table in out database
if account:
# Create session data, we can access this data in other routes
session['loggedin'] = True
session['id'] = account['id']
session['username'] = account['username']
# Redirect to home page
return redirect(url_for('client.home'))
else:
# Account doesnt exist or username/password incorrect
msg = 'Incorrect username/password!'
# Show the login form with message (if any)
return render_template('index.html', msg=msg)
It redirects to this Blueprint:
from flask import Blueprint, render_template
from flask import render_template, request, redirect, url_for, session, flash
from flask_mysqldb import MySQL
import MySQLdb.cursors
import re
from extension import mysql
client = Blueprint('client', __name__,
static_folder="../static", template_folder="../templates")
#client.route('/')
def home():
if 'loggedin' in session:
cur = mysql.connection.cursor()
cur.execute('SELECT * FROM cliente')
data = cur.fetchall()
# User is loggedin show them the home page
return render_template('home.html', username=session['username'], cliente=data)
# User is not loggedin redirect to login page
return redirect(url_for('login'))
It works just fine, but with a condition. On my main.py I also have this:
#app.route('/home')
def home():
pass
And this is the problem, I don't know why I should keep this route on my main.py because if I delete it my app crashes and throws me this error:
werkzeug.routing.BuildError
werkzeug.routing.BuildError: Could not build url for endpoint 'home'. Did you mean 'client.home' instead?
I have no idea why does this happens.
Why should I keep this route? or What I'm doing wrong?
Could you please give me a hand?
I've trying to change the redirect to using multiple routes, but if I delete that /home route.. my app crashes anyway.
in url_for look for the name of the function ie url_for('function_name', parameters)
so to avoid the crash better to change the name of main.py home function to something else.
Solved: I had one ref to Home on other file: Layout.html.
Just removed the ref and it's solved

Flask-Mail [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)

I'm developing a Flask application that implements a user registration system. The application uses Flask-Mail and itsdangerous to confirm a user's registration and reset their password via email. I configured Flask-Mail to use the recommended server settings provided by the email host that I'm using.
MAIL_PORT = 587
MAIL_USE_SSL = False
MAIL_USE_TLS = True
At first, things were working fine; I could submit emails without any issues. However, seemingly without changing any configuration settings, I now receive the following error when attempting to submit an email using Flask-Mail:
[SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123)
I'm not sure where the problem is, and I am wondering if something has changed on the email provider's end? I have tried setting MAIL_PORT = 25 with MAIL_USE_SSL=False and MAIL_USE_TLS=False; and, MAIL_PORT = 465 with MAIL_USE_SSL=True and MAIL_USE_TLS=False as well. Using the former, I receive the same error as with port 587, but using the latter I'm receiving STARTTLS extension not supported by server.
I'm running the Flask app in development mode at localhost:5000. Here's some of my configuration settings and code:
config.py
SECRET_KEY = 'verysecret'
MAIL_SERVER = "smtp.mymailservice.com"
MAIL_PORT = 587
MAIL_USE_SSL = False
MAIL_USE_TLS = True
MAIL_USERNAME = "myemail#myhostname.com"
MAIL_PASSWORD = "mypassword"
MAIL_DEFAULT_SENDER = 'Brand <noreply#myhostname.com>'
app/mailing.py
from flask_mail import Message
from flask import current_app
from .extensions import mail
def send_email(to, subject, template):
msg = Message(
subject,
recipients=[to],
html=template,
sender=current_app.config["MAIL_DEFAULT_SENDER"]
)
mail.send(msg)
app/users/routes.py
(One of the routes where I receive the error)
from flask import (
render_template, session, request, redirect, url_for, g, jsonify, flash
)
import uuid
from passlib.hash import sha256_crypt
from app.mailing import send_email
from app.extensions import db
from app.users import bp
from app.users.forms import *
from app.users.models import *
from app.users.token import *
#bp.route('/register', methods=['POST', 'GET'])
def register():
# Initialize the Register Form
form = RegisterForm()
# If the submitted form is valid
if form.validate_on_submit():
# Check to see if a user already exists with this email address
user = User.query.filter_by(email=form.email.data).first()
# If there is not a user with this email address, create a new user
if not user:
new_user = User(public_id=str(uuid.uuid4()),
email=form.email.data,
password=sha256_crypt.encrypt(
(form.password.data)),
first_name=form.firstname.data,
last_name=form.lastname.data
)
db.session.add(new_user)
db.session.commit()
token = generate_confirmation_token(new_user.email)
confirm_url = url_for("users.confirm_email",
token=token, _external=True)
html = render_template('confirm_email.html',
confirm_url=confirm_url)
subject = "Please confirm your email"
try:
send_email(new_user.email, subject, html)
flash("A confirmation email has been sent to you. Please verify your email address to activate your account.", category="success")
except Exception as e:
flash(
"There was a problem sending the confirmation email. Please try again later.", category="danger")
print(e)
session["user_id"] = new_user.public_id
session["email"] = new_user.email
session["name"] = new_user.first_name
flash("Thanks for registering!", category="success")
return redirect(url_for('users.unconfirmed'))
else:
flash("There is already an account associated with this email address. Log in, or use a different email address.")
return render_template("register_user.html", form=form)
app/extensions.py
from flask_mail import Mail
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
bootstrap = Bootstrap()
mail = Mail()
app/init.py
from flask import Flask
from config import Config, DevelopmentConfig
from .errors import (
page_not_found, forbidden, internal_server_error
)
from .extensions import (
db, mail, bootstrap
)
def create_app(config_class=DevelopmentConfig):
app = MyFlask(__name__)
# Set Configuration
app.config.from_object(config_class)
# Register extensions
# Initialize Boostrap-Flask
bootstrap.init_app(app)
# Initialize Flask-SQLAlchemy
db.init_app(app)
# Initialize Flask-Mail
mail.init_app(app)
# Register error views
app.register_error_handler(404, page_not_found)
app.register_error_handler(403, forbidden)
app.register_error_handler(500, internal_server_error)
with app.app_context():
# register blueprints
from app.main import bp as bp_main
app.register_blueprint(bp_main)
from app.users import bp as bp_users
app.register_blueprint(bp_users)
return app
This answer was almost there but not quite for me. Even more TL:DR.
Put this in your config.py file and forget about the rest...
class Config:
MAIL_USE_TLS = True
MAIL_USE_SSL = False
More details...
You probably have a config.py file that looks like this:
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' # os.environ.get('DATABASE_URI')
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT = os.environ.get('MAIL_PORT')
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS')
MAIL_USE_SSL = os.environ.get('MAIL_USE_SSL')
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')
And then you think you can have something like this in your VSCode debug config or the environment of your server:
"env": {
"MAIL_USE_SSL":"true",
"MAIL_USE_TLS":"true",
Well that doesn't work because of the answer from #serrobit because "true" in VSCode turns into a str instead of a Python True.
So back to the start, hard code TLS to True and SSL to False in the config.py file and go spend time on something useful.
I figured out what was going on. Evidently, you can pass in non-boolean types for MAIL_USE_TLS and MAIL_USE_SSL when initializing the Mail object from Flask-Mail. This becomes a problem when the Connection object calls configure_host() and conditionally checks if self.mail.use_ssl.
Thus, as long as self.mail.use_ssl is not None, the method will set host = smtplib.SMTP_SSL(self.mail.server, self.mail.port), which in my case, lead to [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1123) because mail.port was set to 587.
tl;dr
Ensure the configuration variables for your Flask app are set to the appropriate type, especially if you are using environment variables, as those will always be of type str when accessing them via the os.environ dict.
flask_mail.py
class Connection(object):
"""Handles connection to host."""
def __init__(self, mail):
self.mail = mail
def __enter__(self):
if self.mail.suppress:
self.host = None
else:
self.host = self.configure_host()
self.num_emails = 0
return self
def __exit__(self, exc_type, exc_value, tb):
if self.host:
self.host.quit()
def configure_host(self):
## PROBLEM OCCURRED HERE BECAUSE type(self.mail.use_ssl) = <class 'str'> ##
if self.mail.use_ssl:
host = smtplib.SMTP_SSL(self.mail.server, self.mail.port)
else:
host = smtplib.SMTP(self.mail.server, self.mail.port)
host.set_debuglevel(int(self.mail.debug))
if self.mail.use_tls:
host.starttls()
if self.mail.username and self.mail.password:
host.login(self.mail.username, self.mail.password)
return host
Change this in your config.py:
class Config:
MAIL_USE_TLS = bool(strtobool(os.environ.get('MAIL_USE_TLS', 'False')))
MAIL_USE_SSL = bool(strtobool(os.environ.get('MAIL_USE_SSL', 'False')))

Fix cicular import when importing Flask blueprint

I'm trying to import a Flask blueprint into my main file. However, when I do from flask_first.login_user.login import login, I get cannot import name 'login' from partially initialized module 'flask_first.login_user.login' (most likely due to a circular import) (C:\Users\Max\PycharmProjects\python1\flask_first\login_user\login.py). How do I fix this?
flask_first/login_user/login.py:
from flask_first.main import *
from flask import Blueprint
from flask_first.main import users, db
from flask_sqlalchemy import SQLAlchemy
from datetime import timedelta
#login.route('/')
def login():
error = None
if request.method == 'POST': # checking if the method is POST, it means we got a query from button
if request.form['nm'] == 'admin' or request.form['ps'] == 'secret':
flash("You were successfully logged in into the admin's user page")
session.permanent = True
session['user'] = request.form['nm']
password = request.form['ps']
password = session['password']
return redirect(url_for('administrating'))
else: # if we are not admins, continue with this code
flash(f"You were successfully logged in", category='success')
session.permanent = True # setting the bool of session to permanent
session['user'] = request.form[
'nm'] # we are just saying that the session['user']= the name, which we typed into the field
user1 = session['user'] # user1 not a function
session['password'] = request.form['ps'] # session['password']= field, in which we typed our password
password1 = request.form['ps']
found_user = users.query.filter_by(name=user1,
password=password1).first() # we are filtering all the users in the database by the name and password, we typed while logging in
if found_user: # if we have found this user, we say that the email he typed previously is now in the field of email
session[
'email'] = found_user.email # we are saying that the email user typed previously, is now the session['email']
else:
usr = users(user1, '',
password1) # if we haven't found that user by name and password, we create a new one
db.session.add(usr)
db.session.commit()
return redirect(
url_for('user')) # redirecting to the user's page after logging in(using user's name)
else: # below is a standard script, which checks whether we are logged or not
if 'user' in session: # if user is already logged, it will download the user page.
flash('You are already logged in, to log out, type logout')
return redirect(url_for('user'))
else:
flash("You have not logged yet", category='success')
return render_template('login_user.html', error=error) # if it didn't go properly, we force the comeback
# to the login_user page again
flask_first/main.py:
from datetime import timedelta
from flask import Flask, redirect, url_for, render_template, request, session, flash
from flask_sqlalchemy import SQLAlchemy
from flask_first.admin.second import second
from flask_first.login_user.login import login
app = Flask(__name__)
app.secret_key = 'hello world'
app.register_blueprint(second, url_prefix='/test')
app.register_blueprint(login, url_prefix='login_user')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.sqlite3.html' # access to the SQL
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.permanent_session_lifetime = timedelta(minutes=5) # setting the time for long-lasting session
db = SQLAlchemy(app)
As the error suggests, you have a circular dependency issue. You are importing login from main and main from login. Modules cannot do that.
Check out the "problems" section In this wiki https://en.m.wikipedia.org/wiki/Circular_dependency

Thread that calls function in a HTTP request throws RuntimeError: Working outside of application context

I have route within my Flask application that process a POST request and then sends an email to the client using the Flask-Mail library. I have the route returning a response to the client from checking the database before sending out an email. The email is sent from an individual thread, but I am getting this error thrown when trying to send out the email: RuntimeError: Working outside of application context.. I know that it is being thrown because the request was destroyed before the thread could process its tasks. However, I am not sure how exactly I should fix this error, especially when the route gets processed by a Blueprint.
routes.py:
from flask import request, Blueprint, redirect, render_template
from flask_app import mail, db
from flask_app.users.forms import Form
from flask_app.models import User
from flask_mail import Message
from threading import Thread
import os, re
users = Blueprint("users", __name__)
#users.route("/newsletter-subscribe", methods=["GET", "POST"])
def newsletter_subscribe():
form = Form()
if form.validate_on_submit():
user = User(name=form.name.data, email=form.email.data)
db.session.add(user)
db.session.commit()
thread_a = Compute(user, request.host_url)
thread_a.start()
return "Success"
return "Failure"
class Compute(Thread):
def __init__(self, user, host_url):
Thread.__init__(self)
self.host_url = host_url
self.user = user
def run(self):
send_welcome_email(self.user, self.host_url)
print("Finished")
def send_email(user, host_url):
with mail.connect() as con:
html = render_template("email.html", name=user.name, host_url=host_url)
subject = ""
msg = Message(
subject=subject,
recipients=[user.email],
html=html
)
con.send(msg)
Seeing that this question is over a year old, you may have solved this already, but I'll post this here for in case anyone else comes across it later.
I think the issue you're having is the same I had, where the send function needs the current flask app context.
You can try this within your send_email function:
Instead of:
con.send(msg)
Replace it with:
with app.app_context():
con.send(msg)
Just also make sure that you include your app in your includes section.
Change this:
from flask_app import mail, db
To this:
from flask_app import app, mail, db
That should resolve the RuntimeError: Working outside of application context. issue.

Circular import issue when working with sending mail upon user registration on Flask

I am currently working with a flask application and am trying to send out emails once the user has registered with the site. I am having difficulties with circular imports between the main.py where the app is instantiated and the data_inserts.py where the data is committed to the db and a password is emailed back to the user. For the email functionality, I use Flask-mail extension. The error I get is given below:
ImportError: Cannot import name from 'DataInserts' from relevant_folder.data_inserts
Given below are the details:
main.py:
from relevant_folder.data_inserts import DataInserts
from flask import Flask
from flask_mail import Mail
from conf.mail_settings.py import mail_settings
app = Flask(__name__)
app.config.update[mail_settings]
mail = Mail(app)
#app.route("/register")
def register():
params = request.json
DataInserts.add_user(params)
relevant_folder.data_inserts.py:
from main import app
from main.app import mail
from flask_mail import message
class DataInserts():
def add_user(self, new_user_json):
''' add user name and email to db logic goes here'''
msg = Message(subject="Subject",
sender=app.config.get("MAIL_USERNAME"),
recipients=[new_user_json["email"]],
body="Hello " + new_user_json["name"] + ", your password is password")
mail.send(msg)
I feel I have not structured my application properly. Any help greatly appreciated
It should be enough to move the DataInserts import...
from flask import Flask
from flask_mail import Mail
from conf.mail_settings.py import mail_settings
app = Flask(__name__)
app.config.update[mail_settings]
mail = Mail(app)
from relevant_folder.data_inserts import DataInserts
#app.route("/register")
def register():
params = request.json
DataInserts.add_user(params)
Alternatively you could pass app and mail instances to the DataInsert class, instead of importing the globals...
Update: Another approach would be using "flask.current_app".
from relevant_folder.data_inserts import DataInserts
from flask import Flask
from flask_mail import Mail
from conf.mail_settings.py import mail_settings
app = Flask(__name__)
app.config.update[mail_settings]
mail = Mail(app)
app.mail = mail
#app.route("/register")
def register():
params = request.json
DataInserts.add_user(params)
Notice that I stored the mail instance in app.mail for easy access later.
relevant_folder.data_inserts.py:
from flask import current_app
from flask_mail import message
class DataInserts():
def add_user(self, new_user_json):
''' add user name and email to db logic goes here'''
msg = Message(subject="Subject",
sender=current_app.config.get("MAIL_USERNAME"),
recipients=[new_user_json["email"]],
body="Hello " + new_user_json["name"] + ", your password is password")
current_app.mail.send(msg)
But keep in mind that current_app needs an active application context.
when working on a request, the context should alway be there, otherwise you can manually create the context e.g. using with app.app_context():
For more on that topic, see the flask documentation:
http://flask.pocoo.org/docs/1.0/appcontext/

Categories