2017-12-19T19:08:21Z

The Flask Mega-Tutorial Part III: Web Forms

This is the third installment of the Flask Mega-Tutorial series, in which I'm going to tell you how to work with web forms.

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

In Chapter 2 I created a simple template for the home page of the application, and used fake objects as placeholders for things I don't have yet, like users or blog posts. In this chapter I'm going to address one of the many holes I still have in this application, specifically how to accept input from users through web forms.

Web forms are one of the most basic building blocks in any web application. I will be using forms to allow users to submit blog posts, and also for logging in to the application.

Before you proceed with this chapter, make sure you have the microblog application as I left it in the previous chapter installed, and that you can run it without any errors.

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

Introduction to Flask-WTF

To handle the web forms in this application I'm going to use the Flask-WTF extension, which is a thin wrapper around the WTForms package that nicely integrates it with Flask. This is the first Flask extension that I'm presenting to you, but it is not going to be the last. Extensions are a very important part of the Flask ecosystem, as they provide solutions to problems that Flask is intentionally not opinionated about.

Flask extensions are regular Python packages that are installed with pip. You can go ahead and install Flask-WTF in your virtual environment:

(venv) $ pip install flask-wtf

So far the application is very simple, and for that reason I did not need to worry about its configuration. But for any applications except the simplest ones, you are going to find that Flask (and possibly also the Flask extensions that you use) offer some amount of freedom in how to do things, and you need to make some decisions, which you pass to the framework as a list of configuration variables.

There are several formats for the application to specify configuration options. The most basic solution is to define your variables as keys in app.config, which uses a dictionary style to work with variables. For example, you could do something like this:

app = Flask(__name__)
app.config['SECRET_KEY'] = 'you-will-never-guess'
# ... add more variables here as needed

While the above syntax is sufficient to create configuration options for Flask, I like to enforce the principle of separation of concerns, so instead of putting my configuration in the same place where I create my application I will use a slightly more elaborate structure that allows me to keep my configuration in a separate file.

A format that I really like because it is very extensible, is to use a class to store configuration variables. To keep things nicely organized, I'm going to create the configuration class in a separate Python module. Below you can see the new configuration class for this application, stored in a config.py module in the top-level directory.

config.py: Secret key configuration

import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'

Pretty simple, right? The configuration settings are defined as class variables inside the Config class. As the application needs more configuration items, they can be added to this class, and later if I find that I need to have more than one configuration set, I can create subclasses of it. But don't worry about this just yet.

The SECRET_KEY configuration variable that I added as the only configuration item is an important part in most Flask applications. Flask and some of its extensions use the value of the secret key as a cryptographic key, useful to generate signatures or tokens. The Flask-WTF extension uses it to protect web forms against a nasty attack called Cross-Site Request Forgery or CSRF (pronounced "seasurf"). As its name implies, the secret key is supposed to be secret, as the strength of the tokens and signatures generated with it depends on no person outside of the trusted maintainers of the application knowing it.

The value of the secret key is set as an expression with two terms, joined by the or operator. The first term looks for the value of an environment variable, also called SECRET_KEY. The second term, is just a hardcoded string. This is a pattern that you will see me repeat often for configuration variables. The idea is that a value sourced from an environment variable is preferred, but if the environment does not define the variable, then the hardcoded string is used instead as a default. When you are developing this application, the security requirements are low, so you can just ignore this setting and let the hardcoded string be used. But when this application is deployed on a production server, I will be setting a unique and difficult to guess value in the environment, so that the server has a secure key that nobody else knows.

Now that I have a config file, I need to tell Flask to read it and apply it. That can be done right after the Flask application instance is created using the app.config.from_object() method:

app/__init__.py: Flask configuration

from flask import Flask
from config import Config

app = Flask(__name__)
app.config.from_object(Config)

from app import routes

The way I'm importing the Config class may seem confusing at first, but if you look at how the Flask class (uppercase "F") is imported from the flask package (lowercase "f") you'll notice that I'm doing the same with the configuration. The lowercase "config" is the name of the Python module config.py, and obviously the one with the uppercase "C" is the actual class.

As I mentioned above, the configuration items can be accessed with a dictionary syntax from app.config. Here you can see a quick session with the Python interpreter where I check what is the value of the secret key:

>>> from microblog import app
>>> app.config['SECRET_KEY']
'you-will-never-guess'

User Login Form

The Flask-WTF extension uses Python classes to represent web forms. A form class simply defines the fields of the form as class variables.

