Python perusteet ja CGI-ohjelmointi
Näissä tehtävissä tutustutaan Pythonin ja CGI-ohjelmoinnin perusteisiin.
users.jyu.fi-palvelimessa on käytössä Python 2.7.7. Jalavassa ja Halavassa on vanhempi Python 2.6.6. Näissä ei ole suurtakaan eroa. 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. ... ... ...
CSS:n vaihtaminen vuodenajan mukaan
Tehtävässä vaihdetaan XHTML-sivun tyylitiedostoa vuodenajasta riippuen. Käytettävät tyylit ovat seuraavat:
- Talvi (joulukuu-helmikuu): http://www.w3.org/StyleSheets/Core/Midnight
- Kevät (maaliskuu-toukokuu): http://www.w3.org/StyleSheets/Core/Modernist
- Kesä (kesäkuu-elokuu): http://www.w3.org/StyleSheets/Core/Oldstyle.css
- Syksy (syyskuu-marraskuu): http://www.w3.org/StyleSheets/Core/Steely
- Luo haluamallasi editorilla vuodenaika.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.
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 -*- 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ä # Jos testaa ohjelmaa komentoriviltä niin seuraava rivi kannattaa kommentoida pois käytöstä cgitb.enable() import cgi import time import datetime print """Content-type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <head> <link rel="StyleSheet" href="tyyli.css" type="text/css" /> <title></title> </head> <body> <h1>Kokeillaan CGI-ohjelmointia</h1> <p>Testi</p> </body> </html> """
- Kokeile ohjelman toimintaa komentorivillä jalava/halava-koneessa.
Putty-pääteohjelmassa voit kopioida tekstiä leikepöydälle maalaamalla tekstin hiiren vasemmalla painikkeella. Liittäminen tapahtuu oikealla painikkeella tai SHIFT-INSERT-näppäinyhdistelmällä.
W:-asemallesi pääset seuraavalla komennolla:cd /wwwhome$HOME/html
Kannattaa tehdä symbolinen linkki osoittamaan suoraan W:-aseman tiedostoihin:ln -s /wwwhome$HOME/html ~/html
niin jatkossa pääsee suoraan komennolla:cd html
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 vuodenaika.cgi [tjlahton@halava cgi-bin]$ chgrp users vuodenaika.cgi [tjlahton@halava cgi-bin]$ ls -al vuodenaika.cgi -rwxr-xr-x. 1 tjlahton users 3777 Feb 5 09:59 vuodenaika.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]$ ./vuodenaika.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. Varmista myös, että tiedostosi alussa ei ole UTF-8:aan liittyvää BOM-merkintää.
Komentoriviltä ohjelmaa testattaessa kannattaa yleensä kommentoida pois rivi:
# cgitb.enable()
Ja palauttaa tämän taas kun testaa www-selaimella.
W-asemalla oleviin kansioihin pääset Jalavassa kotisivujen siirtäminen www-palvelimelle-ohjeen avulla.
Jos ohjelmasi toimii moitteetta komentoriviltä voit kokeilla sitä selaimella osoitteesta:
http://users.jyu.fi/~omatunnus/cgi-bin/vuodenaika.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.
- 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.
- Muuta valmista koodia sen verran, että jaat tulostukset useampaan eri osaan. Ensimmäinen osa tulostaa sivun mediatyypin (content-type) ja tyhjän rivin. Toisessa osassa voit tulostaa sivun alkuosan (head) body-elementtiin saakka ja kolmannessa osassa sivun varsinaisen sisällön eli body-osan. Varmista, että ohjelmasi toimii. Muista jatkossa varmistaa ohjelmasi toiminta tarpeeksi usein. Älä kirjoita liian pitkiä ohjelmakoodipätkiä testaamatta.
- Luo ennen head-osan tulostamista merkkijonomuuttuja
tyyli
ja alusta se tyhjäksi. - Luo lukumuuttuja
kuukausi
, johon laitat arvoksi meneillään olevan kuukauden numeron. Tämän saat selville seuraavalla tavalla datetime-kirjaston avulla:kuukausi = datetime.datetime.now().month
tai
tanaan = datetime.datetime.now() kuukausi = tanaan.month
- Tee seuraavaksi if...elif...else-kontrollirakenne, missä sijoitat halutun tyylitiedoston osoitteen
tyyli
-muuttujaan meneillään olevan vuodenajan perusteella.- Tarkista ensin
if
-lauseella, onko meneillään talvi.if
-lauseen ehdoksi täytyy siis laittaa se, että kuukausi on joko 12, 1 tai 2.if kuukausi==12 or kuukausi==1 or kuukausi==2: tyyli = "http://www.w3.org/StyleSheets/Core/Oldstyle.css"
HUOM! Pythonissa lohkot merkitään sisentämällä! Sama sisennystaso tarkoittaa siis samaa lohkoaVarmista, 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
- Tarkista
elif
-lauseilla, onko meneillään kevät tai kesä. Kevään kohdalla ehdoksi täytyy siis määritellä, että kuukausi on välillä 3-5 eli suurempi tai yhtäsuuri kuin 3 ja pienempi tai yhtäsuuri kuin 5. Kesän kohdalla vastaava ehto on suurempi tai yhtäsuuri kuin 6 ja pienempi tai yhtäsuuri kuin 8. Alla esimerkki keväänelseif
-rakenteesta.elif kuukausi >= 3 and kuukausi <= 5: tyyli = "http://www.w3.org/StyleSheets/Core/Modernist"
- Aseta lopuksi
else
-lauseessatyyli
-muuttujan arvoksi syksyn tyylitiedoston osoite.
- Tarkista ensin
- Nyt oikea tyylitiedoston osoite on
tyyli
-muuttujassa, eli enää tarvitsee määrittää sivu käyttämään tyylitiedostoa kyseisestä muuttujasta.- Tulosta tyylimääritys
print
-komennolla. Laita tyylin osoitteen paikalletyyli
-muuttujan arvo. Joudut ensin tutustumaan Pythonin merkkijonojen muotoiluun. Korvaa tyylitiedoston osoite tyyli.css merkinnällä %s<link rel="StyleSheet" href="%s" type="text/css" />
Seuraavaksi lisää tulostuksen lopettavan """-merkinnän perään % tyyli:""" % tyyli
Tämä merkintä tarkoittaa, että %s korvataan merkkijonolla, joka saadaan tyyli-muuttujasta. Merkintätapa pitäisi olla tuttu printf- ja sprintf-funktioista esim. C/C++-kielestä.
- Tulosta tyylimääritys
- Kokeile sivun toimintaa selaimella ja korjaa virheet tarvittaessa.
- Muuta ohjelmaasi siten, että css-tiedostoa ei valitakkaan kuukauden mukaan vaan se arvotaan satunnaisesti.
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 import- ja from-lauseiden jälkeen seuraavat Jinjan käyttöä alustavat rivit:
# 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 kansioit pois eli poistaa jinja.cgin # 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ä try: 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 jinjan kaipaama ympäristö ja asetetaan myös autoescape käyttöön eli jinja automaattisesti # korjaa erikoismerkit html:ään kelpaavaan muotoon try: env = Environment(autoescape=True, loader=FileSystemLoader(tmpl_path), extensions=['jinja2.ext.autoescape']) except: # jinja2.ext.autoescape ei toimi halavassa/jalavassa env = Environment(autoescape=True, loader=FileSystemLoader(tmpl_path)) # 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 sisältämä html-koodi.
- Korvaa aiemmin html-koodiin kirjoittamasi %s merkinnällä {{ tyyli }}
- Poista python-ohjelmastasi kaikki html-koodia tulostavat rivit. Jätä vain ensimmäinen HTTP-protokollaan liittyvä content-type-rivi ja yhden tyhjän rivin tulostus.
- 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(tyyli=tyyli).encode("UTF-8")
- Tutustu Jinjan dokumentaatioon ja luentoesimerkkiin
- Lisää sivulle lomake jolla käyttäjä voi itse antaa sivulla käytettävän css-tiedoston osoitteen
- Laita lomakkeeseen yksi tekstikenttä ja submit-painike. Aseta lomakkeen action-ominaisuuden osoitteeksi python-ohjelmasi osoite. Aseta tekstikentän name-attribuutin arvoksi "url".
- Käytä Pythonin cgi-kirjastoa ja ota sivulla käyttöön lomakkeelle syötetty css-tiedoston osoite.
form = cgi.FieldStorage() # alustaa lomake-olion #pyytää ensimmäisen url-nimisen lomakekentän arvon. Jos sitä ei ole annettu käyttää tyhjää merkkijonoa "" url = form.getfirst("url", "")
- Tulosta annetun css-tiedoston osoite sivulle näkyviin p-elementin sisällä. Vie tulostettava
tieto parametrina Jinjalle ja muuta template-tiedostoa sopivanlaiseksi.
print template.render(tyyli=tyyli, osoite=url).encode("UTF-8")
- Voit testata vaikka seuraavilla css-tiedostoilla:
- http://www.w3.org/StyleSheets/Core/Swiss
- http://www.w3.org/StyleSheets/Core/Ultramarine
- Mahdollista jo tiedossaolevien css-tiedostojen käyttäminen
- Parantele lomaketta ja lisää siihen alasvetovalikko (select-elementti) jossa on listattuna kaikki ohjelmassasi olevassa listassa luetellut css-tiedostot. Taas joudut tuomaan Jinjalle
parametrina tiedostolistauksen. Nyt joudut lisäämään template-tiedostoon Jinja2-syntaksin mukaisen silmukan, joka muodostaa tarvittavat option-elementit css-taulukon perusteella.
{% for u in jutut %} <p>{{ u }}</p> {% endfor %}
- Muuta ohjelman toimintaa sen verran, että jos käyttäjä ei syötä uutta osoitetta eli jättää kentän tyhjäksi niin silloin käytetään käyttäjän alasvetovalikosta valitsemaa css-tiedostoa
- Parantele lomaketta ja lisää siihen alasvetovalikko (select-elementti) jossa on listattuna kaikki ohjelmassasi olevassa listassa luetellut css-tiedostot. Taas joudut tuomaan Jinjalle
parametrina tiedostolistauksen. Nyt joudut lisäämään template-tiedostoon Jinja2-syntaksin mukaisen silmukan, joka muodostaa tarvittavat option-elementit css-taulukon perusteella.
- Edelleen parantele ohjelmaa siten, että käyttäjän itse lisäämät css-tiedostojen osoitteet tulevat mukaan alasvetovalikkoon.
- Älä yritä tallentaa käyttäjän lisäyksiä tiedostoon tms. HTTP-protokolla on tilaton. Et voi tietää
kuka milloinkin lataa ohjelmasi: sinä vai viereisellä koneella istuva kaverisi.
Tulosta kaikki tiedossa olevat css-tiedostojen osoitteet (valmiit ja käyttäjän
lisäämät) lomakkeelle piilokenttinä (hidden)
<input type="hidden" name="css" value="..." />
- Aina kun ohjelmasi saa lomakkeen tiedot käsiteltäväksi sen pitää tarkistaa hidden-kenttinä tuleva mahdollinen listaus css-tiedostoista. Jos sitä ei tule niin alasvetovalikossa pitää käyttää valmista listaa mutta muutoin lomakkeelta tulevia tietoja
- Kokeile toimiiko sovelluksesi jos yrität syöttää sille osoitteita joissa on skandinaavisia merkkejä.
Kokeile syöttää monta skandinaavisia merkkejä ja html:n erikoismerkkejä sisältävä osoitetta. Osoitteiden ei tarvitse olla oikeita
toimivia osoitteita. Mitä tapahtuu?
<td><strong> testi&testi ööää </strong></td>
- Ohjelmasi kaatuu merkistöihin liittyvään dekoodausongelmaan. Rivi, jolla virhe ilmoitetaan, EI OLE oikea paikka ongelman korjaamiseen. Ongelma johtuu Python 2.x:n tavasta käsitellä merkkijonoja ja niiden erilaisia merkistökoodauksia.
- Perusohje: dekoodaa kaikki merkkijonot, jotka saat jostain syötteinä tai ohjelmasi ulkopuolelta.
Enkoodaa kaikki merkkijonot siinä vaiheessa, kun tulostat niitä tai tallennat jonnekin. Muuta lomakekenttien arvojen lukeminen seuraavanlaiseksi:
url = form.getfirst("url", "").decode("UTF-8")
sama operaatio pitää tehdä myös hidden-kentistä luetuille urleille. Sinun pitää silmukassa käydä kaikki urlit läpi ja tehdä jokaiselle decode-operaatio.for i in range(0, len(nimet)): nimet[i] = nimet[i].decode("UTF-8")
vrt. templaten render-metodin yhteydessä käytettyyn encode-metodiin
- Lisää sivun loppuun linkkilista jossa listaat kaikki syötetyt ja valmiiksi annetut css-tiedostot.
Linkkiä klikattaessa otetaan käyttöön juuri kyseinen tyylitiedosto. Yksi linkki näyttäisi esim. seuraavalta:
<a href="v.cgi?url=http%3A//www.w3.org/StyleSheets/Core/Midnight">http://www.w3.org/StyleSheets/Core/Midnight </a>
- Parantele linkkilistaa siten, että linkin mukana kulkee tieto myös kaikista tyylitiedostoista. Ts. sinun
pitää linkissä luetella kaikki tiedostot. Huomaa, että kenttien niminä on käytettävä samoja kuin lomakkeessa.
Kokeile muuttaa lomakkeen metodiksi GET aiemmin käytetyn POST-metodin sijaan niin näet millaisia linkkejä
sinun pitää tuottaa.
<a href="v.cgi?url=http%3A//www.w3.org/StyleSheets/Core/Midnight&css_list=http%3A//www.w3.org/StyleSheets/Core/Midnight&css_list=http%3A//www.w3.org/StyleSheets/Core/Steely&css_list=foobar&css_list=http%3A//www.w3.org/StyleSheets/Core/Modernist&css_list=http%3A//www.w3.org/StyleSheets/Core/Oldstyle.css&css_list=http%3A//appro.mit.jyu.fi/appro2004.css">http://www.w3.org/StyleSheets/Core/Midnight </a>
- Muista varmistaa, että linkkiin ei tule epäkelpoja merkkejä. Käytä urllib-kirjaston quote-funktiota.
import urllib oikein_koodattu_str = urllib.quote( url )
Voit kokeilla mallia tai katsoa malliratkaisun lähdekoodia. Katso myös mallin template
Kokeile käyttää tilan tallentamisen apuna simplejson-kirjastoa. Katso esimerkki ja lähdekoodi. JSON-muodossa voit tallentaa yhteen piilotettuun elementtiin koko listan.
minidom
Myös DOM-rajapintaa voi käyttää apuna. Toteutus on samantapainen kuin Jinjalla mutta kaikki ohjelmakoodi on pidettävä Python-tiedostossa. Voit tutkia myös DOM-version toteutusta.
Python DOM ja Templatet-luento
Käyttäjien kommentit