2018-02-06T15:11:28Z

The Flask Mega-Tutorial Part X: Email Support

This is the tenth installment of the Flask Mega-Tutorial series, in which I'm going to tell you how your application can send emails to your users, and how to build a password recovery feature on top of the email support.

For your reference, below is a list of the articles in this series.

The application is doing pretty well on the database front now, so in this chapter I want to depart from that topic and add another important piece that most web applications need, which is the sending of emails.

Why does an application need to email its users? There are many reasons, but one common one is to solve authentication related problems. In this chapter I'm going to add a password reset feature for users that forget their password. When a user requests a password reset, the application will send an email with a specially crafted link. The user then needs to click that link to have access to a form in which to set a new password.

The GitHub links for this chapter are: Browse, Zip, Diff.

Introduction to Flask-Mail

As far as the actual sending of emails, Flask has a popular extension called Flask-Mail that can make the task very easy. As always, this extension is installed with pip:

(venv) $ pip install flask-mail

The password reset links will have a secure token in them. To generate these tokens, I'm going to use JSON Web Tokens, which also have a popular Python package:

(venv) $ pip install pyjwt

The Flask-Mail extension is configured from the app.config object. Remember when in Chapter 7 I added the email configuration for sending yourself an email whenever an error occurred in production? I did not tell you this then, but my choice of configuration variables was modeled after Flask-Mail's requirements, so there isn't really any additional work that is needed, the configuration variables are already in the application.

Like most Flask extensions, you need to create an instance right after the Flask application is created. In this case this is an object of class Mail:

app/__init__.py: Flask-Mail instance.

# ...
from flask_mail import Mail

app = Flask(__name__)
# ...
mail = Mail(app)

If you are planning to test sending of emails you have the same options I mentioned in Chapter 7. If you want to use an emulated email server, Python provides one that is very handy that you can start in a second terminal with the following command:

(venv) $ python -m smtpd -n -c DebuggingServer localhost:8025

To configure for this server you will need to set two environment variables:

(venv) $ export MAIL_SERVER=localhost
(venv) $ export MAIL_PORT=8025

If you prefer to have emails sent for real, you need to use a real email server. If you have one, then you just need to set the MAIL_SERVER, MAIL_PORT, MAIL_USE_TLS, MAIL_USERNAME and MAIL_PASSWORD environment variables for it. If you want a quick solution, you can use a Gmail account to send email, with the following settings:

(venv) $ export MAIL_SERVER=smtp.googlemail.com
(venv) $ export MAIL_PORT=587
(venv) $ export MAIL_USE_TLS=1
(venv) $ export MAIL_USERNAME=<your-gmail-username>
(venv) $ export MAIL_PASSWORD=<your-gmail-password>

If you are using Microsoft Windows, you need to replace export with set in each of the export statements above.

Remember that the security features in your Gmail account may prevent the application from sending emails through it unless you explicitly allow "less secure apps" access to your Gmail account. You can read about this here, and if you are concerned about the security of your account, you can create a secondary account that you configure just for testing emails, or you can enable less secure apps only temporarily to run your tests and then revert back to the more secure default.

If you'd like to use a real email server, but don't want to complicate yourself with the Gmail configuration, SendGrid is a good option that gives you 100 emails per day using a free account.

Flask-Mail Usage

To learn how Flask-Mail works, I'll show you how to send an email from a Python shell. So fire up Python with flask shell, and then run the following commands:

>>> from flask_mail import Message
>>> from app import mail
>>> msg = Message('test subject', sender=app.config['ADMINS'][0],
... recipients=['your-email@example.com'])
>>> msg.body = 'text body'
>>> msg.html = '<h1>HTML body</h1>'
>>> mail.send(msg)

The snippet of code above will send an email to a list of email addresses that you put in the recipients argument. I put the sender as the first configured admin (I've added the ADMINS configuration variable in Chapter 7). The email will have plain text and HTML versions, so depending on how your email client is configured you may see one or the other.

So as you see, this is pretty simple. Now let's integrate emails into the application.

A Simple Email Framework

I will begin by writing a helper function that sends an email, which is basically a generic version of the shell exercise from the previous section. I will put this function in a new module called app/email.py:

app/email.py: Email sending wrapper function.

from flask_mail import Message
from app import mail

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)