Once again having separation of concerns in mind, I'm going to use a new app/forms.py module to store my web form classes. To begin, let's define a user login form, which asks the user to enter a username and a password. The form will also include a "remember me" check box, and a submit button:

app/forms.py: Login form

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    password = PasswordField('Password', validators=[DataRequired()])
    remember_me = BooleanField('Remember Me')
    submit = SubmitField('Sign In')

Most Flask extensions use a flask_<name> naming convention for their top-level import symbol. In this case, Flask-WTF has all its symbols under flask_wtf. This is where the FlaskForm base class is imported from at the top of app/forms.py.

The four classes that represent the field types that I'm using for this form are imported directly from the WTForms package, since the Flask-WTF extension does not provide customized versions. For each field, an object is created as a class variable in the LoginForm class. Each field is given a description or label as a first argument.

The optional validators argument that you see in some of the fields is used to attach validation behaviors to fields. The DataRequired validator simply checks that the field is not submitted empty. There are many more validators available, some of which will be used in other forms.

Form Templates

The next step is to add the form to an HTML template so that it can be rendered on a web page. The good news is that the fields that are defined in the LoginForm class know how to render themselves as HTML, so this task is fairly simple. Below you can see the login template, which I'm going to store in file app/templates/login.html:

app/templates/login.html: Login form template

{% extends "base.html" %}

{% block content %}
    <h1>Sign In</h1>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}
        </p>
        <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

For this template I'm reusing one more time the base.html template as shown in Chapter 2, through the extends template inheritance statement. I will actually do this with all the templates, to ensure a consistent layout that includes a top navigation bar across all the pages of the application.

This template expects a form object instantiated from the LoginForm class to be given as an argument, which you can see referenced as form. This argument will be sent by the login view function, which I still haven't written.

The HTML <form> element is used as a container for the web form. The action attribute of the form is used to tell the browser the URL that should be used when submitting the information the user entered in the form. When the action is set to an empty string the form is submitted to the URL that is currently in the address bar, which is the URL that rendered the form on the page. The method attribute specifies the HTTP request method that should be used when submitting the form to the server. The default is to send it with a GET request, but in almost all cases, using a POST request makes for a better user experience because requests of this type can submit the form data in the body of the request, while GET requests add the form fields to the URL, cluttering the browser address bar. The novalidate attribute is used to tell the web browser to not apply validation to the fields in this form, which effectively leaves this task to the Flask application running in the server. Using novalidate is entirely optional, but for this first form it is important that you set it because this will allow you to test server-side validation later in this chapter.

The form.hidden_tag() template argument generates a hidden field that includes a token that is used to protect the form against CSRF attacks. All you need to do to have the form protected is include this hidden field and have the SECRET_KEY variable defined in the Flask configuration. If you take care of these two things, Flask-WTF does the rest for you.

If you've written HTML web forms in the past, you may have found it odd that there are no HTML fields in this template. This is because the fields from the form object know how to render themselves as HTML. All I needed to do was to include {{ form.<field_name>.label }} where I wanted the field label, and {{ form.<field_name>() }} where I wanted the field. For fields that require additional HTML attributes, those can be passed as arguments. The username and password fields in this template take the size as an argument that will be added to the <input> HTML element as an attribute. This is how you can also attach CSS classes or IDs to form fields.

Form Views

The final step before you can see this form in the browser is to code a new view function in the application that renders the template from the previous section.

So let's write a new view function mapped to the /login URL that creates a form, and passes it to the template for rendering. This view function can also go in the app/routes.py module with the previous one:

app/routes.py: Login view function

from flask import render_template
from app import app
from app.forms import LoginForm

# ...

@app.route('/login')
def login():
    form = LoginForm()
    return render_template('login.html', title='Sign In', form=form)

What I did here is import the LoginForm class from forms.py, instantiated an object from it, and sent it down to the template. The form=form syntax may look odd, but is simply passing the form object created in the line above (and shown on the right side) to the template with the name form (shown on the left). This is all that is required to get form fields rendered.

To make it easy to access the login form, the base template can include a link to it in the navigation bar:

app/templates/base.html: Login link in navigation bar

<div>
    Microblog:
    <a href="/index">Home</a>
    <a href="/login">Login</a>
</div>

At this point you can run the application and see the form in your web browser. With the application running, type http://localhost:5000/ in the browser's address bar, and then click on the "Login" link in the top navigation bar to see the new login form. Pretty cool, right?

Login Form

Receiving Form Data

