Skip to content

Python in Flask

Zakaj Flask

Flask je majhen in pregleden spletni okvir, zato je zelo primeren za poučevanje osnov spletnih aplikacij. Pri njem dijak zelo hitro vidi:

  • kje nastane aplikacija,
  • kako določimo poti,
  • kako vrnemo odgovor,
  • kako prikažemo HTML predlogo,
  • kako preberemo obrazec,
  • kako dostopamo do baze.

Ta preglednost je v učnem okolju velika prednost.

Kaj mora dijak razumeti

Pri Flasku ni cilj, da osvoji celoten ekosistem. Cilj je, da razume temeljni tok:

zahteva -> route -> Python funkcija -> logika -> predloga / baza -> odgovor

To je osnovni hrbet vsake male spletne aplikacije.

Najmanjša možna aplikacija

from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Pozdrav iz Flask aplikacije!"

if __name__ == "__main__":
    app.run(debug=True)

Ta primer pokaže tri ključne ideje:

  • aplikacijo ustvarimo z Flask(__name__),
  • @app.route("/") pove, katera funkcija odgovori na pot,
  • funkcija vrne vsebino odgovora.

Kaj je route

Route je povezava med URL potjo in Python funkcijo.

@app.route("/")
def index():
    return "Domov"

@app.route("/o-projektu")
def o_projektu():
    return "Opis projekta"

To je didaktično odlično, ker je razmerje med potjo in obnašanjem neposredno.

Vračanje HTML predlog

Resna aplikacija običajno ne vrača samo nizov, ampak HTML predloge. Flask za to uporablja Jinja.

Struktura projekta:

projekt/
├─ app.py
├─ templates/
│  ├─ base.html
│  ├─ index.html
│  └─ dodaj.html
└─ static/
   └─ style.css

app.py:

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/")
def index():
    knjige = [
        {"naslov": "Alamut", "avtor": "Vladimir Bartol"},
        {"naslov": "1984", "avtor": "George Orwell"},
    ]
    return render_template("index.html", knjige=knjige)

templates/index.html:

<!doctype html>
<html lang="sl">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Knjižnica</title>
</head>
<body>
  <h1>Seznam knjig</h1>

  <ul>
    {% for knjiga in knjige %}
      <li>{{ knjiga.naslov }} – {{ knjiga.avtor }}</li>
    {% endfor %}
  </ul>
</body>
</html>

Zakaj so predloge pomembne

Predloge pomagajo ločiti:

  • logiko in podatke v Pythonu,
  • prikaz v HTML-ju.

To je ena prvih res zdravih arhitekturnih navad.

Dedovanje predlog

Ko projekt raste, ni dobro kopirati celotne glave in noge v vsako datoteko. Bolje je uporabiti osnovno predlogo.

templates/base.html:

<!doctype html>
<html lang="sl">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{% block title %}Knjižnica{% endblock %}</title>
  <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
  <header>
    <h1>Moja knjižnica</h1>
    <nav>
      <a href="{{ url_for('index') }}">Domov</a>
      <a href="{{ url_for('dodaj') }}">Dodaj knjigo</a>
    </nav>
  </header>

  <main>
    {% block content %}{% endblock %}
  </main>
</body>
</html>

templates/index.html:

{% extends "base.html" %}

{% block title %}Seznam knjig{% endblock %}

{% block content %}
  <h2>Seznam knjig</h2>
  <ul>
    {% for knjiga in knjige %}
      <li>{{ knjiga["naslov"] }} – {{ knjiga["avtor"] }}</li>
    {% endfor %}
  </ul>
{% endblock %}

S tem dijak hitro vidi, kako se zmanjša podvajanje.

Branje podatkov iz obrazca

from flask import Flask, render_template, request

app = Flask(__name__)

@app.route("/dodaj", methods=["GET", "POST"])
def dodaj():
    if request.method == "POST":
        naslov = request.form.get("naslov", "").strip()
        avtor = request.form.get("avtor", "").strip()
        return f"Prejeli smo: {naslov}{avtor}"

    return render_template("dodaj.html")

S tem primerom lahko razložiš:

  • ista route lahko obravnava prikaz in oddajo obrazca,
  • request.method pove, kaj se dogaja,
  • request.form vsebuje podatke iz obrazca,
  • podatke je treba očistiti in preveriti.

Preusmeritev po uspešni oddaji

Po uspešni oddaji je pogosto bolje uporabnika preusmeriti na seznam ali drugo stran.

from flask import redirect, url_for

@app.route("/shrani", methods=["POST"])
def shrani():
    # shrani podatke
    return redirect(url_for("index"))

