!!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 [[https://wiki.python.org/moin/WebFrameworks | raamistikke]]: Django, TurboGears, Pyramid, Flask ja väga palju muid. Selles peatükis keskendume [[https://flask.palletsprojects.com/en/1.1.x/ | Flaskile]], mis lubab veebirakenduse püsti saada vaid mõne reaga. 

!!!Ettevalmistus
Enne jätkamist tuleb paigaldada moodul [[https://flask.palletsprojects.com/en/1.1.x/installation/ | 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 [[https://progeopik.cs.ut.ee/osa1.html | moodulite paigaldamise juhised]]. 

Et peatükist aru saada, peab olema läbitud õpiku [[https://progeopik.cs.ut.ee/osa1.html | esimene osa]]. Kasuks tuleb HTML-i tundmine, aga selle peatüki läbimiseks ei pea seda oskama. HTML-i õppematerjalid: [[https://courses.cs.ut.ee/t/nooredkoodi/Veebilehed/Veebilehed | eesti keeles]],  [[https://developer.mozilla.org/en-US/docs/Learn/HTML | 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 [[https://github.com/rochacbruno/flask-powered | 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@@. 

(:codestart python gutter='false':)
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "Tere, maailm!"

if __name__ == "__main__":
    app.run(debug=True)
(:codeend:)

Proovi seda programmi jooksutada. Peaks ilmuma selline väljund:

(:codestart gutter='false':)
* 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
(:codeend:)

Koodi jooksutamine käivitas arvutis veebiserveri aadressil [[http://127.0.0.1:5000/ | 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!":

Attach:silmaring5_1.png


Pärast külastamist ilmub konsooli ka selline väljund:
(:codestart gutter='false':)
127.0.0.1 - - [06/Apr/2020 14:24:47] "GET / HTTP/1.1" 200 -
(:codeend:)

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 | 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.

(:codestart python gutter='false':)
from flask import Flask
app = Flask(__name__)
(:codeend:)

Impordime Flaski rakenduse klassi ja salvestame sellest isendi muutujasse @@app@@. Parameeter @@__name__@@ annab Flaskile teada, kuidas programmi käivitati.

(:codestart python gutter='false':)
@app.route("/")
(:codeend:)

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/ | http://127.0.0.1:5000/]]. "@@/tere@@" vastaks aadressile [[http://127.0.0.1:5000/tere | http://127.0.0.1:5000/tere]]. [[https://www.python.org/dev/peps/pep-0318/ | Dekoraator]] teeb põhimõtteliselt seda, et sellele järgnevat funktsiooni kasutatakse ühes teises funktsioonis, aga sellest ei ole Flaski kasutamisel vaja aru saada.

(:codestart python gutter='false':)
def index():
    return "Tere, maailm!"
(:codeend:)

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.

(:codestart python gutter='false':)
if __name__ == "__main__":
        app.run(debug=True)
(:codeend:)

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:

(:codestart python gutter='false':)
@app.route("/teine")
def teine():
    return "Oled teisel leheküljel!"
(:codeend:)

Attach:silmaring5_2.png


Ühel leheküljel võib olla mitu aadressi:

(:codestart python gutter='false':)
@app.route("/teine")
@app.route("/2")
def teine():
    return "Oled teisel leheküljel!"
(:codeend:)

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.

(:codestart python gutter='false':)
@app.route("/leht/<arv>")
def leht(arv):
    if arv == "saladus":
        return "Leidsid üles salalehekülje!"
    return "Oled praegu leheküljel: " + arv
(:codeend:)

Attach:silmaring5_3.png

Attach:silmaring5_4.png

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 [[https://random.org | random.org]] on maas ja kirjutame sellest enda versiooni. Algul tagastame lihtsalt juhusliku arvu 1-st 10-ni:

(:codestart python gutter='false':)
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)
(:codeend:)

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:

(:codestart python gutter='false':)
@app.route("/")
@app.route("/<maksimum>")
@app.route("/<miinimum>/<maksimum>")
(:codeend:)

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 | https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=43]] 

Selle aadress on tegelikult lihtsalt [[https://www.youtube.com/watch | https://www.youtube.com/watch]], aga sellel on kaasas kaks parameetrit:

(:codestart python gutter='false':)
{
    "v": "dQw4w9WgXcQ",  # mis videot näidatakse
    "t": 43  # mitmendalt sekundilt videot alustada
}
(:codeend:)

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:

(:codestart python gutter='false':)
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("/")
    ...
(:codeend:)

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:

(:codestart gutter='false':)
templates/
├─index.html
└─about.html
static/
├─style.css
├─pilt.jpg
└─script.js
app.py
(:codeend:)

Salvesta järgmine HTML-kood faili @@index.html@@ ja paiguta see kausta @@templates/@@.

(:codestart html gutter='false':)
<!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>
(:codeend:)

Salvesta järgmine CSS-kood faili @@style.css@@ ja paiguta see kausta @@static/@@.

(:codestart python gutter='false':)
body {
    font-family: sans-serif;
    text-align: center;
}
input {
    margin: 0.5rem 0;    
}
(:codeend:)

Nüüd on vaja Flaskiga HTML tagastada. Seda saab teha funktsiooniga @@render_template()@@. Sulgudesse läheb HTML-faili nimi.

(:codestart python gutter='false':)
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)
(:codeend:)

Kui HTML- ja CSS-fail on õigesti paigutatud, tuleb lehekülg selline:

Attach:silmaring5_5.png

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:

(:codestart gutter='false':)
127.0.0.1:5000/?miinimum=1&maksimum=10
(:codeend:)

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:

(:codestart python gutter='false':)
return "Sinu juhuslik arv on {}.".format(juhuslik_arv)
(:codeend:)

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:

(:codestart python gutter='false':)
<h2>Sinu juhuslik arv on: {{arv}}</h2>
(:codeend:)

Need väärtused tuleb lisada @@render_template()@@ funktsiooni parameetritena:

(:codestart python gutter='false':)
juhuslik_arv = random.randint(1, 10)
return render_template("index.html", arv=juhuslik_arv)
(:codeend:)

See töötab, sest Flask kasutab [[https://jinja.palletsprojects.com/en/2.11.x/templates/ | Jinja]] malle. Jinjaga saab teha palju enamat, kui ainult andmeid kuvada. Näiteks saab selle abil lisada HTML-koodi [[https://jinja.palletsprojects.com/en/2.11.x/templates/#if | if-lauseid]] (kas muutuja saadeti üldse kaasa? kas on vaja kuvada veateade? kas kasutaja on sisselogitud?) või [[https://jinja.palletsprojects.com/en/2.11.x/templates/#for | 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.

Attach:silmaring5_6.gif

!!!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 [[https://www.eu.pythonanywhere.com/ | PythonAnywhere]] ja [[https://devcenter.heroku.com/articles/python-gunicorn | Heroku]]. Saab ka kasutada virtuaalservereid nagu [[https://m.do.co/c/d2af08f8a509 | DigitalOcean]] ([[https://www.digitalocean.com/community/tutorials/how-to-deploy-a-flask-application-on-an-ubuntu-vps | õpetus]]), mis on natuke keerulisem. 

PythonAnywhere lubab Flaski veebirakendust tasuta üles laadida mõningate piirangutega. Veebirakendus ilmub aadressile [[https://kasutajanimi.eu.pythonanywhere.com/ | https://kasutajanimi.eu.pythonanywhere.com/]]. 

Juhised:

# Tee uus ''Beginner'' kasutaja ja logi sisse: [[https://www.pythonanywhere.com/registration/register/beginner/ | 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/ | 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 on @@flask_app.py@@, mille sisu tuleb muuta. Kopeeri sinna selle peatüki raames tehtud kood.
# Loo uued kaustad @@templates@@ ja @@static@@ ja laadi üles failid nendesse kaustadesse.
# Rakenduse leheküljel vajuta "''Reload''" nupule.
# Rakendus jookseb nüüd aadressil [[https://kasutajanimi.eu.pythonanywhere.com/ | 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. [[https://github.com/kamranahmedse/developer-roadmap | Developer Roadmap]] teeb hea ülevaate sellest, mida kõike peab korralik veebiarendaja teadma. 

Flaskiga jätkamiseks tasub järgida selle [[https://flask.palletsprojects.com/en/1.1.x/tutorial/ | dokumentatsiooni õpetust]], kus kirjutatakse võimas blogirakendus. Kui Flask ei meeldi, siis [[https://www.djangoproject.com/ | Django]] raamistik teeb rohkem asju sinu eest ära, aga sellega alustamine võib olla keerulisem.

Informaatika õppekavas minnakse süvitsi veebirakendustesse aines "Veebirakenduste loomine" ([[https://ois2.ut.ee/#/courses/LTAT.05.004/details | LTAT.05.004]]). 

!!!Enesekontrolliküsimused
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring5_1.html width=100% height="260px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring5_2.html width=100% height="260px" border="0" :)
(:includeurl https://courses.cs.ut.ee/LTAT.03.001/2020_fall/uploads/Main/silmaring5_3.html width=100% height="260px" border="0" :)

!!!Ü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:

(:codestart html gutter='false':)
<label for="kogus">Kogus:</label>
<input id="kogus" name="kogus" type="number" value="1"><br>
(:codeend:)

Attach:silmaring5_7.gif

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/ | https://pokeapi.co/]]
** Koerte pildid - [[https://dog.ceo/dog-api/documentation/ | https://dog.ceo/dog-api/documentation/]] 
** Kasside faktid - [[https://cat-fact.herokuapp.com/facts/random | https://cat-fact.herokuapp.com/facts/random]] 
* Vikipeedia artiklist viidete eemaldaja (peatükk "Regulaaravaldised")
** Siin tuleks kasutada [[https://developer.mozilla.org/fa/docs/Web/HTML/Element/textarea | <textarea>]] HTML elementi
* Anonüümne foorum
** Sisestatud tekst lisatakse tekstifaili ja faili sisu kuvatakse veebilehel
** Veel parem, kui kasutada andmebaasi
** Siin tuleks kasutada [[https://stackoverflow.com/a/22949613/12123296 | POST-päringut]] 

Soovitatav on taaskasutada juhusliku arvu generaatori HTML- ja CSS-koodi.
