Web Forms Flask
Flask Web Forms
mkdir heihei_ru
cd heihei_ru
sudo apt-get install python3-venv
[sudo] password for andrei: Reading package lists... Done Building dependency tree Reading state information... Done The following additional packages will be installed: python3.8-venv The following NEW packages will be installed: python3-venv python3.8-venv 0 upgraded, 2 newly installed, 0 to remove and 220 not upgraded. Need to get 6 668 B of archives. After this operation, 38,9 kB of additional disk space will be used. Do you want to continue? [Y/n] Y Get:1 http://fi.archive.ubuntu.com/ubuntu focal-updates/universe amd64 python3.8-venv amd64 3.8.5-1~20.04 [5 440 B] Get:2 http://fi.archive.ubuntu.com/ubuntu focal/universe amd64 python3-venv amd64 3.8.2-0ubuntu2 [1 228 B] Fetched 6 668 B in 0s (75,2 kB/s) Selecting previously unselected package python3.8-venv. (Reading database ... 192621 files and directories currently installed.) Preparing to unpack .../python3.8-venv_3.8.5-1~20.04_amd64.deb ... Unpacking python3.8-venv (3.8.5-1~20.04) ... Selecting previously unselected package python3-venv. Preparing to unpack .../python3-venv_3.8.2-0ubuntu2_amd64.deb ... Unpacking python3-venv (3.8.2-0ubuntu2) ... Setting up python3.8-venv (3.8.5-1~20.04) ... Setting up python3-venv (3.8.2-0ubuntu2) ... Processing triggers for man-db (2.9.1-1) ...
python3 -m venv venv
source ./venv/bin/activate
pip install Flask
Collecting Flask Using cached Flask-1.1.2-py2.py3-none-any.whl (94 kB) Collecting Jinja2>=2.10.1 Using cached Jinja2-2.11.2-py2.py3-none-any.whl (125 kB) Collecting click>=5.1 Using cached click-7.1.2-py2.py3-none-any.whl (82 kB) Collecting itsdangerous>=0.24 Using cached itsdangerous-1.1.0-py2.py3-none-any.whl (16 kB) Collecting Werkzeug>=0.15 Using cached Werkzeug-1.0.1-py2.py3-none-any.whl (298 kB) Collecting MarkupSafe>=0.23 Using cached MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl (32 kB) Installing collected packages: MarkupSafe, Jinja2, click, itsdangerous, Werkzeug, Flask Successfully installed Flask-1.1.2 Jinja2-2.11.2 MarkupSafe-1.1.1 Werkzeug-1.0.1 click-7.1.2 itsdangerous-1.1.0
pip freeze < requirements.txt
touch app.py
vi app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "home"
export FLASK_ENV=development
export FLASK_APP=app.py
flask run
* Serving Flask app "app.py" (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: 377-210-320 127127.0.0.1 - - [04/Nov/2020 22:41:50] "GET / HTTP/1.1" 200 -
mkdir templates
cd templates
touch base.html
vi base.html
Copy from the site getbootstrap.com the styles and scripts
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock %}heihei_ru</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
</head>
<body>
{% block content %}{% endblock %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
</body>
</html>
touch home.html
vi home.html
{% extends 'base.html' %}
{% block content %}
<h1>Home Page</h1>
{% endblock %}
Update the code: you need to import render_template and add the return of the home.html template in return
cd ..
vi app.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template("home.html")
mkdir static
cd static
mkdir css
cd css
touch style.css
vi style.css
body {
padding-top: 56px;
}
.navbar-brand {
padding-top: 0;
padding-bottom: 0;
}
cd ../../templates/
vi base.html
Add the line to head
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
And in body add
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a href="{{ url_for('home') }}" class="navbar-brand">
<img src="{{ url_for('static', filename='images/logo.png') }}" height="40px">
</a>
</div>
</nav>
Now base.html looks like this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock %}heihei_ru</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a href="{{ url_for('home') }}" class="navbar-brand">
<img src="{{ url_for('static', filename='images/logo.png') }}" height="40px">
</a>
</div>
</nav>
{% block content %}{% endblock %}
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
</body>
</html>
cd ../static
mkdir images
Upload some logo to images. Name the file logo.png
Responsive menu
Right after the logo insert the code for the menu
vi templates/base.html
<button class="navbar-toggler" type="button"
data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a href="{{ url_for('home') }}" class="nav-link">Home</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link">Add new item</a>
</li>
</ul>
</div>
Now base.html should look like this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock %}heihei_ru</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a href="{{ url_for('home') }}" class="navbar-brand">
<img src="{{ url_for('static', filename='images/logo.png') }}" height="40px">
</a>
<button class="navbar-toggler" type="button"
data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a href="{{ url_for('home') }}" class="nav-link">Home</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link">Add new item</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
</body>
</html>
Highlighting the active page
In the template you need to create a variable active_page and assign it the name of the template
vi templates/home.html
{% set active_page = 'home' %}
And in the base template you need to add a condition for the style of the menu item
vi templates/base.html
<li class="nav-item {{ 'active' if active_page == 'home' else '' }}">
Now base.html should look like this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock %}heihei_ru</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a href="{{ url_for('home') }}" class="navbar-brand">
<img src="{{ url_for('static', filename='images/logo.png') }}" height="40px">
</a>
<button class="navbar-toggler" type="button"
data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item {{ 'active' if active_page == 'home' else '' }}">
<a href="{{ url_for('home') }}" class="nav-link">Home</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link">Add new item</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
</body>
</html>
Make changes to home.html. Remove <h1>Home Page</h1> and insert the new code instead
vi templates/home.html
{% extends 'base.html' %}
{% set active_page = 'home' %}
{% block content %}
<div class="row">
<div class="col-lg-3">
<div class="my-4">
<h1>Filters</h1>
</div>
</div>
<div class="col-lg-9">
<div class="row my-5 card-wrapper">
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100">
<div class="embed-responsive embed-responsive-16by9">
<a href="#">
<img class="card-img-top embed-responsive-item" src="{{ url_for('static', filename='images/placeholder.png') }}" alt="placeholder">
</a>
</div>
<div class="card-body">
<h4 class="card-title">
<a href="#">Item title</a>
</h4>
<h5>$2.00</h5>
<p class="card-text">Description</p>
</div>
<div class="card-footer">
<small class="text-muted">
Category -> Subcategory
</small>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Update the styles
vi static/css/style.css
body {
padding-top: 56px;
}
.navbar-brand {
padding-top: 0;
padding-bottom: 0;
}
.card-img-top {
object-fit: cover;
}
a:hover {
text-decoration: none;
}
Adding a new item
vi app.py
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def home():
return "home"
@app.route("/item/new", methods=["GET", "POST"])
def new_item():
return render_template("new_item.html")
touch templates/new_item.html
vi templates/new_item.html
{% extends 'base.html' %}
{% set active_page = 'new_item' %}
{% block title%}Add new item - {% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-7 offset-lg-2 my-5">
<h1>Add new item</h1>
<hr>
<form method="POST" action="{{ url_for('new_item') }}">
<div class="form-group">
<label>Title</label>
<input class="form-control" type="text" name="title">
</div>
<div class="form-group">
<label>Description</label>
<textarea class="form-control" name="description" rows="3"></textarea>
</div>
<hr>
<input class="btn btn-primary form-control" type="submit" value="Submit">
</form>
</div>
</div>
{% endblock %}
Let us update base.html
vi templates/base_item.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, shrink-to-fit=no">
<title>{% block title %}{% endblock %}heihei_ru</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top">
<div class="container">
<a href="{{ url_for('home') }}" class="navbar-brand">
<img src="{{ url_for('static', filename='images/logo.png') }}" height="40px">
</a>
<button class="navbar-toggler" type="button"
data-toggle="collapse" data-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-item {{ 'active' if active_page == 'home' else '' }}">
<a href="{{ url_for('home') }}" class="nav-link">Home</a>
</li>
<li class="nav-item {{ 'active' if active_page == 'new_item' else '' }}">
<a href="{{ url_for('new_item') }}" class="nav-link">Add new item</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>
</body>
</html>
Let us add a style
vi static/css/style.css
textarea {
resize: none;
}
Let us make some changes to app.py to make debugging easier
Debug
vi app.py
from flask import Flask, request
import pdb
app = Flask(__name__)
@app.route("/")
def home():
return "home"
@app.route("/item/new", methods=["GET", "POST"])
def new_item():
pdb.set_trace()
return render_template("new_item.html")
Now if you send data (for example test 1) through the form, Flask freezes and you see something like:
> /home/andrei/python/projects/heihei_ru/app.py(13)new_item() -> return render_template("new_item.html") (Pdb)
To continue without any actions, enter c and press Enter
You can study the request sent from the form by running the command request
request
<Request 'http://127.0.0.1:5000/item/new' [POST]>
request.method
'POST'
request.form
ImmutableMultiDict([('title', 'test'), ('descripton', '1')])
ImmutableMultiDict is a class provided by Werkzeug
request.form["title"]
'test'
There was no password in the form, let us try to access a non-existent field
request.form["password"]
*** werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
request.form.get("password")
c
127.0.0.1 - - [05/Nov/2020 22:23:54] "POST /item/new HTTP/1.1" 200 -
If you change method in the form from POST to GET, the behaviour will change
vi templates/new_item.html
<form method="GET" action="{{ url_for('new_item') }}">
Enter new Title and Description in the form (Title for get, get-get)
request
<Request 'http://127.0.0.1:5000/item/new?title=Title+for+get&descripton=get-get' [GET]>
request.method
'GET'
request.form
ImmutableMultiDict([])
request.args
ImmutableMultiDict([('title', 'Title for get'), ('descripton', 'get-get')])
Now the line can be deleted
pdb.set_trace()
And return the POST method to the form
vi templates/new_item.html
<form method="POST" action="{{ url_for('new_item') }}">
Processing data from the form
from flask import Flask, render_template, request, redirect, url_for
import pdb
app = Flask(__name__)
@app.route("/")
def home():
return render_template("home.html")
@app.route("/item/new", methods=["GET", "POST"])
def new_item():
#pdb.set_trace()
if request.method == "POST":
# Process the form data
print("Form data:")
print("Title: {}, Description: {}".format(
request.form.get("title", request.form.get("description")
))
# Redirect to some page
return redirect(url_for("home")
return render_template("new_item.html")
Connecting the database
Usually you need to create several scripts for working with the database.
The very first of them is initialisation, that is, creating the database and the tables in it.
The easiest way is to work with SQLite; this DBMS is supported by Python out of the box and everything is stored in one file.
mkdir db
touch db/db_init.py
vi db_init.py
import sqlite3 import os db_abs_path = os.path.dirname(os.path.realpath(__file__)) + '/heihei_ru.db' conn = sqlite3.connect(db_abs_path) c = conn.cursor() c.execute("DROP TABLE IF EXISTS items") c.execute("DROP TABLE IF EXISTS categories") c.execute("DROP TABLE IF EXISTS subcategories") c.execute("DROP TABLE IF EXISTS comments") c.execute("""CREATE TABLE categories( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT )""") c.execute("""CREATE TABLE subcategories( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, category_id INTEGER, FOREIGN KEY(category_id) REFERENCES categories(id) )""") c.execute("""CREATE TABLE items( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, description TEXT, price REAL, image TEXT, category_id INTEGER, subcategory_id INTEGER, FOREIGN KEY(category_id) REFERENCES categories(id), FOREIGN KEY(subcategory_id) REFERENCES subcategories(id) )""") c.execute("""CREATE TABLE comments( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, item_id INTEGER, FOREIGN KEY(item_id) REFERENCES items(id) )""") categories = [ ("Food",), ("Technology",), ("Books",) ] c.executemany("INSERT INTO categories (name) VALUES (?)", categories) subcategories = [ ("Fruit", 1), ("Dairy product", 1), ("Cassette", 2), ("Phone", 2), ("TV", 2), ("Historical fiction", 3), ("Science fiction", 3) ] c.executemany("INSERT INTO subcategories (name, category_id) VALUES (?,?)", subcategories) items = [ ("Old Tape", "You can record anything.", 2.0, "", 2, 3), ("Bananas", "1kg of fresh bananas.", 1.0, "", 1, 1), ("Vintage TV", "In color!", 150.0, "", 2, 5), ("Cow Milk", "From the best farms.", 5.0, "", 1, 2) ] c.executemany("INSERT INTO items (title, description, price, image, category_id, subcategory_id) VALUES (?,?,?,?,?,?)", items) comments = [ ("This item is great!", 1), ("Whats up?", 2), ("Spam spam", 3) ] c.executemany("INSERT INTO comments (content, item_id) VALUES (?,?)", comments) conn.commit() conn.close() print("Database is created and initialized.") print("You can see the tables with the show_tables.py script.")
Run the script
python3 db/db_init.py
Database is created and initialized. You can see the tables with the show_tables.py script.
You will also need scripts for working with the DB, for example:
touch db/show_tables.py
vi show_tables.py
import sqlite3 import os db_abs_path = os.path.dirname(os.path.realpath(__file__)) + '/heihei_ru.db' print("Options: (items, comments, categories, subcategories, all)") table = input("Show table: ") conn = sqlite3.connect(db_abs_path) c = conn.cursor() def show_items(): try: items = c.execute("""SELECT i.id, i.title, i.description, i.price, i.image, c.name, c.id, s.name, s.id FROM items AS i INNER JOIN categories AS c ON i.category_id = c.id INNER JOIN subcategories AS s ON i.subcategory_id = s.id """) print("ITEMS") print("#############") for row in items: print("ID: ", row[0]), print("Title: ", row[1]), print("Description: ", row[2]), print("Price: ", row[3]), print("Image: ", row[4]), print("Category: ", row[5], "(", row[6], ")"), print("SubCategory: ", row[7], "(", row[8], ")"), print("\n") except: print("Something went wrong, please run db_init.py to initialize the database.") conn.close() def show_comments(): try: comments = c.execute("""SELECT c.id, c.content, i.title, i.id FROM comments AS c INNER JOIN items AS i ON c.item_id = i.id """) print("COMMENTS") print("#############") for row in comments: print("ID: ", row[0]), print("Content: ", row[1]), print("Item: ", row[2], "(", row[3], ")") print("\n") except: print("Something went wrong, please run db_init.py to initialize the database.") conn.close() def show_categories(): try: categories = c.execute("SELECT * FROM categories") print("CATEGORIES") print("#############") for row in categories: print("ID: ", row[0]), print("Name: ", row[1]) print("\n") except: print("Something went wrong, please run db_init.py to initialize the database.") conn.close() def show_subcategories(): try: subcategories = c.execute("SELECT s.id, s.name, c.name, c.id FROM subcategories AS s INNER JOIN categories AS c ON s.category_id = c.id") print("SUBCATEGORIES") print("#############") for row in subcategories: print("ID: ", row[0]), print("Name: ", row[1]), print("Category: ", row[2], "(", row[3], ")") print("\n") except: print("Something went wrong, please run db_init.py to initialize the database.") conn.close() if table == "items": show_items() elif table == "comments": show_comments() elif table == "categories": show_categories() elif table == "subcategories": show_subcategories() elif table == "all": show_items() show_comments() show_categories() show_subcategories() else: print("This option does not exist.") conn.close()
Run the script
python3 db/show_tables.py
Options: (items, comments, categories, subcategories, all) Show table: all ITEMS ############# ID: 1 Title: Old Tape Description: You can record anything. Price: 2.0 Image: Category: Technology ( 2 ) SubCategory: Cassette ( 3 ) ID: 2 Title: Bananas Description: 1kg of fresh bananas. Price: 1.0 Image: Category: Food ( 1 ) SubCategory: Fruit ( 1 ) ID: 3 Title: Vintage TV Description: In color! Price: 150.0 Image: Category: Technology ( 2 ) SubCategory: TV ( 5 ) ID: 4 Title: Cow Milk Description: From the best farms. Price: 5.0 Image: Category: Food ( 1 ) SubCategory: Dairy product ( 2 ) COMMENTS ############# ID: 1 Content: This item is great! Item: Old Tape ( 1 ) ID: 2 Content: Whats up? Item: Bananas ( 2 ) ID: 3 Content: Spam spam Item: Vintage TV ( 3 ) CATEGORIES ############# ID: 1 Name: Food ID: 2 Name: Technology ID: 3 Name: Books SUBCATEGORIES ############# ID: 1 Name: Fruit Category: Food ( 1 ) ID: 2 Name: Dairy product Category: Food ( 1 ) ID: 3 Name: Cassette Category: Technology ( 2 ) ID: 4 Name: Phone Category: Technology ( 2 ) ID: 5 Name: TV Category: Technology ( 2 ) ID: 6 Name: Historical fiction Category: Books ( 3 ) ID: 7 Name: Science fiction Category: Books ( 3 )
Update the app.py file
from flask import Flask, render_template, request, redirect, url_for, g, flash
flash is needed to show instant messages
import pdb
import sqlite3
app = Flask(__name__)
flash will not work without SECRET_KEY
app.config["SECRET_KEY"] = "secretkey"
@app.route("/")
def home():
conn = get_db()
c = conn.cursor()
items_from_db = c.execute("""SELECT
i.id, i.title, i.description, i.price, i.image, c.name, s.name
FROM
items AS i
INNER JOIN categories AS c ON i.category_id = c.id
INNER JOIN subcategories AS s ON i.subcategory_id = s.id
ORDER BY i.id DESC
""")
items = []
for row in items_from_db:
item = {
"id": row[0],
"title": row[1],
"description": row[2],
"price": row[3],
"image": row[4],
"category": row[5],
"subcategory": row[6]
}
items.append(item)
return render_template("home.html", items=items)
@app.route("/item/new", methods=["GET", "POST"])
def new_item():
conn = get_db()
c = conn.cursor()
if request.method == "POST":
# Process the form data
print("Form data:")
print("Title: {}, Description: {}".format(
request.form.get("title"), request.form.get("description")
))
# Redirect to some page
flash("Item {} has been successfully submitted".format(request.form.get("title")), "success")
return redirect(url_for("home"))
return render_template("new_item.html")
def get_db():
db = getattr(g, "_database", None)
if db is None:
db = g.database = sqlite3.connect("db/globomantics.db")
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, "_database", None)
if db is not None:
db.close()
Add another block to the new_item.html template
<div class="form-group">
<label>Price</label>
<input class="form-control" type="text" name="price">
</div>
Make the following changes to the home.html template:
{% extends 'base.html' %}
{% set active_page = 'home' %}
{% block content %}
<div class="row">
<div class="col-lg-3">
<div class="my-4">
<h1>Filters</h1>
</div>
</div>
<div class="col-lg-9">
<div class="row my-5 card-wrapper">
{% if items %}
{% for item in items %}
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100">
<div class="embed-responsive embed-responsive-16by9">
<a href="#">
<img class="card-img-top embed-responsive-item" src="{{ url_for('static', filename='images/placeholder.png') }}" alt="placeholder">
</a>
</div>
<div class="card-body">
<h4 class="card-title">
<a href="#">{{ item.title }}</a>
</h4>
<h5>{{ "$%.2f" | format(item.price) }}</h5>
<p class="card-text">{{ item.description }}</p>
</div>
<div class="card-footer">
<small class="text-muted">
{{ item.category }} -> {{ item.subcategory }}
</small>
</div>
</div>
</div>
{% endfor %}
{% else %}
<h1 class="offset-lg-3">No items to show.</h1>
{% endif %}
</div>
</div>
</div>
{% endblock %}
Make the following changes to the base.html template to show flash messages:
<div class="container">
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for type, content in messages %}
<div class="alert alert-{{ type }} alert-dismissible my-4" role="alert">
<button type="button" class="close"
data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
{{ content }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
flask_wtf
Installation
pip install flask_wtf
Collecting flask_wtf Downloading Flask_WTF-0.14.3-py2.py3-none-any.whl (13 kB) Requirement already satisfied: Flask in ./venv/lib/python3.8/site-packages (from flask_wtf) (1.1.2) Collecting WTForms Downloading WTForms-2.3.3-py2.py3-none-any.whl (169 kB) |████████████████████████████████| 169 kB 3.8 MB/s Requirement already satisfied: itsdangerous in ./venv/lib/python3.8/site-packages (from flask_wtf) (1.1.0) Requirement already satisfied: Werkzeug>=0.15 in ./venv/lib/python3.8/site-packages (from Flask->flask_wtf) (1.0.1) Requirement already satisfied: Jinja2>=2.10.1 in ./venv/lib/python3.8/site-packages (from Flask->flask_wtf) (2.11.2) Requirement already satisfied: click>=5.1 in ./venv/lib/python3.8/site-packages (from Flask->flask_wtf) (7.1.2) Requirement already satisfied: MarkupSafe in ./venv/lib/python3.8/site-packages (from WTForms->flask_wtf) (1.1.1) Installing collected packages: WTForms, flask-wtf Successfully installed WTForms-2.3.3 flask-wtf-0.14.3
pip freeze > requirements.txtdddddddddddddddddddddddddddddddd
Make changes to app.py
from flask import Flask, render_template, request, redirect, url_for, g, flash
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
import pdb
import sqlite3
app = Flask(__name__)
app.config["SECRET_KEY"] = "secretkey"
class NewItemForm(FlaskForm):
title = StringField("Title")
price = StringField("Price")
description = TextAreaField("Description")
submit = SubmitField("Submit")
@app.route("/")
def home():
conn = get_db()
c = conn.cursor()
items_from_db = c.execute("""SELECT
i.id, i.title, i.description, i.price, i.image, c.name, s.name
FROM
items AS i
INNER JOIN categories AS c ON i.category_id = c.id
INNER JOIN subcategories AS s ON i.subcategory_id = s.id
ORDER BY i.id DESC
""")
items = []
for row in items_from_db:
item = {
"id": row[0],
"title": row[1],
"description": row[2],
"price": row[3],
"image": row[4],
"category": row[5],
"subcategory": row[6]
}
items.append(item)
return render_template("home.html", items=items)
@app.route("/item/new", methods=["GET", "POST"])
def new_item():
conn = get_db()
c = conn.cursor()
form = NewItemForm()
if request.method == "POST":
# Process the form data
c.execute("""INSERT INTO items
(title, description, price, image, category_id, subcategory_id)
VALUES(?,?,?,?,?,?)""",
(
form.title.data,
form.description.data,
float(request.form.get("price")),
"",
1,
1
)
)
conn.commit()
# Redirect to some page
flash("Item {} has been successfully submitted".format(request.form.get("title")), "success")
return redirect(url_for("home"))
return render_template("new_item.html", form=form)
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g.database = sqlite3.connect('db/globomantics.db')
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
| Python | |
| Flask | |
| Running Flask on hosting | |
| Running Flask on a Linux server | |
| The first Flask project | |
| Jinja templates | |
| Flask FAQ | |
| Web Forms | |
| Errors |
Article author: Andrei Olegovich