# Application web gérant les clients d'une maison d'édition

import os, cherrypy, sqlite3

# === Définition des gestionnaires de requetes HTTP ====

#################################################
#### Gestionnaire PRINCIPAL de l'application ####
#################################################

class HomePage(object):
    "Classe proposant un menu de gestion des clients d'une base de données"

    def __init__(self):
        # Les objets gestionnaires de requetes peuvent eux-memes
        # instancier d'autres gestionnaires "esclaves" et ainsi de suite

        self.recherche = RechercheClient()  # gestionnaire des recherches de clients
        self.ajout = AjouterClient()        # gestionnaire des ajouts de clients
        self.edition = EditionClient()      # gestionnaire d'édition de clients

        
    def index(self):
        """Retourne la page d'accueil de l'application web"""
        # Cherrypy invoquera cette méthode comme URL racine du site.
        # Sa valeur de retour sera la page web contenant le menu de
        # l'application Web dont le code HTML est situé dans un
        # fichier externe.

        # Récupération du code HTML du modèle de pages:
        fichier = open('annexes/modele.html', 'r', encoding = 'utf8')
        code = fichier.read()
        fichier.close()

        # Insertion du code HTML propre au menu de l'application:
        css = "annexes/MonStyle.css"
        title = "Menu de l'application"
        # Récupération du code de la page d'accueil
        accueil = open('annexes/accueil.html', 'r', encoding = 'utf8')
        content = accueil.read()
        accueil.close()
        content = content.format(gestionRecherche = "/recherche/", gestionAjout = '/ajout/')
        foot = "OCI, Collège du Sud"
        
        code = code.format(style = css, sousTitre = title, contenu = content, pied=foot)

        # Retourner le code HTML de la page demandée:
        return code

    index.exposed = True


##################################################
#### Gestionnaire de RECHERCHE et SUPPRESSION ####
##################################################

