====== 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 %} {% 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]]).