TigerJython xx
für Gymnasien

Datenbanken mit grafischen Benutzeroberfläche


Im professionellen Umgang mit Datenbanken werden meisten GUI-Dialogfenster verwendet, in welchen der Benutzer die wichtigsten Datenbankmanipulationen durchführen kann:

  • Einfügen von neuen Datensätzen (INSERT)
  • Aktualisieren von Datensätzen (UPDATE)
  • Suchen von Datensätzen (SELECT)
  • Löschen von Datensätzen (DELETE)
  • Durchlaufen der Datensätze (Navigation)

Mit TigerJython lassen sich solche GUI-Dialoge verhältnismässig einfach erstellen. Das Programm DBManager.py verwendet die Datenbank demo.db und die Tabelle person. Die Tabelle person sollte zu Beginn bereits die 6 Datensätze enthalten, die man mit dem Programm im vorhergehenden Kapitel erzeugt hat. Beim Start wird folgendes Dialogfenster angezeigt:


Die Bedienung ist wie folgt implementiert:

  • Beim Start wird man bei einer leeren Tabelle in den Insert-Modus versetzt. Sonst befindet man sich im Navigationsmodus und es wird der erste Datensatz angezeigt. Die Datensätze werden automatisch nach Name und Vorname geordnet.
  • Klick man auf Insert, kommt man in den Insert-Modus. Dabei werden leere Felder angezeigt, die man ausfüllen muss. Mit Save wird die Eingabe überprüft und falls alles korrekt ist, in die Tabelle eingefügt. Die Eingabe kann mit Cancel abgebrochen werden.
  • Delete löscht den angezeigten Datensatz (ohne Rückfrage). Danach wird wieder der erste Datensatz angezeigt.
  • Mit den Navigationsbuttons kann man die Datensätze durchgeben
  • Für die Datensatzsuche werden die Eingabefelder Name und Vorname verwendet, die beide ausgefüllt werden müssen, bevor man den Search-Button Klickt. Man kann aber auch Wildcards % verwenden. Bei Eingabe Huber im Feld Name und % im Feld Vorname, wird Huber Lia gefunden.
  • In der Statuszeile werden wichtige Meldungen angezeigt

Das Programm DbManager.py ist etwas länger, ist aber gut verständlich und lässt sich einfach an andere Datenbankanwendungen anpassen. Es ist vollumfänglich unten angezeigt, kann aber auch von http://www.jython.ch/download/DbManager.zip herunterladen und in den TigerJython-Editor kopiert werden.

 

  Verwaltung eines Sporttages

Auch für die Verwaltung eines Sporttages lässt sich ein GUI-Dialogfenster entwickeln. Verwendet werden hier die Datenbank demo.db mit den Tabellen person und sport. Es ist vorteilhaft, wenn die Tabelle person zu Beginn bereits einige Datensätze enthält, z. B. die 6 Datensätze, die man mit dem Programm InsertPersonEx1.py erzeugt. Die Tabelle sport kann die Datensätze mit den Sportergebnissen dieser 6 Personen enthalten oder auch leer sein, da man die Sportergebnisse mit der neuen Benutzeroberfläche problemlos eingeben kann. Die beiden Tabellen sind über das Schlüsselfeld id miteinander verknüpft.

Beim Start wird folgendes Dialogfenster angezeigt:

Zuerst sind alle Leistungsfelder leer und kann man die Leistungsdaten aller Personen eingeben. Bei solchen, die eine Sportart nicht ausüben, belässt man das leere Eingabefeld. Beim Klicken von Save werden die Leistungsdaten in die Tabelle geschrieben.

Das kann auch unter http://www.jython.ch/download/SporManager.zip heruntergeladen und in den TigerJython Editor eingefügt werden.

Die Resultate und Ranglisten können wie unter Menüpunkt Tabellen verknüpfen zusammengestellt werden.



Das Programm DbManager.py:

# DbManager.py

from sqlite3 import *
from entrydialog import *

database = "demo.db"

