Skip to content

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.

Lag filstrukturen:

databehandling/
├── app.py
├── revyen.json
├── karakterer.csv
└── templates/
└── index.html
karakterer.csv
navn,fag,karakter,termin
Amelia Hansen,Matematikk,5,1
Amelia Hansen,Norsk,4,1
Amelia Hansen,IT2,6,1
Benjamin Larsen,Matematikk,3,1
Benjamin Larsen,Norsk,4,1
Benjamin Larsen,IT2,5,1
Celine Olsen,Matematikk,6,1
Celine Olsen,Norsk,5,1
Celine Olsen,IT2,6,1
Daniel Andersen,Matematikk,2,1
Daniel Andersen,Norsk,3,1
Daniel Andersen,IT2,4,1
Emilie Berg,Matematikk,4,1
Emilie Berg,Norsk,5,1
Emilie Berg,IT2,5,1
Fredrik Dahl,Matematikk,5,1
Fredrik Dahl,Norsk,3,1
Fredrik Dahl,IT2,4,1
Amelia Hansen,Matematikk,5,2
Amelia Hansen,Norsk,5,2
Amelia Hansen,IT2,6,2
Benjamin Larsen,Matematikk,4,2
Benjamin Larsen,Norsk,4,2
Benjamin Larsen,IT2,6,2
Celine Olsen,Matematikk,6,2
Celine Olsen,Norsk,6,2
Celine Olsen,IT2,6,2
Daniel Andersen,Matematikk,3,2
Daniel Andersen,Norsk,3,2
Daniel Andersen,IT2,5,2
Emilie Berg,Matematikk,4,2
Emilie Berg,Norsk,5,2
Emilie Berg,IT2,5,2
Fredrik Dahl,Matematikk,5,2
Fredrik Dahl,Norsk,4,2
Fredrik Dahl,IT2,5,2
revyen.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, request
from filbib import JSONfil, CSVfil
app = Flask(__name__)
app.run(debug=True)

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.

from flask import Flask, render_template, request
import 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)

Les inn dataene og send dem til templaten med render_template:

@app.get("/")
def index():
return render_template("index.html", grupper=data)

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>

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.

from flask import Flask, render_template, request
import 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)

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

Kakediagrammer egner seg for å vise andeler. Her viser vi størrelsen på hver revygruppe.

from flask import Flask, render_template, request
import 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)

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)
<div id="revyDiagram"></div>
<script>
const navn = {{ navn | tojson }}
const medlemmer = {{ medlemmer | tojson }}
Plotly.newPlot("revyDiagram", [{
labels: navn,
values: medlemmer,
type: "pie"
}]);
</script>

Vi kan la brukeren velge sortering ved å sende en verdi i URL-en med ?sorter=kolonnenavn. Flask leser denne verdien med request.args.get().

from flask import Flask, render_template, request
import 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)

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.

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.