class RechercheClient(object):
    "Classe gérant la recherche et la suppression de clients"

    def index(self):
        "Retourne le formulaire permettant la recherche des clients"

        # Récupération du code HTML du modèle de pages:
        fichier = open('annexes/modele.html', 'r', encoding = 'utf8')
        code = fichier.read()
        fichier.close()

        # Insertion du code HTML propre au formulaire de recherche:
        css = "../annexes/MonStyle.css"
        title = "Recherche de clients"
        formulaire = open('annexes/formulaire.html', 'r', encoding = 'utf8')
        content=formulaire.read()
        content=content.format(action="Rechercher")
        foot='[<a href="../">Retour au menu principal</a>]'
        formulaire.close()
        
        code = code.format(style=css, sousTitre=title, contenu=content, pied=foot)

        # Retourne le code HTML de la page demandée:
        return code

    index.exposed = True
    

    def gestion(self, NomClient, AdresseClient, NoPostalClient, VilleClient, CantonClient):
        """Gère les requetes HTTP transmises par le formulaire de recherche de clients"""

        # Dictionnaire associant chaque champ à la valeur recherchée:
        recherche = {'Nom':NomClient, 'Adresse':AdresseClient, 'NoPostal':NoPostalClient,\
                     'Ville':VilleClient, 'Canton':CantonClient}

        # Requete à créer en fonction de la valeur des champs du formulaire remplis
        requete = "SELECT * FROM Client WHERE "

        critere = 0

        for c in recherche:
            if recherche[c] != "":
                critere += 1
                requete += c + "='" + str(recherche[c]) + "' AND "

        # Si un champ au moins a été entré
        if critere > 0:

            # Suppression du dernier AND:
            requete = requete[:-5]

        # Si aucun champ n'a été complété:
        else:
            requete = "SELECT * FROM Client"

        # Ouverture de connexion et définition du curseur:
        conn = sqlite3.connect('annexes/Librairie.sq3')     # connexion à la base de données
        conn.row_factory = sqlite3.Row                      # retourner les enregistrements sous forme de tableaux associatifs
        cur = conn.cursor()                                 # initialisation du curseur
            
        # Exécution de la requete sur la base de données:
        try:
            cur.execute(requete)

        except TypeError as e:
            print("Erreur dans la requete exécutée:")
            print(requete)
            print(e)

        else:

            # Création du code HTML du tableau réceptionnant les résultats:
            table = self.html_from_sql_result(cur)

            # Récupération du code HTML du modèle de pages:
            reponse = open('annexes/modele.html', 'r', encoding='utf-8')
            code = reponse.read()
            reponse.close()

            # Insertion du code HTML propre au résultat de la recherche:
            css = "../annexes/MonStyle.css"
            title = "Résultat de la recherche"
            foot = '[<a href="index">Retour au formulaire de recherche</a>]'
            code = code.format(style=css, sousTitre=title, contenu=table, pied=foot)

            # Fermeture du curseur et de la connexion:
            cur.close()
            conn.close()

            # Envoi du fichier réponse à l'utilisateur:
            return code
               
    gestion.exposed = True

    def suppression(self, toDelete=None):
        """Supprime les enregistrements sélectionnés dans le tableau des
        résultats d'une recherche. Les identifiants des enregistrements à
        supprimer se trouvent dans la liste <toDelete> récupérée en argument."""

        # Récupération du code HTML du modèle de pages à retourner:
        fichier = open('annexes/modele.html', 'r', encoding = 'utf8')
        code = fichier.read()
        fichier.close()

        # Gestion des requetes ou aucun enregistrement à supprimer n'a été sélectionné:
        if toDelete == None:
            
            # Insertion du code HTML traitant l'erreur:
            css = "../annexes/MonStyle.css"
            title = "Erreur"
            content = "<p>Aucun enregistrement à supprimer n'a été sélectionné !</p>"
            foot="""[<a href="index">Retour au formulaire de recherche</a>]"""
        
            code = code.format(style=css, sousTitre=title, contenu=content, pied=foot)

        # Si au moins un enregistrement à supprimer a été sélectionné:
        else:

            # Si l'utilisateur n'a sélectionné qu'un enregistrement à
            # supprimer, le paramètre <toDelete> sera une chaine de
            # caractères et non une liste.

            # Construction de la requete de suppression:
            requete = "DELETE FROM Client WHERE ClientID IN ("

            # Si <toDelete> est de type <str>:
            if isinstance(toDelete, str):
                requete += toDelete+")"

            # Si <toDelete> est de type <list>:
            else:
                
                for identifiant in toDelete:
                    requete += identifiant+", "

                # Suppression de la dernière virgule et fermeture de la parenthèse:
                requete = requete[:-2]+")"

            # Ouverture de connexion et définition du curseur:
            conn = sqlite3.connect('annexes/Librairie.sq3')     # connexion à la base de données
            conn.row_factory = sqlite3.Row                      # retourner les enregistrements sous forme de tableaux associatifs
            cur = conn.cursor()                                 # initialisation du curseur
            
            # Exécution de la requete sur la base de données:
            try:
                cur.execute(requete)

            except TypeError as e:
                print("Erreur dans la requete exécutée:")
                print(requete)
                print(e)

            else:

                # Transmission définitive de la requete à la base de données et fermeture de connexion:
                conn.commit()
                cur.close()
                conn.close()
                
                # Insertion du code HTML traitant l'erreur:
                css = "../annexes/MonStyle.css"
                title = "Suppression"
                content = "<p>Le(s) enregistrement(s) sélectionné(s) a(ont) été supprimé(s) !</p>"
                foot="""[<a href="index">Retour au formulaire de recherche</a>]"""
        
                code = code.format(style=css, sousTitre=title, contenu=content, pied=foot)

        return code

    suppression.exposed = True


    def html_from_sql_result(self, cursor):
        """
        Retourne le code HTML affichant les enregistrements situés dans le curseur <cursor>
        sous la forme d'un tableau
        """

        # Récupération de tous les enregistrements du curseur sous forme de liste d'objets Row
        enregistrements = cursor.fetchall()

        try:
            # Récupération de la liste des noms de champs
            nomAttributs = enregistrements[0].keys()
        
        except:
            code = '<p> La recherche n\'a retourné aucun résultat.</p>'

        else:
            # en-tête du tableau en tant qu'élément de la classe css .sqlResultHeader
            table_header = '<tr class ="sqlResultHeader"><th>{donnees}</th><th>Supprimer</th></tr>\n'\
                           .format(donnees='</th><th>'.join(nomAttributs))

            # corps du tableau: les lignes sont des éléments de la classe css .rowclass0 ou .rowclass1
            # selon leur position dans la table (=> alternance de couleurs des lignes)
            table_enregistrements = ''
            row_index = 0

            for e in enregistrements:

                # récupération des valeurs des attributs associés à l'enregistrement traité
                valeurs = []
                for champ in nomAttributs:
                    if champ == nomAttributs[0]: # si le champs est l'identifiant, rajouter le lien pour l'édition
                        numero = '<a href="../edition/index?identifiant={no}">{ClientID}</a>'.format(no=str(e[champ]), ClientID=str(e[champ]))
                        valeurs.append(numero)
                    else:
                        valeurs.append(str(e[champ]))


                # construction de la ligne du tableau associée à l'enregistrement traité
                elements = '</td><td>'.join(valeurs)
                elements += '</td><td><input type="checkbox" name="toDelete" value="{identifiant}"/>'\
                            .format(identifiant=str(e[nomAttributs[0]])) # la case à cocher possède l'identifiant de l'enregistrement comme valeur
                table_enregistrements += '<tr class = "rowclass{index}"><td>{donnees}</td></tr>\n'.format(index=row_index, donnees=elements)
                row_index = (row_index + 1) % 2         # moduler l'index de la ligne pour assurer l'alternance des couleurs

            # ajout des boutons du formulaire dans la dernière ligne du tableau:
            nbreColonnes = len(valeurs)+1
            ligne = '<tr><td><input type="submit" value="Supprimer"/></td><td><input type="reset" value="Effacer"/></td><td colspan="{n}"></td></tr>'\
                    .format(n=str(nbreColonnes-2))
            table_enregistrements += ligne

            # création du tableau HTML inséré dans un formulaire
            debutForm = '<form action="suppression" method="GET">\n'
            tableau = '<table>\n {entete} \n {contenu} \n</table>\n'\
                    .format(entete=table_header, contenu=table_enregistrements)
            finForm = "</form>"
            code = debutForm + tableau + finForm
                
        return code


