Flash web development 1.5 web program and sending e-mail

Posted by delickate on Thu, 16 Sep 2021 03:19:15 +0200

preface:

When we don't have a special background management platform, many types of applications need to remind administrators when specific events occur in the program, and the common communication method is e-mail, which is also the most stable way. The SMTP lib package in the Python standard library can be used to send e-mail in the flash program, but the flash mail extension wrapped with SMTP lib can be better integrated with flash.

1, Providing e-mail support using flash mail

pip instruction installation:

pip install flask-mail

Installation in pycharm:
Search and install flash mail:

Flash mail connects to the Simple Mail Transfer Protocol (SMTP) server and sends mail to the server. If not configured, flash mail will connect to port 25 on localhost and send e-mail without authentication. The following table lists the configurations that can be used to set up the SMTP server:

The following describes how to configure the program for sending mail:
Note: never write the account password and key information directly into the script, especially when you plan to open source your own works. In order to protect the account information, you need to let the script import sensitive information from the environment.
If we import a program from the local environment, we can set it as follows:
Using bash in Mac OS X, you can set these two variables as follows:

(venv) $ export MAIL_USERNAME=<Email account name>
(venv) $ export MAIL_PASSWORD=<Mailbox server key>

Microsoft Windows users can set environment variables as follows:

(venv) $ set MAIL_USERNAME=<Email account name>
(venv) $ set MAIL_PASSWORD=<Mailbox server key>

Note when setting local environment variables:
(1)MAIL_USERNAME or mail_ To the right of the password equal sign are strings corresponding to the content prompt, such as MAIL_PASSWORD=‘123’;
(2) The mailbox server key refers to the key applied in the corresponding mailbox (qq or 163) (like the activation code of Microsoft computer system). As for how to apply, a method link is released here: python - developing automatic mail sending program based on yagmail Library
, please read the mailbox setting steps in detail.
The following key information related to me is replaced by xxxx. Just fill in your own data according to the corresponding content requirements!
Configure flash mail in app.py:

import os
app.config['MAIL_SERVER'] = 'smtp.163.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')

Note: 'MAIL_USE_TLS' is set to False, 'mail'_ USE_ If SSL 'is set to True, the success rate of sending mail will be high. Otherwise, the default setting will generally lead to the error prompt of mailbox connection failure or positive rejection of the computer!
Initialize flash mail in app.py:

from flask.ext.mail import Mail
mail = Mail(app)

However, the Microsoft system environment is unstable. It is not recommended for beginners to use local environment variables. In this way, many people may fail to read in the system. It is recommended to configure directly here!
In addition, mail instantiation must be configured. The following code demonstration of direct configuration will be provided!
Directly configure in app.py:

app.config['MAIL_SERVER'] = 'smtp.163.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = "xxxx@163.com"
app.config['MAIL_PASSWORD'] = "xxxx" #In order to protect personal privacy, replace it with x and fill it in according to the information you have obtained        
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail = Mail(app)#mail instantiation must be placed after configuration

2, Send email in Python shell

If flash is already installed_ Script, I won't talk about it here. See details for installation and use Flash Web development-1.4 web program and database processing The tenth integrated shell!
Then we can test whether our program can send an email correctly in the shell environment:
We can enter the following codes in turn:

(venv) $ python hello.py shell
>>> from flask_mail import Message
>>> from hello import mail
>>> msg = Message('test subject', sender='sender @example.com',recipients=['Receiver@example.com'])
>>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> with app.app_context():
... 	mail.send(msg)

The operations in pycharm are as follows:

After testing, it can successfully receive the mail and the configuration is correct!
Similarly, in app.py, add:

with app.app_context():
    message = Message(subject='hello flask-mail', sender="xxxx@163.com", recipients=['xxxx@qq.com'], body='Test mail')
    mail.send(message)

Note that the send() function in flask mail uses current_app, so it should be executed in the context of the activated program.

3, Integrate the function of sending e-mail in the program

In order to avoid writing e-mail messages manually every time, we'd better abstract the general part of the program sending e-mail and define it as a function. In addition, this has the advantage that the function can render the mail body using the Jinja2 template, which is very flexible.
Use functions in app.py to implement:

from flask_mail import Mail,Message
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
app.config['FLASKY_MAIL_SENDER'] = 'xxxx@163.com'
def send_email(to, subject, template, **kwargs):
     msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
     msg.body = render_template(template + '.txt', **kwargs)
     msg.html = render_template(template + '.html', **kwargs)
     mail.send(msg)

This send_ The email function uses two program specific configuration items to define the prefix 'flag' of the email subject_ MAIL_ SUBJECT_ Prefix 'and the address of the sender [' flag_mail_sender ']. send_ The parameters of the email function are the recipient address, subject, template for rendering the mail body and keyword parameter list (keyword parameters are mainly used to pass in template variables here). The extension cannot be included when specifying the template, so that the two templates can be used to render plain text body (txt) and rich text body (html and other files) respectively. The caller passes the keyword parameter to render_template() function to use in the template to generate the e-mail body.
The index() view function is easily extended so that whenever the form receives a new name (that is, a new user accesses and logs in), the program sends an email to the administrator.
In app.py, the index function is written as follows:

