deutsch     english    français     Imprimer

 

9.3 SYSTÈME DE RÉSERVATION EN LIGNE

 

 

INTRODUCTION

 

De nos jours, les réservations de places de spectacle sont effectuées presque exclusivement au moyen de systèmes de réservation en ligne qui utilisent des bases de données pour gérer les données. Des spécialistes en bases de données pourvus de très grandes compétences en programmation sont responsables du développement de ce genre de systèmes.

CONCEPTS DE PROGRAMMATION :
Systèmes multi-utilisateurs, conflits d’accès

 

 

SYSTÈME DE RÉSERVATION, ÉTAPE 1 : INTERFACE GRAPHIQUE

 

Il faut prévoir une interface graphique attractive pour présenter le système à l’utilisateur. Une telle interface peut être réalisée au travers d’une application Desktop classique ou d’une application Web. L’interface d’un système de réservation affiche généralement la disposition des sièges dans la salle de spectacle ou dans l’avion pour permettre à l’utilisateur de réserver son siège de manière fondée, sur la base d’une bonne représentation spatiale. La disponibilité des sièges est rendue immédiatement apparente par un code couleur : rouge pour les sièges réservés et vert pour ceux qui sont encore libres.

L’utilisateur peut se contenter de cliquer sur un siège vide pour en demander la réservation, de sorte qu’il va changer de couleur. Cette sélection n’est à ce stade pas encore communiquée à la base de données puisque l’utilisateur va généralement réserver plusieurs chaises ou changer d’avis au cours de sa réservation. Le processus de réservation définitive n’est initié que lorsque l’utilisateur confirme sa réservation en cliquant sur un bouton approprié.

La bibliothèque de jeux JGameGrid est idéale pour implémenter cette interface graphique puisque les sièges sont généralement arrangés selon une structure de grille. L’identifiant de sprite (0, 1, 2) peut être utilisé pour permettre de changer l’état de disponibilité des sièges (libre, en cours de réservation, réservé).

Dans le cas présent, on modélise une petite salle de spectacle ne disposant que de 30 sièges numérotés de 1 à 30 et disposés dans la grille représentée ci-contre.

Les deux boutons sont réalisés grâce à la classe GGButton. Pour pouvoir détecter les clics sur les boutons, il est nécessaire de définir une nouvelle classe de boutons dérivant de GGButtons et de GGButtonListener (héritage multiple). La méthode buttonPressed() est invoquée lors d’un clic sur l’un des deux bouton. On peut déterminer lequel des boutons a été cliqué sur la base du paramètre button.
 

Le programme suivant constitue déjà une interface graphique pleinement fonctionnelle. Il lui manque cependant encore la connexion à la base de données [plus... Au cas où certains éléments de ce programme devaient vous échapper,
ils sont bien présentés dans le chapitre « Jeux & POO »
] .

from gamegrid import *

def toLoc(seat):
    i = ((seat - 1) % 6) + 1
    k = ((seat - 1) // 6) + 2
    return Location(i, k)

def toSeatNb(loc):
    if loc.x < 1 or loc.x > 6 or loc.y < 2 or loc.y > 6:
       return None
    i = loc.x - 1
    k = loc.y - 2
    seatNb = k * 6 + i + 1
    return seatNb

class MyButton(GGButton, GGButtonListener):
    def __init__(self, imagePath):
        GGButton.__init__(self, imagePath)
        self.addButtonListener(self)
        
    def buttonClicked(self, button):
        if button == confirmBtn:
            confirm()
        if button == renewBtn:
            renew()
            
    def buttonPressed(self, button):
        pass

    def buttonReleased(self, button):
        pass
           
def renew():
    setStatusText("View refreshed")

def confirm():
    for seatNb in range(1, 31):
       if seats[seatNb - 1].getIdVisible() ==  1:
           seats[seatNb - 1].show(2)
           refresh()
    setStatusText("Reservation successful")

def pressCallback(e):
    loc = toLocation(e.getX(), e.getY())
    seatNb = toSeatNb(loc)
    if seatNb == None:
        return
    seatActor = seats[seatNb - 1]
    if seatActor.getIdVisible() == 0:  # free
        seatActor.show(1) # option
        refresh()
    elif seatActor.getIdVisible() == 1:  # option
       seatActor.show(0) # free
       refresh()

makeGameGrid(8, 8, 40, None, "sprites/stage.gif", False, 
             mousePressed = pressCallback)
addStatusBar(30)
setTitle("Seat Reservation")
setStatusText("Please select free seats and press 'Confirm'")
confirmBtn = MyButton("sprites/btn_confirm.gif")
renewBtn = MyButton("sprites/btn_renew.gif")
addActor(confirmBtn, Location(1, 7))
addActor(renewBtn, Location(6, 7))
seats = []
for seatNb in range(1, 31):
    seatLoc = toLoc(seatNb)
    seatActor = Actor("sprites/seat.gif", 3)
    seats.append(seatActor)
    addActor(seatActor, seatLoc)
    addActor(TextActor(str(seatNb)), seatLoc)
show()
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller )

 

 

MEMENTO

 

Le passage d’un numéro de siège en sa position dans la grille (Location) et vis-versa est réalisé au sein de deux fonctions de transformation. Le nom de telles fonctions de conversion est généralement préfixé de to (vers en anglais).

 

 

SYSTÈME DE RÉSERVATION, ÉTAPE 2 : CONNEXION À LA BASE DE DONNÉES

 

Les bases de données en ligne sont des systèmes multi-utilisateurs. Du fait que de nombreux clients manipulent les données en même temps, de nombreux problèmes de conflits d’accès peuvent survenir dans certaines situations. Les problèmes de ce type sont par nature sporadiques et donc très difficilement gérables. Le scénario suivant décrit un cas typique de conflit d’accès pour notre système de réservation : Deux clients A et B effectuent une réservation en même temps. Au début, les deux clients A et B vont interroger la base de données pratiquement en même temps pour connaître les sièges encore libres. Les deux reçoivent donc exactement la même information et possèdent donc la même vision de la situation (vert = siège libre, rouge = siège réservé).

Aussi bien A que B peut à présent, par un clic de souris, réserver temporairement des sièges qui seront alors affichés en jaunes (réservation temporaire). Ces réservations temporaires ne sont à ce stade pas encore communiquées à la base de données centrale puisque le client pourrait encore changer d’avis ou réserver davantage de sièges. Assez rapidement, le client A, plus rapide à se décider, confirme sa réservation, ce qui a pour effet de réserver les sièges définitivement dans la base de données centrale en passant les sièges à l’état réservé booked='Y'). Moins rapide à la détente, l’utilisateur B n’a pas connaissance de ces modifications du côté serveur puisque la base de données ne peut pas directement lui donner un feedback de manière non sollicitée. De plus, le client B n’est pas non plus en contact direct avec le client A. [plus... Dans un environnement client-serveur traditionnel, le serveur ne prend jamais
l’initiative de communiquer des données au client de son propre chef].
Juste un instant plus tard, le client B valide également sa réservation en cliquant sur le bouton de confirmation.

Comme le stipule la loi de Murphy « S’il y a un truc qui peut foirer, ça va foirer », il se trouve que les clients A et B ont réservé exactement les mêmes sièges. Que va-t-il se produire ? Les sièges vont-ils alors être attribués à l’utilisateur B qui est le dernier à écrire sa réservation vers la base de données ou sont programmes va-t-il se planter ?

Il existe une solution toute simple à ce problème : juste avant que la réservation temporaire ne soit confirmée, il faut interroger à nouveau la base de données pour s’assurer que les sièges demandés n’aient pas été réservés par un autre client dans l’intervalle. Si tel est malheureusement le cas, la base de données n’a pas d’autre choix que d’avertir poliment l’utilisateur B qu’il a manqué de rapidité et que les sièges qu’il demande ont déjà été attribués [plus.. Cette stratégie ne permet cependant pas de régler le problème où deux clients valident
leur réservation à quelques millisecondes d’intervalle. Pour pallier à ce scénario bien plus
difficile à gérer, il faut implémenter des mécanismes de transactions et de verrouillage
des tables du côté du serveur de base de données
]
 

Remarque : L’exécution du programme suivant présuppose que le serveur de bases de données est démarré et que la table reservation a été créée et remplie avec les données d’initialisation (voir chapitre précédent).

from dbapi import *
from gamegrid import *

serverURL = "derbyserver:localhost"
#serverURL = "derbyserver:10.1.1.123"
dbname = "casino"
username = "admin"
password = "solar"
tbl = "reservation"

