CGI-ohjelmointi
Näissä tehtävissä tutustutaan Pythonin ja CGI-ohjelmoinnin perusteisiin.
users.jyu.fi-palvelimessa on käytössä Python 2.7.5.
Tämän tehtävän tekemisen apuna kannattaa käyttää seuraavia dokumentteja:
Malliratkaisu:
Autolaskuri
Toteutetaan autolaskuri CGI-ohjelmana
- Luo uusi cgi-ohjelma (ohjaus2.cgi, joka tekee seuraavat:
#ensin content-type print """Content-type: text/html; charset=UTF-8 """ laskuri = 0 # toisena varsinainen sisältö. Paras pitää erillään toisistaan print (u""" <!DOCTYPE html> <html> <head> <link rel="StyleSheet" href="tyyli.css" type="text/css" /> <title>Autolaskuri</title> </head> <body> <h1>Autolaskuri</h1> <div>%s</div> <form action="ohjaus2.cgi" method="get"> <p><input type="submit" value="Lisää auto" /></p> </form> </body> </html> """ % laskuri).encode("UTF-8")
- 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:
<p><input type="submit" name="painike" value="Lisää auto" /></p>
Lataa sivu uudelleen ja kokeile painiketta. Mikä muuttuu? - Lomake käyttää oletuksena samaa merkistöä kuin mikä sivulla on käytössä. Merkistön voi kuitenkin varmistaa määrittelemällä accept-charset-ominaisuuden. Käytä aina UTF-8-merkistöä
- Lomakkeelle määritelty method on GET, joka tarkoittaa, että kaikki lomakkeen tiedot
koodataan sivun osoitteeseen. Sivun osoite siis muuttuu. Lisääppä lomakkeeseen vielä yksi kenttä:
<p>Lisättävä määrä <input type="text" value="1" name="lkm" /></p>
Kokeile nyt painiketta. Osoite muuttuu taas. Lomakkeen muuttujat lisätään osoitteen perään ?-merkin jälkeen ja ne erotellaan &-merkillä. - 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.
- Lomakkeelta tuleviin tietoihin pääsee käsiksi
Pythonin cgi-kirjaston avulla. Lisää ohjelmasi alkuun rivi
import cgi
Muista myös jo aiemmin käytetty cgitb-kirjasto.form = cgi.FieldStorage() # alustaa lomake-olion #pyytää ensimmäisen url-nimisen lomakekentän arvon. Jos sitä ei ole annettu käyttää #oletusmerkkijonoa "1". Dekoodaus on ehdottomasti muistettava! #Tiedot ovat aina merkkijonoja paitsi oletusarvoksi voi antaa myös muita tyyppejä lkm = form.getfirst(u"lkm", u"1").decode("UTF-8")
-
Yritä lisätä laskurimuuttujaan lkm-muuttujan arvo. Muista muuttaa lkm ensin kokonaisluvuksi. Älä luota siihen, että lomakkeelle syötetyt tiedot olisivat järkevä
try: lkm = int(lkm) except: lkm = 0
- Kokeile nyt selaimella lomaketta. Sivulla näkyvän luvun pitäisi muuttua. Luku ei kuitenkaan kasva,
koska ohjelma ei muista laskurin arvoa. Aina kun sivu latautuu uudelleen palataan samaan tilanteeseen. Vain lomakkeelta lähetetyt tiedot voivat vaikuttaa 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. Tiedostoja ei voi käyttää. Miksei? Mitä tapahtuisi
jos sovellusta käyttäisi useampi henkilö samaan aikaan...
Lisää lomakkeelle piilokenttä, johon tallennetaan laskurin arvo:
<input type="hidden" value="%d" name="laskuri" />
Korjaa myös print-lause oikeanlaiseksi eli laskurin arvo halutaan nyt merkkijonoon kahteen eri paikkaan joten se pitää myös luetella kahteen kertaan...""" % (laskuri, laskuri)).encode("UTF-8")
- 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. 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.
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.
- Lisää ohjelmaasi käyttöön seuraavat kirjastot:
import os from jinja2 import Template, Environment, FileSystemLoader
- Lisää ohjelmasi alkuun content-type-tulostuksen jälkeen seuraavat Jinjan käyttöä alustavat rivit:
try: # antaa polun alikansiossa olevaan jinja.html-tiedostoon: # ei tarvitse huolehtia siitä onko polku riippuvainen palvelimenasetuksista # os.environ['SCRIPT_FILENAME'] palauttaa polun suoritettavaan ohjelmaan (jinja.cgi) # on syytä huomata, että tämä polku ei ole sama kuin tiedostopolku halava/jalava-palvelimissa # os.path.dirname tipauttaa polusta muut kuin kansiot pois eli poistaa cgi-ohjelman lopusta # os.path.join liittää os.path.dirnamen palauttaman polun ja 'templates' yhdeksi toimivaksi poluksi # jos tätä haluaa kokeilla komentoriviltä niin tuloksena on keyerror. SCRIPT_FILENAME-ympäristömuuttuja löytyy # vain www-palvelimen CGI-ympäristöstä eikä normaalista shellistä tmpl_path = os.path.join(os.path.dirname(os.environ['SCRIPT_FILENAME']), 'templates') except: # jos tänne päädytään www-palvelimessa niin koko sovellus kaatuu... tmpl_path = "templates" # alustetaan Jinja sopivilla asetuksilla env = Environment(autoescape=True, loader=FileSystemLoader(tmpl_path), extensions=['jinja2.ext.autoescape']) # ladataan oma template template = env.get_template('jinja.html')
- Luo cgi-bin-kansioosi alihakemisto templates. Luo sinne uusi tiedosto jinja.html ja aseta tiedoston sisällöksi python-ohjelmasi tuottama html-koodi.
- Poista python-ohjelmastasi kaikki html-koodia tulostavat rivit. Jätä vain ensimmäinen HTTP-protokollaan liittyvä content-type-tulostus
- Korvaa jinja.html-tiedostossa laskuri-muuttujaan viittaavat %d-merkinnät merkinnällä{{ laskuri }}. Tee samaan tapaan lkm-muuttujan suhteen.
- kokeile selaimella toimiiko ohjelmasi, kun lisäät sen loppuun rivin:
# Renderoidaan Jinjan template jossa tyyli-muuttujan tilalle sijoitetaan css-tiedoston osoite # samassa yhteydessä voidaan määritellä useampiakin muuttujia jinjalle vietäväksi print template.render(laskuri=laskuri, lkm=lkm).encode("UTF-8")
- Tutustu Jinjan dokumentaatioon ja luentosivun jinja-esimerkkeihin
- Lisätään laskuriin mukaan automerkin valitseminen. Luo ensin python-tiedostossa dict automerkeistä:
automerkit = {"1": "Tesla", "2": "Lada", "3":"Mini"}
Vie myös automerkit-muuttuja parametrina Jinjalle. - Lisää jinja.html-tiedostoon seuraava koodi:
<select name="automerkki"> {% for u in automerkit %} <option value="{{u}}">{{automerkit[u]}}</option> {% endfor %} </select>
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. Lomaketietoja käsiteltäessä tee muunnos toiseen suuntaan eli muunnat
lomakkeelta tulevan merkkijonon, joka on JSON-dataa, takaisin automerkit-muuttujan arvoksi.
JSONin käyttäminen on käsitelty ensimmäisessä pääteohjauksessa. Voit katsoa myös ostoskori.cgi-esimerkin (lähdekoodi, template)
Muista varautua tilanteeseen jossa lomakkeelta ei tulekkaan järkevää JSON-dataa.
- Tietorakenteen sisällön muuttamisen jälkeen muunna tietorakenne merkkijonoksi (json.dumps
- Vie tietorakennetta kuvaava merkkijono jinja-templatelle ja tallenna lomakkeelle
- Seuraavalla suorituskerralla lue merkkijono lomakkeelta samaan tapaan kuin kaikki muutkin parametrit ja muunna takaisin python-tietorakenteeksi (json.loads)
-
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 %}
Linkki vs lomake
- Muuta lomakkeesi käyttämään GET-metodia
- 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.urlencode-funktiota.
>>> import urllib >>> mydict = {"lkm": 1, "automerkki": "1"} >>> urllib.urlencode(mydict) 'lkm=1&automerkki=1'
Jos vielä jäi jotakin epäselväksi niin voit lisäharjoitella.
Käyttäjien kommentit