5. Veebirakenduste loomine
Kindlasti kasutad igapäevaselt veebilehti nagu Google, YouTube, Facebook, Instagram, Twitter, Vikipeedia või vähemalt oled neist kuulnud. Need on kõik tegelikult veebirakendused, mille taga jookseb mingi kood, mis saadab veebilehitsejate päringutele andmeid vastu. Uurime, kuidas luua enda lihtne veebirakendus ja kuidas seda Internetti püsti panna.
Veebirakendusi kirjutatakse keeltega PHP, Java, Ruby ja muidugi ka Python. Veebirakenduste jaoks on Pythonil palju erinevaid raamistikke: Django, TurboGears, Pyramid, Flask ja väga palju muid. Selles peatükis keskendume Flaskile, mis lubab veebirakenduse püsti saada vaid mõne reaga.
Ettevalmistus
Enne jätkamist tuleb paigaldada moodul flask. Seda saab teha käsuga pip install flask
. Katsetamiseks kirjuta Pythonis import flask
. Kui erindit ei visata, on moodul paigaldatud. Kui ei ole kindel, kuidas mooduleid installida, siis on õpikus moodulite paigaldamise juhised.
Et peatükist aru saada, peab olema läbitud õpiku esimene osa. Kasuks tuleb HTML-i tundmine, aga selle peatüki läbimiseks ei pea seda oskama. HTML-i õppematerjalid: eesti keeles, inglise keeles.
Flask
Flask on Pythoni moodul lihtsate veebirakenduste loomiseks, mis keskendub kiiresti alustamisele ja lihtsusele. Selle lihtsus ei takista ka suuremate veebirakenduste loomist. Seda kasutavad teiste raamistike kõrval näiteks Netflix, Airbnb, Reddit, Uber ja muud. Selles peatükis kirjutame lihtsa veebirakenduse, mis võtab kasutajalt sisendit ja arvutab selle põhjal tulemuse. Vaatame ka, kuidas seda Internetti üles laadida ja teistele jagada.
Tere, maailm!
Paneme kõigepealt püsti kõige lihtsama võimaliku veebirakenduse: üksainus lehekülg, mis tagastab mingit teksti. Salvesta järgnev kood faili teremaailm.py
.
from flask import Flask app = Flask(__name__) @app.route("/") def index(): return "Tere, maailm!" if __name__ == "__main__": app.run(debug=True)
Proovi seda programmi jooksutada. Peaks ilmuma selline väljund:
* Serving Flask app "teremaailm" (lazy loading) * Environment: production WARNING: Do not use the development server in a production environment. Use a production WSGI server instead. * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 123-456-789
Koodi jooksutamine käivitas arvutis veebiserveri aadressil http://127.0.0.1:5000/. 127.0.0.1 on IP aadress, mis tähendab praeguse arvuti võrku ja 5000 tähendab porti, mille peal server jookseb praeguse arvuti võrgus. Nüüd proovi minna veebilehitsejas sinna aadressile ja seal peaks olema tekst "Tere, maailm!":
Pärast külastamist ilmub konsooli ka selline väljund:
127.0.0.1 - - [06/Apr/2020 14:24:47] "GET / HTTP/1.1" 200 -
See tähendab, et lehekülge "/" külastati sellel kellaajal IP-aadressilt 127.0.0.1.
Kui programm ei käivitunud õigesti, siis Flaski dokumentatsioon pakub välja erinevaid lahendusi: https://flask.palletsprojects.com/en/1.1.x/quickstart/#what-to-do-if-the-server-does-not-start
Mis just juhtus?
Käime rakenduse ridahaaval läbi.
from flask import Flask app = Flask(__name__)
Impordime Flaski rakenduse klassi ja salvestame sellest isendi muutujasse app
. Parameeter __name__
annab Flaskile teada, kuidas programmi käivitati.
@app.route("/")
See on dekoraator, mis ütleb rakendusele, et järgmine funktsioon kutsutakse välja, kui minnakse aadressile "/
". Kaldkriips tähendab juurt ehk antud juhul http://127.0.0.1:5000/. "/tere
" vastaks aadressile http://127.0.0.1:5000/tere. Dekoraator teeb põhimõtteliselt seda, et sellele järgnevat funktsiooni kasutatakse ühes teises funktsioonis, aga sellest ei ole Flaski kasutamisel vaja aru saada.
def index(): return "Tere, maailm!"
See funktsioon määrab ära, mida veebirakendus teeb, kui minnakse dekoraatoris määratud leheküljele. Funktsiooni tagastus saadetakse lehe külastajale. Praegu tagastatakse lihtsalt tekst "Tere, maailm!", aga tavaliselt laaditakse kuskilt andmeid, tehakse arvutusi ja tagastatakse HTML-kood, mille veebilehitseja teeb ilusaks veebileheks. Siin saab ka vastu võtta kasutaja sisendeid ja nende põhjal muuta tagastust. Funktsiooni nimi ei ole praegu tähtis.
if __name__ == "__main__": app.run(debug=True)
Siin käivitatakse rakendus, kui programm käivitati, mitte ei imporditud.
Debug tähendab, et kui lehekülje laadimisel juhtub viga, siis seda kuvatakse veebilehitsejas, mitte ainult konsoolis. See teeb ka seda, et kui programmi koodi muudetakse, siis programmi ei pea uuesti jooksutama, vaid see taaskäivitatakse automaatselt.
Proovi muuta väärtust, mida index()
funktsioon tagastab ja vaata, mis konsooli ilmub. Ilma programmi taaskäivitamiseta, külasta veebilehitsejaga aadressi uuesti. Kas veebilehel on uus tekst?
Lehekülgede aadressid
Esimeses näites tegime ühe lehekülje aadressile "/". Teeme teise lehekülje teisele aadressile:
@app.route("/teine") def teine(): return "Oled teisel leheküljel!"
Ühel leheküljel võib olla mitu aadressi:
@app.route("/teine") @app.route("/2") def teine(): return "Oled teisel leheküljel!"
Aadressid võivad olla isegi dünaamilised, s.t aadressi sees on muutuja. Selleks tuleb aadressis teravnurksulgude vahele panna muutujanimi ning sama muutuja panna funktsiooni parameetritesse. Seda muutujat saab nüüd funktsioonis kasutada.
@app.route("/leht/<arv>") def leht(arv): if arv == "saladus": return "Leidsid üles salalehekülje!" return "Oled praegu leheküljel: " + arv
Katseta erinevaid aadresse. Leia vastused küsimustele:
- Mis juhtub, kui minna lehele "/leht/" ilma muutujata?
- Kui defineerida aadress "/leht<number>", kas see töötab ka?
- Kas aadressil võib olla mitu muutujat?
Huvitavam sisu
Lehekülje sisu ei pea samuti staatiline olema. Oletame, et lehekülg random.org on maas ja kirjutame sellest enda versiooni. Algul tagastame lihtsalt juhusliku arvu 1-st 10-ni:
from flask import Flask import random app = Flask(__name__) @app.route("/") def index(): juhuslik_arv = random.randint(1, 10) return "Sinu juhuslik arv on {}.".format(juhuslik_arv) if __name__ == "__main__": app.run(debug=True)
Proovi täiendada seda rakendust nii, et kasutaja saab ise määrata juhusliku arvu ülem- ja alampiiri.
Kui külastada lehekülje juurt ehk "/"
, on piirid 1
ja 10
.
Kui külastada aadressi "/<maksimum>"
, on piirid 1
ja <maksimum>
.
Kui külastada aadressi "/<miinimum>/<maksimum>"
, on piirid <miinimum>
ja <maksimum>
. Seda peaks lahendama ainult ühe funktsiooniga.
Vihje: tuleta meelde, kuidas määrata funktsiooni parameetritele vaikeväärtused.
Aadressiparameetrid
Eelmise ülesandega tegime rakenduse, millel on kaks parameetrit: ülem- ja alampiir. On kolm võimalust neid sisestada: mitte ükski; ainult ülempiir; nii alampiir kui ülempiir. Dekoraatorid tulevad sellised:
@app.route("/") @app.route("/<maksimum>") @app.route("/<miinimum>/<maksimum>")
Aga mis siis, kui tahame, et kasutaja sisestab ainult alampiiri ning ülempiir saab vaikeväärtuse? Mis siis, kui meil on palju parameetreid ja neil on kõigil mingid vaikeväärtused? Saaksime teha mingi väärtuse, mille puhul võetakse vaikeväärtus, praegu näiteks "/<miinimum>/default"
, aga see ei ole väga ilus lahendus. Selle jaoks on olemas võimalus lisada aadressidele parameetrid.
Vaatame YouTube'i video linki: https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=43
Selle aadress on tegelikult lihtsalt https://www.youtube.com/watch, aga sellel on kaasas kaks parameetrit:
{ "v": "dQw4w9WgXcQ", # mis videot näidatakse "t": 43 # mitmendalt sekundilt videot alustada }
Flaskis saab parameetrite väärtustele ligi requests.args
muutuja kaudu, mille peab flask
moodulist importima. Väärtused saab kätte get() meetodiga. Neile saab ka määrata vaikeväärtuse ja tüübi. Sama lehekülg oleks Flaskis pool-pseudokoodina umbes selline:
from flask import Flask, request, redirect app = Flask(__name__) @app.route("/watch") def watch(): video_id = request.args.get("v") start_time = request.args.get("t", default=0, type=int) if not video_id: return redirect("/") ...
Mida YouTube video_id
-ga teeb, on ärisaladus. Muutuja start_time
tehakse automaatselt täisarvuks ja selle vaikeväärtus on 0.
Proovi lisada oma juhusliku arvu generaatorile võimalus valida alam- ja ülempiirid aadressiparameetritega. Kasuta YouTube'i näidet, et parameetrite väärtused kätte saada.
Kui külastada lehekülje juurt ilma parameetriteta ehk aadressi "/"
, on piirid 1
ja 10
.
Kui külastada aadressi "/?maksimum=20"
, on piirid 1
ja 20
.
Kui külastada aadressi "/?maksimum=20&miinimum=10"
, on piirid 10
ja 20
.
Ilusamad leheküljed
Siiani oleme tagastanud ainult teksti, aga leheküljed ei ole ju tavaliselt sellised. Lehekülgedel on struktuuri, värve, suuri tekste, väikeseid tekste, pilte, linke. Et anda leheküljele struktuur, tuleb kasutada HTML-koodi. Ilusaks saab seda teha CSS-ga. Selle peatüki eesmärk ei ole neist kumbagi õpetada. Kasutame olemasolevat disaini ja vaatame, kuidas Flaskiga seda tagastada.
Flask nõuab, et lehekülje failid on spetsiifilistes kaustades. HTML-failid lähevad kausta templates/
ning CSS-failid, pildid ja muud staatilised failid lähevad kausta static/
. Failipuu peaks tulema järgmine, kui app.py
on Pythoni fail, kus käivitatakse Flaski rakendus:
templates/ ├─index.html └─about.html static/ ├─style.css ├─pilt.jpg └─script.js app.py
Salvesta järgmine HTML-kood faili index.html
ja paiguta see kausta templates/
.
<!DOCTYPE html> <html> <head> <title>Juhusliku arvu generaator</title> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="/static/style.css"> </head> <body> <h1>Juhusliku arvu generaator</h1> <h2>Sinu juhuslik arv on: 3</h2> <form action="."> <label for="miinimum">Alampiir:</label> <input id="miinimum" name="miinimum" type="number" value="1"><br> <label for="maksimum">Ülempiir:</label> <input id="maksimum" name="maksimum" type="number" value="10"><br> <input type="submit" value="Genereeri"> </form> </body> </html>
Salvesta järgmine CSS-kood faili style.css
ja paiguta see kausta static/
.
body { font-family: sans-serif; text-align: center; } input { margin: 0.5rem 0; }
Nüüd on vaja Flaskiga HTML tagastada. Seda saab teha funktsiooniga render_template()
. Sulgudesse läheb HTML-faili nimi.
from flask import Flask, render_template app = Flask(__name__) @app.route("/") def index(): return render_template("index.html") if __name__ == "__main__": app.run(debug=True)
Kui HTML- ja CSS-fail on õigesti paigutatud, tuleb lehekülg selline:
Lehekülg näeb nüüd välja nagu lehekülg. Muidugi, genereeritav arv on alati 3 ja nupule vajutamine seda ei muuda. Järgmisena tuleb see tööle saada.
Kui vajutada nupu "Genereeri" peale, siis teeb veebilehitseja leheküljele värskenduse ja aadressi lõppu lisatakse juba parameetrid sisendikastidelt:
127.0.0.1:5000/?miinimum=1&maksimum=10
Need tulevad tänu sellele, et <form>
elemendil on atribuudid action="."
ja method="GET"
. Need määravad ära, et nupu (<input type="submit">
) vajutamisel minnakse aadressile "."
(mis on praegune lehekülg) ja lisatakse parameetrid vastavalt sisendkastidele ja nende "name"
atribuutidele.
Aadressiparameetreid oskame juba lugeda. Nüüd on jäänud ainult küsimus: kuidas me sellisel leheküljel andmeid kuvame?
Leheküljel andmete kuvamine
Kui lehekülg oli ainult üks sõne, siis oli sinna andmeid lihtne sisestada:
return "Sinu juhuslik arv on {}.".format(juhuslik_arv)
Kui kasutame HTML-koodi ja render_template()
funktsiooni, siis on lahendus natuke keerulisem, aga siiski sarnane. HTML-koodis tuleb lisada muutujad kahe loogelise sulu vahele:
<h2>Sinu juhuslik arv on: {{arv}}</h2>
Need väärtused tuleb lisada render_template()
funktsiooni parameetritena:
juhuslik_arv = random.randint(1, 10) return render_template("index.html", arv=juhuslik_arv)
See töötab, sest Flask kasutab Jinja malle. Jinjaga saab teha palju enamat, kui ainult andmeid kuvada. Näiteks saab selle abil lisada HTML-koodi if-lauseid (kas muutuja saadeti üldse kaasa? kas on vaja kuvada veateade? kas kasutaja on sisselogitud?) või for-tsükleid (kuva kõik järjendi elemendid). Siin me neid täpsemalt läbi ei katseta.
Proovi panna kokku kõik siiamaani tehtud kood ja saada juhusliku arvu generaator lõpuks valmis.
Rakenduse avalikustamine
Kui Flaski server jookseb, saab samas arvutis sellele ligi aadressil 127.0.0.1:5000, aga teised sellele ligi ei saa. Idee poolest saab ligi samas võrgus teisest arvutist või isegi teisest võrgust, kui ruuter seda lubab, aga eesmärk on saada lihtne ligipääs kõigile. Selle jaoks tuleb panna rakendus püsti kuskile serverisse. Seda lubavad lihtsasti teenused nagu PythonAnywhere ja Heroku. Saab ka kasutada virtuaalservereid nagu DigitalOcean (õpetus), mis on natuke keerulisem.
PythonAnywhere lubab Flaski veebirakendust tasuta üles laadida mõningate piirangutega. Veebirakendus ilmub aadressile https://kasutajanimi.eu.pythonanywhere.com/.
Juhised:
- Tee uus Beginner kasutaja ja logi sisse: https://www.pythonanywhere.com/registration/register/beginner/
- Kodulehel vali "Open web tab" ja "Add a new web app".
- Läbi protsess vajutades "Next" nupule. Valida "Flask" raamistik ja kõige uuem Pythoni versioon.
- Rakenduse faili asukoht võib jääda samaks.
- https://kasutajanimi.eu.pythonanywhere.com/ viib nüüd rakenduseni, mis ütleb "Hello from Flask!".
- Liigu kodulehele ja vajuta "Browse files" nupule. Vasakul liigu kausta
mysite
. Seal onflask_app.py
, mille sisu tuleb muuta. Kopeeri sinna selle peatüki raames tehtud kood. - Loo uued kaustad
templates
jastatic
ja laadi üles failid nendesse kaustadesse. - Rakenduse leheküljel vajuta "Reload" nupule.
- Rakendus jookseb nüüd aadressil https://kasutajanimi.eu.pythonanywhere.com/.
Kui rakendus avalikuks teha, peaks koodist ära võtma debug=True
.
Kokkuvõte
Ehitasime väga lihtsa veebirakenduse ja panime selle Internetti üles. Veebirakendused võivad muidugi olla palju keerulisemad ja järgmise YouTube'i, Instagrami või ÕIS-i versiooni loomiseks on veel palju õppida. Developer Roadmap teeb hea ülevaate sellest, mida kõike peab korralik veebiarendaja teadma.
Flaskiga jätkamiseks tasub järgida selle dokumentatsiooni õpetust, kus kirjutatakse võimas blogirakendus. Kui Flask ei meeldi, siis Django raamistik teeb rohkem asju sinu eest ära, aga sellega alustamine võib olla keerulisem.
Informaatika õppekavas minnakse süvitsi veebirakendustesse aines "Veebirakenduste loomine" (LTAT.05.004).
Enesekontrolliküsimused
Ülesanded
1. Juhusliku arvu generaatoril lähevad alam- ja ülempiirid nupu vajutamisel tagasi vaikeväärtustele. Täienda rakendust nii, et need säilitavad oma väärtused vastavalt parameetritele.
2. Täienda juhusliku arvu generaatorit nii, et genereerida saab mitu juhuslikku arvu. Selleks tuleb lisada uus aadressiparameeter, mis määrab ära, mitu arvu peab genereerima. Tuleb ka lisada HTML-koodi sisendikast, mis lubab seda parameetrit muuta. Näide:
<label for="kogus">Kogus:</label> <input id="kogus" name="kogus" type="number" value="1"><br>
3. Kirjuta uus kasulik veebirakendus, mis kasutab kasutajasisendit. Mõned ideed:
- Kuupäeva kalkulaator (peatükk "Standardteek ja moodulid")
- Mingist rakendusliidesest saadud andmete kuvamine
- Pokémoni andmed ja pilt - https://pokeapi.co/
- Koerte pildid - https://dog.ceo/dog-api/documentation/
- Kasside faktid - https://cat-fact.herokuapp.com/facts/random
- Vikipeedia artiklist viidete eemaldaja (peatükk "Regulaaravaldised")
- Siin tuleks kasutada <textarea> HTML elementi
- Anonüümne foorum
- Sisestatud tekst lisatakse tekstifaili ja faili sisu kuvatakse veebilehel
- Veel parem, kui kasutada andmebaasi
- Siin tuleks kasutada POST-päringut
Soovitatav on taaskasutada juhusliku arvu generaatori HTML- ja CSS-koodi.