def toLoc(seat):
    i = ((seat - 1) % 6) + 1
    k = ((seat - 1) // 6) + 2
    return Location(i, k)

def toSeatNb(loc):
    if loc.x < 1 or loc.x > 6 or loc.y < 2 or loc.y > 6:
       return None
    i = loc.x - 1
    k = loc.y - 2
    seatNb = k * 6 + i + 1
    return seatNb

def pressCallback(e):
    loc = toLocation(e.getX(), e.getY())
    seatNb = toSeatNb(loc)
    if seatNb == None:
        return
    seatActor = seats[seatNb - 1]
    if seatActor.getIdVisible() == 0:  # free
        seatActor.show(1) # option
        refresh()
    elif seatActor.getIdVisible() == 1:  # option
       seatActor.show(0) # free
       refresh()

class MyButton(GGButton, GGButtonListener):
    def __init__(self, imagePath):
        GGButton.__init__(self, imagePath)
        self.addButtonListener(self)
        
    def buttonClicked(self, button):
        if button == confirmBtn:
            confirm()
        if button == renewBtn:
            renew()
 
    def buttonPressed(self, button):
        pass

    def buttonReleased(self, button):
        pass
 
def renew():
    with connect(serverURL, dbname, username, password) as con:    
        cursor = con.cursor()
        sql = "SELECT * FROM " + tbl
        cursor.execute(sql)
        con.commit()
        result = cursor.fetchall()
        for record in result:
            seatNb = record[0]
            isBooked = (record[1] != 'N')
            if isBooked:
                seats[seatNb - 1].show(2)
            else:
                seats[seatNb - 1].show(0)
        refresh()
        setStatusText("View refreshed")

def confirm():
    # check if seats is still available
    with connect(serverURL, dbname, username, password) as con:    
        cursor = con.cursor()
        for seatNb in range(1, 31):
           if seats[seatNb - 1].getIdVisible() == 1:
               sql = "SELECT * FROM "  + tbl + "  WHERE seat=" + str(seatNb)
               cursor.execute(sql)
               result = cursor.fetchall()
               for record in result:
                   if record[1] == 'Y':
                        setStatusText("One of the seats are already taken.")
                        return

        isReserved = False                    
        for seatNb in range(1, 31):
           if seats[seatNb - 1].getIdVisible() == 1:
               sql = "UPDATE "  + tbl + " SET booked='Y' WHERE seat=" + \
                     str(seatNb)
               cursor.execute(sql)
               isReserved = True
           con.commit()
        renew()
        if isReserved:
            setStatusText("Reservation successful")
        else:
            setStatusText("Nothing to do")
        
makeGameGrid(8, 8, 40, None, "sprites/stage.gif", False, 
             mousePressed = pressCallback)
addStatusBar(30)
setTitle("Seat Reservation - Loading...")
confirmBtn = MyButton("sprites/btn_confirm.gif")
renewBtn = MyButton("sprites/btn_renew.gif")
addActor(confirmBtn, Location(1, 7))
addActor(renewBtn, Location(6, 7))
seats = []
for seatNb in range(1, 31):
    seatLoc = toLoc(seatNb)
    seatActor = Actor("sprites/seat.gif", 3)
    seats.append(seatActor)
    addActor(seatActor, seatLoc)
    addActor(TextActor(str(seatNb)), seatLoc)
show()
renew()
setTitle("Seat Reservation - Ready")
setStatusText("Select free seats and press 'Confirm'")
while not isDisposed():
    delay(100)
Sélectionner le code (Ctrl+C pour copier, Ctrl+V pour coller )

 

 

MEMENTO

 

 

Vous pouvez tester le système de réservation avec plusieurs fenêtres en démarrant successivement plusieurs fois TigerJython et en exécutant le programme client dans chacune d’elles. (Si tous les sièges sont réservés, effacer la table comme montré dans le chapitre précédant et refaire la création/initialisation.)

 

 

EXERCICES

 

1.


Étendre le système de réservation de telle manière qu’un clic sur le bouton de confirmation entraîne l’ouverture d’une boîte de dialogue demandant au client de saisir son numéro de client. Enregistrer le numéro de client récupéré sous forme de nombre entier dans la base de données. Utiliser l’appel inputInt(prompt, False) pour afficher la boîte de dialogue et lire la valeur puisque le deuxième paramètre False permet de ne pas fermer le programme lorsque l’utilisateur clique sur Annuler mais de plutôt retourner la valeur None.


2.


Développer un outil administrateur affichant la disposition actuelle des sièges ainsi qu’une liste des sièges réservés avec le nom de client. Il devrait de plus être possible d’annuler une réservation en cliquant sur un siège rouge.