# 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."""

        # code à compléter

        return """<p>Cette méthode 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. Notons que le
        paramètre <toDelete> sera de type <None> si aucun
        enregistrement n'a été précédemment sélectionné, de type <str>
        si un seul élément a été sélectionné et de type <list> si
        plusieurs éléments ont été sélectionnés.  </p>"""

    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></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:
                    valeurs.append(str(e[champ]))

                # construction de la ligne du tableau associée à l'enregistrement traité
                elements = '</td><td>'.join(valeurs)     
                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

            code = '<table>\n {entete} \n {contenu} \n</table>\n'\
                    .format(entete=table_header, contenu=table_enregistrements)

        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 en cours de mise à jour"

        self.id = 0 # l'identifiant du client traité est initialisé à
                    # 0 mais sera modifié à chaque invocation de la méthode index


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

        # code à compléter

        return """<p>Cette méthode gestionnaire de requete récupère
        l'identifiant du client dont il faut modifier des données,
        fait une requete sur la base de données afin de récupérer
        toutes les données actuelles du client et crée un formulaire
        contenant ses données sur la base du fichier
        annexes/formulaireEdition.html. Les modifications apportées au
        client via le formulaire seront traitées par une autre méthode
        de gestion </p>"""

    index.exposed = True

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

        # code à compléter

        return """<p>Cette méthode gère les données transmises par le
        formulaire d'édition en faisant une mise à jour du client
        traité dans la base de données selon les données entrées dans
        le formulaire</p>"""
    
        
    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')
        