app.config['FLASKY_ADMIN'] = "xxxxx@163.com"
@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username = form.name.data)
            db.session.add(user)
            session['known'] = False
            if app.config['FLASKY_ADMIN']:
                send_emails(app.config['FLASKY_ADMIN'], 'new user','mailnew_user', user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('home.html',form = form, name = session.get('name'),known = session.get('known', False))

The recipient of the e-mail is saved in the environment variable flasky_ In admin, it will be loaded into a configuration variable with the same name during program startup. We will create two template files to render the message body in plain text and HTML respectively. Both template files are saved in the templates folder. In the template of mail e-mail, one of the template parameters is user, so send is called_ Mail() function is passed in as a keyword parameter to the user.
Other environment variable settings:

set name=<String form corresponding to content>

We configure, set and write the program code. If we succeed, the manager (receiver) will receive an email prompt every time we log in a new user on the login page!

4, Send email asynchronously

If you send e-mail as above, the mail.send() function will stop for a few seconds when sending e-mail. In order to avoid unnecessary delay in processing requests, we can move the function of sending e-mail to the background thread and use the relevant contents of the threading library. Note: threading is a local library and does not need to be downloaded and installed separately!.
In app.py, we can make the following changes:

from threading import Thread
def send_async_email(app, msg):
     with app.app_context():
        mail.send(msg)
def send_email(to, subject, template, **kwargs):
     msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
     sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
     msg.body = render_template(template + '.txt', **kwargs)
     msg.html = render_template(template + '.html', **kwargs)
     thr = Thread(target=send_async_email, args=[app, msg])
     thr.start()
     return thr

Open the mailbox to be accepted in advance. We can find that sending mail with a dedicated thread is faster than sending mail without a dedicated thread!, This is the excellent effect of special threads! Remember, however, that when a program sends a large number of e-mails, it is more appropriate to use a job dedicated to sending e-mails than to create a new thread for each message. For example, we can execute send_ async_ The operation of email() function is sent to Celery( http://www.celeryproject.org/ )Task queue.
If you want to save the functions without threads and with threads at the same time, you can wait for another use. Just distinguish the function name with threads from the function name without threads, divide it into two different functions (in fact, the main parameters are similar), and then decide whether to use the function with threads in the index function!

The above implementation involves an interesting problem. Many flash extensions assume that the active program context and request context already exist. The send() function in Flask mail uses current_app, so the program context must be activated. However, when executing the mail.send() function in different threads, the program context uses app.app_context() is created manually.

5, Summary of possible problems and solutions in development

pip command failed
Using Python flash_ Script.py -- help error: ModuleNotFoundError:no module named 'flash_ compat‘

6, Related code module

Only the parts that have been changed to the modules earlier in this column are shown, and other defaults remain unchanged.

1.mailnew_user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>{{ user }}Log in to your website!</h1>
</body>
</html>

Note: mailnew_user.txt is a temporarily empty TXT text file

2.app.py

from flask import Flask, render_template,url_for,redirect,session,flash
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from datetime import datetime
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import Required
from flask_sqlalchemy import SQLAlchemy
from flask_script import Shell,Manager
from flask_migrate import Migrate,MigrateCommand
from flask_mail import Mail,Message
from threading import Thread
import pymysql

#Program initial configuration
pymysql.install_as_MySQLdb()
app = Flask(__name__)
bootstrap = Bootstrap(app)
moment = Moment(app)
manager = Manager(app)

def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)
manager.add_command("shell", Shell(make_context=make_shell_context))

#Database connection configuration
app.config['SECRET_KEY'] = 'hard to guess string'
app.config['SQLALCHEMY_DATABASE_URI'] ='mysql://root:xxxxx@localhost/me'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)

#Mailbox configuration
app.config['MAIL_SERVER'] = 'smtp.163.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USERNAME'] = "xxxxx@163.com"
app.config['MAIL_PASSWORD'] = "xxxxx"
app.config['MAIL_USE_TLS'] = False
app.config['MAIL_USE_SSL'] = True
mail = Mail(app)
#The following is the code used to test whether sending a single message is successful. The completed messages can be deleted or hidden
'''with app.app_context():
    message = Message(subject='hello flask-mail', sender="xxxxx@163.com", recipients=['xxxxx@qq.com'], body='Test mail')
    mail.send(message)'''
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
app.config['FLASKY_MAIL_SENDER'] = 'xxxxxxx@163.com'
app.config['FLASKY_ADMIN'] = "xxxxx@163.com"
def send_email(to, subject, template, **kwargs):
     msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
     msg.body = render_template(template + '.txt', **kwargs)
     msg.html = render_template(template + '.html', **kwargs)
     mail.send(msg)

def send_async_email(app, msg):
     with app.app_context():
        mail.send(msg)
def send_emails(to, subject, template, **kwargs):
     msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
     sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
     msg.body = render_template(template + '.txt', **kwargs)
     msg.html = render_template(template + '.html', **kwargs)
     thr = Thread(target=send_async_email, args=[app, msg])
     thr.start()
     return thr

class NameForm(FlaskForm):
    name = StringField('What's your name??', validators=[Required()])
    submit = SubmitField('Submit')

#Database model definition
class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    users = db.relationship('User', backref='role', lazy='dynamic')

    def __repr__(self):
        return '<Role %r>' % self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return '<User %r>' % self.username

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username = form.name.data)
            db.session.add(user)
            session['known'] = False
            if app.config['FLASKY_ADMIN']:
                send_emails(app.config['FLASKY_ADMIN'], 'new user','mailnew_user', user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('home.html',form = form, name = session.get('name'),known = session.get('known', False))

@app.route('/user/<name>')
def users(name):
    return render_template('user.html',name=name,current_time=datetime.utcnow())

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html',current_time=datetime.utcnow()),404

@app.errorhandler(500)
def internal_error(e):
    return render_template('500.html',current_time=datetime.utcnow()),500

@app.route('/form')
def useform():
    form=NameForm()
    return  render_template('FORM.html',form=form)

if __name__ == '__main__':
    manager.run()

Finally, if there are deficiencies in the article, criticism and correction are welcome!

Topics: Python Web Development Flask smtp webmail