################################
#### Gestionnaire d'EDITION ####
################################   

class EditionClient(object):
    "Classe gérant l'édition de clients"

    def __init__(self):
        "Définit l'identifiant de l'enregistrement actuellement traité"

        self.id = 0 # l'identifiant du client traité est initialisé à 0

    def index(self, identifiant=0):
        """
        Permet de modifier les données d'un client à partir de son <identifiant>
        """

        self.id = identifiant   # transmission de la valeur de l'identifiant à l'attribut id

        requete = "SELECT * FROM Client WHERE ClientID={}".format(identifiant)

        # Ouverture de connexion et définition du curseur:
        conn = sqlite3.connect('annexes/Librairie.sq3')     # connexion à la base de données
        conn.row_factory = sqlite3.Row                      # retourner les enregistrements sous forme de tableaux associatifs
        cur = conn.cursor()                                 # initialisation du curseur
        
        # Exécution de la requete sur la base de données:
        try:
            cur.execute(requete)

        except TypeError as e:
            print("Erreur dans la requete exécutée:")
            print(requete)
            print(e)

        else:

            # Récupération des données du client à éditer:
            client = cur.fetchone()
            nom = client['Nom']
            adresse = client['Adresse']
            noPostal = client['NoPostal']
            ville = client['Ville']
            canton = client['Canton']

            # Récupération du code HTML du modèle de pages:
            fichier = open('annexes/modele.html', 'r', encoding = 'utf8')
            code = fichier.read()
            fichier.close()

            # Insertion du code HTML propre au formulaire d'édition de clients:
            css = "../annexes/MonStyle.css"
            title = "Formulaire d'édition"
            # Récupération du code du formulaire d'édition:
            fichier = open('annexes/formulaireEdition.html', 'r', encoding='utf8')
            formulaire = fichier.read()
            fichier.close()
            formulaire = formulaire.format(nom=nom, rue=adresse, numero=noPostal, ville=ville, canton=canton, action="Modifier")

            foot = """[<a href="../index">Retour au menu de l'application</a>]"""
            
            code = code.format(style = css, sousTitre = title, contenu = formulaire, pied=foot)

            return code        

    index.exposed = True

    def gestion(self, NomClient, AdresseClient, NoPostalClient, VilleClient, CantonClient):
        """Gère les requetes HTTP transmises par le formulaire d'édition de clients"""
    
        # Dictionnaire associant chaque champ à la valeur à éditer:
        edition = {'Nom':NomClient, 'Adresse':AdresseClient, 'NoPostal':NoPostalClient,\
                     'Ville':VilleClient, 'Canton':CantonClient}

        # Requete à créer en fonction de la valeur des champs du formulaire remplis
        requete = "UPDATE Client SET "

        for c in edition:
            requete += c + "='" + str(edition[c]) + "', "

        # Suppression de la dernière virgule
        requete = requete[:-2]

        # Identification de l'enregistrement à mettre à jour
        requete += " WHERE ClientID={}".format(self.id)

        # Ouverture de connexion et définition du curseur:
        conn = sqlite3.connect('annexes/Librairie.sq3')     # connexion à la base de données
        conn.row_factory = sqlite3.Row                      # retourner les enregistrements sous forme de tableaux associatifs
        cur = conn.cursor()                                 # initialisation du curseur
        
        # Exécution de la requete sur la base de données:
        try:
            cur.execute(requete)

        except TypeError as e:
            print("Erreur dans la requete exécutée:")
            print(requete)
            print(e)

        else:
            # Transmission définitive de la requete et fermeture de la connexion:
            conn.commit()
            cur.close()
            conn.close()

            # Récupération du code HTML du modèle de pages:
            fichier = open('annexes/modele.html', 'r', encoding='utf-8')
            code = fichier.read()
            fichier.close()

            # Insertion du code HTML informant que la modification a été faite:
            css = "../annexes/MonStyle.css"
            title = "Edition de clients"
            content = "<p>Les modifications ont été apportées à l'enregistrement sélectionné !</p>"
            foot="""[<a href="../index">Retour au menu principal</a>]"""
        
            code = code.format(style=css, sousTitre=title, contenu=content, pied=foot)

        return code
            
    gestion.exposed = True


