reservationssystem

10. ONLINE-RESERVATIONSSYSTEM

 

 

DU LERNST HIER...

 

wie ein Online-Reservationssystem funktioniert. Die Bestellung von Karten für Konzerte erfolgt heute fast ausnahmslos über das Internet. Dabei wird dem Kunden gewöhnlich eine aktuelle Sitzplatzbelegung gezeigt, damit dieser per Mausklick die gewünschten Sitzplätze reservieren kann. Der Belegungsplan ist in praktisch allen Fällen in einer Datenbank auf einem Datenbankserver gespeichert und der Kunde greift über einen Web-Browser auf die Datenbank zu.

 

 

MUSTERBEISPIEL

 

In deinem Beispiel verwendest du zwar keinen Web-Browser, um auf einen Datenbankserver zuzugreifen, sondern ein Python-Programm, das auf eine gemeinsam zugängliche Datenbank-Datei zugreift, die beispielsweise - wie im vorhergehenden Kapitel gezeigt - auf einer Cloud (z.B. Dropbox) für mehrere Benutzer freigegeben ist. Das Prinzip bleibt aber dasselbe, denn das Einlesen der Tabelleninformation mit restore() und das speichern mit save() entspricht weitgehend einer Browserabfrage. (Du kannst das Vorgehen auch mit zwei TigerJython-Fenstern auf dem gleichen PC simulieren.)

Beim Start des Programms wird dem Benutzer eine Grafik angezeigt, aus der er in einer möglichst realistischen Sitzplatzanordnung die freien und die bereits reservierten Sitzplätze entnehmen kann. Diese Information wird aus der Datenbank bezogen. Mit einem linken Mausklick kann er nun mehrmals hintereinander einen freien Sitzplatz auswählen und dann mit einem rechten Mausklick diese Wahl bestätigen. Die Benutzer wird nun eventuell aufgefordert, sich zu identifizieren und die Reservation wird in der Datenbank vollzogen.

Deine Aufgabe kannst du in zwei Teile zerlegen: Das eine Teilproblem betrifft die graphische Darstellung der Sitzplätze mit der Möglichkeit, gewünschte Plätze mit einem linken Mausklick zu selektieren und mit einem rechtem Mausklick die Auswahl zu bestätigen. Die zweite Teilaufgabe besteht darin, die Datenbankmanipulationen auszuführen.

1. Teilaufgabe: Grafische Benutzeroberfläche

Für die erste Teilaufgabe verwendest du die Turtlegrafik. Um die Aufgabe zu erleichtern, sind die Sitzplätze in einer einzigen Reihe angeordnet. Trotzdem ist der Aufwand für die graphische Benutzeroberfläche recht gross und du musst deine Kenntnisse über Turtlegrafik und Mausevents heranziehen, um die Einzelheiten zu verstehen.

from gturtle import *
import time

# --------------- Callback executors ------------
def apply():
    if selected == []:
        setStatusText("Please make a seat selection.")
        return
    print("Selected seats:", selected)
    for seat in selected:
        occupied.append(seat)
    renew()    
    setStatusText("Your reservation is confirmed. All reservations updated.")

def renew():
    for seat in range(1, 16):
        setPos(toSeatPos(seat))
        if seat in occupied:
            drawImage("sprites/seat_2.png")
        else:
            drawImage("sprites/seat_0.png")
    selected.clear()

# -------------- Mouse callback --------------------
def onMouseHit(x, y):
    if isRightMouseButton():
        apply()
        return
    if  y < -20 or y > 20 or x < -300 or x > 300:
        return
    seat = toSeat(x)
    setPos(toSeatPos(seat))
    if seat in occupied:
        setStatusText("This seat is occupied.")
        return
    if seat not in selected:
        drawImage("sprites/seat_1.png")
        selected.append(seat)
    else: 
        drawImage("sprites/seat_0.png")
        selected.remove(seat)


# ----------------- Coordinate convertions ---------
def toSeatPos(seat):
    return -320 + seat * 40, 0