def doFirst():
    global currentIndex
    currentIndex = 0
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def doLast():
    global currentIndex
    currentIndex = len(resultSet) - 1
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def doNext():
    global currentIndex
    if currentIndex == len(resultSet) - 1:
        status.setValue("Ende der Datenbank erreicht.")        
        return
    currentIndex += 1
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def doPrevious():
    global currentIndex
    if currentIndex == 0:
        status.setValue("Beginn der Datenbank erreicht.")        
        return
    currentIndex -= 1
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def setInsertMode(enable):
    global isInsertMode
    if enable:
        isInsertMode = True
        firstBtn.setEnabled(False)
        lastBtn.setEnabled(False)
        prevBtn.setEnabled(False)
        nextBtn.setEnabled(False)
        insertBtn.setEnabled(False)
        searchBtn.setEnabled(False)
        deleteBtn.setEnabled(False)
        saveBtn.setEnabled(True)
        cancelBtn.setEnabled(True)
        initDialog()
    else:
        isInsertMode = False
        firstBtn.setEnabled(True)
        lastBtn.setEnabled(True)
        prevBtn.setEnabled(True)
        nextBtn.setEnabled(True)
        insertBtn.setEnabled(True)
        searchBtn.setEnabled(True)
        deleteBtn.setEnabled(True)
        saveBtn.setEnabled(True)
        cancelBtn.setEnabled(False)

def initDialog():
    vText.setValue("")
    fText.setValue("")
    jText.setValue(None)
    wText.setValue("")
    gText.setValue("")
    jText.setValue(None)

def initSearchDialog():
    fSearch.setValue("")
    vSearch.setValue("")
    
def showPerson(person):
    fText.setValue(person[1])
    vText.setValue(person[2])
    wText.setValue(person[3])
    gText.setValue(person[4])
    jText.setValue(person[5])

# -------------- Validierung der Eingaben ----------------
def validate(name, vorname, wohnort, geschlecht, jahrgang):
    illegal = "Illegale Eingabe. "
    if not isLegal(name):
        status.setValue(illegal + "Name illegal.")
        return False
    if not isLegal(vorname):
        status.setValue(illegal + "Vorname illegal.")
        return False
    if not isLegal(wohnort):
        status.setValue(illegal + "Wohnort illegal.")
        return False
    if geschlecht not in ['m', 'w']:
        status.setValue(illegal + "Geschlecht muss 'm' oder 'w' sein.")
        return False
    if jahrgang == None:
        status.setValue(illegal + "Jahrgang muss eine Zahl sein.")
        return False
    return True

def isLegal(text):
    if text == "":
        return False    
    forbidden = []
    for i in range(65):
        if i != 32 and i != 46 and i != 45: # space, ., -
            forbidden.append(chr(i))
    for c in text:
        if c in forbidden:
            return False
    return True
        
def setCurrentIndex():
    global currentIndex
    name = fText.getValue().strip()
    vorname = vText.getValue().strip()
    person = find(name, vorname)
    if person == None:    
        return False
    personid = person[0]
    indexList = [item[0] for item in resultSet]
    currentIndex = indexList.index(personid)
    return True

def search():
    global currentIndex
    name = fSearch.getValue().strip()
    vorname = vSearch.getValue().strip()
    person = find(name, vorname)
    if person == None:    
        status.setValue("Suche misslungen. Person nicht in Datenbank gefunden.")
        return
    personid = person[0]
    indexList = [item[0] for item in resultSet]
    currentIndex = indexList.index(personid)
    showPerson(resultSet[currentIndex])
    status.setValue("Suche erfolgreich. Person in Datenbank gefunden.")
    initSearchDialog()      

# ---------------- Datenbank-Operationen ----------------
def getAllRecords():
    global resultSet
    with connect(database) as con:
        cursor = con.cursor()
        sql = "SELECT * FROM person ORDER BY name, vorname"
        cursor.execute(sql)
        resultSet = cursor.fetchall()

def find(name, vorname):
    with connect(database) as con:
        cursor = con.cursor()
        sql = "SELECT * FROM person WHERE name LIKE '%s' AND vorname LIKE '%s'" %(name, vorname)
        cursor.execute(sql)
        person = cursor.fetchone()
    return person # None, if not found    
        
