Flask, sessiot ja autentikointi
- virtualenv
- Hello World Flask-sovelluksena
- Flask ja templatet
- Laskuri sessioilla
- Autentikointi
- Flask-WTF
- Lisätehtävä: tietojen validointi ilman WTForms-kirjastoa
Harjoitellaan Flask-sovelluskehyksen ja sessioiden käyttöä WWW-sovelluksen tekemisessä. Toteutaan sessioilla yksinkertainen lomake, joka laskee lukuja yhteen.
mallivastaus ( lähdekoodi oma.py, lähdekoodi flask.py, kirjaudu, laskuri, lomake, wtlomake )
virtualenv
Käyttämällä virtualenv-työkalua voimme asentaa vapaasti Python-kirjastoja. Alustetaan oma virtuaalinen python-ympäristö:
- Ota Puttylla/Kittyllä ssh-yhteys jalava- tai halava-palvelimeen
- Jos et ole vielä tehnyt niin luo nyt W:-asemalle kansio cgi-bin/tiea2080/. Kansion koko polku halava/jalava-koneessa on /wwwhome/home/oma_tunnus/public_html/cgi-bin/tiea2080/
- Siirry cgi-bin/tiea2080/-kansioon
- Kirjoita seuraavat komento:
virtualenv venv
virtualenv luo sinulle nyt oman virtuaalisen python-ympäristön venv-alikansioon - Aktivoi virtuaaliympäristö komennolla (Bash-shellissä):
. venv/bin/activate
Jos käytätkin esim. csh/tcsh niin aktivointikomento on:source venv/bin/activate.csh
- Aktivoitumisen merkiksi komentokehotteesi alkuun ilmestyy merkintä (venv)
- Nyt voit asentaa kirjastoja pip-komennolla. Päivitä ensimmäiseksi pip uusimpaan versioon:
pip install --upgrade pip
- Asenna seuraavaksi Flask- ja Flask-WTF-kirjastot:
pip install Flask pip install Flask-WTF
- Sulje virtuaaliympäristö deactivate-komennolla
- Virtuaaliympäristösi on nyt käyttövalmis. Voit suorittaa Python-ohjelmia tässä ympäristössä, kun
asetat shebang-rivin osoittamaan
virtuaaliympäristössä olevaan python-tulkkiin. users.jyu.fi-palvelimessa suoritettavalla ohjelmalle polku on muotoa:
#!/home/oma_tunnus/public_html/cgi-bin/tiea2080/venv/bin/python
Huomaathan, että polku ei ole sama kuin halava/jalava-koneissa. - Voit tarvittaessa asentaa lisää kirjastoja virtuaaliympäristöösi. Riittää, että aktivoit edellä tehdyllä tavalla ympäristön ja käytät pip-komentoa.
Malliloki virtualenvin luomisesta ja kirjastojen asennuksesta
[tjlahton@halava tiea2080]$ virtualenv venv New python executable in /autowebhome/home/tjlahton/public_html/cgi-bin/tiea2080/venv/bin/python Installing setuptools, pip, wheel...done. [tjlahton@halava tiea2080]$ . venv/bin/activate (venv) [tjlahton@halava tiea2080]$ pip install Flask Collecting Flask Using cached https://files.pythonhosted.org/packages/7f/e7/08578774ed4536d3242b14dacb4696386634607af824ea997202cd0edb4b/Flask-1.0.2-py2.py3-none-any.whl Collecting Werkzeug>=0.14 (from Flask) Using cached https://files.pythonhosted.org/packages/20/c4/12e3e56473e52375aa29c4764e70d1b8f3efa6682bef8d0aae04fe335243/Werkzeug-0.14.1-py2.py3-none-any.whl Collecting click>=5.1 (from Flask) Using cached https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl Collecting itsdangerous>=0.24 (from Flask) Using cached https://files.pythonhosted.org/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl Collecting Jinja2>=2.10 (from Flask) Using cached https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl Collecting MarkupSafe>=0.23 (from Jinja2>=2.10->Flask) Using cached https://files.pythonhosted.org/packages/bc/3a/6bfd7b4b202fa33bdda8e4e3d3acc719f381fd730f9a0e7c5f34e845bd4d/MarkupSafe-1.1.0-cp27-cp27mu-manylinux1_x86_64.whl Installing collected packages: Werkzeug, click, itsdangerous, MarkupSafe, Jinja2, Flask Successfully installed Flask-1.0.2 Jinja2-2.10 MarkupSafe-1.1.0 Werkzeug-0.14.1 click-7.0 itsdangerous-1.1.0 You are using pip version 9.0.1, however version 19.0.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. (venv) [tjlahton@halava tiea2080]$ pip install --upgrade pip Cache entry deserialization failed, entry ignored Collecting pip Using cached https://files.pythonhosted.org/packages/46/dc/7fd5df840efb3e56c8b4f768793a237ec4ee59891959d6a215d63f727023/pip-19.0.1-py2.py3-none-any.whl Installing collected packages: pip Found existing installation: pip 9.0.1 Uninstalling pip-9.0.1: Successfully uninstalled pip-9.0.1 Successfully installed pip-19.0.1 (venv) [tjlahton@halava tiea2080]$ pip install Flask-WTF DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. Collecting Flask-WTF Using cached https://files.pythonhosted.org/packages/60/3a/58c629472d10539ae5167dc7c1fecfa95dd7d0b7864623931e3776438a24/Flask_WTF-0.14.2-py2.py3-none-any.whl Collecting WTForms (from Flask-WTF) Using cached https://files.pythonhosted.org/packages/9f/c8/dac5dce9908df1d9d48ec0e26e2a250839fa36ea2c602cc4f85ccfeb5c65/WTForms-2.2.1-py2.py3-none-any.whl Requirement already satisfied: Flask in ./venv/lib/python2.7/site-packages (from Flask-WTF) (1.0.2) Requirement already satisfied: Werkzeug>=0.14 in ./venv/lib/python2.7/site-packages (from Flask->Flask-WTF) (0.14.1) Requirement already satisfied: click>=5.1 in ./venv/lib/python2.7/site-packages (from Flask->Flask-WTF) (7.0) Requirement already satisfied: itsdangerous>=0.24 in ./venv/lib/python2.7/site-packages (from Flask->Flask-WTF) (1.1.0) Requirement already satisfied: Jinja2>=2.10 in ./venv/lib/python2.7/site-packages (from Flask->Flask-WTF) (2.10) Requirement already satisfied: MarkupSafe>=0.23 in ./venv/lib/python2.7/site-packages (from Jinja2>=2.10->Flask->Flask-WTF) (1.1.0) Installing collected packages: WTForms, Flask-WTF Successfully installed Flask-WTF-0.14.2 WTForms-2.2.1
Hello World Flask-sovelluksena
- Tutustu Flaskia käsittelevään materiaaliin ja avaa avuksesi seuraavat sivut:
Flask on rakennettu Werkzeug-WSGI-kirjaston päälle joten osa tarpeellisesta dokumentaatiosta on suoraan Werkzeugin dokumentaatiossa.
- Valitettavasti users.jyu.fi-palvelimella ei voida suoraan ajaa Flaskia vaan sen suorittaminen on kikkailtava CGI-ohjelmana.
- Luo W:-aseman cgi-bin-hakemistoon uusi alihakemisto ohjaus3
-
Luo kansioon uusi tiedosto flask.cgi ja kirjoita siihen seuraava ohjelmakoodi. Muista muuttaa ensimmäiselle riville oma käyttäjätunnuksesi.
#!/home/oma_tunnus/public_html/cgi-bin/tiea2080/venv/bin/python # -*- coding: utf-8 -*- import sys from wsgiref.handlers import CGIHandler from werkzeug.debug import DebuggedApplication try: from oma import app as application if __name__ == '__main__': handler = CGIHandler() handler.run(DebuggedApplication(application)) except: print "Content-Type: text/plain;charset=UTF-8\n" print "Syntaksivirhe:\n" for err in sys.exc_info(): print unicode(err).encode("UTF-8")
- Luo samaan kansioon myös oma.py-tiedosto ja kirjoita siihen seuraava ohjelmakoodi. Tässä tiedostossa shebang-rivi pitää olla kuten alla:
#!/usr/bin/python # -*- coding: utf-8 -*- from flask import Flask, session, redirect, url_for, escape, request, Response, render_template import os import werkzeug.exceptions app = Flask(__name__) # voidaan napata kiinni palvelimen virheet @app.errorhandler(werkzeug.exceptions.InternalServerError) def handle_internal_server_error(e): return Response(u'Internal Server Error\n' + unicode(e), status=500, content_type="text/plain; charset=UTF-8") # @app.route määrää mille osoitteille tämä funktio suoritetaan @app.route('/') def hello_world(): return Response("Hello World", content_type="text/plain; charset=UTF-8") if __name__ == '__main__': app.debug = True app.run(debug=True)
- Varmista, että tiedostojen ja kansioiden oikeudet ovat samat kuin aiemmin tehdyissä cgi-ohjelmissa.
- Kokeile sovellustasi osoitteessa http://users.jyu.fi/~omatunnus/cgi-bin/tiea2080/ohjaus3/flask.cgi/. Viimeinen /-merkki on olennainen!
- Jos sovelluksesi ei toimi niin lue ohjetta eteenpäin ja kokeile omalla / mikroluokan koneelle asennetulla Flaskilla
- Yleensä syntaksivirheet löytyvät, kun yrität suorittaa sovelluksesi komentoriviltä:
python flask.cgi
taipython oma.py
Huom! Näin suoritettuna python ei löydä esim. werkzeug- ja flask-kirjastoja. Jos haluat näiden löytyvän niin sinun pitää suorittaa ohjelmasi virtualenvillä asentamallasi python-tulkilla. Sinun pitää ensin aktivoida virtuaaliympäristö ja sen jälkeen kokeilla ohjelmasi suorittamista.
Komentoriviltä suoritettuna flask-ohjelma ei voi toimia, koska se vaatii www-ympäristön. Komentoriviltä ajettuna saat kuitenkin kiinni koko sovelluksen kaatavat syntaksivirheet.
- Yleensä syntaksivirheet löytyvät, kun yrität suorittaa sovelluksesi komentoriviltä:
- WWW-palvelin suorittaa Flask-kehyksen ja oma.py-tiedostosta löytyy hello_world-funktio, joka on määrätty suoritettavaksi,
kun pyydetään sovelluksen juurikansiota (/). HTTP-protokollaan liittyviä asioita ei tarvitse itse tehdä vaan Flask hoitaa ne. Riittää, että
funktio palauttaa merkkijonon, jossa on sivulle haluttu sisältö.
- WWW-palvelin (users.jyu.fi) suorittaa flask.cgi-sovelluksen tiedoston ensimmäisellä rivillä kerrotulla ohjelmalla eli virtuaaliympäristön python-tulkilla #!/home/oma_tunnus/public_html/cgi-bin/tiea2080/venv/bin/python
- Sovellus yrittää ladata oma.py-tiedoston (kirjaston) sisällön (from oma import app as application). Jos ei onnistu eli kyseisessä tiedostossa on esim. syntaksivirheitä niin näytetään virhe sivulle
- Käynnistetään handler = CGIHandler(). Suoritetaan CGIHandlerilla edellä ladattu oma sovellushandler.run(DebuggedApplication(application))
- oma.py-tiedostossa käynnistetään Flask.
app = Flask(__name__) if __name__ == '__main__': app.debug = True app.run(debug=True)
- Flask ottaa ohjat käsiinsä ja suorittaa @app.route('/')-merkinnän määräämän funktion eli hello_world()
- hello_world() asettaa halutun mediatyypin ja palauttaa tekstin "Hello World"
Flask omalle koneelle
Asenna Anaconda (Python 2.7), jonka mukana tulee myös Flask. Anaconda on jo valmiiksi asennettuna Agoran mikroluokkiin.
Tämän jälkeen riittää, kun käynnistää oman sovelluksen Windowsin CMD-promptissa komennolla
python oma.py
Jos python-tulkkia ei löydy cmd-ympäristössä niin etsi Anacondan mukana asentunut Anaconda Prompt ja kokeile käynnistää sovelluksi sen kautta.
ja voi kokeilla selaimella osoitteessa:
http://127.0.0.1:5000/tai
http://localhost:5000/
Nyt saa myös selkeät virheilmoitukset suoraan selaimeen, koska Flaskin debug-tila toimii omalla koneella.
Halavassa / jalavassa tämä ei onnistu, koska Flaskia ei niistä löydy. Yliopiston mikroluokkien koneisiin on osaan asennettuna Anaconda.
Lopullinen sovellus pitää yleensä saada toimimaan muualla kuin omalla koneella ja toiminnallisuudessa voi olla eroja esim. tiedostopolut voivat olla erilaisia. Debuggaus on kuitenkin huomattavasti helpompaa omalla koneella. Jos on W:-asema verkkolevynä niin voi suoraan sieltä ajaa sovellustaan oman koneen Flaskilla ja testailla samaan aikaan myös users.jyu.fin kautta.
Huomaa, että samat absoluuttiset tiedostopolut eivät toimi lokaalisti suoritettuina ja users.jyu.fi-palvelimen kautta suoritettuna. Voit try..exceptin avulla asettaa eri polut. Kannattaa käyttää suhteellisia polkuja niin ne toimivat aina.
Debuggaus
Sitten kun flask-sovellus lähtee toimimaan niin virheiden debuggaus on haastavaa, koska users.jyu.fi:ssä ei välttämättä toimi flaskin oma debug-tila. Ohjelman kaatuessa syntaksivirheeseen tai ajonaikaiseen virheeseen on tuloksena helposti vain internal server error. Cgitb-kirjasto ei auta.
Täytyy itse logata virheet. Seuraava tekee lokitiedoston kansioon '../piilo/'. Korjaa polku omaan kansiorakenteeseesi sopivaksi:
import logging logging.basicConfig(filename=os.path.abspath('../piilo/flask.log'),level=logging.DEBUG) logging.debug("Tänne mitä haluaa lokiin kirjoittaa")
Absoluuttinen polku omiin tiedostoihisi on users.jyu.fi-palvelimessa muotoa:
/home/oma_tunnus/public_html/kansio/tiedosto
Varmista, että lokitiedostoon on kaikilla kirjoitusoikeus. Kts. ohjaus 1.
Älä kirjoita lokia cgi-bin-kansioon tai sen alikansioihin.. Älä myöskään kirjoita lokia w:\hidden-kansioon, koska se on users.jyu.fi-palvelimen asetuksissa määritelty erikoiskansioksi. Jostain syystä sinne kirjoittaminen ei toimi users.jyu.fi:ssä.
Älä luo flask-sovelluksen käyttämiä kansioita Windowsin kautta tai esim. WinSCP:llä vaan vain ja ainoastaan Unix-komentoriviltä. Valitettavasti users.jyu.fissä käytetty SELinux sotkee kansioiden ja tiedostojen oikeuksia ja tiedostokontekstiä jos ne tehdään Windows-levyjaon kautta.
Jotta lokista olisi iloa on käytettävä try..exceptiä. Nappaa exceptillä kiinni mahdolliset virheet ja dumppaa virheilmoitus lokiin:
try: #tähän virheellistä koodia except Exception as e: logging.debug("Tapahtui virhe. Tähän järkevä virheilmoitus") # logataan myös varsinainen virheilmoitus: logging.debug(unicode(e))
Lokin sisältöä voi seurailla helpoiten ottamalla pääteyhteyden halavaan ja käyttämällä tail-komentoa:
tail -f flask.log
Tämä kyttää koko ajan tiedoston sisältöä ja näyttää heti sinne tulevat uudet rivit.
Flask ja templatet
Luodaan seuraavan kuvan esittämä lomake käyttäen jinja-templatea. Kopioi itsellesi valmis pohja ja tallenna se ohjaus3/templates-kansioon laskuri.html-nimellä.
- Lisää oma.py-tiedoston alkuun rivi:
from flask import render_template
- Nyt voit käyttää Jinja-templatea aivan samaan tapaan kuin aiemmin jo opit. Kokeile muuttaa hello_world-funktiota:
return render_template('laskuri.html')
- Muuta lomakkeen käsittelijäksi sivu itse. Vaihda templateen seuraava rivi:
<form action="{{request.base_url}}" method="get">
Template saa automaattisesti käyttöönsä Flaskin request-, session- ja g-objektit. Näiden lisäksi tulevat tietysti objektit, jotka annat render_template-kutsussa.
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> <link rel="stylesheet" href="filename='http://appro.mit.jyu.fi/sovellukset/demot/demo3/lomake.css') }}"> <title>{% block title %}{% endblock %} - Mallisivu</title> </head> <body> <div id="content">{% block content %}{% endblock %}</div> <div id="footer"> {% block footer %} TIEA2080 {% endblock %} </div> </body> </html>
Muuta laskuri.html seuraavanlaiseksi eli käytä edellä luotua layout.html-templatea ja muuta sen sisältöä
{% extends "layout.html" %} {% block title %}Laskuri{% endblock %} {% block content %} <h1>Laskuri</h1> <p>Summa on </p> <form action="" method="POST"> <p> <label>Anna luku<input type="text" name="luku" /></label> </p> <p> <input type="submit" name="laheta" value="Laske" /> </p> </form>{% endblock %}
Kokeile miltä sivu näyttää
Laskuri sessioilla
- Kokeile edellä luomaasi lomaketta. Toimiiko vai saatko virheilmoituksen? Kokeile vaihtaa lomakkeen lähetysmetodia POST- ja GET-metodien välillä.
- Hyväksytyt metodit pitää erikseen luetella:
# method-parametrissa luetellaan metodit joita tämä funktio kelpuuttaa. @app.route('/laskuri', methods=['POST','GET']) def laskuri(): return render_template('laskuri.html')
- Varsinaisessa laskurissa tarvitset request-objektin
form-, args- tai values -ominaisuutta. Vrt. CGI-ohjelmassa käytetty cgi.fieldstorage.
- request.form sisältää POST-metodilla lähetetyt tiedot
- request.args sisältää query stringin sisältämät tiedot eli GET-metodilla lähetetyt tiedot
- request.values sisältää molempien edellämainittujen sisällöt
Kts. myös Werkzeug BaseRequest
Werkzeug dekoodaa valmiiksi lomakkeilta tulevat tiedot joten itse ei tarvitse erikseen kutsua decode-funktiota.
Toinen vaihtoehto on käyttää get- ja getlist-metodeja samaan tapaan kuin cgi.fieldstoragen kanssa. Kts. Werkzeug datastructures.
avain = request.form.get("avain", "oletusarvo") avain = request.form.getlist("avain")
- Muuta ohjelmaa siten, että tulostat ennen lomaketta näkyville mahdollisesti syötetyn luvun:
@app.route('/laskuri', methods=['POST','GET']) def laskuri(): try: luku = request.form['luku'] except: luku = 0 # sama lyhyemmin. voidaan antaa myös oletusarvo: luku = request.form.get("luku", 0) # jos lukuja olisi useampi niin pitää käyttää getlist-metodia # luvut = request.form.getlist("luku") return render_template('laskuri.html', luku=luku)
- Muutetaan ohjelmaa siten, että voidaan laskea yhteen syötettyjä lukuja. Ohjelman pitää siis
muistaa aiemmin saatu summa. Tämä onnistuu session avulla.
Sessioon tallennetut muuttujat säilyttävät arvonsa koko istunnon ajan. Lisää ohjelmakooditiedostosi alkuun rivi:
from flask import Flask, session, redirect, url_for, escape, request, Response, render_template
Tämän lisäksi tarvit salaisen avaimen jota käytetään sessioon liittyvän evästeen käsittelyssä. Lisää seuraava rivi app-objektin määrittelyn jälkeen:app.secret_key = '"\xf9$T\x88\xefT8[\xf1\xc4Y-r@\t\xec!5d\xf9\xcc\xa2\xaa'
Sinun pitäisi muodostaa itse oma salainen avain. Se onnistuu esimerkiksi käynnistämällä python-tulkki komentoriviltä ja antamalla komennot:
import os os.urandom(24)
- Luo uusi sessiomuuttuja:
session['summa'] = 10
sessiomuuttujia käytetään samaan tapaan kuin dictejä - sessio-muuttujien pitäisi näkyä suoraan templateen. Kokeile tulostaa session['summa']-muuttujan arvo www-sivulle
- Yritä kasvattaa sessiomuuttujasi arvoa ohjelman jokaisella suorituskerralla.
Ensimmäisellä käyttökerralla sessio-muuttujaa ei ole määritelty joten varminta on testata pythonin poikkeuskäsittelyn (try...except) avulla onko sessiomuuttujalla jo arvo vai ei. Jos muuttuja on alustamaton niin yritys lisätä muuttujan arvoa aiheuttaa poikkeuksen jolloin voidaan tehdä muuttujan alustaminen.
- Ota lomakkeelle annettu luku talteen. Tarkista, että lomakkeelle on varmasti syötetty kokonaisluku. Lisää luku sessiomuuttujaan. Tulosta yhteenlaskettu summa sivulle.
-
Istunnon ja siihen liittyvien muuttujien ylläpitäminen edellyttää, että
käytetty selain tukee evästeitä. Evästeessä kuljetetaan selaimen ja WWW-palvelimen
välillä istuntoon liittyvää tunnistetta.
Katso Firefoxin Tools|Web developer|Network-välilehdeltä minkälaisia HTTP-otsakkeita liikkuu selaimesi ja WWW-palvelimen välillä.
Sieltä pitäisi löytyä Set-Cookie-alkuisia rivej. Kokeile myös Web Developer Toolbarin
Cookies|View Cookie Information-valinnan avulla millaisen evästeen flask asettaa käyttäessäsi sessioita.
Seuraavassa kuvassa hahmotetaan sessioon liittyvää toimintaa PHP-sovelluksessa. Toiminta on täysin vastaavaa flask-sovelluksilla.
Testauksessa kannattaa käyttää myös Web Developer Toolbarista löytyvää valintaa Cookies | Delete Domain Cookies. Tämä poistaa näkyvillä olevaan sivuun liittyvät evästeet. Poistamalla evästeet palaa laskuri alkutilaan.
- Tee uusi sivu joka poistaa laskurisi session.pop('summa',None)-metodilla. Kokeile nollautuuko laskuri.
@app.route('/nollaa') def nollaa(): session.pop('summa',None) # url_for-metodilla voidaan muodostaa osoite haluttuun funktioon. redirect taas ohjaa suoraan tälle sivulle joten # nollaa-osoite ei tarvitse omaa sisältöä return redirect(url_for('laskuri'))
Autentikointi
Käyttäjän kirjautuminen ja autentikointi järjestelmään tehdään sessioiden avulla seuraavalla tavalla:
- Täytyy ensimmäisenä luoda sivu jolla voi kirjautua sisään. Tee uusi sivu (/kirjaudu) ja sijoita sivulle kirjautumislomake jolla kysytään tunnus ja salasana
- Tarkista sivulla, että käyttäjätunnus on syötetty ja salasanaksi on syötetty "testi". Salasanan vertailu on tehtävä seuraavalla tavalla:
import hashlib m = hashlib.sha512() avain = u"omasalainenavain" m.update(avain.encode("UTF-8")) m.update(salasana.encode("UTF-8")) if len(tunnus) and m.hexdigest() == "366e90b5fe29a9d9c1420afa334c4b19c4d63dcd200f424b7a9fe3328a352da5818fc03cffa463c2362db3535b612df4eb27df33d4720fbf592964571ad7572e": # jos kaikki ok niin asetetaan sessioon tieto kirjautumisesta ja ohjataan laskurisivulle session['kirjautunut'] = "ok" return redirect(url_for('laskuri')) # jos ei ollut oikea salasana niin pysytään kirjautumissivulla. return render_template('kirjaudu.html')
Selkokielistä salasanaa ei siis vertailla vaan salasanasta jollakin algoritmilla laskettua merkkijonoa. Testi-salasanaa vastaava merkkijono on tuotettu seuraavalla tavalla. Oikean salasanan vertailu pitää tehdä samalla tavalla jolloin voi verrata onko m.hexdigest-funktion palauttama merkkijono sama kuin haluttu.
>>> import hashlib >>> m = hashlib.sha512() >>> m.update(u"omasalainenavain".encode("UTF-8")) >>> m.update(u"testi".encode("UTF-8")) >>> m.hexdigest() "366e90b5fe29a9d9c1420afa334c4b19c4d63dcd200f424b7a9fe3328a352da5818fc03cffa463c2362db3535b612df4eb27df33d4720fbf592964571ad7572e"
- Kokeile toimiiko kirjautuminen eli ohjaudutko laskurisivulle jos syötät oikean salasanan
- Tee myös logout-sivu, joka poistaa sessiosta kirjautunut-avaimen ja ohjaa sen jälkeen kirjaudu-sivulle
- Vielä tarvitaan oma decorator, jolla saadaan helposti yhdistetty kirjautumisvaatimus halutuille sivuille. Vrt. app.routes.
Lisää sivun alkuun:
from functools import wraps
Kirjoita seuraavanlainen funktio:def auth(f): ''' Tämä decorator hoitaa kirjautumisen tarkistamisen ja ohjaa tarvittaessa kirjautumissivulle ''' @wraps(f) def decorated(*args, **kwargs): # tässä voisi olla monimutkaisempiakin tarkistuksia mutta yleensä tämä riittää if not 'kirjautunut' in session: return redirect(url_for('kirjaudu')) return f(*args, **kwargs) return decorated
- Kokeile nyt käyttää omaa @auth-decoratoria. Lisää se esim. laskuri-funktion eteen:
@app.route('/laskuri', methods=['POST','GET']) @auth def laskuri():
- Kokeile mennä selaimella laskurisivulle. Ohjaudutko kirjautumiseen? Jos et niin muista poistaa vanha kirjautuminen logout-sivulla ja kokeile sen jälkeen uudelleen.
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
- Luo uusi funktio
from flask_wtf import FlaskForm 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ä FlaskForm.
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(FlaskForm): etunimi = StringField(u'Etunimi') sukunimi = StringField(u'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:
<!DOCTYPE html> <html> <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>
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.Kokeile toimiiko sivu
- Lisää kenttiin validaattorit:
etunimi = StringField(u'Etunimi', validators=[validators.InputRequired()]) sukunimi = StringField(u'Sukunimi', validators=[validators.InputRequired()])
Kokeile miten lomake toimii nyt. WTForms lisää HTML-koodiin kentille automaattisesti required-attribuutit. - Lisää mukaan vielä toinenkin validaattori, joka määrää minimi- ja/tai maksimipituuden kentälle
etunimi = StringField(u'Etunimi', validators=[validators.InputRequired(),validators.Length(min=2, message=u"Liian lyhyt etunimi")])
Virheilmoitusta ei vielä näytetä sivulla, koska lomaketta ei validoida. Lisää ensin lomakkeen validointi:if request.method == 'POST': form.validate()
Lisää sitten virheet näkyville templatessa esim. seuraavalla tavalla. Virheilmoituksia voi olla useita ja tämä pitää huomioida myös templatessa:{% for error in form.etunimi.errors %} <span>{{ error|e }}</span> {% endfor %}
Kokeile saatko virheilmoituksen, jos syötät etunimeksi vain yhden merkin. Lisää samanlainen validointi myös sukunimelle. - Lisätään lomakkeelle myös tiedekunnan valinta. Ensin tarvitaan lista tiedekunnista. Tämä on esitettävä avain-arvo-pareina esim. seuraavaan tapaan:
tiedekunnat = [(0,u"Valitse tiedekunta"), (1,u"Humanistinen tiedekunta"),(2,u"Informaatioteknologian tiedekunta"), (3,u"Kasvatustieteiden tiedekunta"), (4,u"Liikunta- ja terveystieteiden tiedekunta"), (5,u"Matemaattis-luonnontieteellinen tiedekunta"), (6,u"Taloustieteiden tiedekunta"), (7,u"Yhteiskuntatieteellinen tiedekunta")]
Lisää luokkaan uusi kenttä:tiedekunta = SelectField(u'Tiedekunta', choices=tiedekunnat)
Lisää uusi kenttä myös templateen. Lisää templateen näkyville myös kenttään liittyvät virheilmoitukset. Kokeile toimiiko. - 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(u'Tiedekunta', choices=tiedekunnat, coerce=int)
- 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" + unicode(t), IntegerField(unicode(t), validators=[validators.optional(),validators.NumberRange(min=0, max=5, message=u"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
- 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:
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")
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)Voit kirjoittaa myös yleiskäyttöisempiä omia validaattoreita. KTs. Custom validators.
- 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 }}
Toinen vaihtoehto on disabloida koko CSRF-suojaus, mutta se ei ole suositeltavaa:
form = FlaskForm(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ä.
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.
Käyttäjien kommentit