def toLabelPos(seat):
    return -322 + seat * 40, -40

def toSeat(x):
    return int((x + 300) / 40 + 1)
        
# --------------- main -----------------------------        
selected = []
occupied = []
makeTurtle(mouseHit = onMouseHit)
setTitle("Multiuser Seat Reservation")
addStatusBar(20)
ht()
pu()
for seat in range(1, 16):
    setPos(toSeatPos(seat))
    drawImage("sprites/seat_0.png")
    setPos(toLabelPos(seat))
    label(str(seat), adjust = "c")

setStatusText("Left-click to select a free seat. Right-click for reservation.") 
while not isDisposed():
    time.sleep(1)
► In Zwischenablage kopieren


 

2. Teilaufgabe: Datenbankmanipulationen

Zuerst entwirfst du das Konzept der Datenbank mit der Tabellenstruktur, indem du dir überlegst, welche Informationen in welchen Tabellenfeldern abgespeichert werden. In einfachen Fällen genügt eine einzige Tabelle, in den meisten Fällen ist es aber angebracht, mehrere miteinander verknüpfte Tabellen zu verwenden. Bei deinem Reservationssystem ist es ziemlich offensichtlich, zwei Tabellen zu verwenden, eine Personentabelle customer mit den Kundendaten (Felder personid, name, firstname, creditcard) und eine Tabelle reservation mit der Sitzplatz-Belegung (Felder: seatnum, booked, custid).

 reservation    customer
 seatnum  booked  custid
  1   N   0
  2   N   0
  3   N   0
  4   N   0
 persid  name  firsname  creditcard
  1  Smith  John  3421 1002 7768 4646
  2   Berger  Marie  4431 5657 4356 9856
  3  Mercier  jean  4453 6547 6577 9901

booked kann die Werte 'N' (frei) und 'Y' (reserviert) annehmen. Ist ein Sitzplatz nicht reserviert, so wird als custid 0 eingetragen. Um die Aufgabe zu vereinfachen, betrachtest du vorerst nur die Tabelle reservation und erstellst und initialisierst diese mit einem einfachen Programm für die 16 Sitze in der Datenbank casino.

from dbtable import *

reservation = DbTable("seatnum", "booked", "custid")
for seat in range(1, 16):
    reservation.insert(seat, 'N', 0)
print(reservation)
reservation.save("casino")  
► In Zwischenablage kopieren

Der Ablauf einer Reservation geht folgendermassen vor sich: Ein Benutzer A fragt nach der aktuellen Belegung der Sitzplätze. Diese Information wird aus der Datenbank geholt und grafisch darstellt. Während einer gewissen Bearbeitungszeit wählt A mit linken Mausklicks einen oder mehrere freie Plätze und bestätigt diese Wahl am Ende durch einen rechten Mausklick. Er wird nun aufgefordert, seine Personendaten einzugeben (falls er noch nicht Kunde ist) und die Reservation wird in der Datenbank vollzogen.

Bei diesem Ablauf kann sich aber folgendes Problem ergeben: Falls während der Bearbeitungszeit von A ein zweiter Benutzer B ebenfalls eine Reservation vornimmt, erhält dieser zu Beginn die gleiche aktuelle Sitzplatz-Belegung wie A und es kann durchaus sein, dass A und B dieselben Plätze reservieren möchten. Je nachdem, wer weniger lang überlegt, sagen wir A, erhält dieser die Plätze, aber B merkt davon nichts, da ja die Datenbank von sich aus keine Rückmeldungen an B machen kann. Wenn B seine Wahl bestätigt, gibt es einen schwerwiegenden Zugriffskonflikt für die gemeinsam gewählten Sitze.

Was ist zu tun, um den Zugriffskonflikt zu vermeiden? Eine Lösung besteht darin, dass bei der Bestätigungsoperation mit dem rechten Mausklick das Programm nachmals prüft, ob die Plätze immer noch frei sind. Ist dies nicht der Fall, so erhält der Benutzer eine Rückmeldung, dass die Plätze in der Zwischenzeit leider bereits vergeben wurden.