def insert():
    a = fText.getValue().strip()
    b = vText.getValue().strip()
    c = wText.getValue().strip()
    d = gText.getValue().strip().lower()
    e = jText.getValue()
    if not validate(a, b, c, d, e):
        return
    if find(a, b) != None:
       status.setValue("Person bereits in Datenbank enthalten.")
       return
    with connect(database) as con:
        cursor = con.cursor()
        sql = """INSERT INTO person 
                 (name, vorname, wohnort, geschlecht, jahrgang) 
                 VALUES 
                 ('%s', '%s', '%s', '%s',  %d)""" %(a, b, c, d, e)
        cursor.execute(sql)
    status.setValue("Person erfolgreich in Datenbank aufgenommen.") 
    setInsertMode(False)
    getAllRecords()       
    setCurrentIndex()
    showPerson(resultSet[currentIndex])
 
def update():
    name = fText.getValue().strip()
    vorname = vText.getValue().strip()
    wohnort = wText.getValue().strip()
    geschlecht = gText.getValue().strip()
    jahrgang = jText.getValue()
    nameOld = resultSet[currentIndex][1]
    vornameOld = resultSet[currentIndex][2]
    if not validate(name, vorname, wohnort, geschlecht, jahrgang):
        return
    with connect(database) as con:
        cursor = con.cursor()
        sql = """UPDATE person SET 
               name = '%s', 
               vorname = '%s', 
               wohnort = '%s', 
               geschlecht = '%s', 
               jahrgang = '%s' 
               WHERE name = '%s' AND vorname = '%s'""" \
               %(name, vorname, wohnort, geschlecht, jahrgang, nameOld, vornameOld)
        # use global name, vorname
        cursor.execute(sql)
    status.setValue("Person erfolgreich mutiert.")
    getAllRecords()       
    setCurrentIndex()

def delete():
    name = fText.getValue()
    vorname = vText.getValue()
    if not find(name, vorname):
       status.setValue("Löschen misslungen. Person nicht in Datenbank")
       return
    with connect(database) as con:
        cursor = con.cursor()
        sql = "DELETE FROM person WHERE name = '%s' AND vorname = '%s'" %(name, vorname)
        cursor.execute(sql)
    getAllRecords()       
    if resultSet == []:  # Datenbank leer
        setInsertMode(True)
        status.setValue("Person erfolgreich aus Datenbank entfernt. Datenbank leer.")
    else:
       doFirst()
       status.setValue("Person erfolgreich aus Datenbank entfernt. Zeige ersten Datensatz an.")

# ================== Hauptprogramm ===================
# --------------- Dialog aufbauen -------------
fText = StringEntry("Name: ") 
vText = StringEntry("Vorname: ") 
wText = StringEntry("Wohnort: ") 
gText = StringEntry("Geschlecht: (m/w)): ")
jText = IntEntry("Jahrgang (Zahl): ") 
pane1 = EntryPane("Information", fText, vText, wText, gText, jText)
firstBtn = ButtonEntry("First") 
lastBtn = ButtonEntry("Last") 
nextBtn = ButtonEntry("Next") 
prevBtn = ButtonEntry("Prev") 
pane2 = EntryPane("Navigation", firstBtn, prevBtn, nextBtn, lastBtn)
deleteBtn = ButtonEntry("Delete") 
insertBtn = ButtonEntry("Insert") 
saveBtn = ButtonEntry("Save") 
cancelBtn = ButtonEntry("Cancel") 
pane3 = EntryPane("Mutation", deleteBtn, insertBtn, saveBtn, cancelBtn)
fSearch = StringEntry("Name: ") 
vSearch = StringEntry("Vorname: ") 
pane4 = EntryPane("Suche", fSearch, vSearch)
searchBtn = ButtonEntry("Search") 
pane5 = EntryPane(searchBtn)
status = StringEntry("")
status.setEditable(False)
pane6 = EntryPane("Status", status)
dlg = EntryDialog(pane1, pane2, pane3, pane4, pane5, pane6)
dlg.setTitle("Databank-Manager")

# --------------- ResultSet für ganze Datenbank holen -------------
resultSet = []
getAllRecords()
if resultSet == []:  # database empty
    setInsertMode(True)
    status.setValue("Datenbank leer.")
