Flask ja templatet
- Autolaskuri
- Templatet
- Millä varmistetaan HTML-rakenteen validius ja ehjyys?
- Linkki vs lomake
- Flask-WTF
- Lisätehtävä: tietojen validointi ilman WTForms-kirjastoa
Näissä tehtävissä tutustutaan html-koodin tuottamiseen Flaskilla users.jyu.fi-palvelimella. Lisäksi opitaan mitä tarkoittaa HTTP-protokollan tilattomuus.
Tämän tehtävän tekemisen apuna kannattaa käyttää seuraavia dokumentteja:
Autolaskuri
Toteutetaan autolaskuri Flask-ohjelmana
-
Luo omalle koneellesi seuraavanlainen flask-ohjelma:
# -*- coding: utf-8 -*- from flask import Flask, request, Response, render_template app = Flask(__name__) laskuri = 0 @app.route('/') def autolaskuri(): global laskuri html = """<!DOCTYPE html> <html> <head> <title>Autolaskuri</title> </head> <body> <h1>Autolaskuri</h1> <div>%s</div> <form action="/" method="get"> <p><input type="submit" value="Lisää auto" /></p> </form> </body> </html> """ % laskuri return html
- Lomake käyttää oletuksena samaa merkistöä kuin mikä sivulla on käytössä. Merkistön voi kuitenkin varmistaa määrittelemällä form-elementin accept-charset-attribuutin. Käytä aina UTF-8-merkistöä
- Kokeile lomakkeella olevaa painiketta. Sivu latautuu aina uudelleen eli selain lähettää lomakkeen tiedot www-palvelimelle. Nyt lomaketta ei vielä ohjelmassa huomioida mitenkään
- Lisää lomakkeelle painikkeelle name-attribuutti:
Lataa sivu uudelleen ja kokeile painiketta. Mikä muuttuu? Tarkastele osoiteriviä.<p><input type="submit" name="painike" value="Lisää auto" /></p>
- Lomakkeelle määritelty method on GET, joka tarkoittaa, että kaikki lomakkeen tiedot
koodataan sivun osoitteeseen. Sivun osoite siis muuttuu. Lisää lomakkeeseen vielä yksi kenttä:
Kokeile nyt painiketta. Osoite muuttuu taas. Lomakkeen muuttujat lisätään osoitteen perään ?-merkin jälkeen ja ne erotellaan &-merkillä. Kyseessä on siis edellisviikolla käsitelty querystring.<p>Lisättävä määrä <input type="text" value="1" name="lkm" /></p>
- Kokeile muuttaa lkm-kentän tyypiksi number. Kokeile muuttuvatko parametrit. Mitään ei pitäisi muuttua. Lomakkeen kenttien tyyppejä ei välitetä palvelimelle vaan ainoastaan kenttien arvot. Kaikki tieto välittyy aina merkkijonomuodossa.
-
Yritä lisätä laskurimuuttujaan lkm-muuttujan arvo. Muista muuttaa lkm ensin kokonaisluvuksi. Älä koskaan luota siihen, että lomakkeelle syötetyt tiedot olisivat järkevä
try: lkm = int( request.args.get("lkm", 0) ) except: lkm = 0 laskuri = laskuri + lkm
- Kokeile selaimella toimiiko laskuri. Se vaikuttaisi toimivan.
Sammuta flask-palvelimesi (CTRL-C) ja käynnistä se uudelleen. Mitä laskuri näyttää nyt?
Laskuri nollautuu aina python-tulkin käynnistyessä uudelleen.
Käynnistä kaksi eri selainta. Yritä molemmilla käyttää autolaskuriasi samaan aikaan. Onko eri selaimilla (eri käyttäjillä) eri vai sama laskuri? Miksi näin? Miten toteuttaisit jokaiselle oman laskurin? Globaali muuttuja ei kelpaa ratkaisuksi.
Kokeile suorittaa sama ohjelma users.jyu.fi-palvelimella.
Katso edellisistä tehtävistä miten saat flask-sovellukset toimimaan users.jyu.fi-palvelimella.
Muuta lomakkeen action-parametrin osoite users.jyu.fi-palvelimella toimivaan muotoon kuten /~omatunnus/cgi-bin/ties4080/ohjaus2/flask.cgi/Laskuri ei toimi ollenkaan users.jyu.fi-palvelimella, koska CGI-ohjelmissa käynnistetään uusi python-tulkki jokaisella suorituskerralla.
Älä käytä globaaleja muuttujia flask-sovelluksissa
-
Vain lomakkeelta lähetetyt tiedot voivat vaikuttaa autolaskurin tilaan. HTTP-protokolla on tilaton. Tilan tallentamiseen on erilaisia tapoja joihin palaamme kurssin tulevilla viikoilla. Nyt ainoa mahdollisuus on tallentaa tila www-sivulla olevaan lomakkeeseen. Globaali muuttuja on epäluotettava eikä myöskään toimi useammalla käyttäjällä. Myöskään tiedostoja ei voi käyttää. Miksei? Ongelma on tismalleen sama kuin globaalin muuttujan kanssa.
Lisää lomakkeelle piilokenttä, johon tallennetaan laskurin arvo:
Muuta sovelluksen globaali laskurimuuttuja lokaaliksi ja lue sen arvo aina piilokentästä.<input type="hidden" value="%d" name="laskuri" />
Nyt kannattaa myös vaihtaa merkkijonon muodostaminen f-stringiksi, jotta saadaan helposti laskurimuuttuja kahteen paikkaan:
html = f"""<!DOCTYPE html> <html> <head> <title>Autolaskuri</title> </head> <body> <h1>Autolaskuri</h1> <div>{laskuri}</div> <form action="" method="get"> <input type="hidden" value="{laskuri}" name="laskuri" /> <p>Lisättävä määrä <input type="number" value="1" name="lkm" /></p> <p><input type="submit" name="painike" value="Lisää auto" /></p> </form> </body> </html>"""
- Kokeile lomaketta. Laskurin arvon pitäisi nyt näkyä sivun osoitteessa. Muuta ohjelmakoodia siten, että luetkin laskurin aloitusarvon nyt lomakkeelta tulevista tiedoista. Jos tietoja ei ole tarjolla tai ne ovat pielessä niin käytä alkuarvona nollaa.
- Parantele lomaketta siten, että lomake muistaa myös edellisen lisättävän määrän
- Kokeile muuttaa lomakkeen methodiksi POST.
Nyt sinun pitää muuttaa myös
request.args
tilallerequest.form
. Lisäksi routen metodeihin pitää lisätä POST, koska oletuksena flask käsittelee vain GET-metodit
Muuttuuko lomakkeen toiminta? Post-metodia käytetään silloin, kun ei haluta lomaketietojen näkyvän osoitteessa. Get-metodia käytetään silloin kun haetaan tietoja mutta ei tehdä muutoksia. Jos lisätään, poistetaan tai muutetaan tietoja niin yleensä käytetään POST-metodia.@app.route('/',methods=['GET', 'POST'])
- Malliratkaisu
Templatet
Ohjelmakoodin ja HTML-koodin kirjoittaminen sekaisin ei ole hyvä idea vaan aiheuttaa vaikeasti ylläpidettävää ohjelmakoodia ja myös alttius virheille kasvaa. Otetaan käyttöön Jinja2-template, jonka avulla voimme sijoittaa html-koodin omaan tiedostoonsa.
Jinja2-templateen voi kirjoittaa ohjelmakoodia. Syntaksi on melkein kuin pythonia, mutta ei kuitenkaan. Varmista syntaksi aina Jinjan dokumentaatiosta.
- Templateja etsitään "templates"-alikansiosta, joka sijaitsee samassa kansiossa kuin sovelluksesi. Templateissa voit käyttää render_template-kutsussa vietyjen parametrien lisäksi request-, session- ja g-objekteja.
- Luo templates-kansioon uusi tiedosto jinja.html ja aseta tiedoston sisällöksi python-ohjelmakoodissa oleva html-koodi.
- Poista python-ohjelmastasi kaikki html-koodia sisältävät rivit.
- Korvaa jinja.html-tiedostossa laskuri-muuttujaan viittaavat
{laskuri}
-merkinnät merkinnällä{{ laskuri }}. Tee samaan tapaanlkm
-muuttujan suhteen. - kokeile selaimella toimiiko ohjelmasi, kun vaihdat funktion viimeisen rivin muotoon:
return render_template("jinja.html", laskuri=laskuri, lkm=lkm)
- Tutustu Jinjan dokumentaatioon ja luentosivun jinja-esimerkkeihin
- Lisätään laskuriin mukaan automerkin valitseminen. Luo ensin python-tiedostossa dict automerkeistä:
Vie myös automerkit-muuttuja parametrina Jinjalle.automerkit = {"1": "Tesla", "2": "Lada", "3":"Mini"}
return render_template("jinja.html", laskuri=laskuri, lkm=lkm, automerkit=automerkit)
- Lisää jinja.html-tiedostoon lomakkeelle seuraava koodi:
<p> <label>Automerkki <select name="automerkki"> {% for u in automerkit %} <option value="{{u}}">{{automerkit[u]}}</option> {% endfor %} </select> </label> </p>
Jinjassa koodi erotetaan html:stä {% %}-merkinnällä. Yksittäisten muuttujien arvot voidaan tulostaa html-koodiin {{}}-merkinnän sisällä. Yllä luodaan siis silmukka, joka käy läpi automerkit ja muodostaa niiden avainten ja arvojen perusteella sisällön select-elementille. Kokeile miten sovellus toimii.
- Muuta automerkit-listan rakennetta siten, että voit samaan rakenteeseen tallentaa tiedon kuinka monta kertaa kutakin automerkkiä on laskurilla laskettu. Tulosta kaikkien automerkkien laskurit nykyisen yhden laskurin tilalle. Varmista, että myös lomake toimii uudella tietorakenteella.
- Korjaa myös laskuri toimimaan siten, että määrä lisätään aina vain valittuna
olevalle automerkille. Nyt joudut tallentamaan lomakkeelle koko
automerkit-tietorakenteesi. Käytä json-kirjastoa apunasi.
- Muunna automerkit-tietorakenne JSON-muotoon ja tallenna tämä muoto lomakkeelle hidden-kenttään. (json.dumps)
- Lomakkeen tietoja käsiteltäessä tee muunnos toiseen suuntaan eli muunnat lomakkeelta
tulevan merkkijonon, joka on JSON-dataa, takaisin
automerkit
-muuttujan arvoksi. (json.loads)
JSONin käyttäminen on käsitelty ensimmäisessä pääteohjauksessa.
Katso ostoskoriesimerkki ( kori.py, simple.html-template)
Vanhemmat esimerkit
Voit katsoa myös ostoskori (flask)-esimerkin (lähdekoodi, template)
Voit katsoa myös ostoskori.cgi-esimerkin (lähdekoodi, template)
Muista varautua tilanteeseen, jossa lomakkeelta ei tulekkaan järkevää JSON-dataa.
-
Laita lomake vielä muistamaan mikä on ollut valittu automerkki. Tuo valittu automerkki parametrina
jinjalle ja testaa jinjan templatessa mille automerkille asetetaan selected
{% if u == automerkki %} <option selected="selected" ...>...</option> {% else %} <option ...>...</option> {% endif %}
Jinjan templatet ja perintä
Jinjan templateilla on helppo tehdä pohjatemplate, joka määrittää kaikille sivuille yhteisen rungon. Lue Template Inheritance. Kokeile tehdä seuraavat templatet.
layout.html, joka toimii kaikkien sivujen yleispohjana
<!doctype html>
<html>
<head>
<title>{% block title %}{% endblock %} - Mallisivu</title>
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
TIES4080
{% endblock %}
</div>
</body>
</html>
Muuta jinja.html seuraavanlaiseksi eli käytä edellä luotua layout.html-templatea ja muuta sen sisältöä
{% extends "layout.html" %}
{% block title %}Laskuri{% endblock %}
{% block content %}
Tähän väliin pääsisältö
{% endblock %}
Kokeile miltä sivu näyttää
Millä varmistetaan HTML-rakenteen validius ja ehjyys?
Valitettavasti Jinja ei mitenkään estä kirjoittamasta rikkinäistä ja epävalidia HTML-koodia. On olemassa myös XML-pohjaisia template-kirjastoja (esim. genshi), jotka voisivat olla avuksi, mutta emme nyt käsittele niitä. Jinjan korvikkeena voisi myös käyttää DOM-rajapintaa esim. minidom-kirjaston avulla, mutta kyseinen ratkaisu on raskaampi ja hitaampi kuin Jinja. Jinja ei ole paras ratkaisu XML-datan tuottamiseen, mutta koitamme pärjätä, koska HTML-muodossa jaetut sivut kelpaavat selaimille virheellisinäkin. Lue: HOWTO Avoid Being Called a Bozo When Producing XML.
Sovelluksen kehitysvaiheessa voidaan käyttää apuna XHTML-mediatyyppiä ja selaimessa Javascriptin avulla suoritettavaa validointia. Näitä ei kannata pitää päällä tuotantokäytössä olevassa sovelluksessa. Tämän kurssin tehtävissä nämä voivat olla aina käytössä.
HTML5 ja application/xhtml+xml
HTML5-kielestä on olemassa myös XML-kieliopin mukainen versio, jota monesti kutsutaan nimellä XHTML5. Riittää, että kirjoitat HTML-koodisi XML:n sääntöjen mukaan ja jaat valmiin dokumentin application/xhtml+xml-mediatyypillä. Tämän tyyppisissä dokumenteissa selain ei hyväksy koodia, joka ei ole XML-sääntöjen mukaista. Tätä voi helposti kokeilla esim. users.jyu.fi-palvelimella: kirjoita html-dokumentti, jonka tiedostopääte on .xhtml ja lataa tämä dokumentti Firefox-selaimella. Tee dokumenttiin kirjoitusvirhe esim. unohda jonkun elementin lopetustagi. Sinun pitäisi saada nyt selaimeen virheilmoitus.
XHTML-dokumentissa täytyy nimiavaruusmäärittelyn olla oikein. Kts. seuraava valmis pohja XHTML-dokumentille:
<!DOCTYPE html>
<html lang="fi" xmlns="http://www.w3.org/1999/xhtml" xml:lang="fi">
<head>
<meta charset="utf-8"/>
<title>Mallipohja</title>
</head>
<body>
</body>
</html>
Kokeile miten yllämainittu pohja toimii ilman oikeaa nimiavaruusmäärittelyä.
Mahdollinen virheilmoitus näyttää seuraavanlaiselta:
XML Parsing Error: mismatched tag. Expected: </p>.
Location: https://foobar.example/sivu.xhtml
Line Number 45, Column 3:
</body>
--^
Oletuksena Flask käyttää aina text/html-mediatyyppiä eli olettaa kaiken olevan tavallisia www-sivuja. Mediatyypin voi muuttaa seuraavalla tavalla:
resp = make_response(render_template('template.xhtml'))
resp.headers['Content-type'] = 'application/xhtml+xml;charset=UTF-8'
return resp
tai seuraavalla tavalla:
return Response( render_template('template.html', param1=param1), mimetype="application/xhtml+xml")
Kokeile toimiiko aiemmin luomasi sovellus application/xhtml+xml-medityypillä. Korjaa mahdolliset virheet. Jos saat näkyviin pelkän sivun lähdekoodin, muista lisätä templateen nimiavaruusmäärittely.
Automaattinen validointi Javascriptilla
Selain ei mitenkään validoi sille annettua XHTML-dokumenttia vaan ainoastaan tarkistaa, että sen rakenne on XML-sääntöjen mukaista. Validointi pitää tehdä itse. Valmiissa pohjadokumentissa on lisättynä javascriptilla toteutettu HTML-validointi (HTML-inspector), joka suoritetaan aina sivun latauduttua, sekä W3C:n validaattori (suoritetaan CTRL+ALT+V-näppäinyhdistelmällä). Mahdolliset virheet näkyvät selaimen konsolissa.
Muuta aiempi sovelluksesi käyttämään valmista pohjaa ja tutki tuleeko konsoliin virheilmoituksia.
Linkki vs lomake
- Muuta lomakkeesi käyttämään GET-metodia.
request.values
sisältää sekä GET- ja POST-metodeilla lähetetyt tiedot - Yritä lisätä laskurilla eri automerkkejä. Tutki miltä sivun osoite näyttää.
- Lisää sivulle painikkeen alapuolelle jokaista automerkkiä varten linkki, jolla voi lisätä kyseisen
automerkin laskuria aina yhdellä. Älä kopioi linkkejä suoraan selaimesta vaan yritä muodostaa ne ohjelmallisesti. Lue Creating URL query strings in Python.
Linkkien koostaminen kannattaa tehdä pythonissa. Minimoi Jinja-templatessa olevan ohjelmakoodin määrä. Valmistele kaikki rakenteet ja tiedot pythonilla mahdollisimman pitkälle. Templatessa ei pidä olla koodia yhtään ylimääräistä.
Käytä apunasi urllib.parse.urlencode-funktiota.
>>> import urllib >>> mydict = {"lkm": 1, "automerkki": "1"} >>> urllib.parse.urlencode(mydict) 'lkm=1&automerkki=1'
urllib.parse.quote_plus-funktiolla voi koodata yksittäisiä merkkijonoja.
- Validoi lopuksi sovelluksesi tuottama sivu W3C-validaattorilla, joka kertoo, jos esim. linkeissä on laittomia merkkejä.
Flask-WTF
Flask-WTF on laajennettu ja integroitu versio WTForms-kirjastosta, joka helpottaa lomakkeiden käsittelyä.
Käy läpi WTForms crash course. Lue myös Flask-WTF-quickstart, jossa kerrotaan lyhyesti miten tämä eroaa perus WTFormsista.
Toteuta edellä olevaa kuvaa vastaava arvosanalaskuri Flask-WTF-kirjaston avulla
- Kopioi pohja.zip-paketista polyglot.py-tiedosto samaan kansioon ohjelmasi kanssa
- Luo uusi funktio
#from flask_wtf import FlaskForm #tämä versio ei ole xml-yhteensopiva from polyglot import PolyglotForm #tämä versio on xml/xhtml-yhteensopiva from wtforms import Form, BooleanField, StringField, validators, IntegerField, SelectField, widgets, SelectMultipleField, ValidationError @app.route('/wtlomake', methods=['POST','GET']) def wtlomake():
- Luo edellisen funktion sisään uusi lomakeluokka perimällä PolyglotForm.
Lisätietoa lomakkeista löytyy dokumentista WTForms - Forms.
Lomakeluokan voisi luoda myös globaalisti, mutta jos haluaa muuttaa luokan sisältöä dynaamisesti niin sen on oltava funktion sisällä. CGI-ohjelmassa toimisi dynaaminen muokkaus myös globaalina, koska CGI-ohjelmatiedoston koodi suoritetaan kokonaisuudessaan jokaisella suorituskerralla uudelleen, mutta normaalisti Flask-sovellukset jäävät palvelimen muistiin.
Lomakkeen kentät (fields) ovat luokan attribuutteja. Kenttien tyyppejä ovat mm. StringField, IntegerField jne. Kts. Basic fields. Kentän otsikko (label) annetaan ensimmäisenä parametrina.
class Arvosanalaskuri(PolyglotForm): etunimi = StringField('Etunimi') sukunimi = StringField('Sukunimi')
- Luo luokan esittelyn jälkeen heti esiintymä luokasta. Vie form-objekti Jinja-templatelle.
form = Arvosanalaskuri() return render_template('wtlomake.html', form=form)
- Luo seuraavanlainen template (wtlomake.html), jolla voit kokeilla sivua:
Lomakkeen kenttiin voi suoraan viitata attribuuttien nimillä (form.etunimi tai taulukkosyntaksilla (form["etunimi"]). Kenttien labelit löytyvät taas kenttien ominaisuuksina. Esim. etunimi-kentän label on etunimi.label.<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Arvosanalaskuri</title> </head> <body> <h1>Arvosanalaskuri</h1> <form action="{{request.base_url}}" method="POST"> <p>{{form.etunimi.label}}{{ form.etunimi }}</p> <p>{{form.sukunimi.label}}{{ form.sukunimi }}</p> <p><input type="submit" name="laheta" value="Lähetä!" /></p> </form> </body> </html>
Kokeile toimiiko sivu
- Lisää kenttiin validaattorit:
Kokeile miten lomake toimii nyt. WTForms lisää HTML-koodiin kentille automaattisesti required-attribuutit.etunimi = StringField('Etunimi', validators=[validators.InputRequired()]) sukunimi = StringField('Sukunimi', validators=[validators.InputRequired()])
- Lisää mukaan vielä toinenkin validaattori, joka määrää minimi- ja/tai maksimipituuden kentälle
Virheilmoitusta ei vielä näytetä sivulla, koska lomaketta ei validoida. Lisää ensin lomakkeen validointi:etunimi = StringField('Etunimi', validators=[validators.InputRequired(),validators.Length(min=2, message="Liian lyhyt etunimi")])
Lisää sitten virheet näkyville templatessa esim. seuraavalla tavalla. Virheilmoituksia voi olla useita ja tämä pitää huomioida myös templatessa:if request.method == 'POST': form.validate()
Kokeile saatko virheilmoituksen, jos syötät etunimeksi vain yhden merkin. Lisää samanlainen validointi myös sukunimelle.{% for error in form.etunimi.errors %} <span>{{ error|e }}</span> {% endfor %}
- Lisätään lomakkeelle myös tiedekunnan valinta. Ensin tarvitaan lista tiedekunnista. Tämä on esitettävä avain-arvo-pareina esim. seuraavaan tapaan:
Lisää luokkaan uusi kenttä:tiedekunnat = [(0,"Valitse tiedekunta"), (1,"Humanistinen tiedekunta"),(2,"Informaatioteknologian tiedekunta"), (3,"Kasvatustieteiden tiedekunta"), (4,"Liikunta- ja terveystieteiden tiedekunta"), (5,"Matemaattis-luonnontieteellinen tiedekunta"), (6,"Taloustieteiden tiedekunta"), (7,"Yhteiskuntatieteellinen tiedekunta")]
Lisää uusi kenttä myös templateen. Lisää templateen näkyville myös kenttään liittyvät virheilmoitukset. Kokeile toimiiko.tiedekunta = SelectField('Tiedekunta', choices=tiedekunnat)
- Saat edellä todennäköisesti virheilmoituksen Not a valid choice. Tämä johtuu siitä, että select-listasta valitut arvot tulevat merkkijonoina eli eivät vastaa tiedekuntalistassa olevia avainarvoja, jotka ovat kokonaislukuja. Ongelma korjaantuu lisäämällä
kentän määrittelyyn coerce-parametri:
tiedekunta = SelectField('Tiedekunta', choices=tiedekunnat, coerce=int)
- Lisää tiedekunnalle oletusvalinnaksi Informaatioteknologin tiedekunta. Tämä onnistuu lisäämällä default-parametri: default=2.
- Lisää tiedekunnalle vielä NumberRange-validaattori, joka sallii arvot ykkösestä (1) aina seitsemään (7) asti. Vaihtoehto nolla (0) ei siis kelpaa.
- Lisätään lomakkeelle dynaamisesti haluttu määrä arvosanakenttiä. Asetetaan kentät ei-pakollisiksi eli jos kenttään ei ole syötetty arvoa niin muita validaattoreja ei suoriteta.
Jos kenttään on syötetty arvo niin sen on oltava 0-5. IntegerField osaa automaattisesti
muuntaa syötteensä kokonaisluvuksi eli edellä käytettyä coerce-ominaisuutta ei tarvita.
Seuraava koodi on suoritettava luokan määrittelyn jälkeen.
lkm = 10 for t in range(1, lkm+1): # setattr-funktiolla voi luoda uuden attribuutin setattr(Arvosanalaskuri, "t" + t, IntegerField(t, validators=[validators.optional(),validators.NumberRange(min=0, max=5, message="Virheellinen arvosana")]))
- Vie lukumäärä parametrina myös templatelle. Tulosta arvosanakentät templatessa esim. seuraavalla tavalla. Kenttiä voi myös kutsua kuten funktiota ja viedä parametrina html-ominaisuuksia. Esim. input-kentän pituuden saa määrättyä size-parametrilla.
<table> <tr> <th scope="row">Tehtävänro</th> {% for i in range(1, lkm+1) %} <th scope="col">{{form["t"+i|string].label}}</th> {% endfor %} </tr> <tr> <th scope="row">Pisteet</th> {% for i in range(1, lkm+1) %} <td>{{ form["t"+i|string](size=2) }}</td> {% endfor %} </tr> </table>
- Kokeile toimiiko lomake. Miten saisit virheilmoitukset näkyville? Yritä värjätä arvosanakentän reuna punaisella värillä, jos kentässä on virheellinen syöte.
- Virheilmoituksien käsittelyyn voit käyttää valmista Jinja-makroa
- Miten tiedekunnan valinta toimisi radiopainikkeilla? Muuta kentän tyypiksi RadioField ja kokeile. Radiopainikkeen yhteydessä on ehdottomasti oltava myös oletusvalinta
- Mitä jos opiskelija voisi kuulua useampaan tiedekuntaan? Mahdollista usean tiedekunnan valitseminen SelectMultipleField-tyyppisellä kentällä.
- Miten validointi toimii nyt tiedekuntakentässä, kun vaihdoit kentän tyypiksi
SelectMultipleField? NumberRange-validaattori ei enää toimi. Korvataan se omalla
validaattorilla kirjoittamalla seuraava funktio luokan sisään:
Kullekin kentälle voi kirjoittaa oman validaattorin, jos nimeää funktion seuraavalla tavalla: validate_kentan_nimi. Validointifunktio ottaa aina kaksi parametria: form, field. Esim. def validate_etunimi(form, field)def validate_tiedekunta_multi(form, field): for value in field.data: if value < 1 or value > len(tiedekunnat): raise ValidationError(u"Valitse vähintään yksi tiedekunta")
Voit kirjoittaa myös yleiskäyttöisempiä omia validaattoreita. KTs. Custom validators.
-
Oletuksena wtforms huomioi vain POST-metodin eli jos luo lomakkeen näin:
form = Lomake()
Jos taas tiedot tulevat GET-metodilla eli suoraan urliin koodattuina täytyy tehdä näin:
form = Lomake(request.args)
Tämäkään ei kuitenkaan vielä riitä tekemään validointia vaan tarvitsee tehdä vielä:
form.validate()
Koko toimintalogiikka voisi mennä esim. seuraavasti, jos halutaan hyväksyä molemmat metodit:
if request.method == "POST": form = Lomake() form.validate() elif request.method == "GET" and request.args: form = Lomake(request.args) form.validate() else: form = Lomake()
Kokeile saatko arvosanalaskurisi toimimaan samaan aikaan kummallakin metodilla
- Vertaa Malliratkaisuun (Lähdekoodi, template)
- Lisätietoa: Solving Specific Problems
FlaskWTF ja CSRF-suojaus (Cross-site request forgery)
Jos saatte lomakkeen virheisiin (form.errors) ilmoituksen:
{'csrf_token': ['The CSRF token is missing.']}
niin teiltä uupuu lomakkeelta FlaskFormin edellyttämä CSRF-suojaus. Lomakkeeseen pitää lisätä:
{{ form.csrf_token }}
Tähän liittyen voi määritellä oman salaisen avaimen:
# set the secret key. keep this really secret:
app.secret_key = '"\xf9$T\x88\xefT8[\xf1\xc4Y-r@\t\xec!5d\xf9\xcc\xa2\xaa'
Salaisen avaimen on pysyttävä vakiona ohjelman eri suorituskertojan (latausten) välillä.
Varsinainen CSRF-suojauksen aktivoiminen tehdään seuraavalla tavalla:
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
Jos saat seuraavan virheilmoituksen, ei salainen avaimesi ole pysynyt vakiona ohjelman eri suorituskertojen välillä:
Bad Request
The CSRF session token is missing.
Toinen vaihtoehto on disabloida koko CSRF-suojaus, mutta se ei ole suositeltavaa:
form = PolyglotForm(csrf_enabled=False)
Flask yrittää suojautua hyökkäykseltä lisäämällä jokaiseen lomakkeeseen uniikin tunnisteen. Mahdollinen hyökkääjä ei osaa tätä tehdä.
Jos CSRF-suojaus on käytössä, niin itse suoraan osoitteeseen kirjoitettujen parametrien mukana ei tule tarvittavaa csrf-tokenia. csrf-tarkistuksen voi joissakin tilanteissa ottaa pois päältä myös seuraavalla tavalla:
@app.route('/foo', methods=('GET'))
@csrf.exempt
def foo():
# ...
return '...'
Lisätietoa: CSRF Protection in Flask
Lisätehtävä: tietojen validointi ilman WTForms-kirjastoa
Luo edellä olevaa kuvaa vastaava Flask-sivu. Voit käyttää valmista pohjaa.
Lisää tarkistus jossa varmistetaan, että etunimi ja sukunimi on täytetty ja tiedekunta on valittu. Tee tarkistus vain jos lomake on täytetty eli tarkistusta ei pidä tehdä sivulle ensimmäistä kertaa tultaessa.
- Lisää virheilmoitukset yhteen virhesanakirjaan, jossa avaimena käytetään virheellisen kentän nimeä ja arvona virheilmoitustekstiä.
-
# luodaan dict kentat = {"etunimi":"","sukunimi":"","tdk":""} errors = dict(kentat) #tekee kopion samoilla avaimilla if request.method == 'POST': #varmistetaan, että lomake on lähetetty for k in errors: try: kentat[k] = request.form[k] except KeyError: errors[k] = "!"
- Muodosta python-sanakirja (dict) tiedekunnista ja vie se templatelle ja luo sen perusteella näkymässä olevan alasvetovalikon sisältö.
Käytä avaimena tiedekunnan numeroa.
tiedekunnat = {0:"Valitse tiedekunta", 1:"Humanistinen tiedekunta",2:"Informaatioteknologian tiedekunta", 3:"Kasvatustieteiden tiedekunta", 4:"Liikunta- ja terveystieteiden tiedekunta", 5:"Matemaattis-luonnontieteellinen tiedekunta", 6:"Taloustieteiden tiedekunta", 7:"Yhteiskuntatieteellinen tiedekunta"}
Huomaa, että dictissä olevat avaimet ovat kokonaislukuja ja lomakkeelta saadut tiedot ovat aina merkkijonoja.
- Jos tiedoissa oli virheitä, niin tulosta virhe-ilmoitus seuraavilla tavoilla:
- Ensimmäinen versio: Jos virheitä on niin tulosta yleinen virheilmoitus.
- Toinen versio: Käy läpi kaikki virhetaulukon virheet ja tulosta ne lomakkeen alussa.
- Kolmas versio: Tulosta kunkin lomake-elementin viereen siihen liittyvä virheilmoitus.
- Aseta kuhunkin lomakkeen kenttään oletusarvoksi edellisellä kerralla syötetty teksti tai valinta
- Tekstikentissä, joihin pitäisi syöttää viikkotehtävien pisteet, ei ole vielä
name
-ominaisuutta asetettu. Mitäname
-ominaisuuksiin pitäisi laittaa, että saat kaikki viikkotehtäväpisteet varmasti oikein ja kätevästi käsiteltäväksesi? Huomaathan, että selain voi lähettää lomakkeen kenttien arvot satunnaisessa järjestyksessä. - Tarkista, että viikkotehtäviin on laitettu numero. Tarkista, että numero on väliltä 0-5. Älä kuitenkaan herjaa tyhjästä tekstilaatikosta. Kokeile tehdä tarkistus siten, että käyt yhdessä silmukassa läpi kaikki viikkotehtävien syötteet. Aseta virheellisten pistekenttien taustaväriksi punainen.
Jos vielä jäi jotakin epäselväksi niin voit lisäharjoitella.
Käyttäjien kommentit