====== 3. Propojení aplikace s SQLite ======
===== Cíle cvičení =====
* Vyzkoušet si práci s SQL databází (+ [[https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer|VS Code plugin]] )
* Vyzkoušet šablony ve Flask Aplikaci
===== Ukládání dat do SQL databáze =====
Pro uložení dat můžeme využít [[https://www.sqlite.org/index.html|SQLite]] databázi. Její výhodou je, že pracuje s lokálním binárním souborem a má minimální nároky na paměť a výkon počítače - veškerá potřebná funkcionalita je totiž vytvářena knihovnou, která pracuje s binárním souborem.
==== Knihovna pro práci s databází ====
Pro jednoduchost můžete využít modul ''nsidb'', který umožňuje vytvořit databázi a tabulku pro ukládání dvou hodnot typu ''float'' reprezentující teplotu a vlhkost přečtenou z hypotetického senzoru. Hodnoty jsou funkci pro zápis předávány jako datový typ ''tuple''. Následující kód uložte do pracovního adresáře jako ''nsidb.py''.
import sqlite3
from sqlite3 import Error
def db_conn(file):
""" Create a database connection
: param file: name of database file
: return:
"""
conn = None
try:
conn = sqlite3.connect(file)
except Error as e:
print("Error", e)
return conn
def db_exec(conn, sql, data = None):
""" execute a SQL command
:param conn: Connection object
:param sql: SQL statement
"""
try:
c = conn.cursor()
if data is not None:
c.execute(sql, data)
else:
c.execute(sql)
except Error as e:
print(e)
return c
def db_create(conn):
sql = """CREATE TABLE IF NOT EXISTS hodnoty (id integer PRIMARY KEY, timestamp integer NOT NULL, temperature integer NOT NULL, humidity integer NOT NULL);"""
cur = db_exec(conn, sql)
def db_insert(conn, data):
""" Create a new record
:param conn: Connection object
:param data: tuple of data
:return: ID of last inserted row
"""
sql = """ insert into hodnoty (timestamp, temperature, humidity) values (datetime(), ?, ?); """
cur = db_exec(conn, sql, data)
conn.commit()
return cur.lastrowid
def db_fetch(conn):
"""
Fetch data from table
:param conn: the Connection object
:return:
"""
cur = db_exec(conn, "SELECT * FROM hodnoty")
return cur.fetchall()
==== Hlavní program ====
V hlavním programu je importován modul ''nsidb'' a volány přislušné funkce z modulu. Program pro uložení dat může vypadat třeba takto:
from nsidb import *
conn = db_conn("iot.db")
db_create(conn)
db_insert (conn, (3.14, 25.5))
rows = db_fetch (conn)
for row in rows:
print(row)
Rozšiřte kód tak, aby vložil do databáze větši množství náhodných hodnot, které budou simulovat fluktuaci hodnot senzorů. Pokuste se co nejlépe simulovat časovou řadu.
===== Další funkcionalita frameworku Flask =====
==== Přesměrování ====
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/')
def main():
return redirect(url_for('hello', username='Peter'))
# Also pass an optional URL variable
@app.route('/hello/')
def hello(username):
return 'Hello, {}'.format(username)
if __name__ == '__main__':
app.run(debug=True)
==== Šablona ====
Pro správu šablon se v prostředí Flask implicitně používá engine [[https://realpython.com/primer-on-jinja-templating/|jinja]]. Pro využití možností engine je třeba importovat z Flask submodul ''render_template'' a dodržet strukturu, kterou engine předpokládá:
project-directory
|
+-- templates (for Jinja2 HTML templates)
+-- static
|
+-- css
+-- js
+-- img
Jednoduchá šablona může vypadat třeba takto:
Response Page
Hello, {{ username }}
Na místo ''username'' se do šablony dosadí hodnota, která byla předána při volání funkce ''render_template''
@app.route('/name/')
def process(name):
return render_template('name.html', username=name)
Kromě dosazování hodnot a případně vyhodnocování výrazů je možné pracovat i s běžnými algoritmickými konstrukty.
Podmínka:
{% if ...... %}
......
{% elif ...... %}
......
{% else %}
......
{% endif %}
Cyklus:
{% for item in list %}
......
{% endfor %}
==== Kmbinace šablony a přesměrování ====
Stávající aplikaci rozšiřte o zpracování URL ''/login''
from flask import Flask, render_template, redirect, url_for, request
# ...
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != 'admin' or request.form['password'] != 'admin':
error = 'Invalid Credentials. Please try again.'
else:
return redirect(url_for('main'))
return render_template('login.html', error=error)<<
V pracovním adresáři vytvořte podadresář ''templates'' a do něj umístěte následující soubor ''login.html''. Na stejné úrovni vytvořte ještě další adresář ''static'' a do něj rozbalte obsah {{ :courses:b0b37nsi:tutorials:bootstrap.min.zip |}}.
NSI application - login page
Please login
{% if error %}
Error: {{ error }}
{% endif %}
Pro stylování HTML dokumetu je použit framework [[https://getbootstrap.com/|Bootstrap]]. Pokud vás zajímají podrobnosti, zkuste [[https://getbootstrap.com/docs/5.3/getting-started/introduction/|dokumentaci]] nebo [[https://www.tutorialrepublic.com/twitter-bootstrap-tutorial/|tutorial]].
===== Zadání 2. miniprojektu =====
Rozšiřte aplikaci vytvořenou v rámci 1. miniprojektu tak, aby byla propojena s SQL databází. Hlavním úkolem je zobrazení nasbírané časové řady, která je uložena v databázi. Pokud máte chuť a čas, rozšiřte databázi o tabulku s uživateli a zobrazujte data jen po autorizaci.
Při propojování s databází možná narazíte na problémy, které souvisí s tím, že modul ''sqlite3'' nepodporuje sdílení jedné instance více vlákny, které aplikce ve ''Flasku'' vytváří pro odbavení dotazů na jednotlivá URL. Řešením (suboptimálním) je vytvářet lokální instance v jednotlivých funkcích. S ohledem na (demonstrační) účel aplikace to nijak nevadí. Pokud bychom od aplikace očekávali větší výkon, je lépe využít databázi, která sdílení instance umožňuje (nebo zavést v aplikaci frontu zpráv, jako např. [[https://code.activestate.com/recipes/526618/|zde]]).