IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
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 :

Sur Classer QSqlTableModel ou QSqlQueryModel


Sujet :

PyQt Python

  1. #1
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut Sur Classer QSqlTableModel ou QSqlQueryModel
    Bonjour à tous,
    Je présente rapidement mon projet pour lequel j'essaye de respecter le principe MCV :
    J'ai une base de données que je souhaite présenter à l'intérieur d'uen QTableView. Cette table est en lecture seule. Les sélections sur cette table se font par ligne uniquement (pas de sélection de cellule seule). La sélection d'une ligne entraîne le chargement des données de la ligne dans un formulaire présent à coté du tableau (système QMapperDataWidget). Des boutons permettent d'enregistrer les modifications du formulaire, supprimer, insérer un enregistrement, ... et grâce à la magie de PyQt toutes les modifications se font automatiquement dans la base sans que je fasse rien.

    Le problème qui se pose est le suivant :
    Tout fonctionne correctement si je travaille sur une table complète de ma base : model.selectTable("clients"). Mais si je souhaite ne travailler que sur une partie de ma table en utilisant une requête : model.setQuery("SELECT * FROM 'clients' WHERE local=0"), les données apparaissent bien dans le TableView et dans le formulaire, si je modifie le formulaire et enregistre les données modifiées apparaissent bien dans le tableau, par contre l'enregistrement dans la base ne se fait plus.

    J'ai bien tenté de créer une classe modèle en surclassant QSqlTableModel et QSqlQueryModel, mais je ne suis pas à l'aise avec cette solution et ne trouve pas d'exemples qui utilise cette technique avec une base de données. De plus je me demande si c'est une solution étant donné que mon modèle est bien modifié, mais que c'est l'enregistrement dans la base qui ne l'est pas.
    Voici le code (j'ai mis un fichier SQL en fichier joint avec ma table installé sur SQLITE).

    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
     
    import sys
    from tableModel import Ui_MainWindow
    from PySide6.QtSql import QSqlDatabase, QSqlTableModel
    from PySide6.QtWidgets import QMainWindow, QApplication, QAbstractItemView, QDataWidgetMapper
     
     
    class controler(QMainWindow):
        def __init__(self):
            super().__init__()
            self.nouvelEnregistrement = False  # à utiliser lors du click sur nouveau et annuler
     
            # ouverture d'une base de données
            self.base = QSqlDatabase.addDatabase("QSQLITE")
            self.base.setDatabaseName("test.db")
            if self.base.open():
                print(self.base.lastError())
     
            # création d'un modèle sur la table de la base
            self.model_table = QSqlTableModel()
            self.model_table.setTable("clients")
            self.model_table.select()
     
            # creation de la vue
            self.fenetre = Ui_MainWindow()
            self.fenetre.setupUi(self)
            self.fenetre.tableView.setModel(self.model_table)
     
            # options de la vue
            self.fenetre.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)  # selection de ligne uniquement
     
            # creation d'un mapper pour le formulaire
            self.mapper = QDataWidgetMapper()
            self.mapper.setModel(self.model_table)
            self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)  # enregistrement par le bouton enregistrer
            self.mapper.addMapping(self.fenetre.lineEdit, 0)
            self.mapper.addMapping(self.fenetre.lineEdit_2, 1)
            self.mapper.addMapping(self.fenetre.lineEdit_3, 2)
            self.mapper.addMapping(self.fenetre.lineEdit_4, 3)
            self.mapper.toFirst()
     
            # gestion des évènements
            self.fenetre.tableView.selectionModel().selectionChanged.connect(self.on_selectionChanged_connected)
            self.fenetre.pushButton_enregistrer.clicked.connect(self.on_pushButton_enregistrer_connected)
            self.fenetre.pushButton_supprimer.clicked.connect(self.on_pushButton_supprimer_connected)
            self.fenetre.pushButton_nouveau.clicked.connect(self.on_pushbutton_nouveau_connected)
            self.fenetre.pushButton_annuler.clicked.connect(self.on_pushButton_annuler_connected)
     
            # sélection de la première ligne
            self.fenetre.tableView.selectRow(0)
     
        def on_selectionChanged_connected(self, selected, deselected):
            """
            Modification des données du formulaire en fonction de la ligne sélectionnée
            """
            self.mapper.setCurrentIndex(selected.indexes()[0].row())
            pass
     
        def on_pushButton_enregistrer_connected(self):
            """
            Enregistrement des données du formulaire
            """
            self.mapper.submit()
            self.nouvelEnregistrement = False
            pass
     
        def on_pushButton_supprimer_connected(self):
            """
            Suppression de la ligne du modèle et sélection de la ligne précédente
            """
            numLigne = self.fenetre.tableView.currentIndex().row()
            self.model_table.removeRow(numLigne)
            self.fenetre.tableView.selectRow(numLigne-1)
            self.model_table.select()
            pass
     
        def on_pushbutton_nouveau_connected(self):
            """
            Dans un premier temps, création d'une nouvelle ligne avec aucune donnée, sauf l'id qui
            est un champ clé primaire autoincrémenté dans la base, les données seront écrites
            dans le formulaire puis enregistrées
            """
            self.nouvelEnregistrement = True
            record = self.model_table.record()
            record.setGenerated("id", False)
            self.model_table.insertRecord(-1, record)
            self.fenetre.tableView.selectRow(self.model_table.rowCount()-1)
            pass
     
        def on_pushButton_annuler_connected(self):
            """
            Test pour savoir si l'enregistrement d'un nouvel enregistrement est en cours, si c'est
            le cas, on supprime la ligne.
            Puis, on sélectionne la ligne suivante et on sélectionne à nouveau la ligne en cours pour
            annuler les changements dans le formulaire
            """
            if self.nouvelEnregistrement:
                self.on_pushButton_supprimer_connected()
                self.nouvelEnregistrement = False
            indexEnCours = self.mapper.currentIndex()
            self.fenetre.tableView.selectRow(indexEnCours+1)
            self.fenetre.tableView.selectRow(indexEnCours)
            pass
     
     
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        fenetre = controler()
        fenetre.show()
        sys.exit(app.exec())
    clients.sql

  2. #2
    Membre actif
    Homme Profil pro
    Animateur Numérique
    Inscrit en
    Février 2013
    Messages
    140
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Animateur Numérique
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Février 2013
    Messages : 140
    Points : 208
    Points
    208
    Par défaut
    Salut,

    Difficile de tester sans le fichier Ui_MainWindow.py

  3. #3
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 483
    Points : 9 282
    Points
    9 282
    Billets dans le blog
    6
    Par défaut
    Bonjour

    Citation Envoyé par jefmiss Voir le message
    Mais si je souhaite ne travailler que sur une partie de ma table en utilisant une requête : model.setQuery("SELECT * FROM 'clients' WHERE local=0"), les données apparaissent bien dans le TableView et dans le formulaire, si je modifie le formulaire et enregistre les données modifiées apparaissent bien dans le tableau, par contre l'enregistrement dans la base ne se fait plus.
    A ma connaissance, l'utilisation de SELECT crée une nouvelle table provisoire qui n'est plus automatiquement reliée à la table d'origine. Si on veut conserver les données d'une session à l'autre, rien n'empêche de créer cette table (non provisoire) comme une nouvelle table de la base de données.

    Pour avoir une restriction d'affichage de la table normale tout en restant "branché" sur cette table pour les modifications, voir plutôt "QSortFilterProxyModel" qui restera "en ligne" dans la transmission QTableView <=> base de données.

    A noter que si vous voyez la possibilité d'évoluer avec plusieurs tables dans la base de données, je vous suggère d'utiliser plutôt "QSqlRelationalTableModel" qui vous permettra d'utiliser des contraintes d'intégrité entre les tables.

  4. #4
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Citation Envoyé par Diablo76 Voir le message
    Salut,

    Difficile de tester sans le fichier Ui_MainWindow.py
    Excuses moi, la facilité de création avec Pyside-designer me fait parfois oublié ce qu'il y a derrière.
    Voici le code du fichier en question ci dessous, si tu as une idée complémentaire à tyrtamos je suis preneur :
    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
    150
    151
    152
    153
    154
    155
    156
    157
    # -*- coding: utf-8 -*-
     
    ################################################################################
    ## Form generated from reading UI file 'test_tableModel.ui'
    ##
    ## Created by: Qt User Interface Compiler version 6.5.1
    ##
    ## WARNING! All changes made in this file will be lost when recompiling UI file!
    ################################################################################
     
    from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
        QMetaObject, QObject, QPoint, QRect,
        QSize, QTime, QUrl, Qt)
    from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
        QFont, QFontDatabase, QGradient, QIcon,
        QImage, QKeySequence, QLinearGradient, QPainter,
        QPalette, QPixmap, QRadialGradient, QTransform)
    from PySide6.QtWidgets import (QApplication, QFormLayout, QHBoxLayout, QHeaderView,
        QLabel, QLineEdit, QMainWindow, QPushButton,
        QSizePolicy, QSpacerItem, QStatusBar, QTableView,
        QVBoxLayout, QWidget)
     
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            if not MainWindow.objectName():
                MainWindow.setObjectName(u"MainWindow")
            MainWindow.resize(792, 400)
            self.centralwidget = QWidget(MainWindow)
            self.centralwidget.setObjectName(u"centralwidget")
            self.layoutWidget = QWidget(self.centralwidget)
            self.layoutWidget.setObjectName(u"layoutWidget")
            self.layoutWidget.setGeometry(QRect(0, 18, 781, 341))
            self.verticalLayout = QVBoxLayout(self.layoutWidget)
            self.verticalLayout.setObjectName(u"verticalLayout")
            self.verticalLayout.setContentsMargins(0, 0, 0, 0)
            self.horizontalLayout = QHBoxLayout()
            self.horizontalLayout.setObjectName(u"horizontalLayout")
            self.tableView = QTableView(self.layoutWidget)
            self.tableView.setObjectName(u"tableView")
            self.tableView.setMinimumSize(QSize(500, 0))
     
            self.horizontalLayout.addWidget(self.tableView)
     
            self.formLayout = QFormLayout()
            self.formLayout.setObjectName(u"formLayout")
            self.label = QLabel(self.layoutWidget)
            self.label.setObjectName(u"label")
     
            self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label)
     
            self.lineEdit = QLineEdit(self.layoutWidget)
            self.lineEdit.setObjectName(u"lineEdit")
     
            self.formLayout.setWidget(1, QFormLayout.FieldRole, self.lineEdit)
     
            self.label_2 = QLabel(self.layoutWidget)
            self.label_2.setObjectName(u"label_2")
     
            self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_2)
     
            self.lineEdit_2 = QLineEdit(self.layoutWidget)
            self.lineEdit_2.setObjectName(u"lineEdit_2")
     
            self.formLayout.setWidget(3, QFormLayout.FieldRole, self.lineEdit_2)
     
            self.label_3 = QLabel(self.layoutWidget)
            self.label_3.setObjectName(u"label_3")
     
            self.formLayout.setWidget(5, QFormLayout.LabelRole, self.label_3)
     
            self.lineEdit_3 = QLineEdit(self.layoutWidget)
            self.lineEdit_3.setObjectName(u"lineEdit_3")
     
            self.formLayout.setWidget(5, QFormLayout.FieldRole, self.lineEdit_3)
     
            self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(2, QFormLayout.FieldRole, self.verticalSpacer)
     
            self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(4, QFormLayout.FieldRole, self.verticalSpacer_2)
     
            self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(0, QFormLayout.FieldRole, self.verticalSpacer_3)
     
            self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(6, QFormLayout.FieldRole, self.verticalSpacer_4)
     
            self.lineEdit_4 = QLineEdit(self.layoutWidget)
            self.lineEdit_4.setObjectName(u"lineEdit_4")
     
            self.formLayout.setWidget(7, QFormLayout.FieldRole, self.lineEdit_4)
     
            self.label_4 = QLabel(self.layoutWidget)
            self.label_4.setObjectName(u"label_4")
     
            self.formLayout.setWidget(7, QFormLayout.LabelRole, self.label_4)
     
            self.verticalSpacer_5 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(8, QFormLayout.FieldRole, self.verticalSpacer_5)
     
     
            self.horizontalLayout.addLayout(self.formLayout)
     
     
            self.verticalLayout.addLayout(self.horizontalLayout)
     
            self.horizontalLayout_2 = QHBoxLayout()
            self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
            self.pushButton_enregistrer = QPushButton(self.layoutWidget)
            self.pushButton_enregistrer.setObjectName(u"pushButton_enregistrer")
     
            self.horizontalLayout_2.addWidget(self.pushButton_enregistrer)
     
            self.pushButton_supprimer = QPushButton(self.layoutWidget)
            self.pushButton_supprimer.setObjectName(u"pushButton_supprimer")
     
            self.horizontalLayout_2.addWidget(self.pushButton_supprimer)
     
            self.pushButton_nouveau = QPushButton(self.layoutWidget)
            self.pushButton_nouveau.setObjectName(u"pushButton_nouveau")
     
            self.horizontalLayout_2.addWidget(self.pushButton_nouveau)
     
            self.pushButton_annuler = QPushButton(self.layoutWidget)
            self.pushButton_annuler.setObjectName(u"pushButton_annuler")
     
            self.horizontalLayout_2.addWidget(self.pushButton_annuler)
     
     
            self.verticalLayout.addLayout(self.horizontalLayout_2)
     
            MainWindow.setCentralWidget(self.centralwidget)
            self.statusbar = QStatusBar(MainWindow)
            self.statusbar.setObjectName(u"statusbar")
            MainWindow.setStatusBar(self.statusbar)
     
            self.retranslateUi(MainWindow)
     
            QMetaObject.connectSlotsByName(MainWindow)
        # setupUi
     
        def retranslateUi(self, MainWindow):
            MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
            self.label.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_2.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_3.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_4.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.pushButton_enregistrer.setText(QCoreApplication.translate("MainWindow", u"Enregistrer", None))
            self.pushButton_supprimer.setText(QCoreApplication.translate("MainWindow", u"Supprimer", None))
            self.pushButton_nouveau.setText(QCoreApplication.translate("MainWindow", u"Nouveau", None))
            self.pushButton_annuler.setText(QCoreApplication.translate("MainWindow", u"Annuler", None))
        # retranslateUi

  5. #5
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    Pour avoir une restriction d'affichage de la table normale tout en restant "branché" sur cette table pour les modifications, voir plutôt "QSortFilterProxyModel" qui restera "en ligne" dans la transmission QTableView <=> base de données.

    A noter que si vous voyez la possibilité d'évoluer avec plusieurs tables dans la base de données, je vous suggère d'utiliser plutôt "QSqlRelationalTableModel" qui vous permettra d'utiliser des contraintes d'intégrité entre les tables.
    Merci pour cette réponse,
    Je vais tenter maintenant de comprendre le fonctionnement de QSortFilterProxyModel que je ne connaissais pas (Qt est vraiment trop vaste)
    Par contre, j'avais déjà commencé à travailler sur QSqlRelationalTableModel, mais pour l'instant j'essayais de gérer par niveau de complexité et de ne travailler que sur une seule table. Mais effectivement, je vais arriver ensuite à devoir travailler sur plusieurs tables.
    Je reviens vers vous si ça ne répond pas à mon problème.
    Merci encore
    JefMiss

  6. #6
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Pour avoir une restriction d'affichage de la table normale tout en restant "branché" sur cette table pour les modifications, voir plutôt "QSortFilterProxyModel" qui restera "en ligne" dans la transmission QTableView <=> base de données.
    Re-bonjour tyrtamos
    Je viens de regarder plus en détail la classe QSortFilterProxyModel et le test est plutôt concluant sauf que le seul problème que je n'arrive pas à résoudre (peut-être pas bien cherché) est que mon filtre doit se faire sur une colonne contenant des valeur (int) et j'ai l'impression que le filtre ne fonctionne qu'avec des string. J'ai bien tenté d'aller dans les expressions régulières, mais je ne suis pas très au point sur le sujet.
    Merci pour ton retour
    JefMiss

  7. #7
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 483
    Points : 9 282
    Points
    9 282
    Billets dans le blog
    6
    Par défaut
    J'ai retrouvé un code de test qui m'a permis de creuser un peu les possibilités du QSortFilterProxyModel. Il y a beaucoup d'autres possibilités, mais ça devient compliqué... j'ai déjà fait des recherches multi-colonnes, avec des algorithmes basés sur les regex, et même sur la recherche de mots similaires (à un ratio près).

    C'est du PyQt5, mais ça ne devrait pas être trop difficile de convertir en PySide6.

    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
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    # Python 3
     
    import sys
     
    import locale # pour tri selon le dictionnaire français
    locale.setlocale(locale.LC_ALL, ('fr_FR', 'UTF-8'))
     
    from PyQt5 import (QtWidgets, QtCore, QtGui)
     
    #############################################################################
    def cmp(x1, x2):
        """simulation de la fonction cmp de Python 2
        """
        return (x1 > x2) - (x1 < x2)
     
    #############################################################################
    def compdicofr(v1, v2):
        """comparaison de 2 chaines (unicode) selon le dictionnaire français
        """
        # on retire les tirets et les blancs insécables
        v1 = v1.replace('-', '')
        v1 = v1.replace('\xA0', '')
        v2 = v2.replace('-', '')
        v2 = v2.replace('\xA0', '')
     
        # on retourne le résultat de la comparaison
        return locale.strcoll(v1, v2)
     
    #############################################################################
    class ProxyModel(QtCore.QSortFilterProxyModel):
        """ProxyModel pour ajouter des méthodes de tri et de filtrage particuliers
           s'interpose entre le modèle normal et le QTableView
        """
     
        #========================================================================
        def __init__(self, parent=None):
            super(ProxyModel, self).__init__(parent)
     
            self.filtreperso = False
     
        #========================================================================
        def lessThan(self, left, right):
            """fonction de comparaison particulière
               left et right: QModelIndex de 2 cases d'une même colonne à trier
               retourne un booléen (True si left<right)
            """
            role = self.sortRole()
            vleft = left.data(role)  # 1ère valeur à comparer
            vright = right.data(role)  # 2e valeur à comparer
     
            col = left.column()  # numéro de la colonne en cours de tri
            if col == 1:
                # la 2e colonne (indice 1) est composée de nombres entiers
     
                # au cas où on aurait ajouté des données chaine
                vleft, vright = int(vleft), int(vright)
     
                # comparaison standard (similaire à cmp de Python 2)
                return cmp(vleft, vright) < 0
            else:
                # les autres colonnes sont des chaines
     
                # pour éviter une erreur lors d'insertion d'une nouvelle ligne
                if vleft is None: vleft = ""
                if vright is None: vright = ""
     
                # comparaison des chaines selon le dictionnaire français
                return compdicofr(vleft, vright) < 0
     
        #========================================================================
        def filterAcceptsRow(self, sourceRow, sourceParent):
            """Surcharge de la méthode de filtrage des lignes de la table
               source_row: int, source_parent: QModelIndex
               retour: True si la ligne est retenue, False sinon
            """
     
            if self.filtreperso:
                # ici, les filtrages particuliers
     
                # role de filtrage (par défaut: QtCore.Qt.DisplayRole)
                role = self.filterRole()
     
                # index de la colonne sur laquelle s'exerce le filtrage (-1=toutes)
                col = self.filterKeyColumn()
     
                if col == 0:
                    # ici, le filtrage ne s'exerce que sur la colonne d'index col
     
                    # fonction d'index de la ligne et de la colonne en cours
                    index = self.sourceModel().index(sourceRow, col, sourceParent)
                    # valeur à filtrer
                    item = self.sourceModel().data(index, role)
     
                    if item is None:
                        item = ""
     
                    " dit si la ligne doit être retenue dans le filtrage"
                    return item >= "a" and item <= "z"
                else:
                    # les tests sur une autre colonne que 0 ne génèrent pas de refus!
                    return True
     
            else:
                # hors filtrage particuliers: exécution du filtrage prédéfini
                return QtCore.QSortFilterProxyModel.filterAcceptsRow(self, sourceRow, sourceParent)
     
    #############################################################################
    class Fenetre(QtWidgets.QWidget):
     
        def __init__(self, datas=[], parent=None):
            super(Fenetre, self).__init__(parent)
            self.resize(800, 600)
     
            headers = datas[0]  # titre des colonnes: 1ère ligne de datas
            self.datas = datas[1:]  # stocke les données hors headers
     
            #--------------------------------------------------------------------
            # initialise le modèle
            self.model = QtGui.QStandardItemModel()
     
            # met les titres des colonnes
            for j, header in enumerate(headers):
                titrecol = QtGui.QStandardItem(header)
                self.model.setHorizontalHeaderItem(j, titrecol)
     
            # peuple le modèle
            for i, row in enumerate(self.datas):
                for j, col in enumerate(row):
                    item = QtGui.QStandardItem()
                    # item.setTextAlignment(QtCore.Qt.AlignHCenter)
                    # item.setEditable(False)
                    item.setData(col, QtCore.Qt.DisplayRole)
                    self.model.setItem(i, j, item)
     
            #--------------------------------------------------------------------
            # initialise le proxy
            # self.proxy = QtCore.QSortFilterProxyModel() # cas du proxy standard
            self.proxy = ProxyModel()  # sous-classement d'un QSortFilterProxyModel
     
            # tri non automatique: il faudra cliquer en haut des colonnes
            self.proxy.setDynamicSortFilter(False)
     
            # connecte le proxy au modèle
            self.proxy.setSourceModel(self.model)
     
            #--------------------------------------------------------------------
            # initialise le QTableView
            self.tableview = QtWidgets.QTableView(self)
     
            # connecte le QTableView au proxy
            self.tableview.setModel(self.proxy)
            # ajuste la largeur des colonnes à leur contenu
            self.tableview.resizeColumnsToContents()
     
            # active la possibilité de tri des colonnes du QTableView
            self.tableview.setSortingEnabled(True)
            # pas de tri pour l'affichage intial (=ordre d'insertion)
            self.proxy.sort(-1)
     
            # positionne le QTableView dans la fenêtre
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.tableview, 0, 0)
            self.setLayout(posit)
     
        # =======================================================================
        def ajouteligne(self, valeurs=None):
            """ajoute une nouvelle ligne en fin de tableau
               - si tri en cours, elle est affichée à la fin, mais pas triée
               - si filtrage en cours, elle ext crée mais pas affichée
               valeurs: liste éventuelle des valeurs des colonnes à mettre
            """
            # nb total de lignes du tableau (affichées ou non!)
            nligne = self.tableview.model().sourceModel().rowCount()
            # ajoute une ligne vide en fin de tableau
            self.tableview.model().sourceModel().insertRow(nligne)
            # remplit la nouvelle ligne avec les valeurs si elles sont présentes
            if valeurs != None:
                for col, valeur in enumerate(valeurs):
                    index = self.tableview.model().sourceModel().index(nligne, col)
                    self.tableview.model().sourceModel().setData(index, valeur)
     
        # =======================================================================
        def insereligne(self, valeurs=None):
            """insere une nouvelle ligne  à l'endroit du curseur et décale les
               lignes suivantes. N'a de sens que sans tri ni filtrage
               => doit être désactivé en cas de tri et/ou filtrage
               valeurs: liste des valeurs à mettre dans la ligne vide
            """
            if self.tableview.model().sourceModel().rowCount() == 0:
                # insère la 1ère ligne d'un tableau vide
                nligne = 0
                self.tableview.model().insertRow(nligne)
            else:
                # insère une nouvelle ligne avant la ligne sélectionnée
                # calcule l'index de la ligne dans laquelle se trouve le curseur
                index = self.tableview.currentIndex()
                # calcule le numéro de ligne
                nligne = index.row()
                # insère la nouvelle ligne vide
                self.tableview.model().insertRow(nligne)
            # remplit la nouvelle ligne avec les valeurs si elles sont présentes
            if valeurs != None:
                for col, val in enumerate(valeurs):
                    index = self.tableview.model().sourceModel().index(nligne, col)
                    self.tableview.model().sourceModel().setData(index, val)
     
        # =======================================================================
        def supprimeligne(self):
            """supprime la ligne sélectionnée entière
                 => une case sélectionnée ne suffit pas
               fonctionne avec ou sans tri et avec ou sans filtrage
            """
            # récupère la liste des QModelIndex des lignes sélectionnées
            listindex = self.tableview.selectionModel().selectedRows()
            # en extrait la liste des numéros de lignes sélectionnées
            # note: ce sont les numéros de lignes affichées y compris après tri
            # et non les numéros de lignes d'origine dans l'ordre d'insertion
            nlignes = [index.row() for index in listindex]
            # trie dans l'ordre inverse pour supprimer les grands numéros de ligne d'abord
            nlignes.sort(reverse=True)
            # et supprime les lignes en question une par une dans le bon ordre
            for nligne in nlignes:
                self.tableview.model().removeRow(nligne)
     
        # =======================================================================
        def keyPressEvent(self, event):
     
            if self.tableview.hasFocus():
     
                #----------------------------------------------------------------
                # Alt-A ajoute une nouvelle ligne à la fin du tableau
                if event.key() == QtCore.Qt.Key_A and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    self.ajouteligne(valeurs=["y", 50, "u"])
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-I insère une nouvelle ligne à l'endroit du curseur
                elif event.key() == QtCore.Qt.Key_I and  (event.modifiers() & QtCore.Qt.AltModifier):
                    # ligne = self.tableview.selectionModel().currentIndex().row()
     
                    self.insereligne(valeurs=["Z", 99, "z"])
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-S supprime la ou les lignes(s) sélectionnée(s)
                elif event.key() == QtCore.Qt.Key_S and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    self.supprimeligne()
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-T retrie le tableau dans l'ordre initial des données
                elif event.key() == QtCore.Qt.Key_T and  (event.modifiers() & QtCore.Qt.AltModifier):
                    self.proxy.sort(-1)
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-F déclenche le filtrage sur la colonne 0: mot>=a et mot<=z
                elif event.key() == QtCore.Qt.Key_F and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    # active le filtrage perso
                    self.proxy.filtreperso = True
     
                    # donne la colonne sur laquelle le filtrage s'exerce (-1=toutes)
                    self.proxy.setFilterKeyColumn(0)
                    # tenir compte ou non de la casse (majusc/minusc)
                    casse = QtCore.Qt.CaseSensitive
                    # crée et configure l'objet QRegExp (le motif n'a aucune importance ici)
                    rx = QtCore.QRegExp("", casse, QtCore.QRegExp.RegExp)
                    # et filtrer!
                    self.proxy.setFilterRegExp(rx)
     
                    # désactive le filtrage perso
                    self.proxy.filtreperso = True
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-X annule le filtrage en cours pour réafficher toutes les lignes
                elif event.key() == QtCore.Qt.Key_X and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    # donne la colonne sur laquelle le filtrage s'exerce (-1=toutes)
                    self.proxy.setFilterKeyColumn(-1)
                    # crée et configure un objet QRegExp qui attrape tout
                    rx = QtCore.QRegExp("^.*$", QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp)
                    # et filtrer pour tout retrouver!
                    self.proxy.setFilterRegExp(rx)
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-R récupére la liste de listes datas après modifications mais non triée
                elif event.key() == QtCore.Qt.Key_R and  (event.modifiers() & QtCore.Qt.AltModifier):
                    imax, jmax = self.tableview.model().rowCount(), self.tableview.model().columnCount()
                    data2 = []
                    for i in range(0, imax):
                        data2.append([])  # nouvelle ligne
                        for j in range(0, jmax):
     
                            # si on veut dans l'ordre des lignes
                            # item = self.model.item(i, j)
     
                            # si on veut dans l'ordre affiché trié
                            item = self.proxy.index(i, j)
     
                            elem = item.data(QtCore.Qt.DisplayRole)
                            data2[-1].append(elem)
                    print(data2)
                    event.accept()
                else:
                    event.ignore()
            else:
                event.ignore()
     
    #############################################################################
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
     
        # données à afficher. 1ère ligne => nom des colonnes
        datas = [['col1', 'col2', 'col3'],
                 ["a", 1, "d"], ["é", 2, "g"], ["h", 3, "j"], ["X", 4, "Z"],
                 ["M", 5, "h"], ["n", 6, "C"], ["g", 7, "p"], ["D", 8, "w"]]
     
        fen = Fenetre(datas)
        fen.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        fen.show()
        sys.exit(app.exec_())
    En cliquant en haut des colonnes, on voit pour les colonne 0 et 2, que les chaînes sont bien triées selon l'ordre de tri français. La colonne 1 est triée comme des entiers.

    On peut modifier les données, qui sont bien prises en compte dans les tris suivants. Mais je n'avais rien fait pour récupérer les données modifiées car ce n'était pas mon but. Il faudrait faire une modif du code avec une base de données sqlite3

    Il existe aussi des opérations par lignes: voir dans la méthode keyPressEvent

    Vous en avez pour un moment à éplucher tout ça...

  8. #8
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Citation Envoyé par tyrtamos Voir le message

    Vous en avez pour un moment à éplucher tout ça...
    Ben je vais m'y mettre alors si encore des soucis après avoir tester tout cela je refais signe.
    Merci en tout cas.

  9. #9
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    J'ai retrouvé un code de test qui m'a permis de creuser un peu les possibilités du QSortFilterProxyModel. I
    Bonjour,
    J'ai effectivement à partir des éléments envoyés réussi à avancer d'un grand pas et je pense que QSortFilterProxiModel est une des solutions à mon problème. Mais il me reste encore quelques difficultés.

    Je précise mon projet : l'idée est effectivement ne ne présenter qu'une partie des données qui est dans la table issue de la base de donnée. Le filtre s'effectue sur les champs d'une colonne qui sont dans la réalité des dates, mais avec SQLITE un entier (le nombre de seconde). Dans mon projet global, cette date pourra être modifiée par l'utilisateur et entrainera la modification des valeurs qui vont apparaitre dans le tableau. Dans mon exemple simplifié que je reproduis ci-dessous, j'ai donc la quatrième colonne qui possède des entiers et mon test consiste à ne faire apparaitre que les lignes dont la valeur de la quatrième colonne est supérieure à 5.

    Le résultat avec l'adaptation de ce que tu m'as envoyé ( est le suivant : les lignes qui apparaissent sur le tableau correspondent bien à ce que je veux. Lorsque je modifie les informations par l'intermédiaire du formulaire, elles sont bien enregistrées dans la base de données, la suppression, l'insertion d'une nouvelle ligne fonctionne également correctement. C'est donc presque parfait

    La difficulté apparait si dans mon formulaire, je modifie la valeur de la quatrième colonne en la passant à un nombre inférieur à 5 (la ligne serait alors sensé disparaitre de mon tableau) mais là c'est une bonne partie des lignes qui disparaissent et la logique est perdue. J'ai essayé de voir si ce n'était pas un problème de rowCount() du proxi_modele (puisque une ligne du proxi est en moins) mais je n'arrive à rien de concluant.

    Je remets ci-dessous le code complet et la table sql correspondant au test.
    Merci pour vos retours.

    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
    import sys
     
    from PySide6.QtCore import QSortFilterProxyModel
     
    from tableModel import Ui_MainWindow
    from PySide6.QtSql import QSqlDatabase, QSqlTableModel
    from PySide6.QtWidgets import QMainWindow, QApplication, QAbstractItemView, QDataWidgetMapper
     
     
    class controler(QMainWindow):
        def __init__(self):
            super().__init__()
            self.nouvelEnregistrement = False  # à utiliser lors du click sur nouveau et annuler
     
            # ouverture d'une base de données
            self.base = QSqlDatabase.addDatabase("QSQLITE")
            self.base.setDatabaseName("test.db")
            if self.base.open():
                print(self.base.lastError())
     
            # création d'un modèle sur la table de la base
            self.model_table = QSqlTableModel()
            self.model_table.setTable("clients")
            self.model_table.select()
     
            # creation d'un proxi du modèle
            self.proxy_model = proxi_model(5)
            self.proxy_model.setSourceModel(self.model_table)
            self.proxy_model.setFilterKeyColumn(3)
            self.proxy_model.setFilterRegularExpression("")
     
     
     
            # creation de la vue
            self.fenetre = Ui_MainWindow()
            self.fenetre.setupUi(self)
            self.fenetre.tableView.setModel(self.proxy_model)
     
            # options de la vue
            self.fenetre.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)  # selection de ligne uniquement
            self.fenetre.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) # empêche la modif du tableau
     
            # creation d'un mapper pour le formulaire
            self.mapper = QDataWidgetMapper()
            self.mapper.setModel(self.proxy_model)
            self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)  # enregistrement par le bouton enregistrer
            self.mapper.addMapping(self.fenetre.lineEdit, 0)
            self.mapper.addMapping(self.fenetre.lineEdit_2, 1)
            self.mapper.addMapping(self.fenetre.lineEdit_3, 2)
            self.mapper.addMapping(self.fenetre.spinBox, 3)
            self.mapper.toFirst()
     
            # gestion des évènements
            self.fenetre.tableView.selectionModel().selectionChanged.connect(self.on_selectionChanged_connected)
            self.fenetre.pushButton_enregistrer.clicked.connect(self.on_pushButton_enregistrer_connected)
            self.fenetre.pushButton_supprimer.clicked.connect(self.on_pushButton_supprimer_connected)
            self.fenetre.pushButton_nouveau.clicked.connect(self.on_pushbutton_nouveau_connected)
            self.fenetre.pushButton_annuler.clicked.connect(self.on_pushButton_annuler_connected)
     
            # sélection de la première ligne
            self.fenetre.tableView.selectRow(0)
     
        def on_selectionChanged_connected(self, selected, deselected):
            """
            Modification des données du formulaire en fonction de la ligne sélectionnée
            """
            if selected.indexes():
                self.mapper.setCurrentIndex(selected.indexes()[0].row())
            else:
                return
            pass
     
        def on_pushButton_enregistrer_connected(self):
            """
            Enregistrement des données du formulaire
            """
            nombreLigneModelProxi = self.proxy_model.rowCount()
            numLigneEnCours = self.mapper.currentIndex()
            self.mapper.submit()
            self.nouvelEnregistrement = False
            if self.proxy_model.rowCount() != nombreLigneModelProxi:
                # une ligne a disparu
                print ("ok")
                self.fenetre.tableView.setModel(self.proxy_model)
            pass
     
        def on_pushButton_supprimer_connected(self):
            """
            Suppression de la ligne du modèle et sélection de la ligne précédente
            """
            numLigne = self.fenetre.tableView.currentIndex().row()
            self.proxy_model.removeRow(numLigne)
            self.fenetre.tableView.selectRow(numLigne-1)
            self.model_table.select()
            pass
     
        def on_pushbutton_nouveau_connected(self):
            """
            Dans un premier temps, création d'une nouvelle ligne avec aucune donnée, sauf l'id qui
            est un champ clé primaire autoincrémenté dans la base, les données seront écrites
            dans le formulaire puis enregistrées
            """
            self.nouvelEnregistrement = True
            record = self.model_table.record()
            record.setGenerated("id", False)
            self.model_table.insertRecord(-1, record)
            self.fenetre.tableView.selectRow(self.model_table.rowCount()-1)
            pass
     
        def on_pushButton_annuler_connected(self):
            """
            Test pour savoir si l'enregistrement d'un nouvel enregistrement est en cours, si c'est
            le cas, on supprime la ligne.
            Puis, on sélectionne la ligne suivante et on sélectionne à nouveau la ligne en cours pour
            annuler les changements dans le formulaire
            """
            if self.nouvelEnregistrement:
                self.on_pushButton_supprimer_connected()
                self.nouvelEnregistrement = False
            indexEnCours = self.mapper.currentIndex()
            self.fenetre.tableView.selectRow(indexEnCours+1)
            self.fenetre.tableView.selectRow(indexEnCours)
            pass
     
     
    class proxi_model(QSortFilterProxyModel):
        def __init__(self, valeurAFiltrer):
            super().__init__()
            self.valeurAFiltrer = valeurAFiltrer
     
        def filterAcceptsRow(self, source_row, source_parent):
            role = self.filterRole()
            col = self.filterKeyColumn()
            if col == 3:
                index = self.sourceModel().index(source_row, col, source_parent)
                item = self.sourceModel().data(index, role)
                if not item:
                    item = 10
                if item > self.valeurAFiltrer:
                    return True
                else:
                    return False
     
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        fenetre = controler()
        fenetre.show()
        sys.exit(app.exec())
    Le code pour la vue Ui_MainWindow

    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
    150
    151
    152
    153
    154
    155
    ###############################################################################
    ## Form generated from reading UI file 'test_tableModel.ui'
    ##
    ## Created by: Qt User Interface Compiler version 6.5.1
    ##
    ## WARNING! All changes made in this file will be lost when recompiling UI file!
    ################################################################################
     
    from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
        QMetaObject, QObject, QPoint, QRect,
        QSize, QTime, QUrl, Qt)
    from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
        QFont, QFontDatabase, QGradient, QIcon,
        QImage, QKeySequence, QLinearGradient, QPainter,
        QPalette, QPixmap, QRadialGradient, QTransform)
    from PySide6.QtWidgets import (QApplication, QFormLayout, QHBoxLayout, QHeaderView,
        QLabel, QLineEdit, QMainWindow, QPushButton,
        QSizePolicy, QSpacerItem, QSpinBox, QStatusBar,
        QTableView, QVBoxLayout, QWidget)
     
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            if not MainWindow.objectName():
                MainWindow.setObjectName(u"MainWindow")
            MainWindow.resize(792, 400)
            self.centralwidget = QWidget(MainWindow)
            self.centralwidget.setObjectName(u"centralwidget")
            self.layoutWidget = QWidget(self.centralwidget)
            self.layoutWidget.setObjectName(u"layoutWidget")
            self.layoutWidget.setGeometry(QRect(0, 18, 781, 341))
            self.verticalLayout = QVBoxLayout(self.layoutWidget)
            self.verticalLayout.setObjectName(u"verticalLayout")
            self.verticalLayout.setContentsMargins(0, 0, 0, 0)
            self.horizontalLayout = QHBoxLayout()
            self.horizontalLayout.setObjectName(u"horizontalLayout")
            self.tableView = QTableView(self.layoutWidget)
            self.tableView.setObjectName(u"tableView")
            self.tableView.setMinimumSize(QSize(500, 0))
     
            self.horizontalLayout.addWidget(self.tableView)
     
            self.formLayout = QFormLayout()
            self.formLayout.setObjectName(u"formLayout")
            self.label = QLabel(self.layoutWidget)
            self.label.setObjectName(u"label")
     
            self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label)
     
            self.lineEdit = QLineEdit(self.layoutWidget)
            self.lineEdit.setObjectName(u"lineEdit")
     
            self.formLayout.setWidget(1, QFormLayout.FieldRole, self.lineEdit)
     
            self.label_2 = QLabel(self.layoutWidget)
            self.label_2.setObjectName(u"label_2")
     
            self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_2)
     
            self.lineEdit_2 = QLineEdit(self.layoutWidget)
            self.lineEdit_2.setObjectName(u"lineEdit_2")
     
            self.formLayout.setWidget(3, QFormLayout.FieldRole, self.lineEdit_2)
     
            self.label_3 = QLabel(self.layoutWidget)
            self.label_3.setObjectName(u"label_3")
     
            self.formLayout.setWidget(5, QFormLayout.LabelRole, self.label_3)
     
            self.lineEdit_3 = QLineEdit(self.layoutWidget)
            self.lineEdit_3.setObjectName(u"lineEdit_3")
     
            self.formLayout.setWidget(5, QFormLayout.FieldRole, self.lineEdit_3)
     
            self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(2, QFormLayout.FieldRole, self.verticalSpacer)
     
            self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(4, QFormLayout.FieldRole, self.verticalSpacer_2)
     
            self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(0, QFormLayout.FieldRole, self.verticalSpacer_3)
     
            self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(6, QFormLayout.FieldRole, self.verticalSpacer_4)
     
            self.label_4 = QLabel(self.layoutWidget)
            self.label_4.setObjectName(u"label_4")
     
            self.formLayout.setWidget(7, QFormLayout.LabelRole, self.label_4)
     
            self.verticalSpacer_5 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(8, QFormLayout.FieldRole, self.verticalSpacer_5)
     
            self.spinBox = QSpinBox(self.layoutWidget)
            self.spinBox.setObjectName(u"spinBox")
     
            self.formLayout.setWidget(7, QFormLayout.FieldRole, self.spinBox)
     
     
            self.horizontalLayout.addLayout(self.formLayout)
     
     
            self.verticalLayout.addLayout(self.horizontalLayout)
     
            self.horizontalLayout_2 = QHBoxLayout()
            self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
            self.pushButton_enregistrer = QPushButton(self.layoutWidget)
            self.pushButton_enregistrer.setObjectName(u"pushButton_enregistrer")
     
            self.horizontalLayout_2.addWidget(self.pushButton_enregistrer)
     
            self.pushButton_supprimer = QPushButton(self.layoutWidget)
            self.pushButton_supprimer.setObjectName(u"pushButton_supprimer")
     
            self.horizontalLayout_2.addWidget(self.pushButton_supprimer)
     
            self.pushButton_nouveau = QPushButton(self.layoutWidget)
            self.pushButton_nouveau.setObjectName(u"pushButton_nouveau")
     
            self.horizontalLayout_2.addWidget(self.pushButton_nouveau)
     
            self.pushButton_annuler = QPushButton(self.layoutWidget)
            self.pushButton_annuler.setObjectName(u"pushButton_annuler")
     
            self.horizontalLayout_2.addWidget(self.pushButton_annuler)
     
     
            self.verticalLayout.addLayout(self.horizontalLayout_2)
     
            MainWindow.setCentralWidget(self.centralwidget)
            self.statusbar = QStatusBar(MainWindow)
            self.statusbar.setObjectName(u"statusbar")
            MainWindow.setStatusBar(self.statusbar)
     
            self.retranslateUi(MainWindow)
     
            QMetaObject.connectSlotsByName(MainWindow)
        # setupUi
     
        def retranslateUi(self, MainWindow):
            MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
            self.label.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_2.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_3.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_4.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.pushButton_enregistrer.setText(QCoreApplication.translate("MainWindow", u"Enregistrer", None))
            self.pushButton_supprimer.setText(QCoreApplication.translate("MainWindow", u"Supprimer", None))
            self.pushButton_nouveau.setText(QCoreApplication.translate("MainWindow", u"Nouveau", None))
            self.pushButton_annuler.setText(QCoreApplication.translate("MainWindow", u"Annuler", None))
        # retranslateUi
    clients_2.sql

  10. #10
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 483
    Points : 9 282
    Points
    9 282
    Billets dans le blog
    6
    Par défaut
    Bonjour

    Bravo pour en avoir fait autant en aussi peu de temps!

    Je pense que pour prendre en compte une modification d'affichage qui touche les règles du filtrage, il faudrait "rafraichir" ce filtrage pour qu'il prenne en compte la nouvelle situation de la base.

    J'ai pu exécuter ton programme PySide6, mais ta base "test.db" est vide: ce serait bien d'en donner une à titre d'exemple qui me permette de mieux voir le problème et, peut-être, de trouver une solution.

  11. #11
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    Bonjour Tyrtamos,
    J'ai un petit soucis, je n'arrive pas à voir ton message dans la discussion sur mon PC (je l'ai vu sur mon portable), je réponds donc en décaler du fil de discussion.
    J'ai bien penser au rafraichissement du modèle. Mais je n'ai pas trouvé de fonction qui fait ce genre de chose. Par contre, j'ai tenté de relancer le modèle (tableView.setModel(proxi_model) mais ça n'a pas été concluant.

    Pour la table, voici le code en dur en sql. Merci pour les retours

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    BEGIN TRANSACTION;
    CREATE TABLE IF NOT EXISTS "clients" (
    	"id"	INTEGER,
    	"name"	TEXT,
    	"second"	TEXT,
    	"local"	INTEGER DEFAULT 0,
    	PRIMARY KEY("id")
    );
    INSERT INTO "clients" ("id","name","second","local") VALUES (4,'tutu','Loulou',2);
    INSERT INTO "clients" ("id","name","second","local") VALUES (5,'machins','louis',6);
    INSERT INTO "clients" ("id","name","second","local") VALUES (6,'machin','tartenpion',6);
    INSERT INTO "clients" ("id","name","second","local") VALUES (13,'encore','toujooures',4);
    INSERT INTO "clients" ("id","name","second","local") VALUES (14,'toto','riri',6);
    COMMIT;

  12. #12
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 483
    Points : 9 282
    Points
    9 282
    Billets dans le blog
    6
    Par défaut
    Bonjour

    Avant d'avoir essayé, il me semble que le "rafraichissement" souhaité se fait par "fetchmore" et ce, tant que "canfetchmore" est vrai. De "fetch" = "aller chercher". C'est une méthode du modèle (et non du QTableView). Il s'agit de ce qu'on appelle "peupler" l'objet graphique avec les données.

    La suite quand j'aurais essayé...

  13. #13
    Expert confirmé Avatar de papajoker
    Homme Profil pro
    Développeur Web
    Inscrit en
    Septembre 2013
    Messages
    2 226
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Septembre 2013
    Messages : 2 226
    Points : 4 710
    Points
    4 710
    Par défaut
    bonjour

    pour indiquer de rafraichir le(s) widget(s) lié(s) à un modèle

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.model_table.model().layoutChanged.emit()
    a voir : https://doc.qt.io/qt-6/qabstractitem...#layoutChanged

  14. #14
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 483
    Points : 9 282
    Points
    9 282
    Billets dans le blog
    6
    Par défaut
    Bonjour

    @ jefmiss

    J'ai beaucoup de mal avec ton code. Par contre, pour le rafraichissement, c'est papajoker qui a raison. Cela donne avec tes variables:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.proxy_model.sourceModel().layoutChanged.emit()
    Il faudrait que ce soit le submit qui déclenche le rafraichissement après la modification de la base.

    Je continue demain...

  15. #15
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    @Tyrtamos et @papajoker

    Excusez moi pour le retour un peu tardif sur vos derniers messages. Je n'ai pas eu beaucoup le temps de regarder. Mais un essai rapide pour l'instant de la fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    self.proxy_model.sourceModel().layoutChanged.emit()
    n'est pas très concluant. je l'ai positionné juste après mon self.mapper.submit() et en retour j'ai une erreur : Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

    J'avoue ne pas avoir eu beaucoup de temps pour regarder beaucoup plus loin. Si vous avez une idée plus précise de ce qui se passe. J'essayerai de travailler dessus demain dans la journée.

    Merci pour vos retours
    JefMiss

  16. #16
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 483
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 483
    Points : 9 282
    Points
    9 282
    Billets dans le blog
    6
    Par défaut
    Bonjour

    J'ai enfin trouvé un code qui marche. Mais c'est en PyQt5, et je n'ai pas utilisé "QDataWidgetMapper" pour ne pas multiplier les difficultés.

    Deux commandes nécessitent un raccourcis clavier:

    - Alt-S qui fait un "switch" entre l'affichage filtré ou non filtré, ce qui permet de réintroduire une ligne non affichée par le filtrage

    - Alt-E qui fait l'enregistrement d'une valeur modifiée

    Ainsi, si à partir d'un affichage filtré on met une valeur entière <5, l'affichage se met à jour automatiquement après l'enregistrement (Alt-E) et la ligne disparait.

    Et si à partir d'un affichage non filtré, on met une valeur entière >5, l'affichage non filtré se met à jour, et l'appel à Alt-S montre bien que la ligne est maintenant introduite dans l'affichage filtré.

    Voilà le code. Je l'ai simplifié au maximum et il est largement commenté. Bien sûr, il faudrait, à partir de ce code de principe, ajouter des "parachutes" pour récupérer les mauvaises données et les mauvaises manipulations.

    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
    150
    151
    152
    153
    154
    155
    156
    # -*- coding: utf-8 -*-
     
    import sys
     
    from PyQt5 import (QtWidgets, QtCore, QtSql)
     
    #############################################################################
    class ProxyModel(QtCore.QSortFilterProxyModel):
        """ProxyModel pour ajouter des méthodes de tri et de filtrage particuliers
           s'interpose entre le modèle normal et le QTableView
        """
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
            self.filtreperso = True # drapeau de filtage
     
       #========================================================================
        def filterAcceptsRow(self, sourceRow, sourceParent):
            """Surcharge de la méthode de filtrage des lignes de la table
               source_row: int, source_parent: QModelIndex
               retour: True si la ligne est retenue, False sinon
            """
     
            if self.filtreperso:
                # ici, les filtrages particuliers
     
                # role de filtrage (par défaut: QtCore.Qt.DisplayRole)
                role = self.filterRole()
     
                # index de la colonne sur laquelle s'exerce le filtrage (-1=toutes)
                col = self.filterKeyColumn()
                if col == 3:
                    # ici, le filtrage ne s'exerce que sur la colonne d'index col
     
                    # fonction d'index de la ligne et de la colonne en cours
                    index = self.sourceModel().index(sourceRow, col, sourceParent)
                    # valeur à filtrer
                    item = int(self.sourceModel().data(index, role))
     
                    if item is None:
                        item = ""
     
                    # dit si la ligne doit être retenue ou non dans le filtrage
                    return item >= 5
                else:
                    # les tests sur une autre colonne ne génèrent pas de refus!
                    return True
     
            else:
                # hors filtrage particuliers: exécution du filtrage prédéfini
                return QtCore.QSortFilterProxyModel.filterAcceptsRow(self, sourceRow, sourceParent)
     
    #############################################################################
    class Fenetre(QtWidgets.QWidget):
     
        def __init__(self, basedb, tabledb, parent=None):
            super().__init__(parent)
     
            # ouverture d'une base de données
            self.base = QtSql.QSqlDatabase.addDatabase("QSQLITE")
            self.base.setDatabaseName(basedb)
            if not self.base.open():
                QtWidgets.QMessageBox.critical(self,
                            "Ouverture de la base de données",
                            "Erreur d'ouverture")
                self.close()
                sys.exit()
     
            # création d'un modèle sur la table de la base
            self.model = QtSql.QSqlRelationalTableModel(self, self.base)
            self.model.setTable(tabledb)
            self.model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit)
            self.model.select()
     
            #--------------------------------------------------------------------
            # initialise le proxy
            self.proxy = ProxyModel()  # sous-classement d'un QSortFilterProxyModel
     
            # tri non automatique: il faudra cliquer en haut des colonnes
            self.proxy.setDynamicSortFilter(False)
     
            # donne la colonne sur laquelle le filtrage s'exerce (-1=toutes)
            self.proxy.setFilterKeyColumn(3)
            # connecte le proxy au modèle
            self.proxy.setSourceModel(self.model)
     
            #--------------------------------------------------------------------
            # initialise le QTableView
            self.tableview = QtWidgets.QTableView(self)
     
            # connecte le QTableView au proxy
            self.tableview.setModel(self.proxy)
            # ajuste la largeur des colonnes à leur contenu
            self.tableview.resizeColumnsToContents()
     
            # active la possibilité de tri des colonnes du QTableView
            self.tableview.setSortingEnabled(True)
            # pas de tri pour l'affichage intial (=ordre d'insertion)
            self.proxy.sort(-1)
     
            # positionne le QTableView dans la fenêtre
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.tableview, 0, 0)
            self.setLayout(posit)
     
        # =======================================================================
        def keyPressEvent(self, event):
     
            if self.tableview.hasFocus():
     
                #----------------------------------------------------------------
                # Alt-E Enregistre
                if event.key() == QtCore.Qt.Key_E and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    if self.model.submitAll():
                        # message ok
                        QtWidgets.QMessageBox.information(self,
                            "Enregistrement des modifications",
                            "Enregistrement terminé")
                    else:
                        # revenir en arrière
                        self.model.revertAll()
                        # message erreur
                        QtWidgets.QMessageBox.warning(self,
                            "Enregistrement des modifications",
                            "Erreur: %s" % self.model.lastError().text())
     
                    self.model.select()  # repeuple à partir de la base de données
     
                    event.accept()
     
                #----------------------------------------------------------------
                # Alt-S switch le filtrage ou non
                elif event.key() == QtCore.Qt.Key_S and  (event.modifiers() & QtCore.Qt.AltModifier):
     
                    # inverse le drapeau de filtrage
                    self.proxy.filtreperso = not self.proxy.filtreperso
     
                    self.model.select()  # repeuple à partir de la base de données
     
                    event.accept()
     
            event.accept()
     
    #############################################################################
    if __name__ == '__main__':
     
        basedb = "test.db"
        tabledb = "clients"
     
        app = QtWidgets.QApplication(sys.argv)
        fen = Fenetre(basedb, tabledb)
        fen.show()
        sys.exit(app.exec_())
    Ouf...

    [Edit] En fait, la conversion PyQt5 => PySide6 est facile: il suffit de remplacer le nom à l'instruction d'importation.

  17. #17
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Merci Tyrtamos pour l'aide. Le programme fonctionne parfaitement et je vais essayer de l'étudier plus en détail (pas eu bcp de temps cette semaine) pour voir comment adapter tout cela dans mon projet.
    JefMiss

  18. #18
    Futur Membre du Club
    Homme Profil pro
    Enseignant
    Inscrit en
    Avril 2022
    Messages
    14
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Nord (Nord Pas de Calais)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Avril 2022
    Messages : 14
    Points : 9
    Points
    9
    Par défaut
    Bonjour à tous,

    Je reviens sur ce post pour le clôturer en donnant ma dernière version que j'ai un peu améliorée et qui fonctionne grâce aux retours de Tyrtamos et Papajoker.

    la solution sur le dernier problème était en effet d'appliquer la méthode layoutChanged.emit() sur la table de ma vue qui affiche alors correctement les données filtrer par le QSortFilterProxyModel().

    Le principe de fonctionnement du coup que j'ai modifié. Dans mon tableau une colonne début (ici des nombres entiers) et une colonne fin (pareil). j'ai rajouté sur ma vue une spinBox() qui sera ma valeur de référence. Le principe de mon filtre est de ne faire apparaître que les lignes ou la valeur Début est inférieure à la valeur de référence et la valeur Fin est supérieure à la valeur de référence. Cela se rapproche grandement de mon projet réel.
    Je mets ici les fichiers correspondants si certains veulent tester.
    Merci en tout cas pour les retours sur ce posts qui m'ont été bien précieux

    Le code principal
    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
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    import sys
     
    from PySide6.QtCore import QSortFilterProxyModel, QModelIndex
     
    from tableModel import Ui_MainWindow
    from PySide6.QtSql import QSqlDatabase, QSqlTableModel
    from PySide6.QtWidgets import QMainWindow, QApplication, QAbstractItemView, QDataWidgetMapper, QMessageBox
     
     
    class controler(QMainWindow):
        def __init__(self):
            super().__init__()
            self.on_spinBox_enCours = None
            self.nouvelEnregistrement = False  # à utiliser lors du click sur nouveau et annuler
     
            # ouverture d'une base de données
            self.base = QSqlDatabase.addDatabase("QSQLITE")
            self.base.setDatabaseName("test.db")
            if not self.base.open():
                QMessageBox.critical(self,
                                     "Ouverture de la base de données",
                                     "Erreur d'ouverture")
                self.close()
                sys.exit()
     
            # creation de la vue
            self.fenetre = Ui_MainWindow()
            self.fenetre.setupUi(self)
     
            # création d'un modèle sur la table de la base
            self.model_table = QSqlTableModel()
            self.model_table.setTable("clients")
            self.model_table.select()
     
            # creation du premier proxi du modèle
            self.proxy_modelDebut = proxi_model(self.fenetre.spinBox_enCours.value())
            self.proxy_modelDebut.setSourceModel(self.model_table)
            self.proxy_modelDebut.setFilterKeyColumn(2)
            # creation du deuxième proxi avec pour modèle le premier proxi
            self.proxy_modelFin = proxi_model(self.fenetre.spinBox_enCours.value())
            self.proxy_modelFin.setSourceModel(self.proxy_modelDebut)
            self.proxy_modelFin.setFilterKeyColumn(3)
     
            # chargement du deuxièeme modele proxi dans la fenetre
            self.fenetre.tableView.setModel(self.proxy_modelFin)
     
            # options de la vue
            self.fenetre.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)  # selection de ligne uniquement
            self.fenetre.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) # empêche la modif du tableau
     
            # creation d'un mapper pour le formulaire
            self.mapper = QDataWidgetMapper()
            self.mapper.setModel(self.proxy_modelFin)
            self.mapper.setSubmitPolicy(QDataWidgetMapper.ManualSubmit)  # enregistrement par le bouton enregistrer
            self.mapper.addMapping(self.fenetre.lineEdit, 0)
            self.mapper.addMapping(self.fenetre.lineEdit_2, 1)
            self.mapper.addMapping(self.fenetre.spinBox_debut, 2)
            self.mapper.addMapping(self.fenetre.spinBox_fin, 3)
            self.mapper.toFirst()
     
            # gestion des évènements
            self.fenetre.tableView.selectionModel().selectionChanged.connect(self.on_selectionChanged_connected)
            self.fenetre.pushButton_enregistrer.clicked.connect(self.on_pushButton_enregistrer_connected)
            self.fenetre.pushButton_supprimer.clicked.connect(self.on_pushButton_supprimer_connected)
            self.fenetre.pushButton_nouveau.clicked.connect(self.on_pushbutton_nouveau_connected)
            self.fenetre.pushButton_annuler.clicked.connect(self.on_pushButton_annuler_connected)
            self.fenetre.spinBox_enCours.valueChanged.connect(self.on_spinBox_enCours_valueChanged)
     
            # sélection de la première ligne
            self.fenetre.tableView.selectRow(0)
     
        def on_selectionChanged_connected(self, selected, deselected):
            """
            Modification des données du formulaire en fonction de la ligne sélectionnée
            """
            if selected.indexes():
                self.mapper.setCurrentIndex(selected.indexes()[0].row())
            else:
                self.mapper.setCurrentIndex(0)
            pass
     
        def on_pushButton_enregistrer_connected(self):
            """
            Enregistrement des données du formulaire
            """
            self.fenetre.tableView.model().layoutAboutToBeChanged.emit()
            self.mapper.submit()
            self.nouvelEnregistrement = False
            self.proxy_modelDebut.setFilterKeyColumn(2)
            self.proxy_modelFin.setFilterKeyColumn(3)
            self.fenetre.tableView.model().layoutChanged.emit()
     
            pass
     
        def on_pushButton_supprimer_connected(self):
            """
            Suppression de la ligne du modèle et sélection de la ligne précédente
            """
            numLigne = self.fenetre.tableView.currentIndex().row()
            self.proxy_modelDebut.removeRow(numLigne)
            self.fenetre.tableView.selectRow(numLigne-1)
            self.model_table.select()
            pass
     
        def on_pushbutton_nouveau_connected(self):
            """
            Dans un premier temps, création d'une nouvelle ligne avec aucune donnée, sauf l'id qui
            est un champ clé primaire autoincrémenté dans la base, les données seront écrites
            dans le formulaire puis enregistrées
            """
            self.nouvelEnregistrement = True
            record = self.model_table.record()
            record.setGenerated("id", False)
            self.model_table.insertRecord(-1, record)
            self.fenetre.tableView.selectRow(self.model_table.rowCount()-1)
            pass
     
        def on_pushButton_annuler_connected(self):
            """
            Test pour savoir si l'enregistrement d'un nouvel enregistrement est en cours, si c'est
            le cas, on supprime la ligne.
            Puis, on sélectionne la ligne suivante et on sélectionne à nouveau la ligne en cours pour
            annuler les changements dans le formulaire
            """
            if self.nouvelEnregistrement:
                self.on_pushButton_supprimer_connected()
                self.nouvelEnregistrement = False
            indexEnCours = self.mapper.currentIndex()
            self.fenetre.tableView.selectRow(indexEnCours+1)
            self.fenetre.tableView.selectRow(indexEnCours)
            pass
     
        def on_spinBox_enCours_valueChanged(self, value):
            """
            lors du changement de la valeur en cours on relance le filtrage et on met
            à jour la table view
            """
            self.proxy_modelDebut.valeurAFiltrer = value
            self.proxy_modelDebut.setFilterKeyColumn(2)
            self.proxy_modelFin.valeurAFiltrer = value
            self.proxy_modelFin.setFilterKeyColumn(3)
            self.fenetre.tableView.model().layoutChanged.emit()
     
            pass
     
     
    class proxi_model(QSortFilterProxyModel):
        def __init__(self, valeurAFiltrer):
            super().__init__()
            self.valeurAFiltrer = valeurAFiltrer
     
        def filterAcceptsRow(self, source_row, source_parent):
            role = self.filterRole()
            col = self.filterKeyColumn()
            if col == 2:
                index = self.sourceModel().index(source_row, col, source_parent)
                item = self.sourceModel().data(index, role)
                if not item:
                    item = 0
                if item < self.valeurAFiltrer:
                    return True
                else:
                    return False
            if col == 3:
                index = self.sourceModel().index(source_row, col, source_parent)
                item = self.sourceModel().data(index, role)
                if not item:
                    item = 10
                if item > self.valeurAFiltrer:
                    return True
                else:
                    return False
     
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        fenetre = controler()
        fenetre.show()
        sys.exit(app.exec())
    le fichier pour la vue
    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
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    # -*- coding: utf-8 -*-
     
    ################################################################################
    ## Form generated from reading UI file 'tableModel.ui'
    ##
    ## Created by: Qt User Interface Compiler version 6.5.1
    ##
    ## WARNING! All changes made in this file will be lost when recompiling UI file!
    ################################################################################
     
    from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
        QMetaObject, QObject, QPoint, QRect,
        QSize, QTime, QUrl, Qt)
    from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
        QFont, QFontDatabase, QGradient, QIcon,
        QImage, QKeySequence, QLinearGradient, QPainter,
        QPalette, QPixmap, QRadialGradient, QTransform)
    from PySide6.QtWidgets import (QApplication, QFormLayout, QHBoxLayout, QHeaderView,
        QLabel, QLineEdit, QMainWindow, QPushButton,
        QSizePolicy, QSpacerItem, QSpinBox, QStatusBar,
        QTableView, QVBoxLayout, QWidget)
     
    class Ui_MainWindow(object):
        def setupUi(self, MainWindow):
            if not MainWindow.objectName():
                MainWindow.setObjectName(u"MainWindow")
            MainWindow.resize(792, 400)
            self.centralwidget = QWidget(MainWindow)
            self.centralwidget.setObjectName(u"centralwidget")
            self.layoutWidget = QWidget(self.centralwidget)
            self.layoutWidget.setObjectName(u"layoutWidget")
            self.layoutWidget.setGeometry(QRect(0, 18, 781, 341))
            self.verticalLayout = QVBoxLayout(self.layoutWidget)
            self.verticalLayout.setObjectName(u"verticalLayout")
            self.verticalLayout.setContentsMargins(0, 0, 0, 0)
            self.spinBox_enCours = QSpinBox(self.layoutWidget)
            self.spinBox_enCours.setObjectName(u"spinBox_enCours")
            self.spinBox_enCours.setValue(5)
     
            self.verticalLayout.addWidget(self.spinBox_enCours)
     
            self.horizontalLayout = QHBoxLayout()
            self.horizontalLayout.setObjectName(u"horizontalLayout")
            self.tableView = QTableView(self.layoutWidget)
            self.tableView.setObjectName(u"tableView")
            self.tableView.setMinimumSize(QSize(500, 0))
     
            self.horizontalLayout.addWidget(self.tableView)
     
            self.formLayout = QFormLayout()
            self.formLayout.setObjectName(u"formLayout")
            self.label = QLabel(self.layoutWidget)
            self.label.setObjectName(u"label")
     
            self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label)
     
            self.lineEdit = QLineEdit(self.layoutWidget)
            self.lineEdit.setObjectName(u"lineEdit")
     
            self.formLayout.setWidget(1, QFormLayout.FieldRole, self.lineEdit)
     
            self.label_2 = QLabel(self.layoutWidget)
            self.label_2.setObjectName(u"label_2")
     
            self.formLayout.setWidget(3, QFormLayout.LabelRole, self.label_2)
     
            self.lineEdit_2 = QLineEdit(self.layoutWidget)
            self.lineEdit_2.setObjectName(u"lineEdit_2")
     
            self.formLayout.setWidget(3, QFormLayout.FieldRole, self.lineEdit_2)
     
            self.label_3 = QLabel(self.layoutWidget)
            self.label_3.setObjectName(u"label_3")
     
            self.formLayout.setWidget(5, QFormLayout.LabelRole, self.label_3)
     
            self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(2, QFormLayout.FieldRole, self.verticalSpacer)
     
            self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(4, QFormLayout.FieldRole, self.verticalSpacer_2)
     
            self.verticalSpacer_3 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(0, QFormLayout.FieldRole, self.verticalSpacer_3)
     
            self.verticalSpacer_4 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(6, QFormLayout.FieldRole, self.verticalSpacer_4)
     
            self.label_4 = QLabel(self.layoutWidget)
            self.label_4.setObjectName(u"label_4")
     
            self.formLayout.setWidget(7, QFormLayout.LabelRole, self.label_4)
     
            self.verticalSpacer_5 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
     
            self.formLayout.setItem(8, QFormLayout.FieldRole, self.verticalSpacer_5)
     
            self.spinBox_fin = QSpinBox(self.layoutWidget)
            self.spinBox_fin.setObjectName(u"spinBox_fin")
     
            self.formLayout.setWidget(7, QFormLayout.FieldRole, self.spinBox_fin)
     
            self.spinBox_debut = QSpinBox(self.layoutWidget)
            self.spinBox_debut.setObjectName(u"spinBox_debut")
     
            self.formLayout.setWidget(5, QFormLayout.FieldRole, self.spinBox_debut)
     
     
            self.horizontalLayout.addLayout(self.formLayout)
     
     
            self.verticalLayout.addLayout(self.horizontalLayout)
     
            self.horizontalLayout_2 = QHBoxLayout()
            self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
            self.pushButton_enregistrer = QPushButton(self.layoutWidget)
            self.pushButton_enregistrer.setObjectName(u"pushButton_enregistrer")
     
            self.horizontalLayout_2.addWidget(self.pushButton_enregistrer)
     
            self.pushButton_supprimer = QPushButton(self.layoutWidget)
            self.pushButton_supprimer.setObjectName(u"pushButton_supprimer")
     
            self.horizontalLayout_2.addWidget(self.pushButton_supprimer)
     
            self.pushButton_nouveau = QPushButton(self.layoutWidget)
            self.pushButton_nouveau.setObjectName(u"pushButton_nouveau")
     
            self.horizontalLayout_2.addWidget(self.pushButton_nouveau)
     
            self.pushButton_annuler = QPushButton(self.layoutWidget)
            self.pushButton_annuler.setObjectName(u"pushButton_annuler")
     
            self.horizontalLayout_2.addWidget(self.pushButton_annuler)
     
     
            self.verticalLayout.addLayout(self.horizontalLayout_2)
     
            MainWindow.setCentralWidget(self.centralwidget)
            self.statusbar = QStatusBar(MainWindow)
            self.statusbar.setObjectName(u"statusbar")
            MainWindow.setStatusBar(self.statusbar)
     
            self.retranslateUi(MainWindow)
     
            QMetaObject.connectSlotsByName(MainWindow)
        # setupUi
     
        def retranslateUi(self, MainWindow):
            MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None))
            self.label.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_2.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_3.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.label_4.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None))
            self.pushButton_enregistrer.setText(QCoreApplication.translate("MainWindow", u"Enregistrer", None))
            self.pushButton_supprimer.setText(QCoreApplication.translate("MainWindow", u"Supprimer", None))
            self.pushButton_nouveau.setText(QCoreApplication.translate("MainWindow", u"Nouveau", None))
            self.pushButton_annuler.setText(QCoreApplication.translate("MainWindow", u"Annuler", None))
        # retranslateUi
    et la base de donnée test.db dans le code
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    BEGIN TRANSACTION;
    CREATE TABLE IF NOT EXISTS "clients" (
    	"id"	INTEGER,
    	"name"	TEXT,
    	"Debut"	INTEGER,
    	"Fin"	INTEGER DEFAULT 0,
    	PRIMARY KEY("id")
    );
    INSERT INTO "clients" ("id","name","Debut","Fin") VALUES (1,'un',1,10);
    INSERT INTO "clients" ("id","name","Debut","Fin") VALUES (2,'deux',4,8);
    INSERT INTO "clients" ("id","name","Debut","Fin") VALUES (3,'trois',0,6);
    INSERT INTO "clients" ("id","name","Debut","Fin") VALUES (4,'quatre',3,8);
    INSERT INTO "clients" ("id","name","Debut","Fin") VALUES (5,'cinq',4,8);
    COMMIT;

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [PowerShell] Télécharger des fichiers sur un ftp et les classer
    Par papafred dans le forum Scripts/Batch
    Réponses: 15
    Dernier message: 08/10/2018, 18h17
  2. classer des colonnes sur vb.net
    Par mohcheb90 dans le forum VB.NET
    Réponses: 4
    Dernier message: 23/06/2008, 19h06
  3. Réponses: 4
    Dernier message: 20/06/2008, 12h45
  4. [Order by] classer des résultats sur des nombres
    Par vampiloup dans le forum Requêtes
    Réponses: 2
    Dernier message: 13/01/2006, 15h58

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo