Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PyQt Python Discussion :

PYQT, tableWidget et sqlite3


Sujet :

PyQt Python

  1. #1
    Membre à l'essai
    PYQT, tableWidget et sqlite3
    Bonjour à tous
    je souhaite récupérer le texte d'une table connectée avec sqlite3 afin de récupérer le rowid pour mettre à jour ma table.
    J'arrive à récupérer les lignes et colonnes de la tableWidget, changer un texte en mettant manuellement le rowid mais pour la récupération du texte j'ai toujours l'objet en sortie. Comment faire ?
    je débute et j'ai souvent ce problème

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
        def changeCellule(self):
            row = self.tableWidget_2.currentRow()
            colonne = self.tableWidget_2.currentColumn()
            index = self.tableWidget_2.currentIndex()


    Merci

  2. #2
    Expert éminent
    Bonjour,

    Pour avoir une relation d'affichage et de mise à jour avec une base de données, ce n'est pas un QTableWidget qu'il faut mais un QTableView.

    Et la logique, c'est d'établir une relation permanente et automatique entre la base de données et la partie graphique avec un "modèle", par exemple un QSqlRelationalTableModel.

    Et l'édition des données pour faire des modifications nécessite un "delegate", comme par exemple un QSqlRelationalDelegate

    Avec tout ça, on peut faire des applications en même temps très puissantes et très pratiques à utiliser, mais le code peut être assez complexe.

    Regarde déjà la discussion ici: https://www.developpez.net/forums/d1875215/autres-langages/python/gui/pyqt/afficher-informations-bdd-table-modele-pyqt5/, et essaie d'exécuter le code pour comprendre comment ça marche.

    Doc de QtSql: https://doc.qt.io/qt-5/qtsql-index.html
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  3. #3
    Membre à l'essai
    affichage table dans Qtableview
    Bonjour et merci pour la réponse,
    j'ai repris ton code pour comprendre et il est très clair, un grand merci.
    je me connecte mais je n'arrive pas à afficher la table ? il manque une ligne de code ?

    merci

  4. #4
    Expert éminent
    Citation Envoyé par salpat Voir le message
    je me connecte mais je n'arrive pas à afficher la table ? il manque une ligne de code ?
    Je viens de refaire la manip à partir d'un simple copier-coller du code affiché dans le forum:
    - code de création de la base => création de "mabase.db3" => ok
    - code mini => affichage correct de la table => ok
    - code plus complet => affichage correct de la table => ok

    Reste un éventuel problème de version ou d'OS? J'utilise Python 3.7, PyQt5 5.11 sur Windows 10.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  5. #5
    Membre à l'essai
    Qtableview SQLITE ET LISTE
    Bonjour,
    Encore merci c'est ce que je voulais, pouvez-vous m'orienter, j'aimerai pouvoir cliquer sur la case et proposer une liste ?
    Merci

  6. #6
    Membre à l'essai
    combobox tableview sqlite
    Bonjour,
    finalement en reprenant l'exemple c'est assez simple,
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    editor = QtWidgets.QComboBox(parent)
                editor.addItem("test")
                editor.addItem("test1")

    encore merci à tyrtamos

  7. #7
    Expert éminent
    Limiter les modifications d'un champ d'une table à une liste de valeurs, est un problème souvent rencontré qui se résout d'une autre manière, à mon avis, plus élégante:

    - ajouter une nouvelle table dans la base de données, qui ne comporte qu'un seul champ: le liste des valeurs imposées

    - créer une "contrainte de clé étrangère" dans la base de données entre le champ concerné de la table 1 et le champ de la nouvelle table

    Normalement, à l'affichage de la table dans le QTableView, l'édition (double-clic) d'une donnée du champ concerné de la 1ère table doit faire apparaître un QCombobox avec la liste des valeurs imposées de la 2ème table.

    D'ailleurs, cette solution est plus du SQL que du Python. Et bien que sqlite3 ne suive que la norme SQL de 1992, il est déjà très puissant (en plus d'être très rapide), et on peut exploiter pas mal de fonctionnalités sophistiquées de SQL.

    Si ça t’intéresse, je devrais pouvoir te faire un petit exemple.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  8. #8
    Membre à l'essai
    liste sqlite tableview
    Bonsoir,
    Avec plaisir un petite exemple de clé étrangère pourrait me servir dans l'avenir, d’ailleurs avec additems je n'y arrive pas alors qu'avec additem oui. De plus graphiquement j'aimerai au doubleclick changer la couleur row.

    Comme je n'ai pas toutes les bases j'aimerai ( en cherchant tout seul ) , doubleclick et ouvrir la ligne dans une nouvelle fenêtre, peux-tu simplement me donner la direction ?
    Encore merci

  9. #9
    Expert éminent
    Bonjour,

    Voilà un petit exemple d'affichage d'une table d'une base de données sqlite3 avec un QTableView, et avec une contrainte de clés étrangères.

    Voilà la base de données pour l'exemple. Elle est sous forme de texte encodé utf-8, appelé ici "mabase.txt":

    Code SQL :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    CREATE TABLE nations (
            nation TEXT NOT NULL PRIMARY KEY);
     
    INSERT INTO nations VALUES ('AFGHANISTAN');
    INSERT INTO nations VALUES ('AFRIQUE DU SUD');
    INSERT INTO nations VALUES ('ALBANIE');
    INSERT INTO nations VALUES ('ALGERIE');
    INSERT INTO nations VALUES ('ALLEMAGNE');
    INSERT INTO nations VALUES ('ANDORRE');
     
    CREATE TABLE clients (
            idclient TEXT NOT NULL PRIMARY KEY, 
            nom TEXT NOT NULL, 
            prenom TEXT DEFAULT '',
            nation TEXT,
            FOREIGN KEY(nation) REFERENCES nations(nation));
     
    INSERT INTO clients VALUES (666, 'DTTXL', 'GHUATZFLBT', 'AFRIQUE DU SUD');
    INSERT INTO clients VALUES (442, 'BRGKUKN', 'SBNJERZ', 'ALGERIE');
    INSERT INTO clients VALUES (334, 'APJHB', 'BETKNGPS', 'AFRIQUE DU SUD');
    INSERT INTO clients VALUES (186, 'CRJAU', 'OUOCZH', 'ANDORRE');
    INSERT INTO clients VALUES (313, 'BADJFSZQYW', 'DSQJRNPVD', 'ALBANIE');
    INSERT INTO clients VALUES (64, 'KQBKN', 'LTKLDYQALQ', 'ANDORRE');
    INSERT INTO clients VALUES (699, 'BTRWHDEHT', 'WAVRT', 'ANDORRE');
    INSERT INTO clients VALUES (694, 'CHSRGQVR', 'FSDPJZF', 'ANDORRE');
    INSERT INTO clients VALUES (777, 'DAEEJHN', 'HBKGGOLNW', 'AFGHANISTAN');
    INSERT INTO clients VALUES (676, 'QKUDUOKLHQ', 'SPLGCTGUPI', 'AFGHANISTAN');


    Remarque: tu vois comment on ajoute la contrainte au champ "nation" de la table "clients", qui ne devra contenir que des nations présentes dans le champ "nation" de la table "nations". La manipulation de la base de données par le pilote normal de Python "sqlite3" en tiendra compte, mais curieusement, PyQt5 n'en tiendra pas compte, et il faudra le renseigner autrement! Par ailleurs, la table "nations" doit être crée AVANT la table "clients" (c'est logique).

    A noter aussi que le champ "nation" de la table "clients" pourrait avoir un identifiant de nation au lieu du nom de la nation elle-même, mais moi je préfère cette solution.

    Pour créer la base de données correspondante "mabase.db3", et plutôt que de modifier un code Python de création à chaque modification, je te propose le code de conversion "execscriptsql.py" que j'utilise:

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    import sys
    import os
    import sqlite3
    from unicodedata import normalize
     
    #############################################################################
    def ouvrebase(basesql):
        """Ouvre la base 'basesql' et retourne la connexion ouverte
           retourne None en cas d'échec 
        """
        try:
            cnx = sqlite3.connect(basesql)
            cnx.execute("PRAGMA foreign_keys=on;")  # active le foreign key
            # cnx.text_factory = str    # permet les E/S avec la base en utf-8
        except sqlite3.Error:
            cnx = None
        return cnx
     
    #############################################################################
    def fermebase(cnx, cur=None):
        """Ferme la base ouverte avec la connexion cnx et éventuellement ferme le 
           curseur
        """
        if cur != None:
            cur.close()
        cnx.close()
     
    #############################################################################
    def execscript(srce, dest=None):
        """Exécute un script SQL instruction par instruction, et crée le fichier 
           sqlite3 correspondant
           S'arrête à la 1ère erreur rencontrée
        """
     
        #========================================================================
        # initialise la destination du traitement
     
        if dest is None:
            dest = ":memory:" # => base de données en RAM
        else:
            if os.path.exists(dest):
                os.remove(dest) # supprime le fichier dest s'il existe déjà
     
        #========================================================================
        # crée et ouvre le nouveau fichier sqlite3
     
        cnx = ouvrebase(dest)
        cur = cnx.cursor()
     
        #========================================================================
        # lit le script
     
        S = []
        codage = 'utf-8-sig'  # pour neutraliser le BOM s'il y en a un
        with open(srce, 'r', encoding=codage) as fs:
            for ligne in fs:
                S.append(ligne)
        lg = len(S)  # nb total de lignes
        print(lg, "lignes")
     
        #========================================================================
        # exécute le script, instruction par instruction
     
        c = 0  # compteur d'erreurs
        buffer = ""  # contiendra chaque instruction SQL complète
        i = 0  # compteur de ligne du fichier script lu
        while i < lg:
            if S[i].strip() == "":
                i += 1
                continue # ligne vide non prise en compte
            if buffer == "":
                i2 = i + 1  # numéro de ligne du début d'instruction
            buffer += S[i]
            if sqlite3.complete_statement(buffer):
                buffer = buffer.strip()
                print("=>", i2, buffer)
                if buffer not in ["BEGIN TRANSACTION;", "COMMIT;"]:
                    try:
                        cur.execute(buffer)
                        cnx.commit()
                    except sqlite3.Error as msgerr:
                        c += 1
                        print("ligne:", i+1, "erreur:", "" + msgerr.args[0], "==>", buffer)
                        break
                buffer = "" # réinitialise le buffer
            i += 1
     
        #========================================================================
        # ferme la base et le curseur
     
        fermebase(cnx, cur)
     
    #############################################################################
    if __name__ == "__main__":
     
        srce = "mabase.txt"
        dest = "mabase.db3"
        execscript(srce, dest)


    Son exécution prend en source le script SQL "mabase.txt" et crée le fichier "mabase.db3". On pourrait avoir un code plus simple, mais celui-ci à le gros avantage de détecter et afficher certaines erreurs et s'arrête d'ailleurs à la 1ère d'entre elles.

    Voilà maintenant le code du programme de visualisation de la table clients. J'ai repris le 1er code du fil dont j'ai donné le lien, et j'ai fait pas mal de modifications (avec beaucoup de commentaires):

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    import sys
    from PyQt5 import (QtWidgets, QtCore, QtSql)
     
    #############################################################################
    def ouvrebaseqt(basesql):
        """ouvre la base 'basesql' avec le pilote "QSQLITE" pour sqlite3, et 
           renvoie la connexion (ou None si échec)
        """
        db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        db.setDatabaseName(basesql)
        if not db.open():
            db = None
        return db
     
    #############################################################################
    def fermebaseqt(db):
        if db!=None:
            db.close()
     
    #############################################################################
    class VoirTableSql(QtWidgets.QMainWindow):
     
        def __init__(self, basesql, nomtable, relations=[], titres=[], parent=None):
            super().__init__(parent)
     
            self.setWindowTitle("Affichage de la table %s" % (nomtable,))
            self.resize(400, 320)
     
            # enregistre les arguments passés
            self.basesql = basesql # base de données
            self.nomtable = nomtable # nom de la table à afficher
            self.relations = relations # liste des contraintes de clés étrangères
            self.titres = titres # liste des titres des colonnes de la table
     
            # ouvre la base SQL
            self.db = ouvrebaseqt(self.basesql)
            if self.db == None:
                QtWidgets.QMessageBox.critical(self,
                            "Ouverture de la base de données",
                            "Erreur d'ouverture")
                self.close()
                sys.exit()
     
            # crée le modèle et sa liaison avec la base SQL ouverte
            self.model = QtSql.QSqlRelationalTableModel(self, self.db)
     
            # stratégie en cas de modification de données par l'utilisateur
            self.model.setEditStrategy(QtSql.QSqlRelationalTableModel.OnManualSubmit)
     
            # désigne la table à afficher
            self.model.setTable(self.nomtable)
     
            # enregistre les contraintes de clés étrangères (plusieurs possible)
            for col, tab, champ1, champ2 in self.relations:
                self.model.setRelation(col, QtSql.QSqlRelation(tab, champ1, champ2))
                # force le chargement complet de la table contenant la clé étrangère
                modelref = self.model.relationModel(col)
                while modelref.canFetchMore():
                    modelref.fetchMore()
     
            # peuple le modèle avec les données de la table
            self.model.select() 
     
            # trie si nécessaire selon la colonne 0
            self.model.sort(0, QtCore.Qt.AscendingOrder) # ou DescendingOrder
     
            for i, titre in enumerate(self.titres):
                self.model.setHeaderData(i, QtCore.Qt.Horizontal, titre)
     
            # crée la table graphique et son lien avec le modèle
            self.vuetable = QtWidgets.QTableView(self)
            self.vuetable.setModel(self.model)
     
            # active le tri en cliquant sur les têtes de colonnes
            self.vuetable.setSortingEnabled(True)
     
            # met un delegate standard pour l'édition
            self.vuetable.setItemDelegate(QtSql.QSqlRelationalDelegate(self.vuetable))
     
            # ajuste hauteur lignes et largeur colonnes selon contenus
            self.vuetable.resizeColumnsToContents()
            self.vuetable.resizeRowsToContents()
     
            # crée un bouton pour enregistrer les modifications dans la base
            self.bouton1 = QtWidgets.QPushButton("Enregistrer les modifications", self)
            self.bouton1.clicked.connect(self.appliquer)
     
            # crée un bouton pour annuler les modifications non enregistrées
            self.bouton2 = QtWidgets.QPushButton("Annuler les modifications", self)
            self.bouton2.clicked.connect(self.annuler)
     
            # positionne les widgets dans la fenêtre QMainWindow
            self.setCentralWidget(QtWidgets.QFrame())
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.bouton1, 0, 0)
            posit.addWidget(self.bouton2, 0, 1)
            posit.addWidget(self.vuetable, 1, 0, 1, 2)
            self.centralWidget().setLayout(posit)
     
     
        #========================================================================
        def appliquer(self):
            """Enregistre les modifications des données
            """
            if self.model.submitAll():
                # ajuste hauteur lignes et largeur colonnes selon contenus
                self.vuetable.resizeColumnsToContents()
                self.vuetable.resizeRowsToContents()
                # message ok
                QtWidgets.QMessageBox.information(self,
                    "Enregistrement des modifications",
                    "Enregistrement terminé")
            else:
                # message erreur
                QtWidgets.QMessageBox.warning(self,
                    "Enregistrement des modifications",
                    "Erreur: %s" % self.model.lastError().text())
     
        #========================================================================
        def annuler(self):
            """Annule les modifications des données faites avant enregistrement
            """
            self.model.revertAll()
     
        #========================================================================
        def closeEvent(self, event=None):
            """Méthode appelée automatiquement à la fermeture de la fenêtre
            """
            # ferme la base 
            fermebaseqt(self.db) 
            # et accepte la fermeture de la fenêtre
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
     
        nombase = "mabase.db3"
        table = "clients"
        relations = [[3, 'nations', 'nation', 'nation']]
        titres = ['idclient', 'nom', 'prénom', 'nation']
     
        fen = VoirTableSql(nombase, table, relations, titres)
        fen.show()
     
        sys.exit(app.exec_())


    Comme vu plus haut, il faut renseigner PyQt5 de la contrainte de clés étrangères portant sur la table à afficher. On le fait en créant la liste des contraintes comme suit:

    [3, 'nations', 'nation', 'nation'] signifie:
    3 => colonne d'indice 3 (ça commence à 0) de la table à afficher (donc "clients")
    'nations' => nom de la table contenant la clé étrangère
    'nation' => champ de la table 'nations' auquel correspond le contenu du champ 'nation' de la table 'clients'
    'nation' => champ dont le contenu sera affiché en remplacement

    A noter qu'il pourrait y avoir plusieurs contraintes qu'il faudrait écrire ainsi: [[3, 'nations', 'nation', 'nation'], [2ème contrainte], ...]

    Et voilà ce que ça donne, avec un double-clic sur l'une des cellules:



    Cela garantit qu'en modifiant l'une des cellules du champ 'nation' de la table 'clients', les choix sont strictement limités au champ 'nation' de la table 'nations'.

    Ok?
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  10. #10
    Membre à l'essai
    import boite dialogue pyqt5
    Bonjour et encore merci à tyrtamos, je n'ai pas essayé mais ça semble clair et bien renseigné, tu devrais te lancer dans la formation.
    J'ai créé un fichier pour mes boites de dialogue mais j'ai une erreur d'import que je ne comprend pas ?
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    class Class_Dialog(QWidget):
    def showDialog(self):
            home_dir = str(Path.home( ))
            fname = QFileDialog.getOpenFileName(self, 'Ouvrir le fichier', home_dir)


    que je veux afficher dans
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):


    le message d'erreur
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
     File "src\main\python\package\api\dialogs.py", line 53, in showDialog
        fname = QFileDialog.getOpenFileName(self, 'Ouvrir le fichier', home_dir)
    TypeError: getOpenFileName(parent: QWidget = None, caption: str = '', directory: str = '', filter: str = '', initialFilter: str = '', options: Union[QFileDialog.Options, QFileDialog.Option] = 0): argument 1 has unexpected type 'bool'


    En vous remerciant,

  11. #11
    Expert éminent
    Bonjour,

    Citation Envoyé par salpat Voir le message
    tu devrais te lancer dans la formation
    Je ne suis pas formateur de profession, mais j'en ai beaucoup fait dans mon domaine professionnel...

    Citation Envoyé par salpat Voir le message
    J'ai créé un fichier pour mes boites de dialogue mais j'ai une erreur d'import que je ne comprend pas ?
    Il faut en dire un peu plus sur l'organisation du code. En effet, le code "class Ui_MainWindow(object):" est manifestement issu du Designer de Qt, et on ne modifie jamais ce code: on se contente de l'importer. Et dans le code qui l'importe, on peut, bien sûr, ajouter ce qu'on n'a pas pu mettre avec le Designer, et on peut utiliser comme on le souhaite les widgets créés sous le Designer.

    Voilà un petit code de principe. On considère ici l'importation de "monprogramme_ui.py" issu du Designer et traité par pyuic5:

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    import sys
     
    from PyQt5 import QtWidgets #,QtCore, QtGui, etc...
     
    from monprogramme_ui import Ui_MainWindow
     
    ##############################################################################
    class Fenetre(QtWidgets.QMainWindow):
     
        def __init__(self, parent=None):
            super().__init__(parent) # appel à l’ancêtre QMainWindow
     
            # les 2 lignes suivantes permettent de profiter de tout ce qui a été créé sous QtDesigner 
            self.ui = Ui_MainWindow()
            self.ui.setupUi(self)
     
            # tous les objets définis sous QtDesigner seront accessibles avec
            # self.ui.nomdelobjet        
     
            # suite: définir ici tout ce qui est nécessaire au programme et qui 
            # n'a pas été créé sous QtDesigner
     
     
    ############################################################################## 
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        fenetre = Fenetre()
        fenetre.show()
        sys.exit(app.exec_())


    J'ai utilisé "self.ui" pour accéder aux objets du Designer. Ce n'est pas obligatoire, mais je préfère car cela permet d'exclure un conflit d'adresse entre les objets du code importé et ceux définis dans le code qui l'importe.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

###raw>template_hook.ano_emploi###