2. Data og Flask
I dette kapittelet kombinerer vi databehandling og Flask. Vi leser data fra filer og presenterer den via tabeller og plott på nettsider.
Oppsett
Section titled “Oppsett”Lag filstrukturen:
databehandling/├── app.py├── revyen.json├── karakterer.csv└── templates/ └── index.htmlkarakterer.csv
navn,fag,karakter,terminAmelia Hansen,Matematikk,5,1Amelia Hansen,Norsk,4,1Amelia Hansen,IT2,6,1Benjamin Larsen,Matematikk,3,1Benjamin Larsen,Norsk,4,1Benjamin Larsen,IT2,5,1Celine Olsen,Matematikk,6,1Celine Olsen,Norsk,5,1Celine Olsen,IT2,6,1Daniel Andersen,Matematikk,2,1Daniel Andersen,Norsk,3,1Daniel Andersen,IT2,4,1Emilie Berg,Matematikk,4,1Emilie Berg,Norsk,5,1Emilie Berg,IT2,5,1Fredrik Dahl,Matematikk,5,1Fredrik Dahl,Norsk,3,1Fredrik Dahl,IT2,4,1Amelia Hansen,Matematikk,5,2Amelia Hansen,Norsk,5,2Amelia Hansen,IT2,6,2Benjamin Larsen,Matematikk,4,2Benjamin Larsen,Norsk,4,2Benjamin Larsen,IT2,6,2Celine Olsen,Matematikk,6,2Celine Olsen,Norsk,6,2Celine Olsen,IT2,6,2Daniel Andersen,Matematikk,3,2Daniel Andersen,Norsk,3,2Daniel Andersen,IT2,5,2Emilie Berg,Matematikk,4,2Emilie Berg,Norsk,5,2Emilie Berg,IT2,5,2Fredrik Dahl,Matematikk,5,2Fredrik Dahl,Norsk,4,2Fredrik Dahl,IT2,5,2revyen.json
[ { "navn": "Skuespill", "beskrivelse": "Jobber med dialog, karakterutvikling og sceneopptreden.", "leder": "Amelia Hansen", "øvingsdag": "Mandag", "antall_medlemmer": 18, "klar": false }, { "navn": "Dans", "beskrivelse": "Koreograferer og øver på dansenumre til showet.", "leder": "Celine Olsen", "øvingsdag": "Tirsdag", "antall_medlemmer": 12, "klar": false }, { "navn": "Musikk", "beskrivelse": "Bandet som spiller live under forestillingen.", "leder": "Fredrik Dahl", "øvingsdag": "Onsdag", "antall_medlemmer": 8, "klar": true }, { "navn": "Manus", "beskrivelse": "Skriver og redigerer tekstene og sketsjene til revyen.", "leder": "Benjamin Larsen", "øvingsdag": "Torsdag", "antall_medlemmer": 5, "klar": true }, { "navn": "Kostyme og sminke", "beskrivelse": "Lager og koordinerer kostymer, rekvisitter og sminke.", "leder": "Emilie Berg", "øvingsdag": "Fredag", "antall_medlemmer": 9, "klar": true }, { "navn": "Lys og lyd", "beskrivelse": "Styrer sceneteknikk, lysriggen og lydanlegget under showet.", "leder": "Daniel Andersen", "øvingsdag": "Onsdag", "antall_medlemmer": 4, "klar": false }]Start med dette grunnoppsettet i app.py:
from flask import Flask, render_template, requestfrom filbib import JSONfil, CSVfil
app = Flask(__name__)
app.run(debug=True)Tabeller
Section titled “Tabeller”En tabell i HTML bruker taggene <table>, <tr> (rad), <th> (overskriftscelle) og <td> (datacelle).
<table> <tr> <th>Navn</th> <th>Medlemmer</th> </tr> <tr> <td>Skuespill</td> <td>18</td> </tr></table>Med Jinja kan vi bruke for-løkker til å generere rader automatisk fra lister.
Les inn fil i app.py
Section titled “Les inn fil i app.py”from flask import Flask, render_template, requestimport json
app = Flask(__name__)
with open("revyen.json", encoding="utf-8") as fil: data = json.load(fil)
# Legg inn ruter her
app.run(debug=True)Flask-rute
Section titled “Flask-rute”Les inn dataene og send dem til templaten med render_template:
@app.get("/")def index(): return render_template("index.html", grupper=data)HTML-template
Section titled “HTML-template”Bruk en Jinja-løkke til å lage én rad per gruppe:
<!DOCTYPE html><html><head> <title>Revyen</title></head><body> <h1>Revygrupper</h1> <table> <tr> <th>Gruppe</th> <th>Leder</th> <th>Medlemmer</th> <th>Klar?</th> </tr> {% for gruppe in grupper %} <tr> <td>{{ gruppe['navn'] }}</td> <td>{{ gruppe['leder'] }}</td> <td>{{ gruppe['antall_medlemmer'] }}</td> <td>{{ "Ja" if gruppe['klar'] == 'true' else "Nei" }}</td> </tr> {% endfor %} </table></body></html>Plott med Plotly
Section titled “Plott med Plotly”Plotly er et JavaScript-bibliotek for å lage interaktive diagrammer i nettleseren.
Flask beregner dataene og Plotly tegner dem i en <div>-tag.
Last inn Plotly i <head> i HTML-fila:
<script src="https://cdn.plot.ly/plotly-3.4.0.min.js" charset="utf-8"></script>For å sende Python-data til JavaScript bruker vi Jinja-filteret | tojson, som konverterer Python-lister og -ordbøker til JavaScript-format.
Stolpediagram: karakterer per fag
Section titled “Stolpediagram: karakterer per fag”Les inn fil i app.py
Section titled “Les inn fil i app.py”from flask import Flask, render_template, requestimport csv
app = Flask(__name__)
with open("karakterer.csv", encoding="utf-8") as fil: leser = csv.DictReader(fil) karakterer = list(leser)
# Legg inn ruter her
app.run(debug=True)Flask-rute
Section titled “Flask-rute”Beregn data i Python og send to lister til templaten – én med etiketter og én med verdier:
@app.get("/")def index(): fagstatistikk = {}
for k in karakterer: if k["fag"] not in fagstatistikk: fagstatistikk[k["fag"]] = { antall: 1, total: k["karakter"] } else: fagstatistikk[k["fag"]]["antall"] += 1 fagstatistikk[k["fag"]]["totalkarakter"] += 1
fagliste = [] snittliste = [] for f in fagstatistikk: fagliste.append(f) snitt = fagstatistikk[f]["totalkarakter"] / fagstatistikk[f]["antall"] snittliste.append(snitt)
return render_template("index.html", fagliste=fagliste, snittliste=snittliste)HTML-template
Section titled “HTML-template”<!DOCTYPE html><html><head> <title>Karakterer</title> <script src="https://cdn.plot.ly/plotly-3.4.0.min.js" charset="utf-8"></script></head><body> <h1>Gjennomsnittskarakter per fag</h1> <div id="mittDiagram"></div>
<script> const fagliste = {{ fagliste | tojson }}; const snitt = {{ snittliste | tojson }};
Plotly.newPlot("mittDiagram", [{ x: fagliste, y: snitt, type: "bar" }]); </script></body></html>Kakediagram: fordeling av revygrupper
Section titled “Kakediagram: fordeling av revygrupper”Kakediagrammer egner seg for å vise andeler. Her viser vi størrelsen på hver revygruppe.
Les inn fil i app.py
Section titled “Les inn fil i app.py”from flask import Flask, render_template, requestimport json
app = Flask(__name__)
with open("revyen.json", encoding="utf-8") as fil: data = json.load(fil)
# Legg inn ruter her
app.run(debug=True)Flask-rute
Section titled “Flask-rute”Sorter gruppene etter størrelse før du henter ut kolonnene, så diagrammet viser dem i riktig rekkefølge:
@app.get("/revy")def revy(): gruppenavn = [] antall_medlemmer = [] for gruppe in data: gruppenavn.append(gruppe["navn"]) antall_medlemmer.append(gruppe["antall_medlemmer"]) return render_template("revy.html", navn=gruppenavn, medlemmer=antall_medlemmer)HTML-template
Section titled “HTML-template”<div id="revyDiagram"></div>
<script> const navn = {{ navn | tojson }} const medlemmer = {{ medlemmer | tojson }} Plotly.newPlot("revyDiagram", [{ labels: navn, values: medlemmer, type: "pie" }]);</script>Sortering
Section titled “Sortering”Vi kan la brukeren velge sortering ved å sende en verdi i URL-en med ?sorter=kolonnenavn.
Flask leser denne verdien med request.args.get().
Les inn fil i app.py
Section titled “Les inn fil i app.py”from flask import Flask, render_template, requestimport json
app = Flask(__name__)
with open("revyen.json", encoding="utf-8") as fil: data = json.load(fil)
# Legg inn ruter her
app.run(debug=True)Flask-rute
Section titled “Flask-rute”Les inn dataene og send dem til templaten med render_template:
@app.get("/")def index(): sorter_på = request.args.get("sorter") sorterte_grupper = sorted(personer, key=lambda x: x[sorter_på], reversed=True) return render_template("index.html", grupper=sorterte_grupper)Hvis ingen ?sorter=-parameter er oppgitt, returnerer request.args.get("sorter") verdien None, og tabellen vises usortert.
HTML-template
Section titled “HTML-template”Legg til skjemaer med knapper øverst i tabellen:
<form method="get"> <input type="hidden" name="sorter" value="navn"> <button type="submit">Sorter etter navn</button></form><form method="get"> <input type="hidden" name="sorter" value="antall_medlemmer"> <button type="submit">Sorter etter medlemmer</button></form><form method="get"> <button type="submit">Nullstill</button></form>
<table> <tr> <th>Gruppe</th> <th>Leder</th> <th>Medlemmer</th> <th>Klar?</th> </tr> {% for gruppe in grupper %} <tr> <td>{{ gruppe['navn'] }}</td> <td>{{ gruppe['leder'] }}</td> <td>{{ gruppe['antall_medlemmer'] }}</td> <td>{{ "Ja" if gruppe['klar'] == 'true' else "Nei" }}</td> </tr> {% endfor %}</table>Hvert skjema bruker method="get" slik at sorteringsverdien sendes som en del av URL-en (/?sorter=antall_medlemmer).
Det skjulte <input type="hidden">-feltet bestemmer hvilken kolonne det sorteres på, og Flask leser verdien med request.args.get("sorter") som før.