Flask-Mail supports some features that I'm not utilizing here such as Cc and Bcc lists. Be sure to check the Flask-Mail Documentation if you are interested in those options.

Requesting a Password Reset

As I mentioned above, I want users to have the option to request their password to be reset. For this purpose I'm going to add a link in the login page:

app/templates/login.html: Password reset link in login form.

    <p>
        Forgot Your Password?
        <a href="{{ url_for('reset_password_request') }}">Click to Reset It</a>
    </p>

When the user clicks the link, a new web form will appear that requests the user's email address as a way to initiate the password reset process. Here is the form class:

app/forms.py: Reset password request form.

class ResetPasswordRequestForm(FlaskForm):
    email = StringField('Email', validators=[DataRequired(), Email()])
    submit = SubmitField('Request Password Reset')

And here is the corresponding HTML template:

app/templates/reset_password_request.html: Reset password request template.

{% extends "base.html" %}

{% block content %}
    <h1>Reset Password</h1>
    <form action="" method="post">
        {{ form.hidden_tag() }}
        <p>
            {{ form.email.label }}<br>
            {{ form.email(size=64) }}<br>
            {% for error in form.email.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

I also need a view function to handle this form:

app/routes.py: Reset password request view function.

from app.forms import ResetPasswordRequestForm
from app.email import send_password_reset_email

@app.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    form = ResetPasswordRequestForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user:
            send_password_reset_email(user)
        flash('Check your email for the instructions to reset your password')
        return redirect(url_for('login'))
    return render_template('reset_password_request.html',
                           title='Reset Password', form=form)

This view function is fairly similar to others that process a form. I start by making sure the user is not logged in. If the user is logged in, then there is no point in using the password reset functionality, so I redirect to the index page.

When the form is submitted and valid, I look up the user by the email provided by the user in the form. If I find the user, I send a password reset email. The send_password_reset_email() helper function performs this task. I will show you this function below.

After the email is sent, I flash a message directing the user to look for the email for further instructions, and then redirect back to the login page. You may notice that the flashed message is displayed even if the email provided by the user is unknown. This is so that clients cannot use this form to figure out if a given user is a member or not.

Password Reset Tokens

Before I implement the send_password_reset_email() function, I need to have a way to generate a password request link. This is going to be the link that is sent to the user via email. When the link is clicked, a page where a new password can be set is presented to the user. The tricky part of this plan is to make sure that only valid reset links can be used to reset an account's password.

The links are going to be provisioned with a token, and this token will be validated before allowing the password change, as proof that the user that requested the email has access to the email address on the account. A very popular token standard for this type of process is the JSON Web Token, or JWT. The nice thing about JWTs is that they are self contained. You can send a token to a user in an email, and when the user clicks the link that feeds the token back into the application, it can be verified on its own.

How do JWTs work? Nothing better than a quick Python shell session to understand them:

>>> import jwt
>>> token = jwt.encode({'a': 'b'}, 'my-secret', algorithm='HS256')
>>> token
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYiJ9.dvOo58OBDHiuSHD4uW88nfJik_sfUHq1mDi4G0'
>>> jwt.decode(token, 'my-secret', algorithms=['HS256'])
{'a': 'b'}

The {'a': 'b'} dictionary is an example payload that is going to be written into the token. To make the token secure, a secret key needs to be provided to be used in creating a cryptographic signature. For this example I have used the string 'my-secret', but with the application I'm going to use the SECRET_KEY from the configuration. The algorithm argument specifies how the token is to be generated. The HS256 algorithm is the most widely used.

As you can see the resulting token is a long sequence of characters. But do not think that this is an encrypted token. The contents of the token, including the payload, can be decoded easily by anyone (don't believe me? Copy the above token and then enter it in the JWT debugger to see its contents). What makes the token secure is that the payload is signed. If somebody tried to forge or tamper with the payload in a token, then the signature would be invalidated, and to generate a new signature the secret key is needed. When a token is verified, the contents of the payload are decoded and returned back to the caller. If the token's signature was validated, then the payload can be trusted as authentic.

The payload that I'm going to use for the password reset tokens is going to have the format {'reset_password': user_id, 'exp': token_expiration}. The exp field is standard for JWTs and if present it indicates an expiration time for the token. If a token has a valid signature, but it is past its expiration timestamp, then it will also be considered invalid. For the password reset feature, I'm going to give these tokens 10 minutes of life.

When the user clicks on the emailed link, the token is going to be sent back to the application as part of the URL, and the first thing the view function that handles this URL will do is to verify it. If the signature is valid, then the user can be identified by the ID stored in the payload. Once the user's identity is known, the application can ask for a new password and set it on the user's account.

Since these tokens belong to users, I'm going to write the token generation and verification functions as methods in the User model:

app/models.py: Reset password token methods.

from time import time
import jwt
from app import app

class User(UserMixin, db.Model):
    # ...

    def get_reset_password_token(self, expires_in=600):
        return jwt.encode(
            {'reset_password': self.id, 'exp': time() + expires_in},
            app.config['SECRET_KEY'], algorithm='HS256')

    @staticmethod
    def verify_reset_password_token(token):
        try:
            id = jwt.decode(token, app.config['SECRET_KEY'],
                            algorithms=['HS256'])['reset_password']
        except:
            return
        return User.query.get(id)

The get_reset_password_token() function returns a JWT token as a string, which is generated directly by the jwt.encode() function.

The verify_reset_password_token() is a static method, which means that it can be invoked directly from the class. A static method is similar to a class method, with the only difference that static methods do not receive the class as a first argument. This method takes a token and attempts to decode it by invoking PyJWT's jwt.decode() function. If the token cannot be validated or is expired, an exception will be raised, and in that case I catch it to prevent the error, and then return None to the caller. If the token is valid, then the value of the reset_password key from the token's payload is the ID of the user, so I can load the user and return it.

Sending a Password Reset Email

The send_password_reset_email() function relies on the send_email() function I wrote above to generate the password reset emails.

app/email.py: Send password reset email function.

from flask import render_template
from app import app

# ...

def send_password_reset_email(user):
    token = user.get_reset_password_token()
    send_email('[Microblog] Reset Your Password',
               sender=app.config['ADMINS'][0],
               recipients=[user.email],
               text_body=render_template('email/reset_password.txt',
                                         user=user, token=token),
               html_body=render_template('email/reset_password.html',
                                         user=user, token=token))

The interesting part in this function is that the text and HTML content for the emails is generated from templates using the familiar render_template() function. The templates receive the user and the token as arguments, so that a personalized email message can be generated. Here is the text template for the reset password email:

app/templates/email/reset_password.txt: Text for password reset email.

Dear {{ user.username }},

To reset your password click on the following link:

{{ url_for('reset_password', token=token, _external=True) }}

If you have not requested a password reset simply ignore this message.

Sincerely,

The Microblog Team

And here is the nicer HTML version of the same email:

app/templates/email/reset_password.html: HTML for password reset email.

<p>Dear {{ user.username }},</p>
<p>
    To reset your password
    <a href="{{ url_for('reset_password', token=token, _external=True) }}">
        click here
    </a>.
</p>
<p>Alternatively, you can paste the following link in your browser's address bar:</p>
t<p>{{ url_for('reset_password', token=token, _external=True) }}</p>
<p>If you have not requested a password reset simply ignore this message.</p>
<p>Sincerely,</p>
<p>The Microblog Team</p>

The reset_password route that is referenced in the url_for() call in these two email templates does not exist yet, this will be added in the next section. The _external=True argument that I included in the url_for() calls in both templates is also new. The URLs that are generated by url_for() by default are relative URLs that only include the path portion of the URL. This is normally sufficient for links that are generated in web pages, because the web browser completes the URL by taking the missing parts from the URL in the address bar. When sending a URL by email however, that context does not exist, so fully qualified URLs need to be used. When _external=True is passed as an argument, complete URLs are generated, so the previous example would return http://localhost:5000/user/susan, or the appropriate URL when the application is deployed on a domain name.

Resetting a User Password

When the user clicks on the email link, a second route associated with this feature is triggered. Here is the password request view function:

app/routes.py: Password reset view function.

from app.forms import ResetPasswordForm

@app.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    user = User.verify_reset_password_token(token)
    if not user:
        return redirect(url_for('index'))
    form = ResetPasswordForm()
    if form.validate_on_submit():
        user.set_password(form.password.data)
        db.session.commit()
        flash('Your password has been reset.')
        return redirect(url_for('login'))
    return render_template('reset_password.html', form=form)

In this view function I first make sure the user is not logged in, and then I determine who the user is by invoking the token verification method in the User class. This method returns the user if the token is valid, or None if not. If the token is invalid I redirect to the home page.

If the token is valid, then I present the user with a second form, in which the new password is requested. This form is processed in a way similar to previous forms, and as a result of a valid form submission, I invoke the set_password() method of User to change the password, and then redirect to the login page, where the user can now login.

Here is the ResetPasswordForm class:

app/forms.py: Password reset form.

class ResetPasswordForm(FlaskForm):
    password = PasswordField('Password', validators=[DataRequired()])
    password2 = PasswordField(
        'Repeat Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Request Password Reset')

And here is the corresponding HTML template:

app/templates/reset_password.html: Password reset form template.

{% extends "base.html" %}

{% block content %}
    <h1>Reset Your Password</h1>
    <form action="" method="post">
        {{ form.hidden_tag() }}
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>
            {{ form.password2.label }}<br>
            {{ form.password2(size=32) }}<br>
            {% for error in form.password2.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

The password reset feature is now complete, so make sure you try it.

Asynchronous Emails

If you are using the simulated email server that Python provides you may not have noticed this, but sending an email slows the application down considerably. All the interactions that need to happen when sending an email make the task slow, it usually takes a few seconds to get an email out, and maybe more if the email server of the addressee is slow, or if there are multiple addressees.

What I really want is for the send_email() function to be asynchronous. What does that mean? It means that when this function is called, the task of sending the email is scheduled to happen in the background, freeing the send_email() to return immediately so that the application can continue running concurrently with the email being sent.

Python has support for running asynchronous tasks, actually in more than one way. The threading and multiprocessing modules can both do this. Starting a background thread for email being sent is much less resource intensive than starting a brand new process, so I'm going to go with that approach:

app/email.py: Send emails asynchronously.

from threading import Thread
# ...

def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)


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
    Thread(target=send_async_email, args=(app, msg)).start()

The send_async_email function now runs in a background thread, invoked via the Thread class in the last line of send_email(). With this change, the sending of the email will run in the thread, and when the process completes the thread will end and clean itself up. If you have configured a real email server, you will definitely notice a speed improvement when you press the submit button on the password reset request form.

You probably expected that only the msg argument would be sent to the thread, but as you can see in the code, I'm also sending the application instance. When working with threads there is an important design aspect of Flask that needs to be kept in mind. Flask uses contexts to avoid having to pass arguments across functions. I'm not going to go into a lot of detail on this, but know that there are two types of contexts, the application context and the request context. In most cases, these contexts are automatically managed by the framework, but when the application starts custom threads, contexts for those threads may need to be manually created.

There are many extensions that require an application context to be in place to work, because that allows them to find the Flask application instance without it being passed as an argument. The reason many extensions need to know the application instance is because they have their configuration stored in the app.config object. This is exactly the situation with Flask-Mail. The mail.send() method needs to access the configuration values for the email server, and that can only be done by knowing what the application is. The application context that is created with the with app.app_context() call makes the application instance accessible via the current_app variable from Flask.

304 comments

  • #151 Hal Morris said 2019-09-17T00:34:54Z

    Thank you for all the meticulous and creative pedagogy; this is one of the best things I've done for learning python.

    I've had a problem, however, with the interactive python session under "Flask-Mail Usage".

    I'm most interested in whether something was broken between the version of python you were using and 3.7.3

    To get as far as the last line without errors, I had to add":

    import config config=config.Config()

    msg = Message('test subject', sender=config.ADMINS[0], ... recipients=['hal@panix.com'])

    In the tutorial you had "... sender=config['ADMINS'][0]

    which gave me: "'module' object is not subscriptable"

    Later, when I did: >>> import config >>> config=config.Config() >>> message = Message('test subject', sender=config['ADMINS'],

    the error changed to: ... recipients=['hal@panix.com']) Traceback (most recent call last): File "", line 1, in TypeError: 'Config' object is not subscriptable

    Note 'Config' instead of 'module'

    That got me to the last line without error, but then I got:

    Finally, the change config['ADMINS'][0] ==> config

    got me to the last line without an error, but then I got:

    msg.html = 'HTML body' mail.send(msg) Traceback (most recent call last): .7/site-packages/werkzeug/local.py", line 348, in getattr return getattr(self._get_current_object(), name) File "/home/hal/PY/microblog/venv/lib/python3.7/site-packages/werkzeug/local.py", line 307, in _get_current_object return self.__local() File "/home/hal/PY/microblog/venv/lib/python3.7/site-packages/flask/globals.py", line 52, in _find_app raise RuntimeError(_app_ctx_err_msg) RuntimeError: Working outside of application context.

    This typically means that you attempted to use functionality that needed to interface with the current application object in some way. To solve this, set up an application context with app.app_context(). See the documentation for more information.

  • #152 Miguel Grinberg said 2019-09-17T09:13:14Z

    @Hal: it should be app.config['ADMINS'][0]. It seems you left the "app" part out. The app.config attribute is a dictionary that includes all the variables imported into the configuration.

  • #153 Hal Morris said 2019-09-17T13:42:59Z

    Still a a problem but simpler. This:

    from flask import Flask from flask_mail import Message from app import mail, Config from app import mail, Config

    app = Flask(name) app.config.from_object(Config) message = Message('test subject', sender=app.config['ADMINS'][0], recipients=['hal@panix.com']) msg = message msg.body = 'text body' msg.html = 'HTML body' mail.send(msg)

    gets me past the initial errors , but still arriving at:

    File "/home/hal/PY/microblog/venv/lib/python3.7/site-packages/flask/globals.py", line 52, in _find_app raise RuntimeError(_app_ctx_err_msg) RuntimeError: Working outside of application context.

    This typically means that you attempted to use functionality that needed to interface with the current application object in some way. To solve this, set up an application context with app.app_context(). See the documentation for more information.

  • #154 Peter Fletcher said 2019-09-17T21:07:56Z

    I am having a problem with your initial 'simple' email demonstration. I have pip(3) installed flask_mail and pyjwt (the latter is probably not relevant) and fire up Python3. The subsequent input and output as as follows: Python 3.7.3 (default, Apr 3 2019, 05:39:12) [GCC 8.2.0] on linux Type "help", "copyright", "credits" or "license" for more information.

    from flask_mail import Message from app import mail [2019-09-17 15:59:29,063] INFO in init: Microblog startup msg = Message('test_subject', sender=app.config['ADMINS'][0], recipients=['peter@fletchers-uk.com']) Traceback (most recent call last): File "", line 1, in NameError: name 'app' is not defined

    Despite the fact that the interpreter was able to import mail from app, it now claims that app is not defined. Any suggestions?

  • #155 Miguel Grinberg said 2019-09-17T21:13:14Z

    @Hal: This article covers the app context requirement, have you seen how the mail.send() function is invoked using the app context?

  • #156 Miguel Grinberg said 2019-09-17T21:18:35Z

    @Peter: the example you are following assumes you are using a flask shell for this test. You are using a regular Python shell, which does not preload the application instance. When you use flask shell then "app" is automatically created and imported.

  • #157 Androgen said 2019-09-23T11:19:49Z

    Hi Miguel,

    Is it acceptable practice to loop through users table to send individual email to users using gmail smtp if app has less than 1000 users? is there a threshold number of users where an external service such as SendGrid is better suited?

  • #158 Miguel Grinberg said 2019-09-23T12:38:53Z

    @Androgen: If this is a free gmail account, then you should know that using it to send email in any production application is against the TOS. But even if you ignore that, gmail accounts have very limited quotas, they are not good for sending automated email. With paid email services this problem disappears, since you pay for the amount of email that you send.

  • #159 Ron Alomes said 2019-09-26T02:25:00Z

    Miguel More a question. How do you set flask_mail to use MAIL_USE_SSL and port 465. More specifically tied to logging SMTPHandler

    Thanks Ron

  • #160 Miguel Grinberg said 2019-09-26T12:32:23Z

    @Ron: You can se MAIL_USE_SSL to 1 and MAIL_PORT to 465.

  • #161 Juha said 2019-09-28T06:10:06Z

    Finally got past the SMTPServerDisconnected error, it wouldn't take it if I tried to set the variables in the cmd window, I had to put them in the .env file. Now I see different outputs in both the cmd window running the server as well as the one running flask shell, but should the test email actually arrive to the address I specified, because it doesn't? Checked the spam folder as well as doublechecking my spelling, to no avail. Am I missing something?

  • #162 Miguel Grinberg said 2019-09-29T21:47:24Z

    @Juha: the debugging SMTP server is a fake email server, it does not send email for real. You will only see the email in the console.

  • #163 Kaunda said 2019-11-07T07:33:24Z

    Hi Miguel, thanks so much for this tut. I've being following everything, step by step, with no step skipped.

    Chapter 8: I've realized the code below followers = db.Table('followers', db.Column('follower_id', db.Integer, db.ForeignKey('user.id')), db.Column('followed_id', db.Integer, db.ForeignKey('user.id')) )

    should come before the User class ... class User(UserMixin,db.Model): id = db.Column(db.Integer, primary_key= True) username = db.Column(db.String(43),index=True, unique=True) email = db.Column(db.String(64), index=True, unique=True)

    If I place it below it, i get the error message: "NameError: name 'followers' is not defined"

    Chapter 10: Email Support I've done my best to make sure what I have is exactly as yours. I had to manually clear what I typed, and Copy_Paste yours. But no matter what I do, I get the error message: "TypeError: not_found_error() takes 0 positional arguments but 1 was given"

    The "reset_password_request" for opens alright, and I'm able to enter my email. The error message comes when I click the "Request Password Reset" button.

    Below is the complete stack trace.

    TypeError TypeError: not_found_error() takes 0 positional arguments but 1 was given

    Traceback (most recent call last) File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 1949, in full_dispatch_request rv = self.dispatch_request() File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 1925, in dispatch_request self.raise_routing_exception(req) File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 1907, in raise_routing_exception raise request.routing_exception File "c:\program files (x86)\python37-32\lib\site-packages\flask\ctx.py", line 350, in match_request result = self.url_adapter.match(return_rule=True) File "c:\program files (x86)\python37-32\lib\site-packages\werkzeug\routing.py", line 1799, in match raise NotFound() During handling of the above exception, another exception occurred: File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 2463, in call return self.wsgi_app(environ, start_response) File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 2449, in wsgi_app response = self.handle_exception(e) File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 1866, in handle_exception reraise(exc_type, exc_value, tb) File "c:\program files (x86)\python37-32\lib\site-packages\flask_compat.py", line 39, in reraise raise value File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 2446, in wsgi_app response = self.full_dispatch_request() File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 1951, in full_dispatch_request rv = self.handle_user_exception(e) File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 1815, in handle_user_exception return self.handle_http_exception(e) File "c:\program files (x86)\python37-32\lib\site-packages\flask\app.py", line 1743, in handle_http_exception return handler(e) TypeError: not_found_error() takes 0 positional arguments but 1 was given

    Today is my third day of struggling..

    Finally, I think it will be helpful if you made the reader aware that "this x function is not declared yet". You used the function "reset_password_request" in your code. Initially I thought that was coming from the "mail" Object. I got error message of "not defined" I came back to read further, then realized "oh, this is not an in build function".

    Thanks so much, once again.. am learning a lot from this. BTW, I'm Kaunda from Ghana

  • #164 Miguel Grinberg said 2019-11-07T08:35:48Z

    @Kaunda: for every chapter I provide the working source code as a download link near the top of each article. Have you seen those links?

  • #165 kevin said 2019-12-07T09:14:31Z

    Tried this app on firefox 71.0 but failed to just Login. Google chrome works well. Could it be a browser issue or a code issue. my guess is a browser issue

  • #166 Miguel Grinberg said 2019-12-07T11:38:23Z

    @kevin: It would be really strange that Firefox fails with this application. I suggest you open the debugger and then look for errors, either in the console or the network tab.

  • #167 Kevin said 2019-12-13T09:15:10Z

    The CSRF session token is missing. This showed up because i was running the app in chrome and my email was open in firefox, so when i open the password reset link it opens in firefox not chrome and thus that error. @miguel how can i get a work around to make sure i am able to reset the password from another browser while the app is running on another

  • #168 Miguel Grinberg said 2019-12-14T07:51:34Z

    @Kevin: CSRF token is missing is not an error I would expect to see in the situation you describe. Have you made any changes to the application as presented in this tutorial? I suggest you compare your code against mine, or maybe try my version (download link at the top of this article).

  • #169 Chirag Soni said 2019-12-19T12:27:56Z

    Hi Migul,

    i have an error regarding flask_admin when i make my own model view by inheriting ModelView class from flask_admin.contrib.sqla in my own class name UserDeleteView in this when i use current_user( a variable of flask_login ) inside UserDeleteView it give None but when i use it in any function of UserDeleteView it works properly

    like when i use it in is_accessable function of UserDeleteView class it works but when i use it in an if inside UserDeleteView class it Does not work

    Here is my code for flask_admin :-

    class UserDeleteView(ModelView):

    def is_accessible(self): return "User" in {i.split()[0] for i in current_user.permissions() } if "User w" not in current_user.permissions(): can_delete=False

    class PostVarificationView(BaseView): @expose("/") def index(self): return self.render('admin/post_varification.html')

    class MyAdminIndexView(AdminIndexView): def is_accessible(self): return (current_user.is_authenticated and len(current_user.permissions())!=0)

    admin = Admin(app,index_view=MyAdminIndexView(),name="Microblog") admin.add_view(UserDeleteView( User, db.session )) admin.add_view(PostVarificationView(name="Post Varification",endpoint="PostVarification"))

    Here is the error which i got:

    AttributeError: 'NoneType' object has no attribute 'permissions'

    Traceback (most recent call last) File "/home/chirag/Documents/Projects/microblog1/microblog.py", line 1, in from app import app,db File "/home/chirag/Documents/Projects/microblog1/app/init.py", line 21, in from app import routes, models, errors File "/home/chirag/Documents/Projects/microblog1/app/routes.py", line 309, in class UserDeleteView(ModelView): File "/home/chirag/Documents/Projects/microblog1/app/routes.py", line 314, in UserDeleteView if "User w" not in current_user.permissions(): File "/home/chirag/Documents/Project venv/microblog1/lib/python3.7/site-packages/werkzeug/local.py", line 348, in getattr return getattr(self._get_current_object(), name) AttributeError: 'NoneType' object has no attribute 'permissions'

  • #170 Miguel Grinberg said 2019-12-20T10:35:44Z

    @Chirag: the config class is not meant to be initialized independently, it is imported into the Flask application instance, and then you can access it via app.config or current_app.config. Your error indicates that you are trying to access your current_user at a point where no user is logged in to the application.

  • #171 Fredrick said 2019-12-28T12:08:54Z

    Hi Miguel, thanks for this wonderful blog post. I really love it. After configuring my .flaskenv file like so:

    FLASK_APP=microblog.py MAIL_SERVER = smtp.googlemail.com MAIL_PORT = 587 MAIL_USE_TLS = 1 MAIL_USERNAME = Fred Michael MAIL_PASSWORD = *** (my correct email password here)

    i tried to test the reset password functionality by using a real email address. but the below error is what i'm getting on my console:

    (venv) C:\Users\NARUTO\Documents\CODING\Python\Flask\microblog>flask run * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [28/Dec/2019 12:43:05] "GET /index HTTP/1.1" 302 - 127.0.0.1 - - [28/Dec/2019 12:43:05] "GET /login?next=%2Findex HTTP/1.1" 200 - 127.0.0.1 - - [28/Dec/2019 12:43:07] "GET /reset_pwd_request HTTP/1.1" 200 - [2019-12-28 12:43:17,676] ERROR in app: Exception on /reset_pwd_request [POST] Traceback (most recent call last): File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask\app.py", line 2446, in wsgi_app response = self.full_dispatch_request() File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask\app.py", line 1951, in full_dispatch_request rv = self.handle_user_exception(e) File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask\app.py", line 1820, in handle_user_exception reraise(exc_type, exc_value, tb) File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask_compat.py", line 39, in reraise raise value File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask\app.py", line 1949, in full_dispatch_request rv = self.dispatch_request() File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask\app.py", line 1935, in dispatch_request return self.view_functionsrule.endpoint File "C:\Users\NARUTO\Documents\CODING\Python\Flask\microblog\app\routes.py", line 141, in reset_pwd_request send_password_reset_email(user) File "C:\Users\NARUTO\Documents\CODING\Python\Flask\microblog\app\email.py", line 13, in send_password_reset_email send_mail('[Microblog] - Reset your password', sender = app.config['ADMINS'][0], recipients = [user.email], text_body = render_template('email/reset_password.txt', user=user, token=token), html_body = render_template('email/reset_password.html', user=user, token=token)) File "C:\Users\NARUTO\Documents\CODING\Python\Flask\microblog\app\email.py", line 9, in send_mail mail.send(msg) File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask_mail.py", line 491, in send with self.connect() as connection: File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask_mail.py", line 144, in enter self.host = self.configure_host() File "c:\users\naruto\documents\coding\python\flask\microblog\venv\lib\site-packages\flask_mail.py", line 158, in configure_host host = smtplib.SMTP(self.mail.server, self.mail.port) File "C:\Users\NARUTO\AppData\Local\Programs\Python\Python37\lib\smtplib.py", line 251, in init (code, msg) = self.connect(host, port) File "C:\Users\NARUTO\AppData\Local\Programs\Python\Python37\lib\smtplib.py", line 336, in connect self.sock = self._get_socket(host, port, self.timeout) File "C:\Users\NARUTO\AppData\Local\Programs\Python\Python37\lib\smtplib.py", line 307, in _get_socket self.source_address) File "C:\Users\NARUTO\AppData\Local\Programs\Python\Python37\lib\socket.py", line 727, in create_connection raise err File "C:\Users\NARUTO\AppData\Local\Programs\Python\Python37\lib\socket.py", line 716, in create_connection sock.connect(sa) ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it

    What am I missing here? Can you help with this? Thanks

  • #172 Miguel Grinberg said 2019-12-28T20:06:11Z

    @Fredrick: try removing the whitespace before and after the equal signs, there isn't supposed to be any spaces there.

  • #173 Hassan said 2020-01-01T21:00:13Z

    While trying the email reset for password, I noticed that simultaneous user logins on the wsgi server present within Flask isn't possible. My question is this related to the wsgi server or is it due to the Login process itself?

  • #174 Miguel Grinberg said 2020-01-03T11:28:13Z

    @Hassan: can you explain in detail why you think concurrent logins are not possible? There isn't really any limitation imposed by Flask on this. The concurrency options provided by your web server, on the other side, could put a limit on the number of concurrent requests, but that has nothing to do with logins, it's just at the HTTP level.

  • #175 Hassan said 2020-01-08T11:47:19Z

    I was trying to do simultaneous logins with the flask run command issued. When I logged in with a test (fake) user and tried to do another registered user login in another tab window, the wsgi server was responding to only one of them and showing the data of only one user. That is why I asked this question.

Leave a Comment