If you try to press the submit button the browser is going to display a "Method Not Allowed" error. This is because the login view function from the previous section does one half of the job so far. It can display the form on a web page, but it has no logic to process data submitted by the user yet. This is another area where Flask-WTF makes the job really easy. Here is an updated version of the view function that accepts and validates the data submitted by the user:

app/routes.py: Receiving login credentials

from flask import render_template, flash, redirect

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        flash('Login requested for user {}, remember_me={}'.format(
            form.username.data, form.remember_me.data))
        return redirect('/index')
    return render_template('login.html', title='Sign In', form=form)

The first new thing in this version is the methods argument in the route decorator. This tells Flask that this view function accepts GET and POST requests, overriding the default, which is to accept only GET requests. The HTTP protocol states that GET requests are those that return information to the client (the web browser in this case). All the requests in the application so far are of this type. POST requests are typically used when the browser submits form data to the server (in reality GET requests can also be used for this purpose, but it is not a recommended practice). The "Method Not Allowed" error that the browser showed you before, appears because the browser tried to send a POST request and the application was not configured to accept it. By providing the methods argument, you are telling Flask which request methods should be accepted.

The form.validate_on_submit() method does all the form processing work. When the browser sends the GET request to receive the web page with the form, this method is going to return False, so in that case the function skips the if statement and goes directly to render the template in the last line of the function.

When the browser sends the POST request as a result of the user pressing the submit button, form.validate_on_submit() is going to gather all the data, run all the validators attached to fields, and if everything is all right it will return True, indicating that the data is valid and can be processed by the application. But if at least one field fails validation, then the function will return False, and that will cause the form to be rendered back to the user, like in the GET request case. Later I'm going to add an error message when validation fails.

When form.validate_on_submit() returns True, the login view function calls two new functions, imported from Flask. The flash() function is a useful way to show a message to the user. A lot of applications use this technique to let the user know if some action has been successful or not. In this case, I'm going to use this mechanism as a temporary solution, because I don't have all the infrastructure necessary to log users in for real yet. The best I can do for now is show a message that confirms that the application received the credentials.

The second new function used in the login view function is redirect(). This function instructs the client web browser to automatically navigate to a different page, given as an argument. This view function uses it to redirect the user to the index page of the application.

When you call the flash() function, Flask stores the message, but flashed messages will not magically appear in web pages. The templates of the application need to render these flashed messages in a way that works for the site layout. I'm going to add these messages to the base template, so that all the templates inherit this functionality. This is the updated base template:

app/templates/base.html: Flashed messages in base template

<html>
    <head>
        {% if title %}
        <title>{{ title }} - microblog</title>
        {% else %}
        <title>microblog</title>
        {% endif %}
    </head>
    <body>
        <div>
            Microblog:
            <a href="/index">Home</a>
            <a href="/login">Login</a>
        </div>
        <hr>
        {% with messages = get_flashed_messages() %}
        {% if messages %}
        <ul>
            {% for message in messages %}
            <li>{{ message }}</li>
            {% endfor %}
        </ul>
        {% endif %}
        {% endwith %}
        {% block content %}{% endblock %}
    </body>
</html>

Here I'm using a with construct to assign the result of calling get_flashed_messages() to a messages variable, all in the context of the template. The get_flashed_messages() function comes from Flask, and returns a list of all the messages that have been registered with flash() previously. The conditional that follows checks if messages has some content, and in that case, a <ul> element is rendered with each message as a <li> list item. This style of rendering does not look great, but the topic of styling the web application will come later.

An interesting property of these flashed messages is that once they are requested once through the get_flashed_messages function they are removed from the message list, so they appear only once after the flash() function is called.

This is a great time to try the application one more time and test how the form works. Make sure you try submitting the form with the username or password fields empty, to see how the DataRequired validator halts the submission process.

Improving Field Validation

The validators that are attached to form fields prevent invalid data from being accepted into the application. The way the application deals with invalid form input is by re-displaying the form, to let the user make the necessary corrections.

If you tried to submit invalid data, I'm sure you noticed that while the validation mechanisms work well, there is no indication given to the user that something is wrong with the form, the user simply gets the form back. The next task is to improve the user experience by adding a meaningful error message next to each field that failed validation.

In fact, the form validators generate these descriptive error messages already, so all that is missing is some additional logic in the template to render them.

Here is the login template with added field validation messages in the username and password fields:

app/templates/login.html: Validation errors in login form template

{% extends "base.html" %}