To je dober trenutek, da razložiš razliko med:

  • vračanjem strani neposredno,
  • preusmeritvijo na drugo pot.

url_for

url_for je zelo koristna funkcija, ker ne lepimo poti ročno po projektu.

Namesto:

<a href="/dodaj">Dodaj</a>

lahko uporabimo:

<a href="{{ url_for('dodaj') }}">Dodaj</a>

Prednost je v tem, da je povezava vezana na ime route, ne na ročno napisan niz.

Delo s statičnimi datotekami

Za CSS in slike Flask uporablja mapo static.

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

To je ena tistih stvari, ki jih je bolje naučiti pravilno že prvič, ker sicer dijaki hitro končajo v ugibanju poti.

Povezava s SQLite

Flask in SQLite se za učni projekt zelo lepo ujemata.

Primer povezave:

import sqlite3
from flask import Flask, g

app = Flask(__name__)
DATABASE = "knjiznica.db"

def get_db():
    if "db" not in g:
        g.db = sqlite3.connect(DATABASE)
        g.db.row_factory = sqlite3.Row
    return g.db

@app.teardown_appcontext
def close_db(exception):
    db = g.pop("db", None)
    if db is not None:
        db.close()

Ta vzorec je zelo uporaben, ker:

  • povezavo odpre po potrebi,
  • jo zapre ob koncu zahtevka,
  • omogoči bolj urejeno delo z bazo.

Branje podatkov iz baze

@app.route("/")
def index():
    db = get_db()
    knjige = db.execute(
        "SELECT id, naslov, avtor, leto FROM knjige ORDER BY naslov"
    ).fetchall()
    return render_template("index.html", knjige=knjige)

Shranjevanje podatkov v bazo

@app.route("/dodaj", methods=["GET", "POST"])
def dodaj():
    if request.method == "POST":
        naslov = request.form.get("naslov", "").strip()
        avtor = request.form.get("avtor", "").strip()
        leto = request.form.get("leto", "").strip()

        if not naslov or not avtor:
            return render_template(
                "dodaj.html",
                napaka="Naslov in avtor sta obvezna."
            )

        db = get_db()
        db.execute(
            "INSERT INTO knjige (naslov, avtor, leto) VALUES (?, ?, ?)",
            (naslov, avtor, leto or None),
        )
        db.commit()
        return redirect(url_for("index"))

    return render_template("dodaj.html")

To je odličen “aha” trenutek predmeta, ker dijak vidi celoten tok od obrazca do baze.

Organizacija projekta

Za majhen projekt zadošča čista osnovna struktura:

projekt/
├─ app.py
├─ schema.sql
├─ knjiznica.db
├─ templates/
│  ├─ base.html
│  ├─ index.html
│  └─ dodaj.html
└─ static/
   └─ style.css

Ko je struktura jasna, je precej manj kaosa in precej manj “kam pa sem to sploh shranil”.

Debug način

Pri razvoju pogosto uporabljaš:

app.run(debug=True)

To pomaga pri razvoju, ker:

  • hitreje vidiš napake,
  • se aplikacija samodejno ponovno naloži,
  • lažje slediš izpisu.

Pomembno pa je, da dijaki razumejo: to je razvojni način, ne produkcijska rešitev.

Pogoste napake

Napačna metoda v route

Obrazec pošilja POST, route pa ne podpira POST.

Napačno ime polja

request.form.get("naslov") ne bo našel vrednosti, če je name v HTML-ju drugačen.

Predloga ni na pravem mestu

Flask pričakuje mapo templates.

CSS se ne naloži

Statične datoteke morajo biti v static.

Manjka commit()

Podatki se ne shranijo trajno.

Kontrolni seznam

1. Ali route obstaja?
2. Ali route podpira pravo metodo?
3. Ali HTML polja uporabljajo prava name imena?
4. Ali je predloga v mapi templates?
5. Ali so statične datoteke v static?
6. Ali pri INSERT uporabljam placeholderje?
7. Ali po spremembi baze izvedem commit?

Kaj naj dijak zna po tem poglavju

  • ustvariti osnovno Flask aplikacijo,
  • definirati route,
  • vrniti HTML predlogo,
  • prebrati podatke iz obrazca,
  • preusmeriti uporabnika po oddaji,
  • povezati aplikacijo z SQLite,
  • prikazati podatke iz baze na strani.

Najmočnejši del poglavja

Ko dijak vidi, da obrazec pošlje podatke, Flask jih prebere, SQLite jih shrani in se nato prikaže posodobljen seznam, se večina prej ločenih tem končno združi v eno razumljivo sliko.