Im folgenden Programm ist dieser Mechanismus implementiert, was allerdings einen Zusatzaufwand mit sich bringt. Das Programm schreibt bei der Bestätigungsoperation sogar aus, welche der Plätze bereits vergeben wurden.

from gturtle import *
from dbtable import *
import time

# ---------------- Database management -------------
def makeReservation():
    # Get all reserved seats
    reserved = []
    reservation = DbTable()
    reservation.restore("casino")
    for row in reservation.select():
        if row[1] == 'Y':
            reserved.append(row[0])
    # Check if some selected seats are already reserved
    conflict = []
    for seat in selected:
        if seat in reserved:
            conflict.append(seat)
    if conflict != []:
        return conflict    
    # No conflict-> make reservation
    for seat in selected:
        reservation.update(seatnum = seat, _booked = 'Y')   
    reservation.save("casino")
    return []

# --------------- Callback executors ------------
def apply():
    if selected == []:
        setStatusText("Please make a seat selection.")
        return
    taken = makeReservation()
    if taken == []:
        setStatusText("Your reservation is confirmed. All reservations updated.")
        renew()
    else:
        setStatusText("Seat(s) " + str(taken) + 
                      " already taken. Left-click to deselect.")

def renew():
    reservation = DbTable()
    reservation.restore("casino")
    for row in reservation.select():
        if row[1] == 'Y':
            occupied.append(row[0])
    for seat in range(1, 16):
        setPos(toSeatPos(seat))
        if seat in occupied:
            drawImage("sprites/seat_2.png")
        else:
            drawImage("sprites/seat_0.png")
    selected.clear() 
# -------------- Mouse callback --------------------
def onMouseHit(x, y):
    if isRightMouseButton():
        apply()
        return
    if  y < -20 or y > 20 or x < -300 or x > 300:
        return
    seat = toSeat(x)
    setPos(toSeatPos(seat))
    if seat in occupied:
        setStatusText("This seat is occupied.")
        return
    if seat not in selected:
        drawImage("sprites/seat_1.png")
        selected.append(seat)
    else: 
        drawImage("sprites/seat_0.png")
        selected.remove(seat)

# ----------------- Coordinate convertions ---------
def toSeatPos(seat):
    return -320 + seat * 40, 0

def toLabelPos(seat):
    return -322 + seat * 40, -40

def toSeat(x):
    return int((x + 300) / 40 + 1)
        
# --------------- main -----------------------------        
selected = []
occupied = []
makeTurtle(mouseHit = onMouseHit)
setTitle("Multiuser Seat Reservation")
addStatusBar(20)
ht()
pu()
for seat in range(1, 16):
    setPos(toSeatPos(seat))
    drawImage("sprites/seat_0.png")
    setPos(toLabelPos(seat))
    label(str(seat), adjust = "c")

renew()
setStatusText("Left-click to select a free seat. Right-click for reservation.") 
while not isDisposed():
    time.sleep(1)
► In Zwischenablage kopieren

 

 

MERKE DIR...

 

Der Zugriffskonflikt bei Mehrbenutzer-Datenbanken kann so gelöst werden, das vor dem Update der Datenbank nochmals geprüft wird, ob in der Zwischenzeit ein anderer Benutzer die Daten verändert hat.

 

 

ZUM SELBST LÖSEN

 

1.

Erweitere das Reservationssystem auf eine zweidimensionale Saalanordnung.

   

 

10-1
Fachliche Hinweise:

Die Übereinstimmung der Verfahren ist gross: Mit einem Browser erfolgt sowohl die Abfrage der Datenbankinformation als auch das Speichern neuer Daten mit einem HTTP-Request Nach jedem Request wird die Verbindung mit dem Webserver wieder geschlossen, genau gleich wie bei restore() und save() bezüglich der Datenbank-Datei.