{% block content %}
    <h1>Sign In</h1>
    <form action="" method="post" novalidate>
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
            <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
        <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.remember_me() }} {{ form.remember_me.label }}</p>
        <p>{{ form.submit() }}</p>
    </form>
{% endblock %}

The only change I've made is to add for loops right after the username and password fields that render the error messages added by the validators in red color. As a general rule, any fields that have validators attached will have any error messages that result from validation added under form.<field_name>.errors. This is going to be a list, because fields can have multiple validators attached and more than one may be providing error messages to display to the user.

If you try to submit the form with an empty username or password, you will now get a nice error message in red.

Form validation

Generating Links

The login form is fairly complete now, but before closing this chapter I wanted to discuss the proper way to include links in templates and redirects. So far you have seen a few instances in which links are defined. For example, this is the current navigation bar in the base template:

    <div>
        Microblog:
        <a href="/index">Home</a>
        <a href="/login">Login</a>
    </div>

The login view function also defines a link that is passed to the redirect() function:

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        # ...
        return redirect('/index')
    # ...

One problem with writing links directly in templates and source files is that if one day you decide to reorganize your links, then you are going to have to search and replace these links in your entire application.

To have better control over these links, Flask provides a function called url_for(), which generates URLs using its internal mapping of URLs to view functions. For example, the expression url_for('login') returns /login, and url_for('index') return '/index. The argument to url_for() is the endpoint name, which is the name of the view function.

You may ask why is it better to use the function names instead of URLs. The fact is that URLs are much more likely to change than view function names, which are completely internal. A secondary reason is that as you will learn later, some URLs have dynamic components in them, so generating those URLs by hand would require concatenating multiple elements, which is tedious and error prone. The url_for() is also able to generate these complex URLs.

So from now on, I'm going to use url_for() every time I need to generate an application URL. The navigation bar in the base template then becomes:

app/templates/base.html: Use url\_for() function for links

    <div>
        Microblog:
        <a href="{{ url_for('index') }}">Home</a>
        <a href="{{ url_for('login') }}">Login</a>
    </div>

And here is the updated login() view function:

app/routes.py: Use url\_for() function for links

from flask import render_template, flash, redirect, url_for

# ...

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        # ...
        return redirect(url_for('index'))
    # ...

