Python perusteet ja CGI-ohjelmointi
Näissä tehtävissä tutustutaan Pythonin ja CGI-ohjelmoinnin perusteisiin.
Halavassa, Jalavassa jausers.jyu.fi-palvelimessa on käytössä Python 2.7. Tämän tehtävän tekemisen apuna kannattaa käyttää seuraavia dokumentteja:
- Python
- Python ja CGI-ohjelmointi
- Python ohjelmointiopas
- Python 2.7 tutoriaali
- Python 2.7 dokumentaatio
Opettele käyttämään myös Pythonin komentotulkin help-komentoa.:
[tjlahton@halava ~]$ python
Python 2.4.3 (#1, Jun 11 2009, 14:09:37)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> help
Type help() for interactive help, or help(object) for help about object.
>>> import cgi
>>> help(cgi)
Help on module cgi:
NAME
cgi - Support module for CGI (Common Gateway Interface) scripts.
...
...
...
Hello World
Käy ensimmäisenä läpi Python 2.7 tutoriaali.
- Luo haluamallasi editorilla hello.cgi-tiedosto ja tallenna se W-asemasi juureen cgi-bin/-kansioon.
Huomaa, että käyttämäsi editorin täytyy osata tallentaa tekemäsi tiedosto käyttäen unix-muotoisia rivinvaihtoja eikä windows-muotoisia. Notepad++ näyttää ikkunan alalaidassa rivinvaihtojen tyypin ja sen voi vaihtaa valinnalla Edit|EOL Conversion|Unix/OSX Format.
- Notepad++:ssa kannattaa myös muistaa valita käytetty kieli (Python) Language-valikosta, koska Notepad++ ei suoraan .cgi-tiedostopäätteestä pysty sitä päättelemään.
- Luo kansio tarvittaessa. HUOM! Kansion täytyy ehdottomasti olla w:\cgi-bin eikä mikään muu. kts. CGI/SSI-tekniikat users.jyu.fi-palvelimella
- Kopioi ohjelmasi pohjaksi seuraava koodi:
#!/usr/bin/python # -*- coding: utf-8 -*- print u"Hello world"
Huom! Merkitse aina kaikki merkkijonot unicode-merkkijonoiksi u-etuliitteellä
- Kokeile ohjelman toimintaa komentorivillä jalava/halava-koneessa.
Putty-pääteohjelmassa voit kopioida tekstiä leikepöydälle maalaamalla tekstin hiiren vasemmalla painikkeella. Liittäminen tapahtuu hiiren oikealla painikkeella tai SHIFT-INSERT-näppäinyhdistelmällä.
Toimiakseen ohjelma vaatii suoritusoikeuden (execute) kaikille käyttäjille. Lisäksi ohjelman ryhmän (group) täytyy olla users
[tjlahton@halava cgi-bin]$ chmod a+x hello.cgi [tjlahton@halava cgi-bin]$ chgrp users hello.cgi [tjlahton@halava cgi-bin]$ ls -al hello.cgi -rwxr-xr-x. 1 tjlahton users 3777 Feb 5 09:59 hello.cgi
Cgi-ohjelmaan sekä samaan kansioon EI SAA olla kirjoitusoikeutta muilla kuin käyttäjällä itsellään.
Jos edellämainitut asiat ovat kunnossa voit kokeilla suorittaa ohjelmasi:
[tjlahton@jalava cgi-bin]$ ./hello.cgi
Seuraava virhe tarkoittaa, että rivinvaihdot ovat DOS-tyyppisiä vaikka pitäisi olla Unix-tyyppisiä:
usr/bin/python^M: bad interpreter: No such file or directory
Varmista, että editorisi käyttää unix-tyylisiä rivinvaihtoja. Visual Studio Codessa rivinvaihto näkyy alalaidan statusbarissa joko muodossa CRLF tai LF. Oikea muoto on LF.
Varmista myös, että tiedostosi alussa ei ole UTF-8:aan liittyvää BOM-merkintää. Seuraava virhe yleensä vihjaa, että tiedoston alussa on BOM:
Exec format error. Binary file not executable.
less-komennolla on helppo tarkistaa onko tiedoston alussa BOM:

Hello world -selaimessa
Edellä toteutettu hello world -ohjelma toimii komentoriviltä mutta sitä ei voi vielä käyttää selaimella. Toimiakseen oikeana CGI-ohjelmana täytyy ohjelman tuottaa HTTP-protokollan edellyttämät tulosteet.
Muuta ohjelma seuraavanlaiseksi:
Ensimmäisenä rivinä on unix-ympäristön edellyttämä shebang-rivi, joka kertoo mistä käytettävä tulkki löytyy. Toinen rivi on vasta varsinaisesti python-kieltä ja kertoo millä merkistöllä ohjelmakoodi on kirjoitettu. Käytä aina UTF-8-merkistöä.
#!/usr/bin/python # -*- coding: utf-8 -*- # import-komennolla voidaan ladata käyttöön kirjastoja import cgitb # seuraava rivi aktivoi cgitb-kirjaston, joka osaa heittää virheilmoitukset www-sivulle # näkyville. Ilman tätä virheet menevät www-palvelimen lokiin jonne ei tavallisella # käyttäjällä ole pääsyä. Tämä ei kuitenkaan auta jos content-type-otsake on väärin... # Jos testaa ohjelmaa komentoriviltä niin seuraava rivi kannattaa kommentoida pois käytöstä cgitb.enable(format="text") # Jos sovelluksesi tuottaa html-koodia eikä plaintextiä niin edellinen rivi on hyvä olla muodossa # cgitb.enable() # http-protokolla edellyttää vähintään content-type-otsaketta, jossa kerrotaan # mikä on kyseisen sivun/dokumentin/tiedoston MIME-tyyppi ja tiettyjen tyyppien # yhteydessä mikä on käytetty merkistö # HTTP-otsakkeiden jälkeen tulee tyhjä rivi, jonka jälkeen alkaa varsinainen # dokumentti. Tässä tapauksessa tuotetaan pelkkää tekstiä # Pythonissa voi tulostaa monen rivin merkkijonoja """ """ -merkintöjen välissä print u"""Content-type: text/plain; charset=UTF-8 Hello world! """
Jos ohjelmasi toimii moitteetta komentoriviltä voit kokeilla sitä nyt selaimella osoitteesta:
http://users.jyu.fi/~omatunnus/cgi-bin/hello.cgi
Jos ohjelma ei toimi selaimella eli saat server errorin niin tarkista tietohallintokeskuksen ohje CGI/SSI-tekniikat. Yleensä vikana on, että ohjelman ja kansion ryhmät eivät ole samat. Yleensä ryhmänä pitää olla users.
Kokeile pelkän "hello world"-tekstin sijaan tulostaa html-dokumentti. Huomaa, että sinun pitää myös muuttaa content-type-otsakkeesta tyypiksi "text/html". Kokeile mitä eroa on eri content-typen arvoilla. Kokeile myös tyyppiä "application/octet-stream".
Python
Muuta ohjelmasi tulostamaan html-koodia niin saat mahdolliset cgitb-kirjaston tulostamat virheilmoitukset nätisti näkyville selaimeen.
HUOM! Pythonissa lohkot merkitään sisentämällä! Sama sisennystaso tarkoittaa siis samaa lohkoa. Varmista, että editorisi korvaa tabulaattorimerkit neljällä välilyönnillä. Käytä sisennyksessä neljää välilyöntiä. Notepad++:ssa tämä asetus löytyy kohdasta Settings|Preferences|Language
Käytä vain unicode-merkkijonoja eli merkitse kaikki ohjelmakoodiin kirjoittamasi merkkijonot u-etuliitteellä esim. u'Tämä on unicode-merkkijono'.
Lisää ohjelmasi alkuun seuraava tietorakenne:
data = [
{ u'nimi': u'Kalle',
u'ammatti': u'Yliopistonopettaja',
u'syntymävuosi': 1980,
u'palkka': 2000
},
{ u'nimi': u'Ville',
u'ammatti': u'Opiskelija',
u'syntymävuosi': 1995,
u'palkka': None
},
{ u'nimi': u'Maija',
u'ammatti': u'Professori',
u'syntymävuosi': 1970,
u'palkka': 3000
}
]
Avaa Python tutorial ja toteuta sen avulla seuraavat asiat:
- Tulosta tietorakenteen sisältö seuraavaan tapaan:
nimi Kalle ammatti Yliopistonopettaja palkka 2000 syntymävuosi 1980 ...
Kaikissa tulostuksissa täytyy merkkijonot enkoodata UTF-8-muotoon tähän tapaan:print u"merkkijonoöä".encode("UTF-8") print merkkijonomuuttuja.encode("UTF-8") print (u"merkkijono" + muuttuja).encode("UTF-8")type 'exceptions.UnicodeEncodeError'>: 'ascii' codec can't encode characters
Jos saat UnicodeDecode-virheilmoituksia niin sinulta puuttuu edellämainittu enkoodaus.
Muista myös muuntaa kaikki vakiomerkkijonot unicode-merkkijonoiksi ( u"merkkijono"). Pythonissa on kaksi funktiota joita voit myös käyttää. str-funktio tuottaa tavallisia bytestring-tyyppisiä merkkijonoja. Älä käytä str-funktiota! Koska pyrimme aina käyttämään unicode-merkkijonoja niin käytä unicode-funktiota:
unicode(muuttuja)
Jos saat virheilmoituksen:
TypeError: cannot concatenate 'str' and 'int' objects
Niin muista muuntaa kokonaislukutyyppiset tiedot unicode-merkkijonoiksi unicode-funktiolla:
luku = 1 print "merkkijono" + unicode(luku)
Tee tulostaminen siten, että se toimii vaikka lisättäisiin uusia tietoja rakenteeseen. Muuta Villen kohdalta rakennetta seuraavanlaiseksi ja kokeile, että tulostus edelleen toimii:
{ u'nimi': u'Ville', u'ammatti': u'Opiskelija', u'syntymävuosi': 1995, u'kotipaikka': u'Jyväskylä', u'palkka': 1000 } - Kokeile tehdä tulostaminen kahdella eri tavalla. Käytä ensimmäisessä range()-funktiota ja tee toinen ilman range()-funktiota.
- Tulosta vain niiden henkilöiden tiedot, joiden palkka on suurempi kuin 2500
- Tulosta vain henkilöiden ammatit
- Tulosta henkilöiden tiedot siten, että tiedot tulevat avaimen mukaan aakkosjärjestyksessä. Dicteissä ei ole järjestystä joten normaalisti avaimet saadaan satunnaisessa järjestyksessä. Käytä apunasi joko listan sort()-metodia tai sorted-funktiota.
- Tulosta vain kotipaikat. Varmista, että tulostaminen toimii vaikka kaikille ei ole kotipaikkaa määritelty
Ratkaise ongelma try..except-rakenteella:
try: # tämä rivi kaatuu joskus, koska kotipaikkaa ei ole olemassa print u"tulostetaan jostakin rakenteesta kotipaikka" + foobar["kotipaikka"] except KeyError: # Tullaan tänne jos edellinen print kaatuu. pass ei tee mitään pass - Tulosta vain henkilöiden palkat. Jos palkkaa ei ole annettu niin tulosta tyhjä.
- Lisää ohjelmaasi koodirivi, joka lisää tietorakenteeseen omat tietosi. Kts. append(). Varmista, että edellä tehdyt tulosteet toimivat myös omien tietojesi lisäämisen jälkeen.
- Varmista, että ohjelmasi toimii sekä komentoriviltä suoritettuna, että CGI-ohjelmana selaimen avulla ladattuna.
- Laske kaikki palkat yhteen ja tulosta saatu summa. Laske myös mikä on keskipalkka ja tulosta keskipalkka. Sinun pitää
siis laskea myös montako palkkaa löytyy. Jos jollekin ei ole määritelty palkkaa niin käytä palkkana perustulokokeilun määrää eli 560 euroa. Tässä sinun pitää huomioida, että kaikille ei ole annettu
palkkaa. Käytä taas try..except-rakennetta.
Jos haluat ottaa kiinni minkä tahansa virheen niin se onnistuu seuraavalla rakenteella. Huomaa, että tämä voi piilottaa myös omia ohjelmointivirheitä!
try: virheellinen koodirivi except: #tänne tullaan jos tuli virhe
Jos haluat kuitenkin tietää tarkemmin mikä virhe oli kyseessä tai tulostaa saadun virheilmoituksen niin se onnistuu seuraavasti:
import sys try: ... virheellinen rivi except: for i in sys.exc_info(): print i # tai print sys.exc_info()[0] print sys.exc_info()[1] print sys.exc_info()[2] - Järjestä henkilöt nimen mukaan aakkosjärjestykseen ja tulosta ne sen jälkeen aakkosjärjestyksessä. Joudut kertomaan sort()/sorted()-funktiolle miten tietorakenteesi dictejä pitää vertailla. Kts. Key Functions.
#järjestää datan palkka-ominaisuuden mukaan: data.sort(key=lambda hlo: hlo["palkka"]) #sama kuin edellä mutta käytetty erillistä funktiota: def palkka(obj): return obj["palkka"] data.sort(key=palkka) # Sama mutta käänteisessä järjestyksessä data.sort(key=palkka, reverse=True)
Tiedostojen käsitteleminen ja JSON
- Kokeile muuttaa tietorakenne json-muotoon. Lataa käyttöösi simplejson-kirjasto:
import simplejson as json
Nyt voit dumpata tietorakenteen json-muotoon seuraavasti:print json.dumps(data)
Kopioi json-muotoinen tietorakenne omaan tekstitiedostoonsa ja tallenna se "tietorakenne.json"-nimellä.
Kokeile nyt poistaa ohjelmakoodista alkuperäinen tietorakenne ja lataakin se tiedostosta:
import io tiedosto = io.open("tietorakenne.json", encoding="UTF-8") data = json.load(tiedosto)Pidä huoli, että käytät koko ajan UTF-8-merkistöä
- Kokeile lisätä ohjelmakoodissa tietorakenteeseen muutama lisähenkilö. Kokeile saatko json.dump-komennolla tallennettua uuden tietorakenteen vanhan päälle. Katso muuttuiko tiedoston sisältö
- json.dump tarvitsee kaksi parametria: objektin, joka halutaan tallentaa json-muodossa ja kirjoittamista varten avatun tiedosto-objektin
- Kokeile toimiiko sovelluksesi selaimella. Kts. cgi-ssi-tekniikat
Varmista seuraavat asiat:
- Sijoita tietorakenne.json-tiedosto eri kansioon kuin cgi-ohjelmasi
- Anna tietorakenne.json-tiedostoon kirjoitusoikeus kaikille (chmod a+w tiedosto). Muista antaa samat oikeudet myös kansioon.
Polku omaan www-kansioonne on users.jyu.fi-palvelimella muotoa:
/home/tunnus/public_html/
Huomaa, että tämä ei ole sama polku kuin halava/jalava-koneissa
- Kokeile samaan tapaan ladata tietorakenne webbiosoitteesta kuten täältä:
http://appro.mit.jyu.fi/tiea2080/ohjaus/ohjaus1/malli.json
Käytä urllib2-kirjaston urlopen-metodia:
import urllib2 sivu = urllib2.urlopen("tähän sivun osoite") data = json.load(sivu)Mallina käytetty json-data on lainattu JSON Data Set Sample-sivulta. Tulosta tästä tietorakenteesta jokaisen donitsin nimi ja sen päällysteet (topping) ja sisältö (fillings).
Querystring
- Sivun osoitteeseen voidaan lisätä parametreja seuraavalla tavalla:
http://osoite.example?parametri=arvo¶metri2=arvo1¶metri2=arvo2 http://osoite.example?nimi=M%C3%A4lli%20Henkil%C3%B6&arvosana=5&arvosana=3&arvosana=2&kurssi=Helppo+kurssi&kurssi=Vaikea+kurssi&kurssi=%C3%84ll%C3%B6+kurssi
Osoitteen perään lisätään ?-merkki. ?-merkin jälkeen luetellaan avaimia ja arvoja &-merkillä eroteltuina
Osoitteessa esiintyvät erikoismerkit on koodattava Percent-encoding-tavalla. Pythonissa tämän voi tehdä urllib.quote_plus-funktiolla.
Jos query_stringin sisältävän osoitteen sijoittaa html-dokumenttiin on muistettava koodata myös &-merkit. cgi.escape-funktiolla.
Näitä koodauksia tarvitaan vasta tulevissa tehtävissä. Nyt riittää, että osataan lukea QueryStringissä annetut parametrit ohjelman käyttöön.
- Querystringissä annettuihin parametreihin pääsee käsiksi.
Pythonin cgi-kirjaston avulla. WWW-sivuilla
olevien lomakkeiden lähettämät tiedot käsitellään samalla tavalla. Tähän palataan seuraavien viikkojen tehtävissä.
Lisää ohjelmasi alkuun rivi
import cgi
fields = cgi.FieldStorage() # alustetaan FieldStorage-olio #pyytää ensimmäisen nimi-nimisen lomakekentän arvon. Jos sitä ei ole annettu käyttää #oletusmerkkijonoa "Tuntemätön". Dekoodaus on ehdottomasti muistettava! Merkistö on yleensä UTF-8, mutta ei aina #Tiedot ovat aina merkkijonoja paitsi oletusarvoksi voi antaa myös muita tyyppejä #Oletusarvona oleva merkkijono voi olla ilman u-etuliitettä, koska kyseinen merkkijono kuitenkin #dekoodataan. Jos laittaa u"Tuntematon" niin silloin syytä laittaa muodossa #u"Tuntematon".encode("UTF-8"). Kokeile oletusarvona merkkijonoa jossa on skandeja esim. Tuntemätön nimi = fields.getfirst(u"nimi", "Tuntemätön").decode("UTF-8") # tai #nimi = fields.getfirst(u"nimi", u"Tuntemätön".encode("UTF-8")).decode("UTF-8") # Jos samalla parametrilla on useampia arvoja niin ne voidaan pyytää listana # Lista on tyhjä, jos pyydettyä parametria ei löydy # Listan sisältö pitää erikseen muistaa dekoodata! arvosanat = fields.getlist(u"lkm") - Tee uusi cgi-ohjelma, joka osaa tulostaa seuraavan querystringin sisällön
http://users.jyu.fi/~omatunnus/cgi-bin/tiea2080/ohjaus1/query.cgi?nimi=M%C3%A4lli%20Henkil%C3%B6&arvosana=5&arvosana=3&arvosana=2&kurssi=Helppo+kurssi&kurssi=Vaikea+kurssi&kurssi=%C3%84ll%C3%B6+kurssi
Tulosteen pitää näyttää seuraavalta:
Henkilön nimi : Mälli Henkilö Helppo kurssi 5 Vaikea kurssi 3 Ällö kurssi 2 Keskiarvo : 3.33333333333
Kts. Malliohjelma. Kokeile muuttaa malliohjelman parametreja. Jos saat malliohjelman kaatumaan niin ilmoita opettajalle millä tavalla onnistuit. - Muuttele omalle ohjelmallesi annettujen parametrien sisältöä. Varmista, että ohjelmasi toimii vaikka parametreja ei annettaisi tai kursseja ja arvosanoja olisi eri määrä.
- Lue nimi getfirst-funktiolla
- Lue kurssit ja arvosanat getlist-funktiolla
- Käy silmukassa läpi kaikki kurssit ja dekoodaa ne UTF-8-merkistöstä
- Käy silmukassa läpi kaikki arvosanat ja muunna ne kokonaisluvuiksi. Jos muuntaminen kokonaisluvuksi epäonnistuu niin käytä kyseisenä arvosanana lukua nolla (0)
- Jos arvosanoja on annettu vähemmän kuin kursseja niin käytä tulosteessa puuttuvana arvosanana "-"-merkkiä. Laskea arvosanojen keskiarvo oikeiden annettujen arvosanojen perusteella
- Myös decode-funktio voi kaatua. Käsittele nämä tilanteet try...except-rakenteella.
- Malliratkaisun lähdekoodi

Käyttäjien kommentit