JustToThePoint English Website Version
JustToThePoint en español
JustToThePoint in Thai

Flask Web Development

Hello World!

Flask is a lightweight web framework written in Python built for rapid development. It is a popular, extensible web microframework that lets you develop web applications easily.

Installation

  1. Create an environment:

    mkdir myPython 
    cd myPython 
    python3 -m venv venv (macOs/Linux) / py -3 -m venv venv (Windows) 
    
  2. Activate the environment: . venv/bin/activate (macOs/Linux) / venv\Scripts\activate.

  3. Install Flask: python3 -m pip install Flask.

Creating the Hello World Application

Let’s edit hello.py:

from flask import Flask # First, we import the Flask class.

app = Flask(__name__)

Next, we create a Flask application instance with the name app. Its argument is the name of the application’s module or package. You will use the Flask application instance “app” to handle incoming web requests and send responses to the user.

@app.route("/") 

We then use the route() decorator to tell Flask what URL should trigger our function, e.g. “/” signifies that this function will respond to web requests for the URL /, which is the main URL.

It converts the function’s return value into an HTTP response to be displayed by an HTTP client (web browser).

def hello_world(): # It returns the message we want to display in the user’s browser. The default content type is HTML.
    return "Hello Flask!"
``
To run the application, 
``` bash
$ export FLASK_APP=hello.py (bash) / set FLASK_APP=hello.py (CMD) # We define the FLASK_APP environment variable to tell Flask where to find the application 
$ export FLASK_ENV=development / set FLASK_ENV=development # We want to run it in development mode 
$ flask run # Run the application 
* Serving Flask app 'hello' (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 356-293-861 

Open your favorite web browser and type in the URL http://127.0.0.1:5000/ and you will receive the string Hello, World! as a response.

Creating the Hello World ++ Application

The application is going to live in a package. A package in Python is a directory with all the modules combined and a special file named init.py. This file can be empty or can contain some code used to do some initializations before accessing modules under that package. Let’s create a package called app (mkdir app) and edit the init.py for the app package:

from flask import Flask # First, we import the Flask class.

app = Flask(__name__)

Next, we create a Flask application instance with the name app. Its argument is the name of the application’s module or package. You will use the Flask application instance “app” to handle incoming web requests and send responses to the user.

from app import routes # Our application instance "app" imports the routes module.

Now, we are going to edit the module app/routes.py:

from app import app
from datetime import datetime

@app.route('/')

We then use the route() decorator to tell Flask what URL should trigger our function, e.g. “/” signifies that this function will respond to web requests for the URL /, which is the main URL. It converts the function’s return value into an HTTP response to be displayed by an HTTP client (web browser).

@app.route('/index') # In other words, when a web browser requests / or index, Flask is going to invoke this function and pass the return value of it back to the browser.
def index():
    now = datetime.now()
    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
    return "Hello Flask! Today is " + dt_string

Python-dotenv reads key-value pairs from a .env file and can set them as environment variables. It helps in the development of applications. Installation: python3 -m pip install python-dotenv. To configure the development environment, add a .flaskenv in the root directory of your project: FLASK_APP=hello.py FLASK_ENV=development

Finally, we need to have a Python source file at the top-level that imports the Flask application instance, hello.py.

#!flask/bin/python
from app import app # It imports the "app" variable that is a member of the "app" package.
if __name__ == "__main__":
      app.run()

Templates

Generating HTML from within Python is not cool. Python configures the Jinja2 template engine for you automatically. The idea is simple, it is about keeping separated the logic of the application from the design. Templates are written as files stored in a templates folder that is inside our application package.

Let’s edit app/routes.py:

from app import app
from datetime import datetime
from flask import render_template

@app.route('/') 

We then use the route() decorator to tell Flask what URL should trigger our function, e.g. “/” signifies that this function will respond to web requests for the URL /, which is the main URL. It converts the function’s return value into an HTTP response to be displayed by an HTTP client (web browser).

@app.route('/hello/') 
def index(name=None):
    now = datetime.now()
    dt_string = "Today is " + now.strftime("%d/%m/%Y %H:%M:%S")
    return render_template('hello.html', name=name, body= dt_string) # To render a template you can use the render_template() method. Then, you need to provide the name of the template ('hello.html') and the variables (name, body) you want to pass to the template engine as keywords arguments.

Flask will look for templates in the /app/templates folder. Let’s create the directory where templates will be stored (mkdir app/templates) and edit a page template (gedit app/templates/hello.html):

<!doctype HTML>
<title>Hello from Flask</title>
 <body>
     {% if name %} 
     {# There are two kinds of delimiters. {% ... %} and {{ ... }}. 
     The first one is used to execute control statements (if, for) or assign values, the latter prints the result of the expression to the template. #}
         <h1>Hello {{ name }}!</h1>
     {% else %}
         <h1>Hello, World!</h1>
     {% endif %}
 <body>
  <p>Today is {{ body }}</p>
 </body>
Flask web development

Flask web development

Flask-Bootstrap and Dominate

A blueprint defines a collection of views, templates, static files, and other elements that can be applied to an application so you can organize it into distinct components. Flask-Bootstrap packages Bootstrap into an extension that mostly consists of a blueprint named ‘bootstrap’. Installation: pip install Flask-Bootstrap.

Bootstrap is a free and open-source CSS framework directed at responsive, mobile-first front-end web development.

The first step is to import and load the extension, gedit app/init.py:

from flask import Flask
from flask_bootstrap import Bootstrap

app = Flask(__name__)
_Bootstrap(app)_
from app import routes

gedit app/routes.py

from app import app
from myExecuteCommand import saludo # We are going to create a more elaborated example where we are going to greet our visitors with a cute cow.
from flask import render_template

@app.route('/')
def index():
    return render_template('index.html', body=saludo())

Next, we are going to create a new Bootstrap-based template. gedit app/templates/index.html

{% extends 'bootstrap/base.html' %}

“The most powerful part of Jinja is template inheritance. Template inheritance allows you to build a base “skeleton” template that contains all the common elements of your site and defines blocks that child templates can override. The {% extends %} tag is the key here. It tells the template engine that this template “extends” another template,” Flask, Template Inheritance.

{% block title %}Hello from Flask{% endblock %}
{% block head %}
     <ink rel="stylesheet" href="{{ url_for('.static', filename='css/bootstrap.min.css') }}">

Dynamic web applications also use static files, such as .css, .js, etc. These files have to be stored on the directory /static, e.g., /static/css/bootstrap.min.css is where Bootstrap’s compiled CSS has been placed. We build a URL using the url_for() function. To generate URLs for static files, we use the special ‘static’ endpoint name.

{% endblock %}
{% block content %}
 <body>
     <h1>Hello World!</h1>
     <div class="container">

Containers are the most basic layout element in Bootstrap. The default .container class is a responsive, fixed-width container.

        {{ body | safe  }}

When generating HTML from templates, there’s always a risk that a variable will include characters that affect the resulting HTML. We use the filter safe to mark content as safe, and therefore not in need of escaping.

    </div>
 </body>
{% endblock %}
{% block scripts %}
    <script src="{{url_for('.static', filename='js/bootstrap.bundle.min.js')}}"></script>
    <script src="{{url_for('.static', filename='js/jquery.min.js')}}"></script>
{% endblock %}

Child templates are a useful way to reuse common design elements for more complicated layouts. They are based on blocks. We are using four blocks: title, head, content, and scripts. title and head contain the complete content of the and tag respectively, and content is just a convenience block inside the body. We are using the “scripts” block to house bootstrap.bundle.min.js and jquery.min.js. These JavaScript libraries are necessary for Bootstrap to work.

Dominate is a Python library for creating and manipulating HTML documents. It allows you to write HTML pages in pure Python very easily.

  1. Installation: sudo pip install dominate.
  2. Use: print(html(body(h1(‘Hello, World!’)))) returns:
<html>
    <body>
        <h1>Hello, World!</h1>
    </body>
</html>

gedit app/myExecuteCommand.py:

from dominate.tags import * # You need to import the entire tag set.
from asciiArt import cowtalk

""" Display a greeting """
def saludo():  
    miDiv = div(__pretty=False)
    miDiv.set_attribute('class', 'row') # 
with miDiv: miDiv1 = div() miDiv1.set_attribute('class', 'col') # We are going to create a one row and two equal-width columns layout. with miDiv1: cowtalk("Have you mooed today?") miDiv2 = div() miDiv2.set_attribute('class', 'col') with miDiv2: p("""Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex [...] Errors should never pass silently.""") return miDiv.render() # It returns the HTML representation.

Python has a simple algorithm for finding a module, e.g., my_module. It just looks for a file called my_module in the directories listed in the variable sys.path. To make sure a directory is on the Python sys.path list, put the directory on your PYTHONPATH environment variable. PYTHONPATH=$PYTHONPATH:’/home/myUser/myAppPath’ export PYTHONPATH

gedit app/asciiArt.py:

from dominate.tags import *
from dominate.util import text, raw
import cowsay 

pip install cowsay, it generates ASCII pictures of a cow (or other animals) with a message. https://github.com/jcn/cowsay-py/blob/master/cowsay.py is a python implementation of cowsay.

def cowtalk(message):
    miP = div(style="font-family: monospace;white-space: pre;")
    with miP:
        raw(cowsay.get_output_string('cow', message))
        
    return miP.render()
Web development with Flask

Web development with Flask

Handling POST and GET Requests with Flask

Web applications use different HTTP methods when accessing URLs. By default, a route only answers to GET requests. GET is used to request data from a specified resource.

Observe that the query string (key/value pairs) is sent in the URL of a GET request, e.g, http://justtothepoint.com?arg1=value1&arg2=value2. It begins after the question mark character (?) and has key-value pairs separated by an ampersand character (&).

We are going to add a query string to the get-example route: http://127.0.0.1:5000/get-example?string=hola. Let’s edit our app/routes.py file:

from flask import Flask, render_template, request, redirect
def reverse(s):
    if len(s) == 0:
        return s
    else:
        return reverse(s[1:]) + s[0]

@app.route('/get-example')
def get-example():

When the Flask application handles a request, it creates a Request object. The Request object holds all incoming data from the request which includes IP address, HTTP method, headers, etc. Request.args is a MultiDict with the parsed contents of the query string.

    mystring = request.args.get('string') #  If the key doesn't exist, it returns None

    return '''<h1>The string is: {}.</h1><p>Its length is: {}.<br>The reversed string is {}.</p>'''.format(mystring, len(mystring), reverse(mystring))

The string is: hola. Its length is: 4. The reversed string is aloh.

A POST request is used to send data to the server using HTML forms. So instead of seeing the data in the URL, the form data will be passed to the app behind the scenes. Let’s edit our app/routes.py file again.

from flask import Flask, render_template, request, redirect
@app.route('/form-example', methods=['GET', 'POST']) # We will navigate to the URL:  http://127.0.0.1:5000/form-example.
def form_example():
    if request.method == 'POST': # The request.method is the method the request was made with, such as POST or GET. If the request method is POST, we will process the incoming data (base, height) and display the area of the given triangle. 
        base = request.form.get('base')
        height = request.form.get('height')
        return '''
                  <h1>The area of the triangle with base {} and height {} is: {}</h1>'''.format(base, height, 0.5*float(base)*float(height))

    # Otherwise, we need to handle the GET request
    return render_template('form.html') # We will redirect the user to the form.html page so he can introduce the triangle's base and height.

Edit app/templates/form.html

<h1>Calculate the area of a triangle</h1>
<form method="POST">
    <div><label>Base: <input type="text" name="base"></label></div><br>
    <div><label>Height: <input type="text" name="height"></label></div><br>
    <input type="submit" value="Submit">
</form> 
Web Development with Flask

Web Development with Flask

The result is: The area of the triangle with base 4 and height 3 is: 6.0

User Authentication

We will use Flask-Login. It “provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering your users’ sessions over extended periods of time.” Installation: pip install flask-login.

First, we will create a file, app/models.py to store user credentials. It will use SQLite database, Flask_SQLAlchemy, and Flask_Login. SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to our application: pip install flask-sqlalchemy.

There are two base classes for our data model: db.Model and UserMixin.

db.Model is stored on the SQLAlchemy instance that we need to create (*).

A second base class is UserMixin from the Flask_Login library. Our user class needs to implement these properties and methods: is_authenticated (it should return True if the user is authenticated), is_active (it should return True if this is an active user -in addition to being authenticated), is_anonymous (it should return True if this is an anonymous user), and get_id() (it returns a Unicode that uniquely identifies this user). UserMixin provides default implementations for all of these properties and methods.

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin, LoginManager
from werkzeug.security import generate_password_hash, check_password_hash
 
login = LoginManager() # The most important part of an application that uses Flask-Login is the LoginManager class. It contains the code that lets your application and Flask-Login work together, such as how to load a user from an ID, where to send users when they need to log in, and the like. We create a LoginManager instance.
db = SQLAlchemy() # We create a SQLAlchemy instance (*)
 
class User(UserMixin, db.Model):
    __tablename__ = 'myUsers' # We define the table's name 
 
    id = db.Column(db.Integer, primary_key=True, autoincrement=True) # The table myUsers has four columns: id (primary key, it forces the id column to behave as a completely unique index for the table; autoincrement=True means that each successive insert into the table will automatically assign a value greater than any previous row of the table), email, username, and password.
    email = db.Column(db.String(80), unique=True)
    username = db.Column(db.String(50))
    password = db.Column(db.String(255))
    
    def __init__(self, name, email, password):
        self.username = name
        self.password = generate_password_hash(password)

It takes plaintext password, hashing method=‘pbkdf2:sha256’ and salt_length=16 as an input to produce hashed password. The output string contains hashing method, salt, and hashed password concatenated with the $: method$salt$hash, e.g., $pbkdf2:sha256:260000$ffXwWiHydD8S7EVT$00055d5b08b96a78a2ad4dcd7e860cb35a5cd2b823da642ab373d24b829b1fc9

        self.email = email
        
    def set_password(self, password):
        self.password = generate_password_hash(password)
    def check_password(self, password):
        return check_password_hash(self.password, password)
    def __repr__(self):
        return ''.format(self.username)

# We need to provide a user_loader callback. It is used to reload the user object from the user ID stored in the session. It should take the ID of a (logged-in) user and return the corresponding user object. User.query.get retrieves a user by its primary key. 
@login.user_loader
def load_user(id):
    return User.query.get(int(id))

gedit app/ini.py

from flask import Flask
from flask_bootstrap import Bootstrap
from models import db, login

app = Flask(__name__) # We create the Flask application
app.config['SECRET_KEY'] = '7e1dd8c0022ea022cf53ff140b58b964a492e6f6'

In order to use sessions (it allows you to store information specific to a user from one request to the next) you have to set a secret key (signing cookies is a preventive measure against cookie tempering and the secret key is used in a way similar to how a “salt” would be used to muddle a password before hashing it).

How do you get a good secret key? I am glad that you ask me that question:

user@pc:~$ python 
>>> import secrets 
>>> secrets.token.hex(20)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///mydatabase.db' # After creating the Flask application, we load the configuration. Flask-SQLAlchemy loads those values from the main Flask config. SQLALCHEMY_DATABASE_URI, the database URI that should be used for the connection.
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # SQLALCHEMY_TRACK_MODIFICATIONS. If set to True, Flask-SQLAlchemy will track modifications of objects and emit signals.
db.init_app(app) # It assigns the database of SQLAlchemy to our Flask app.

@app.before_first_request # Functions decorated with @app.before_first_request will run once before the first request to the instance of the application.
def create_table():
    db.create_all() # It creates the initial database
# We configure our Flask app for login (login = LoginManager()) with:    
login.init_app(app) # In other words, we are linking the login and the db instances to our app.
login.login_view = 'login'

By default, when a user attempts to access a login_required view without being logged in, Flask-Login will flash a message and redirect them to the login view. The name of the login view is set with LoginManager.login_view.

Bootstrap(app)
from app import routes

gedit app/routes.py

from app import app
from myExecuteCommand import saludo
from flask import Flask,render_template,request,redirect
from flask_login import login_required, current_user, login_user, logout_user
from models import User, db, login

@app.route('/')
@app.route('/index')
@login_required # Views that require their users to be logged in can be decorated with the login_required decorator.
def index():
    return render_template('index.html', body=saludo())

@app.route('/login', methods = ['POST', 'GET'])
def login():
    if current_user.is_authenticated: # flask_login.current_user is a proxy for the current user. If the current user is already authenticated, we redirect it to the index.html page.
        return redirect('/index')
     
    if request.method == 'POST': # We are going to handle a post request. A POST request is used to send HTML form data (login.html) to our app.
        email = request.form['email'] # To access form data, we use request.form, but first we should add a conditional statement to validate we're receiving POST data (If request.method=='POST'). request.form is the key/value pairs in the body from a HTML post form. 
        user = User.query.filter_by(email = email).first() # User.query.filter_by retrieves a user by email.  
        if user is not None and user.check_password(request.form['password']): # If there was a user in the database with this email and his password is the same that the one that we are getting from the login.html form ...
            login_user(user) # ... we log him in with the Flask-Login's login_user function
            return redirect('/index') # and redirect him to the index view
     
    return render_template('login.html') # The current user is not authenticated and we are going to handle a GET request. We will redirect the user to the login.html page so he can introduce his credentials.
 
@app.route('/register', methods=['POST', 'GET'])
def register():
    if current_user.is_authenticated:  # flask_login.current_user is a proxy for the current user. If the current user is already authenticated, we redirect it to the index.html page.
        return redirect('/index')
     
    if request.method == 'POST': # We are going to handle a post request. A POST request is used to send HTML form data (register.html) to our app.
        email = request.form['email'] # We retrieve the email, username and password values from the form.
        username = request.form['username']
        password = request.form['password']
 
        if User.query.filter_by(email=email).first(): # User.query.filter_by retrieves a user by email.
            return ('This email is already in our database') # We send the user a message that the email is already in use
             
        user = User(email=email, name=username, password=password) # If the email is not in our database, we are going to insert a new row into our myUsers table. It is a three step process: 1. We create an instance or an object of our User class.
        db.session.add(user) # 2. We add it to the session.
        db.session.commit() # 3. Commit the session, and by doing so, add the user to the database.
        return redirect('/login') # We redirect the user to the login view so he can log in.
    return render_template('register.html') # We are going to handle a GET request. We will redirect the user to the register.html page so he can register into the application.

You should install SQLite (Ubuntu, Debian, and derivates: sudo apt install sqlite). Then, connect to myDatabase.db: sqlite3 myDatabase.db. After that, you can list all databases (sqlite> .databases) and tables (sqlite> .tables). Finally, you can query the users table: sqlite> select * from users;

@app.route('/logout') # The logout view simply log users out.
def logout():
    logout_user() # we log him out with the Flask-Login's logout_user() function and any cookies for his session will be cleaned up
    return redirect('/index') # and redirect him to the index view

gedit app/templates/login.html: (Read Bootstrap, Docs, Forms)

{% extends "bootstrap/base.html" %}
{% block title %}Hello from Flask{% endblock %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('.static', filename='css/bootstrap.min.css') }}">
{% endblock %}
{% block content %}
<form action="" method = "POST">
<div class="container-fluid">  <!––  We use .container-fluid for a full width container, spanning the entire width of the viewport.––>
    <div class="row">
        <div class="offset-md-4 col-md-4">  <!––  We move columns to the right using .offset-md-* classes. ––>
            <h2>Login</h2>
            <hr> 
            <div class="form-group">
                <label for="exampleInputEmail1">Email address</label>
                <input type = "email" name = "email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" />
                <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
            </div>
            <div class="form-group">
                <label for="exampleInputPassword1">Password</label>
                <input type="password" name = "password" class="form-control" id="exampleInputPassword1" placeholder="Password"v/>
            </div>
            <br>
        <input class="btn btn-primary" type = "submit" value = "Submit" /><br>
        If you don't have an account... <a href = "{{url_for('register') }}">Register Here</a>
        </div>
    </div>
</div>
</form>
{% endblock %}
{% block scripts %}
    <script src="{{url_for('.static', filename='js/bootstrap.bundle.min.js')}}"></script>
    <script src="{{url_for('.static', filename='js/jquery.min.js')}}"></script>
{% endblock %}
Web Development with Flask

Web Development with Flask

gedit app/templates/register.html

{% extends "bootstrap/base.html" %}
{% block title %}Hello from Flask{% endblock %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('.static', filename='css/bootstrap.min.css') }}">
{% endblock %}
{% block content %}
<form action="" method = "POST">
<div class="container-fluid">
    <div class="row">
        <div class="offset-md-4 col-md-4"> 
            <h2>Register</h2>
            <hr> 
            <div class="form-group">
                <label for="exampleInputEmail">Email address</label>
                <input type = "email" name = "email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" />
                <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
            </div>
            <div class="form-group">
                <label for="exampleInputUsername">Username</label>
                <input type = "text" name = "username" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" />
            </div>
            <div class="form-group">
                <label for="exampleInputPassword1">Password</label>
                <input type="password" name = "password" class="form-control" id="exampleInputPassword1" placeholder="Password"v/>
            </div>
            <br>
        <input class="btn btn-primary" type = "submit" value = "Submit" /><br>
        </div>
    </div>
</div>
</form>
{% endblock %}
{% block scripts %}
    <script src="{{url_for('.static', filename='js/bootstrap.bundle.min.js')}}"></script>
    <script src="{{url_for('.static', filename='js/jquery.min.js')}}"></script>
{% endblock %}
Web Development with Flask

Web Development with Flask

gedit app/templates/index.html

{% extends "bootstrap/base.html" %}
{% block title %}Hello from Flask{% endblock %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('.static', filename='css/bootstrap.min.css') }}">
{% endblock %}
{% block content %}
 <body>
     <h1>Hi {{ current_user.username }} </h1>
     <div class="container">
        {{ body | safe  }} 
        <a href="{{ url_for('logout')}}">Logout Here</a>
    </div>
 </body>
 {% endblock %}
{% block scripts %}
    <script src="{{url_for('.static', filename='js/bootstrap.bundle.min.js')}}"></script>
    <script src="{{url_for('.static', filename='js/jquery.min.js')}}"></script>
{% endblock %}

Web Development with Flask

Web Development with Flask

Bibliography: A. AskPython, Python Tutorials and examples. Flask User Authentication. B. J2logo, el blog de Python, Tutorial Flask. C. Digital Oceans Tutorials, How To Make a Web Application Using Flask. D. miguelgrinberg.com, The Flask Mega-Tutorial.

Bitcoin donation

JustToThePoint Copyright © 2011 - 2024 Anawim. ALL RIGHTS RESERVED. Bilingual e-books, articles, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun. Social Issues, Join us.

This website uses cookies to improve your navigation experience.
By continuing, you are consenting to our use of cookies, in accordance with our Cookies Policy and Website Terms and Conditions of use.