501 comments

  • #76 Miguel Grinberg said 2018-04-22T06:36:31Z

    @brian: you need to compare the structure of the project against my version on GitHub. Something is in the wrong place.

  • #77 Varun said 2018-04-24T19:22:34Z

    Hi Miguel,

    I'm getting this error after setting the FLASK_APP variable like so export FLASK_APP=microblog.py Flask was running fine in parts 1 and 2 but now in part 3 I'm getting this error after making changes the blog specifies midway through this article.

    Error I'm getting after executing "flask run" Error: The file/path provided (microblog) does not appear to exist. Please verify the path is correct. If app is not on PYTHONPATH, ensure the extension is .py

    Do you know why this is happening in chapter 3 and didn't happen in chapters 1 and 2?

    Thanks,

  • #78 Miguel Grinberg said 2018-04-25T01:23:15Z

    @Varun: maybe you deleted microblog.py by mistake? Compare your project structure against mine on GitHub if you need help discovering the problem.

  • #79 Marvin said 2018-04-29T19:27:14Z

    First of all i wanna say: This is a great tutorial!

    I keep getting an "ImportError: No module named 'flask_wtf'". I have tried to install flask_wtf both globally and on the virtualenv since i wasn't sure which python is used when i execute 'flask run'. Do you have any idea? I'd appreciate your help, thank you in advance.

  • #80 Miguel Grinberg said 2018-04-30T02:24:26Z

    @Marvin: my recommendation is that you follow the instructions on my articles. All installs need to be made on the virtual environment, which should be activated, so that "flask run" starts with it and not with the global Python interpreter.

  • #81 Abdul said 2018-05-01T12:57:20Z

    Hi Miguel,

    The form.validate_on_submit() will return True or False after checking form fields in addition to csrf. i want to handle issues related to csrf differently. How to know if the the "False" value that came from form.validate_on_submit() is related to csrf in particular?

    Thanks.

  • #82 Animesh said 2018-05-01T14:42:24Z

    Hi Miguel, I have a question regarding the usage of redirect and url_for to replace the functionality of render_template. In this link, the index method in routes.py, uses a render_template call, is there a way to replace this call by redirect and url_for ?

  • #83 Miguel Grinberg said 2018-05-02T05:17:22Z

    @Animesh: not sure I understand what you are trying to do. A redirect() call cannot replace a render_template() call, they do different things. A redirect basically tells the browser to read a different URL. But that new URL must be implemented with a render_template().

  • #84 Miguel Grinberg said 2018-05-02T05:34:17Z

    @Abdul: after a False validation, you can check form.csrf_token.errors. If it is empty, then the CSRF token was valid. If the error list has at list one element, then the CSRF validation failed. Good question!

  • #85 Tbalz said 2018-05-06T02:31:37Z

    Hi Miguel,

    Awesome blog! I have learned a lot but I'm stuck at one part and can't figure it out.

    I get the error below from Flask shell:

    flask shell Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 17:00:18) [MSC v.1900 64 bit (AMD64)] on win32 App: app [production] Instance: C:\Users\Talal\microblog\instance

    db Traceback (most recent call last): File "", line 1, in NameError: name 'db' is not defined

    any idea why name 'db' is showing up as not defined please?

  • #86 Miguel Grinberg said 2018-05-06T05:28:46Z

    @Tbalz: What chapter of the tutorial are you on? Have you done anything to make "db" automatically be imported in a flask shell?

  • #87 Ghouse said 2018-05-07T16:31:02Z

    Hi miguel,

    I'm one of your follower learning flask from ebook.

    My doubt is how to debug my view function written in my routes.py file because i'm getting error as "Internal Server Error". I've gone through microblog application all chapters as mentioned in ebook. i didn't see that you are logging application running info to a log file.

    I need to log flask app running process information to a log file for example when we run command "flask run" we get info as below. (venv) root@sdl18277:/home/ubuntu/TTU_Tracker/Rollup# flask run * Serving Flask app "Rollup" * Forcing debug mode off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [07/May/2018 09:03:51] "POST /Compare HTTP/1.1" 200

    if flask app failed to load any view in browser it will display log on console with python stacktrace,so that we can fix the bug. but as you deployed app on linux(ubuntu) and launching application using gunicorn and supervisorctl. i didn't see that your logging flask app info to log file. if i'm missing anything please correct me.

    Please guide me how to log flask app running process info to a logfile after deploying on linux(ubuntu) machine.it will be great help for me.because

  • #88 Miguel Grinberg said 2018-05-07T19:17:16Z

    @Ghouse: you are posting this question on Chapter 3 of the tutorial? How far are you into it? The deployment chapters are near the end, and they all include the set up of a log file to catch errors.

  • #89 Maunuel Rodriguez said 2018-05-12T11:02:12Z

    This is the best flask tutorial I ever came across. Thank you very much for sharing this. I found your Flask video lessons in safari online, after seeing the quality of your work I will definitively give them a go. All the best!

  • #90 Jon said 2018-05-14T07:08:12Z

    Hey Miguel, your tutorial looks great .. esta genial!

    I ve been following every singles step and looks like after importing flask_wtf everything started failing .. I need help!

    here's my terminal's output: Serving Flask-SocketIO app "app.microblog" Traceback (most recent call last): File "/usr/local/bin/flask", line 11, in sys.exit(main()) File "/Library/Python/2.7/site-packages/flask/cli.py", line 513, in main cli.main(args=args, prog_name=name) File "/Library/Python/2.7/site-packages/flask/cli.py", line 380, in main return AppGroup.main(self, args, kwargs) File "/Library/Python/2.7/site-packages/click/core.py", line 697, in main rv = self.invoke(ctx) File "/Library/Python/2.7/site-packages/click/core.py", line 1066, in invoke return _process_result(sub_ctx.command.invoke(sub_ctx)) File "/Library/Python/2.7/site-packages/click/core.py", line 895, in invoke return ctx.invoke(self.callback, ctx.params) File "/Library/Python/2.7/site-packages/click/core.py", line 535, in invoke return callback(args, kwargs) File "/Library/Python/2.7/site-packages/click/decorators.py", line 64, in new_func return ctx.invoke(f, obj, *args[1:], kwargs) File "/Library/Python/2.7/site-packages/click/core.py", line 535, in invoke return callback(args, **kwargs) File "/Library/Python/2.7/site-packages/flask_socketio/cli.py", line 65, in run run_server() File "/Library/Python/2.7/site-packages/flask_socketio/cli.py", line 50, in run_server app = info.load_app() File "/Library/Python/2.7/site-packages/flask/cli.py", line 237, in load_app rv = locate_app(self.app_import_path) File "/Library/Python/2.7/site-packages/flask/cli.py", line 90, in locate_app import(module) File "/Users/jtarrico/Desktop/microblog/app/init.py", line 7, in from app import routes File "/Users/jtarrico/Desktop/microblog/app/routes.py", line 3, in from app.forms import LoginForm File "/Users/jtarrico/Desktop/microblog/app/forms.py", line 1, in from flask_wtf import FlaskForm ImportError: No module named flask_wtf

  • #91 Miguel Grinberg said 2018-05-14T21:26:22Z

    @Jon: it appears you did not install the Flask-WTF extension. Use "pip install flask-wtf".

  • #92 mohammad said 2018-05-18T14:07:51Z

    Dear Miguel, great tutorial , I don't understand the way the config module has imported . how this ( from config import Config ) work while config modules placed on the parent directory of init.py .

  • #93 Miguel Grinberg said 2018-05-18T17:26:41Z

    The "config" module is in your current directory (the top-level microblog directory), so you can import it directly because the current directory is in the import path.

  • #94 pallavi said 2018-05-20T18:32:31Z

    Hi Miguel, I have trying this wtf forms for the flask application which you explained chapter 3. While doing the forms part and running the application I am getting the below error:

    Error: The file/path provided (microblog.py) does not appear to exist. Please verify the path is correct. If app is not on PYTHONPATH, ensure the extension is .py

    my dir structure is like this: microblog app templates(folder),forms.py,routes.py

    venv __init__.py , config.py,microblog.py
  • #95 Miguel Grinberg said 2018-05-20T21:51:21Z

    @pallavi: you may want to go back to Chapter 1 to learn about the correct application structure. The microblog.py module goes in the top-level folder.

  • #96 sudheer said 2018-05-23T08:17:13Z

    Hi Miguel First of all I have to say this is a brilliant way to learn flask. I got an error on chapter 3. Things were working until i used forms. I have made all the changes. When i run the app i get the following error Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [23/May/2018 09:04:11] "GET / HTTP/1.1" 404 - 127.0.0.1 - - [23/May/2018 09:04:15] "GET / HTTP/1.1" 404 - 127.0.0.1 - - [23/May/2018 09:05:33] "GET / HTTP/1.1" 404 - 127.0.0.1 - - [23/May/2018 09:05:40] "GET /index HTTP/1.1" 404 - [2018-05-23 09:05:51,590] ERROR in app: Exception on /login [GET] Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/flask/app.py", line 2292, in wsgi_app response = self.full_dispatch_request() File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/flask/app.py", line 1816, in full_dispatch_request return self.finalize_request(rv) File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/flask/app.py", line 1831, in finalize_request response = self.make_response(rv) File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/flask/app.py", line 1957, in make_response 'The view function did not return a valid response. The' TypeError: The view function did not return a valid response. The function either returned None or ended without a return statement. 127.0.0.1 - - [23/May/2018 09:05:51] "GET /login HTTP/1.1" 500 -

    from flask import render_template, flash, redirect, url_for from app import app from app.forms import LoginForm This is in my routes.py

    @app.route('/login', methods=['GET', 'POST']) def login(): form = LoginForm() if form.validate_on_submit(): return redirect(url_for('index'))

  • #97 Steve said 2018-05-24T21:58:10Z

    Hi Miguel,

    Thanks for this excellent tutorial. In another course, I learned that web apps should be designed to avoid the "browser must resend data" alert. If I leave login fields blank, I get the error message correctly; but, if I try to reload page, I get the "resend" alert window from Firefox. Isn't there a way to build this so that flash message disappears on reload? BTW I don't get that alert if I click on the "Login" link in navigation bar, i.e. flash message disappears as intended.

  • #98 Miguel Grinberg said 2018-05-24T23:02:48Z

    @sudheer: view functions must always return a response to send to the client. Your login view function does not return a response, you are missing a render_template() call that provides the HTML content of the login page.

  • #99 Miguel Grinberg said 2018-05-24T23:22:58Z

    @Steve: You are 100% correct. But you need to wait until chapter 9 to learn about how to prevent the problem.

  • #100 Phil said 2018-05-30T11:11:14Z

    100th comment!

    I have always been put off web development due to my perceived level of difficulty. This baby steps first tutorial is superb. I like how you wait a chapter or two and then gently add tidbits - brilliant.

    I am looking to teach Web Science and Dev. as part of an IB course starting in August - this is helping to breathe slightly easier!

Leave a Comment