else:
    doFirst()
    setInsertMode(False)

# --------------- Ereignisschleife -------------
while not dlg.isDisposed():
    if firstBtn.isTouched():
         doFirst()
         setInsertMode(False)  
    if lastBtn.isTouched():
         doLast()
         setInsertMode(False)  
    if nextBtn.isTouched():
         doNext()  
    if prevBtn.isTouched():
         doPrevious()  
    elif searchBtn.isTouched():
        search()
    elif saveBtn.isTouched():
        if isInsertMode:
            insert()
        else:
            update()
    elif cancelBtn.isTouched():
        if resultSet != []: # not empty database
            setInsertMode(False)
            doFirst()
        else:
            status.setValue("Bitte Personendaten eintragen und Save klicken oder Fenster schliessen.")
    elif deleteBtn.isTouched():
        delete()
    elif insertBtn.isTouched():
        if not isInsertMode:
            setInsertMode(True)
            initDialog()
            status.setValue("Bitte Personendaten eintragen und Save oder Cancel klicken.")
        else:
            insert()
            finishBtn.setEnabled(True)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)



Das Programm SportManager.py:

# SportManager.py

from sqlite3 import *
from entrydialog import *
from prettytable import *

database = "demo.db"

def doFirst():
    global currentIndex
    currentIndex = 0
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def doLast():
    global currentIndex
    currentIndex = len(resultSet) - 1
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def doNext():
    global currentIndex
    if currentIndex == len(resultSet) - 1:
        status.setValue("Ende der Datenbank erreicht.")        
        return
    currentIndex += 1
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def doPrevious():
    global currentIndex
    if currentIndex == 0:
        status.setValue("Beginn der Datenbank erreicht.")        
        return
    currentIndex -= 1
    showPerson(resultSet[currentIndex])
    status.setValue("Person " + str(currentIndex + 1) + " von " + str(len(resultSet)))

def validate(lValue, hValue, wValue):
    return True
           
def showPerson(person):
    fText.setValue(person[1])
    vText.setValue(person[2])
    personid = person[0]
    with connect(database) as con:
        cursor = con.cursor()
        sql = "SELECT * FROM sport where id = " + str(personid)
        cursor.execute(sql)
        leistungen = cursor.fetchone()
    if leistungen == None: # Person not yet in table; should not happen
        lEntry.setValue(None)
        wEntry.setValue(None)
        hEntry.setValue(None)
    else:
        # if NULL in table, leistungen[i] = None -> empty entry
        lEntry.setValue(leistungen[1])
        wEntry.setValue(leistungen[2])
        hEntry.setValue(leistungen[3])
      
def setCurrentIndex():
    global currentIndex
    name = fText.getValue().strip()
    vorname = vText.getValue().strip()
    person = find(name, vorname)
    if person == None:    
        return False
    personid = person[0]
    indexList = [item[0] for item in resultSet]
    currentIndex = indexList.index(personid)
    return True

def search():
    global currentIndex
    name = fText.getValue().strip()
    vorname = vText.getValue().strip()
    person = find(name, vorname)
    if person == None:    
        status.setValue("Suche misslungen. Person nicht in Datenbank gefunden.")
        return
    personid = person[0]
    indexList = [item[0] for item in resultSet]
    currentIndex = indexList.index(personid)
    showPerson(resultSet[currentIndex])
    status.setValue("Suche erfolgreich. Person in Datenbank gefunden.")

# ---------------- Datenbank-Operationen ----------------
def getAllRecords():
    global resultSet
    with connect(database) as con:
        cursor = con.cursor()
        sql = "SELECT * FROM person ORDER BY name, vorname"
        cursor.execute(sql)
        resultSet = cursor.fetchall()

def find(name, vorname):
    with connect(database) as con:
        cursor = con.cursor()
        sql = "SELECT * FROM person WHERE name LIKE '%s' AND vorname LIKE '%s'" %(name, vorname)
        cursor.execute(sql)
        person = cursor.fetchone()
    return person # None, if not found    

