🏠 | 💻 IT | Flask |

Flask Web Forms

Содержание статьи
Введение
Адаптивное меню
Выделение активной страницы
Добавление нового элемента
Debug
Обработка данных из формы
Подключение базы данных
Чтение базы данных
flask_wtf

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

Скопируйте с сайта getbootstrap.com стили и скрипты

<!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 %}

Обновите код: нужно подключить render_template и добавить возврат шаблона home.html в 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

Добавьте в head строку

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

А в 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>

Теперь base.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> </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

Загрузите в images какой-нибудь логотип. Файл назовите logo.png

Адаптивное меню

Сразу после лого вставьте код для меню

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>

Теперь base.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"> <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>

Выделение активной страницы

Нужно в шаблоне создать переменную active_page и присвоить ей имя шаблона

vi templates/home.html

{% set active_page = 'home' %}

А в базовом шаблоне нужно добавить условие для стиля пункта меню

vi templates/base.html

<li class="nav-item {{ 'active' if active_page == 'home' else '' }}">

Теперь base.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"> <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>

Внесите изменения в home.html. Удалите <h1>Home Page</h1> и вместо него вставьте новый код

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 %}

Обновите стили

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; }

Добавление нового элемента

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 %}

Обновим 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>

Добавим стиль

vi static/css/style.css

textarea { resize: none; }

Внесём кое-какие изменения в app.py чтобы облегчить debug

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")

Теперь если отправить данные (например test 1) через форму Flask замирает и вы видите что-то похожее на:

> /home/andrei/python/projects/heihei_ru/app.py(13)new_item() -> return render_template("new_item.html") (Pdb)

Чтобы продолжить без каких либо действий - введите c и нажмите Enter

Изучить отправленный из формы запрос можно выполнив команду request

request

<Request 'http://127.0.0.1:5000/item/new' [POST]>

request.method

'POST'

request.form

ImmutableMultiDict([('title', 'test'), ('descripton', '1')])

ImmutableMultiDict - это класс предоставленный Werkzeug

request.form["title"]

'test'

В форме не было пароля, попробуем обратиться к несуществующему полю

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 -

Если заменить в форме method с POST на GET поведение изменится

vi templates/new_item.html

<form method="GET" action="{{ url_for('new_item') }}">

Введите в форму новые Title и Description (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')])

Теперь можно удалить строчку

pdb.set_trace()

И вернуть в форму метод POST

vi templates/new_item.html

<form method="POST" action="{{ url_for('new_item') }}">

Обработка данных из формы

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")

Подключение базы данных

Обычно нужно создать несколько скриптов для работы с базой данных.

Самый первый из них - это инициализация, то есть создание базы данных и таблиц в ней.

Проще всего работать с SQLite, эта СУБД поддерживается питоном из коробки и всё хранится в одном файле.

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.")

Запустите скрипт

python3 db/db_init.py

Database is created and initialized. You can see the tables with the show_tables.py script.

Также понадобятся скрипты для работы с БД, например:

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()

Запустите скрипт

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 )

Обновите файл app.py

from flask import Flask, render_template, request, redirect, url_for, g, flash # flash нужен для показа мгновенных сообщений import pdb import sqlite3 app = Flask(__name__) # Без SECRET_KEY не будет работать flash 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()

В шаблон new_item.html добавьте ещё один блок

<div class="form-group"> <label>Price</label> <input class="form-control" type="text" name="price"> </div>

В шаблон 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"> {% 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 %}

В шаблон base.html внесите следующие изменения, чтобы показыать flash сообщения:

<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

Установка

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

Внесите изменения в 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()

Статьи о Flask
Python
Flask
Запуск Flask на хостинге
Запуск Flask на Linux сервере
Flask в Docker
Первый проект на Flask
Шаблоны Jinja
Web Forms
Blueprint - Чертежи Flask
Как разбить приложение Flask на части
Flask FAQ
Ошибки
Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@eth1.ru если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящуюю по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: