A Python programozási nyelv

Adatbázisok

Ebben a fejezetben feltételezzük, hogy az olvasó rendelkezik az adatbázisokkal kapcsolatos alapismeretekkel. A Python Database API nevű programozói interfész teszi lehetővé, hogy a fejlesztők egységes, szabványosított keretek között írhassák meg adatbázis-kezelő moduljaikat. Az API fejlesztői környezet specifikációja a következő részekből áll:

Megjegyzés: ahhoz, hogy a programunk kapcsolódhasson egy adatbázishoz, telepíteni és konfigurálni kell a MySQLdb modult és a MySQL adatbázis-kezelő rendszert.

Miután importáltuk a MySQLdb modult, a modulon belül meg kell adnunk, melyik adatbázishoz akarunk kapcsolódni, majd létre kell hoznunk a kapcsolatot. Ehhez a MySQLdb modul connect() metódusának meghívásával létesítenünk kell egy kapcsolattartó objektumot. Ezután el kell készítenünk még egy úgynevezett kurzor objektumot is. Ehhez az előbb létrehozott kapcsolattartó objektumon keresztül meghívjuk a cursor() metódust. Ez a metódus egy új kurzor objektummal tér vissza, amelyet egyfajta mutatóként használhatunk adatbázisban való keresésénél.
 
Példa:

>>>kapcsolat=MySQLdb.connect(host="localhost", db="Adatbazis_neve", port="8000", user="zita", passwd="1234") >>>kurzor=kapcsolat.cursor()

A kapcsolati objektum metódusai

commit(): ez a metódus minden függőben lévő tranzakciót végrehajt egy adatbázisban. Kezdetben ki van kapcsolva, mert a MySQL automatikusan végrehajd minden frissítést, és az új állapotot azonnal lemezre írja. Ha azt szeretnénk, hogy egy utasítás  csak más utasításokkal együtt, egyetlen fázisban hajtódjon végre, akkor a következő paranccsal kikapcsolható az említett automatikus szolgáltatás:
mysql> set autocommit=0

rollback(): ezzel a metódussal visszaforgathatjuk a függőben lévő tranzakciót, és elölről kezdhetjük a végrehajtását.

close(): ez a metódus lezárja az adatbázissal fennálló kapcsolatot.

A kurzor objektum metódusai

A kurzor objektum metódusain és attribútumain keresztül küldhetünk egyszerű lekérdezéseket az adatbázisra. Az
executeXXX() metódusokkal hajtunk végre bármilyen MySQL  utasítást, és a fetchXXX() metódusokkal kaphatjuk meg tuple adatszerkezetként az előzőleg végrehajtott executeXXX() metódussal beolvasott eredményt.

execute(sql_parancs[paraméterek]): előkészít és végrehajt egy adatbázis-parancsot vagy egy lekérdezést.

fetchone(): a lekérdezés eredménykészletéből a soron következő sort adja vissza.

fetchall(): ezzel a metódussal egy eredménykészlet összes sora kivehető és megjeleníthető. Ha az eredménykészletből előzőleg már kivettünk néhány sort, akkor ez a metódus csak a megmaradt sorokat adja vissza.

rowcount: ez egy attribútum, csak olvasható, amely egy számmal tér vissza, amely szám mutatja, hogy hány sort érintett egy adott executeXXX() metódus.

Példa: egy tábla létrehozásara

import MySQLdb kapcsolat=MySQLdb.connect(host="localhost", db="Hallgato", user="zita") kurzor=kapcsolat.cursor() create_table='create table regadat (nev varchar(20), szul_dat varchar(10), cim varchar(50))' try: kurzor.execute(create_table) print 'A tabla letrehozva' kurzor.close() except: print 'A tabla nem jott letre'

Példa: a két modul együttes használatára. Egy űrlap kitöltésének eredményét eltároljuk egy adatbázis táblába. (A táblát már korábban létrehoztuk.)

import cgi import MySQLdb print "Content-Type: text/html \n" dynhtml=''' <html> <head> <title> Szemelyi adatok</title> </head> <body> <hr> <h2><center> %s szemelyi adatai </center> </h2> <p> %s </p> </body> </html>''' fs=cgi.FieldStorage() nev=fs['hallg_nev'].value szul_datum=fs['hallg_szul_datum'].value cim=fs['hallg_cim'].value orszag=fs['hallg_orszag'].value telefon=fs['hallg_telefon'].value email=fs['hallg_email'].value try: kapcsolat=MySQLdb.connect(host="localhost", db="Hallgato", user="zita") kurzor=kapcsolat.cursor() beszuras_tablaba='insert into regadatok values("%s", "%s", "%s", "%s", "%s", "%s")' % (nev, szul_datum, cim, orszag, telefon, email) kurzor.execute(beszuras_tablaba) uzenet='Sikeresen felvettuk az adatokat' except: uzenet='HIBA' print dynhtml % (nev, uzenet)

Egy konkrét alkalmazás

A feladatunk az, hogy generáljunk egy HTML oldalt, amely először egy nevet kér be, utána tetszőleges kérdésekre vár választ. Az megadott adatokat tároljuk le egy adatbázis táblában. Egy másik  HTML oldalon legyen lehetőség a válaszok megtekintésére, de csak azoknak, akiknek ehhez jogosultságuk van. Az eredmény egy táblázatban jelenítsük meg, amely táblázat első oszlopa legyen a felhasználó neve, a többi oszlopai pedig a kérdések legyenek, egy sorban pedig egy ember válaszai szerepeljenek.

Megoldáshoz először hozzunk létre egy adatbázist közvetlenül a MySQL adatbázis-kezelő rendszerben.

mysql> create database teszt; mysql> use teszt;

Szükségünk lesz egy adatbázis táblára, amelyben eltároljuk a válaszokat, valamint egy másikra, amelyben azoknak az azonosítóját és jelszavát tároljuk el, akik jogosultak lesznek a válaszok megtekintésére.

Megjegyzés: a feladat megoldása szempontjából kézenfekvő lenne, ha a válaszokat tartalmazó adatbázis táblánk sémája az alábbi lenne: név, első kérdésre adott válasz, második kérdésre adott válasz.... Most azért nem ezt a megoldást választottuk, mert a későbbiekben a kérdőív kérdéseinek összeállításához egy editort is írhatnánk, így viszont a kérdések száma nem adott, ezért a tábla sémáját sem tudjuk előre meghatározni. Tehát a válaszokat tároljuk egy, az alábbi szerkezetű adatbázistáblában:

mysql> create table valaszok(nev varchar(30), -> kerdes varchar(255), -> valasz varchar(255)); mysql> create table felhasznalok(azonosito varchar(30) primary key, -> jelszo varchar(10));

Az utóbbi táblába szúrjunk be néhány sort, hogy majd kipróbálhassuk a programunkat. Például:

mysql> insert into felhasznalok values('a', '1');

Megjegyzés: a fenti lépéseket nem írhatjuk bele a Python programunkba, hiszen a program annyiszor fog lefutni, ahányszor a tesztet valaki kitölti. Az viszont nem lenne szerencsés, ha mindannyiszor létrehoznánk egy adatbázist, és azon különböző táblákat, hisz akkor az eredményt elég nehéz lenne összeszedni.

Ezután írjuk meg a tesztet megjelenítő és a válaszokat eltároló programot (teszt.py). (Mivel a program használni fogja a CGI modult, ezért a cgi-bin könyvtárba mentsük a file-t.) Először is állítsuk össze a megfelelő HTML kódot, amely bekéri a nevet, és megjeleníti a kérdéseket. Hogy ne kelljen azonos HTML kódrészleteket sokszor beírnunk, ezért írjunk egy függvényt, amelynek paraméterként megadunk egy kérdést, megadhatunk lehetséges válaszokat -változó hosszúságú paraméter listánk lesz-, és a paramétereknek megfelelően visszaadja egy sztringben a HTML kódot. Ha csak kérdést adtunk meg paraméterként, akkor egy text mezőt jelenítsünk meg az esetleges válasz bevitelére. Ha válaszlehetőségeket is felsoroltunk a paraméterlistában, akkor azokat egy legördülő listából lehessen kiválasztani. Mivel még nem tudjuk, hogy hány kérdésből fog állni a tesztünk, de a későbbiekben erre az információra még szükségünk lesz, ezért ennek a függvénynek a segítségével a paraméterként megadott kérdést eltároljuk egy listában.

#!c:\program files\python22\python.exe import cgi import MySQLdb list_q=[] def create_question(question, *more): list_q.append([question]) a_name="a"+str(len(list_q)) html_code=''' <hr> <h2>''' + str(len(list_q)) + '''. '''+ question + ''' </h2>''' if len(more)==0: html_code=html_code+''' <textarea name="'''+ a_name + '''" rows=2 cols=40></textarea> ''' else: html_code=html_code+''' <select name="''' + a_name + '''" size=1>''' for i in more: html_code=html_code+''' <option>'''+ i +'''</option>''' html_code=html_code+ "</select>" return html_code </div> <p>Mivel a felhasználók a text mezőkbe bármilyen karaktert beírhatnak, ám a MySQL nem képes minden karaktert számunkra olvasható formában tárolni. Gyakran még az is előfordulhat, hogy semmit nem írnak a text mezőbe. Ezért írjunk egy függvényt, amelynek a segítségével a bekért adatokat a MySQL-nek megfelelő formában adhatjuk majd át.</p> <div class="source"> def dbescape(val): if val: return MySQLdb.string_literal(val) else: return "NULL" </div> <p>A fenti create_question függvény segítségével állítsunk elő egy tetszőleges teszt HTML kódját, amely a nevet is bekéri -a feladatnak megfelelően. Ezt a HTML kódot most még csak deklaráljuk, majd a megfelelő helyen a print utasítással kiíratjuk.</p> <div class="source"> f_html=''' <html> <head> <title>Teszt </title> </head> <body> <h1>Válaszolj az alábbi kérdésekre!</h1> <hr> <form method="POST"> Neved: <input type="text" name="nev" value="" size="30"> ''' f_html=f_html+create_question('Hatekonyabban tudtok parban dolgozni', 'igen', 'nem') f_html=f_html+create_question('A munkaido hany szazaleka lesz annak, mintha kulon dolgoznatok? ', '<80', '80-90', '90-100', '100-110', '110-120', '>120') f_html=f_html+create_question('Tobb, vagy kevesebb hiba marad eszrevetlenul a programban'? 'tobb', 'kevesebb') f_html=f_html+create_question('Az hibak kijavitasahoz tobb idore lesz szuksegetek?', 'igen', 'nem') f_html=f_html+create_question('Elvezetesebb lesz-e igy a munka?', 'igen', 'nem') f_html=f_html+create_question('Tovabbi velemeny') f_html=f_html+''' <hr><p> <center> <input type="submit" value="Elkuld"> <input type="reset" value="Torol"> </body></html>'''

Miután a felhasználó kitöltötte az kérdőívet, jelenítsünk meg egy HTML oldalt, amely közli a felhasználóval, hogy sikeresen tároltuk-e az általa megadott válaszokat. Ennek az oldalnak a HTML kódjába dinamikusan kerül bele a felhasználó neve és a neki szánt üzenet.

dynhtml=''' <html> <head> <title> Teszt-visszajelzes</title> </head> <body> <hr> <h2><center> %s valaszai </h2> <p> %s </p> </center> <hr> </body> </html>'''

Most, hogy már deklaráltuk a megfelelő HTML kódokat, írassuk is ki őket. Először is a tesztet kell megjeleníteni. Ezt úgy tehetjük meg, hogy leellenőrizzük, hogy létrejött-e már cgi objektumunk, mert ha még nem, akkor még nem történt meg a válaszok elküldése, ezért még a kérdéseket kell megjelenítenünk.

print "Content-Type: text/html \n" fs=cgi.FieldStorage() if not fs: print f_html

Ha valaki kitöltötte a kérdőívet, és elküldte azt, akkor ellenőrizzük le, hogy megadta-e a nevét. Ha igen, akkor építsük ki a kapcsolatot az adatbázissal, és szúrjuk be a válaszokat a megfelelő táblába. A beszúráskor használjuk a dbescape függvényünket, hogy az adatok a megfelelő formában kerüljenek az adatbázisba. Miután megtörtént az összes beszúrás és bezártuk a kapcsolatot az adatbázissal, akkor már mondhatjuk, hogy az adatokat sikeresen eltároltuk,  és ezzel az üzenettel megjelenítjük a második HTML kódunkat.

Megjegyzés: ha az adatbázissal való kapcsolat felépítését, és a beszúrásokat beágyazzuk egy try blokkba,  akkor valamilyen hiba fellépése esetén az üzenetünket beállíthatjuk "Az adatokat nem sikerült tárolni" -ra.

Ha valaki nem adta meg a nevét, azt figyelmeztessük erre.

else: if fs.has_key('nev'): neve=fs['nev'].value kapcsolat=MySQLdb.connect(host="localhost", db="teszt", user="zita") kurzor=kapcsolat.cursor() for i in range(1, len(list_q)+1): melyik_v="a"+str(i) try: valasz = fs[melyik_v].value except: valasz = "" sql_ut='insert into valaszok values(%s, "%s", %s)' % (dbescape(neve), list_q[i-1], dbescape(valasz)) kurzor.execute(sql_ut) kurzor.close() uzenet="Sikeresen felveve" print dynhtml % (neve, uzenet) else: print "<center><h2><br>Add meg a neved!</h2></center>";

A feladat második része az volt, hogy lehetőség legyen a kérdőívet kitöltők válaszainak a megtekintésére, de csak az erre jogosultaknak. Ennek a megoldását egy új fájlba, a teszt_eredmeny.py fájlba tegyük. Hogy csak a megfelelő jogosultságokkal rendelkezők nézhessék meg a teszt eredményét, ezért először  a programunk megjelenít egy HTML oldalt, amely azonosítót és jelszót kér be. Most is, mint az előző programunkba, itt még csak deklaráljuk a kódot, majd később fogjuk kiírni.

#!c:\program files\python22\python.exe import cgi import MySQLdb #Az azonosítót és jelszót bekérő oldal html kódja formhtml=''' <html> <head> <title>Bejelentkezes</title> </head> <body> <hr><center> <form method="POST"> <p> Azonosító: <input type="text" name="azonosito" value=""> </p> <p> Jelszó: <input type="password" name="jelszo" value=""> </p> <p> <input type="submit" value="Ok"> <input type="reset" value="Töröl"> </p> </form> <hr> </center> </body> </html>'''

Miután valaki megadta az azonosítóját és a jelszavát, egy új HTML oldalra ugorjunk, amelyre a bejelentkezés sikerességének megfelelő üzenetet írunk ki. Tehát az új oldal kódjába dinamikusan kerül majd bele a felhasználó azonosítója és az üzenet. Ha a bejelentkezés sikeres volt, akkor majd ehhez az oldalhoz fogjuk hozzáfűzni a teszt eredményét megjelenítő táblázatot.

dynhtml=''' <html> <head> <title> %s </title> </head> <body> <center><hr> <h2> %s </h2> <h3> %s </h3> <hr> </center> </body> </html>'''

Mivel a kérdőív válaszait megnézni jogosultak azonosítója és jelszava egy adatbázis táblában van letárolva, ezért a megadott azonosító és jelszó helyességének ellenőrzéséhez ki kell építenünk egy kapcsolatot az adatbázissal. Ezután kérdezzük le a felhasználók azonosítóit és jelszavait, és tároljuk el őket egy tuple változóban.

kapcsolat=MySQLdb.connect(host="localhost", db="teszt", user="zita") kurzor=kapcsolat.cursor() db_azon='select * from felhasznalok' kurzor.execute(db_azon) felhasznalok=kurzor.fetchall()

Most úgy, mint ahogy azt előző programunkban is tettük, írjuk ki a megfelelő HTML kódot. Ha még nincs cgi objektumunk, akkor írassuk ki az azonosítót és jelszót bekérő HTML oldal kódját.

print "Content-Type: text/html \n \n" fs=cgi.FieldStorage() if not fs: print formhtml

Ha már van cgi objektumunk, az azt jelenti, hogy valaki megpróbált bejelentkezni. Ellenőrizzük le, hogy a felhasználó megadott-e valamilyen azonosítót, és ha igen, azt is nézzük meg, hogy jelszót is beírtak-e. Ezután keressük meg a megadott azonosító és jelszó párt az adatbázisból lekért azonosító és jelszó párok között. Ha megtaláltuk, akkor a felhasználónak van jogosultsága megtekinteni a kérdőívet kitöltők válaszait, ezért a bejelentkezés után megjelenő HTML oldalt az "Üdvözöljük" üzenettel jelenítsük meg.

elif fs.has_key('azonosito') and fs['azonosito'].value!="": beirt_az=fs['azonosito'].value if fs.has_key('jelszo') and fs['jelszo'].value!="": beirt_jelszo=fs['jelszo'].value elem=(beirt_az, beirt_jelszo) if elem in felhasznalok: kapcs="Letrejött a kapcsolat" uzenet="Üdvözöljük \n" print dynhtml %(kapcs, uzenet, beirt_az)

Sikeres bejelentkezés esetén a teszt eredményét is ki kell írni egy táblázatban úgy, hogy az oszlopok a kérdések legyenek, és egy ember válaszai pedig egy sorba kerüljenek. Ehhez tudnunk kell, hogy pontosan hány kérdésből is állt a kérdőív, de ebben a programban a kérdőívről semmit nem tudunk. A problémát azonban nagyon könnyen megoldhatjuk, mert az adatbázis táblánkban a kérdéseket is letároltuk, ezért, ha lekérdezzük az válaszokat tartalmazó táblából a különböző kérdéseket, akkor a lekérdezés eredményének sorszáma megadja, hogy hány kérdésből állt a teszt. Ugyanezzel a lekérdezéssel a kérdéseket is megszerezzük, ha letároljuk egy tuple adatszerkezetben. A kérdéseket a táblázat fejlécében jelenítsük meg.

Megjegyzés: mivel a lekérdezés eredményét egy tuple adatszerkezetben tároljuk le, ezért ennek az adattípusnak megfelelően az adatszerkezet egy eleme zárójelek között jelenik meg. Azonban elég csúnya lenne, ha a táblázatunk fejlécében is ilyen zárójelek között szerepelnének a kérdések, ezért a replace függvény segítségével a fölösleges zárójeleket egy szóközzel helyettesítsük. Ezt a függvény azonban csak sztringekre lehet meghívni, ezért előbb konvertáljuk át a kérdéseket tartalmazó tuple adatszerkezet elemét.

print '<center><h2>A teszt eredménye</h2><br></center>' kerdes_lekerdezes='select distinct(kerdes) from valaszok' kurzor.execute(kerdes_lekerdezes) kerdesek=kurzor.fetchall() tabla='''<table border="1" width="100%" bordercolor="#000000"> <tr><td>Nevek</td>''' for i in kerdesek: kerdes=str(i).replace('("[', ' ') kerdes=kerdes.replace(']",)',' ') tabla=tabla+'<td>'+ kerdes +'</td>' tabla=tabla+'</tr>'

A táblázat többi sorába pedig a válaszolók nevét, és az általuk adott válaszokat kell kiírni. Ehhez kérdezzük le a különböző neveket a válaszokat tartalmazó adatbázistáblából, a különböző nevek számát pedig a lekérdezés eredményének sorszáma adja. Tehát még ennyi sorból fog állni a táblázatunk. Ezután lekérdezzük a válaszokat tartalmazó tábla összes sorát, és azon majd soronként végigmegyünk. Mivel már tudjuk, hogy hány kérdés volt, ezért tudjuk, hogy egy névhez hány sor tartozik az adatbázis táblából. Így most már össze tudjuk rakni a HTML táblázatunk egy sorának HTML kódját. Ha már minden sort kiírtunk, akkor zárjuk be a kapcsolatot az adatbázissal.

valaszolok_lekerdezes='select distinct(nev) from valaszok' kurzor.execute(valaszolok_lekerdezes) valaszolok_szama=kurzor.rowcount valasz_lekerdezes='select nev, valasz from valaszok' kurzor.execute(valasz_lekerdezes) val=kurzor.fetchone() sor="<tr><td>" for i in range(0, valaszolok_szama): sor=sor+val[0]+'</td><td>'+str(val[1])+'</td>' for j in range(1, len(kerdesek)): val=kurzor.fetchone() sor=sor+'<td>'+str(val[1])+'</td>' val=kurzor.fetchone() tabla=tabla+sor+'</tr>' sor="<tr><td>" tabla=tabla+'</table>' print tabla kurzor.close

Most már csak az van hátra, hogy lekezeljük azokat az eseteket, amikor a felhasználó által megadott azonosító és jelszó páros nem szerepel az adatbázistáblába, azaz a felhasználó nem jogosult a válaszok megtekintésére, illetve amikor nem is adott meg jelszót, vagy amikor a felhasználó nem adott meg azonosítót. Ezekben az esetekben a második HTML kódunkat, ami a felhasználó bejelentkezésekor jelenik meg a megfelelő hibaüzenettel jelenítsük meg.

else: kapcs="Nincs kapcsolat" uzenet="Hibas jelszo vagy azonosító!" print dynhtml %(kapcs, uzenet, beirt_az) else: kapcs="Nincs kapcsolat" uzenet="Nincs jogosultsága megnézni az oldalt, vagy nem adott meg jelszót!" print dynhtml %(kapcs, uzenet, beirt_az) else: kapcs="Nincs kapcsolat" uzenet="Nem adott meg azonosítót!" print dynhtml %(kapcs, uzenet, "")