def insert():
    lValue = lEntry.getValue()
    if lValue == None:
        lValue = 'NULL'
    hValue = hEntry.getValue()
    if hValue == None:
        hValue = 'NULL'
    wValue = wEntry.getValue()
    if wValue == None:
        wValue = 'NULL'
    if not validate(lValue, hValue, wValue):
        return
    personid = resultSet[currentIndex][0]
    with connect(database) as con:
        cursor = con.cursor()
        sql = "DELETE FROM sport WHERE personid = " + str(personid)
        cursor.execute(sql)
        sql = "INSERT INTO sport VALUES (%s, %s, %s, %s)" %(personid, lValue, hValue, wValue)
        cursor.execute(sql)
    status.setValue("Leistungen erfolgreich in Datenbank übernommen.") 

def report(discipline):
    with connect(database) as con:
        cursor = con.cursor()
        if discipline == "all":
            sql = """SELECT name, vorname, lauf, hochsprung, weitsprung 
                  FROM person JOIN sport 
                  ON person.id == sport.id 
                  ORDER BY name, vorname"""
        elif discipline == "lauf":
            sql = """SELECT name, vorname, lauf 
                  FROM person JOIN sport 
                  ON person.id == sport.id 
                  ORDER BY lauf ASC"""
        elif discipline == "hoch":
            sql = """SELECT name, vorname, hochsprung
                  FROM person JOIN sport 
                  ON person.id == sport.id 
                  ORDER BY hochsprung DESC"""
        elif discipline == "weit":
            sql = """SELECT name, vorname, weitsprung
                  FROM person JOIN sport 
                  ON person.id == sport.id 
                  ORDER BY weitsprung DESC"""
        cursor.execute(sql)
        printTable(cursor)
        status.setValue("Report ausgeschrieben.")
    
# ================== Hauptprogramm ===================
# --------------- Dialog aufbauen -------------
fText = StringEntry("name: ") 
vText = StringEntry("Vorname: ") 
pane1 = EntryPane("Sportler/Sportlerin", fText, vText)
firstBtn = ButtonEntry("First") 
lastBtn = ButtonEntry("Last") 
nextBtn = ButtonEntry("Next") 
prevBtn = ButtonEntry("Prev") 
searchBtn = ButtonEntry("Search") 
pane2 = EntryPane(firstBtn, prevBtn, nextBtn, lastBtn, searchBtn)
lEntry = FloatEntry("Laufzeit: ") 
wEntry = FloatEntry("Weitsprung: ")
hEntry = FloatEntry("Hochsprung: ") 
pane3 = EntryPane("Leistungen", lEntry, wEntry, hEntry)
deleteBtn = ButtonEntry("Delete") 
insertBtn = ButtonEntry("Insert") 
saveBtn = ButtonEntry("Save") 
pane4 = EntryPane(saveBtn)
allBtn = ButtonEntry("Alle") 
laufBtn = ButtonEntry("Lauf") 
hochBtn = ButtonEntry("Hoch") 
weitBtn = ButtonEntry("Weit") 
pane5 = EntryPane("Reports/Ranglisten", allBtn, laufBtn, weitBtn, hochBtn)
status = StringEntry("")
status.setEditable(False)
pane6 = EntryPane("Status", status)
dlg = EntryDialog(pane1, pane2, pane3, pane4, pane5, pane6)
dlg.setTitle("Sport-Manager")

# --------------- ResultSet für ganze Datenbank holen -------------
resultSet = []
getAllRecords()
if resultSet == []:  # database empty
    status.setValue("Datenbank leer. Zuerst Persondaten eingeben.")
else:
    doFirst()
    # --------------- Ereignisschleife -------------
    while not dlg.isDisposed():
        if firstBtn.isTouched():
             doFirst()
        if lastBtn.isTouched():
             doLast()
        if nextBtn.isTouched():
             doNext()  
        if prevBtn.isTouched():
             doPrevious()  
        elif searchBtn.isTouched():
            search()
        elif saveBtn.isTouched():
            insert()
        elif allBtn.isTouched():
            report("all")
        elif laufBtn.isTouched():
            report("lauf")
        elif hochBtn.isTouched():
            report("weit")
        elif weitBtn.isTouched():
            report("hoch")
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)