##############################
#### Gestionnaire d'AJOUT ####
##############################


class AjouterClient(object):
    "Classe gérant l'ajout de clients"
    
    def index(self):
        "Retourne le formulaire permettant l'ajout d'un client"

        # Récupération du code HTML du modèle de pages:
        fichier = open('annexes/modele.html', 'r', encoding = 'utf8')
        code = fichier.read()
        fichier.close()

        # Insertion du code HTML propre au formulaire de recherche:
        css = "../annexes/MonStyle.css"
        title = "Ajout de clients"
        formulaire = open('annexes/formulaire.html', 'r', encoding = 'utf8')
        content=formulaire.read()
        content=content.format(action="Enregistrer")
        foot='[<a href="../">Retour au menu principal</a>]'
        formulaire.close()
        
        code = code.format(style=css, sousTitre=title, contenu=content, pied=foot)

        # Retourne le code HTML de la page demandée:
        return code

    index.exposed = True

    def gestion(self, NomClient, AdresseClient, NoPostalClient, VilleClient, CantonClient):
        """Gère les requetes HTTP transmises par le formulaire d'ajout de clients"""

        # Récupération du code HTML du modèle de pages nécessaire à la construction de la réponse:
        fichier = open('annexes/modele.html', 'r', encoding = 'utf8')
        code = fichier.read()
        fichier.close()

        # Liste des valeurs des champs:
        ajout = [NomClient, AdresseClient, VilleClient, CantonClient, NoPostalClient]

        # Vérification du nombre de champs complétés:

        complet = 0

        i=0

        while i < len(ajout):
            if ajout[i] != "":
                complet += 1
            i += 1


        # Si un champ manque, transmission d'un message d'erreur:
        if complet < len(ajout):
            
            # Insertion du code HTML traitant l'erreur:
            css = "../annexes/MonStyle.css"
            title = "Erreur"
            content = "<p>Tous les champs du formulaire doivent être remplis !</p>"
            foot="""[<a href="index">Retour au formulaire d'ajout</a>]"""
        
            code = code.format(style=css, sousTitre=title, contenu=content, pied=foot)

        # Sinon ajouter le client à la base de données:
        else:

            requete = "INSERT INTO Client(Nom, Adresse, Ville, Canton, NoPostal) VALUES("

            i=0
            while i < len(ajout):
                requete += "'{valeur}', ".format(valeur=str(ajout[i]))
                i += 1

            # Suppression de la dernière virgule et ajout de la parenthèse fermante
            requete = requete[:-2]+')'

            # Ouverture de connexion et définition du curseur:
            conn = sqlite3.connect('annexes/Librairie.sq3')     # connexion à la base de données
            conn.row_factory = sqlite3.Row                      # retourner les enregistrements sous forme de tableaux associatifs
            cur = conn.cursor()                                 # initialisation du curseur
                
            # Exécution de la requete sur la base de données:
            try:
                cur.execute(requete)

            except TypeError as e:
                print("Erreur dans la requete exécutée:")
                print(requete)
                print(e)

            else:
                # Insertion du code HTML propre au résultat de la recherche:
                css = "../annexes/MonStyle.css"
                title = "Ajout effectué"
                content = "<p>Le client a été ajouté à la table</p>"
                foot = """[<a href="index">Retour au formulaire d'ajout</a>]"""
                code = code.format(style=css, sousTitre=title, contenu=content, pied=foot)

                # Transmission de la requete à la base de donnée et
                # fermeture du curseur et de la connexion:
                conn.commit()
                cur.close()
                conn.close()

        # Envoi du fichier réponse à l'utilisateur:
        return code
               
    gestion.exposed = True


# === PROGRAMME PRINCIPAL ====

# Reconfiguration et démarrage du serveur web :
cherrypy.config.update({"tools.staticdir.root":os.getcwd()})
cherrypy.quickstart(HomePage(), config='tutoriel.conf')
        
