Afficher dans une fenêtre une image issue d'une base de donnée sqlite3

a marqué ce sujet comme résolu.

Bonjour,

j’ai créé une petite base sqlite3 contenant 2 tables. Sur l’une des tables une colonne contient une image (jpg). Je voudrai dans un programme python afficher cette image dans une fenêtre. Mais ce que j’ai fait ne fonctionne pas. Voici le bout de programme concerné

    def artSel(self):
        print("un artiste a été selectionné")
        artiste = self.ui.cBox_nomArtiste.currentText()
        print(artiste)
        ident_str = artiste[0:1] #récupération de l'ID
        connexion = sqlite3.connect("Z:/Cinema.db")
        curseur = connexion.cursor()
               # acces à la base pour extraire les infos de cet élément
        cmdsql = "SELECT *  FROM Artistes WHERE id_art² = " + ident_str
        print(cmdsql)
        curseur.execute(cmdsql)
        result = curseur.fetchone()
        print(result)
# Remplissage des champs de la fenêtre
        ident_art = result[0]
        nom = result[1]
        prenom = result[2]
        nomPrenom = ident_str + " " + nom + " " + prenom
        date_naiss = result[3]
        genre = result[4]
        nationalite = result[5]
        Photo = result[6]   # image dans la table au format BLOB

        self.ui.LineEd_nom.setText(nom)
        self.ui.LineEd_Prenom.setText(prenom)
        annee = date_naiss[-4:] #recupere l'annee
        int_annee = int(annee ) # convertion de l'annee en integer
        date = datetime.now() # date du jour
        annee_act = (date.year)
        print (annee_act)
        age = annee_act - int_annee
        age_txt = str(age )
        print ("age = ", age , "ans")
        age_txt = age_txt + " ans"
        self.ui.LineEd_age.setText(age_txt)
        self.ui.LineEd_genre.setText(genre)                      #.LineEd_genre.setText(genre)
        self.ui.LineEd_nationalite.setText(nationalite)

        # self.image = QPixmap(result[6])
        # self.ui.label_image.setPixmap(self.image)            
        # self.ui.label_image.show()

#self.ui.tableWidget.
        connexion.close()  # Déconnexion
#self.accesfilms(ident_str)
        self.ui.label_image.show() #Affichage de la fenêtre

Merci de votre aide

Salut,

image dans la table au format BLOB

D’après la doc, le format BLOB n’est pas un paramètre possible:


    QPixmap(const QPixmap &pixmap)
    QPixmap(const char *const [] xpm)
    QPixmap(const QString &fileName, const char *format = nullptr, Qt::ImageConversionFlags flags = Qt::AutoColor)
    QPixmap(const QSize &size)
    QPixmap(int width, int height)
    QPixmap()

https://doc.qt.io/qt-5/qpixmap.html#QPixmap-3

Comme souvent avec les images, il est préférables de stocker un chemin d’accès plutôt que du BLOB.
Sinon utiliser cette méthode qui revient a créer ton image sur le disque puis tu pourra la lire avec QPixmap:
https://pynative.com/python-mysql-blob-insert-retrieve-file-image-as-a-blob-in-mysql/

+0 -0

D’après la doc :

QPixmap provides several ways of reading an image file: The file can be loaded when constructing the QPixmap object, or by using the load() or loadFromData() functions later on. When loading an image, the file name can either refer to an actual file on disk or to one of the application’s embedded resources.

https://doc.qt.io/qtforpython/PySide2/QtGui/QPixmap.html#reading-and-writing-image-files

Dans ton cas tu n’as pas un fichier, tu as directement le flux JPG en mémoire prêt à être lu avec loadFromData dont voici le prototype :

PySide2.QtGui.QPixmap.loadFromData(buf[, format=None[, flags=Qt.AutoColor]])

https://doc.qt.io/qtforpython/PySide2/QtGui/QPixmap.html#PySide2.QtGui.PySide2.QtGui.QPixmap.loadFromData

buf serait ta variable sur le flux JPG (result[6] dans ton code). Par contre, tu dois appliquer la conversion nécessaire entre ce que retourne SQLite pour avoir le flux brut JPG (de type bytes je suppose) si cela s’avère nécessaire. Je ne sais pas bien ce que retourne SQLite dans ton cas, mais si j’en crois la doc de SQLite, tu as déjà tes données brutes sans que ce soit formaté de façon spécifique :

BLOB. The value is a blob of data, stored exactly as it was input.

https://sqlite.org/datatype3.html

Salut,

Mon message ne va pas répondre à la question posée, mais ce code me trigger à plusieurs égards.

Tout d’abord, comme @Angelo l’a aussi précisé, j’estime que ce n’est pas du tout une bonne pratique que de stocker une image comme un BLOB dans une BDD relationnelle, surtout dans une base de données SQLite qui ne supporte pas les accès concurrents. Même si ici on semble se trouver dans le contexte d’une application Qt, c’est quand même généralement plus sain de stocker les images ailleurs, dans un répertoire, et de se contenter de référencer leur adresse dans la BDD.

Ensuite, il y a un sacré problème avec la cohérence du nommage dans ce code : tes noms de variables suivent 5 conventions différentes. Tes noms de méthode deux. Tes attributs, trois.

Pourquoi la colonne s’appelle-t’elle id_art² ? Que vient faire ce ² ici ?

Pour rappel, la convention standard, en Python, c’est :

  • Les noms de classes en UpperCamelCase,
  • Les noms de méthode en lower_snake_case,
  • Les noms de variable en lower_snake_case.

Enfin, plusieurs éléments de ce code le rendent très fragile.

Cette ligne est sensible aux injections SQL :

cmdsql = "SELECT *  FROM Artistes WHERE id_art² = " + ident_str

La doc du module sqlite précise bien que la seule façon correcte de faire ce genre de truc, c’est d’utiliser le placeholder ?, comme ça:

curseur.execute("SELECT *  FROM Artistes WHERE id_art²=?", ident_str)

Un autre problème, c’est de faire ce SELECT * alors que tu récupères ensuite tes variables d’une manière sensible à l’ordre. Une façon plus propre et plus lisible ressemblerait à ceci (modulo le nom réel de tes colonnes…):

curseur.execute(
    "SELECT (nom, prenom, date_naiss, nationalite, genre, photo) "
    "FROM Artistes WHERE id_art²=?",
    ident_str
)
nom, prenom, date_naiss, nationalite, genre, photo = tuple(curseur.fetchone())

De cette façon, si demain tu changes ton schéma et que tes colonnes se retrouvent dans un ordre différent, ton code ne plantera pas.

Ou encore, mieux, accéder aux colonnes par leur nom plutôt que dans l’ordre :

row  = curseur.fetchone()
print("prénom:", row["prenom"])
# etc.

Il y aurait encore d’autres trucs à dire, notamment sur la gestion des erreurs, mais je pense que j’ai couvert le plus urgent.

+1 -0

Re,

Merci à vous pour cette aide précieuse et si intéressante. Je ne m’attendais pas à recevoir des réponses aussi riches. Comme vous avez pu le deviner je suis novice et il me manque beaucoup de bases. Je vais tenir compte de vos remarques et reprendre tout ça.

Pour répondre à nohar, la colonne s’appelle id_art² car lors de la création de la base j’ai du appuyer sur la touche "²" par erreur et je n’ai pas su comment corriger. Comme il s’agit d’un test pour aborder Python et le reste j’ai laissé ainsi.

Avec votre permission je reviendrai surement vous solliciter

Encore merci

Tout d’abord, comme @Angelo l’a aussi précisé, j’estime que ce n’est pas du tout une bonne pratique que de stocker une image comme un BLOB dans une BDD relationnelle, surtout dans une base de données SQLite qui ne supporte pas les accès concurrents. Même si ici on semble se trouver dans le contexte d’une application Qt, c’est quand même généralement plus sain de stocker les images ailleurs, dans un répertoire, et de se contenter de référencer leur adresse dans la BDD.

Je me permets de poser une question non liée directement au sujet, mais quelles sont les raisons qui te conduisent à cette estimation ? Pourquoi est-ce plus sain d’utiliser directement le système de fichiers et de stocker le chemin d’accès, plutôt que de stocker directement dans une colonne de la base de données pour une image ?

+0 -0

Il y a plusieurs raisons qui peuvent (ou non) se superposer.

D’abord, une image ça pèse beaucoup plus lourd que toutes les autres données de la ligne. Ça va donc, d’une façon générale, plomber les requêtes, ainsi que la taille de la base de données.

Je ne sais pas trop quels sont les access patterns de l’application Qt dont il est question ici, mais en général, quand on utilise une BDD, il est quand même préférable de faire des petites IO rapides à servir, et donc se contenter d’une BDD frugale qui ne contient que des métadonnées sur lesquelles on peut requêter, et se laisser par exemple la possibilité de charger le gros contenu séparément, pourquoi pas de façon concurrente, pourquoi pas en en gardant une partie en cache dans la mémoire du programme, pour améliorer la réactivité de l’interface (si une image doit être affichée 100 fois pendant que le programme tourne, mais chargée une seule fois, tu économises l’équivalent de 100 fois sa taille en IO).

Un autre cas où ça va se ressentir c’est sur les interfaces web : il vaut mieux que le backend réponde vite avec l’URL d’une image, et laisser le navigateur se débrouiller pour charger les images à part, notamment parce que lui aussi peut les mettre en cache, et qu’il va de toute façon les charger en réalisant des requêtes HTTP concurrentes dans ce cas.

Le seul bénéfice qu’on peut imaginer tirer de stocker les images comme des blobs en base, c’est une apparente simplicité : "toutes mes données sont au même endroit, je n’ai qu’un fichier dont je dois me préoccuper". Mais c’est reculer pour mieux sauter.

D’abord, parce que ça impose des contorsions comme ici pour les charger. Ensuite, parce que tôt ou tard, on tombera forcément sur une fonctionnalité qui va nous imposer de séparer nos ressources et de maintenir plusieurs sources de données à la fois (ou bien des problèmes de perfs…), et dans ce cas cette simplicité finira de toute façon par être perdue. Du coup autant ne pas s’y accrocher, et s’épargner une refacto pénible qui n’attend qu’à devenir indispensable au moment où on aura le moins envie de la faire. :)

+0 -0

Bonsoir,

Me revoici avec mon petit projet sur lequel je m’exerce. Tout d’abord bravo @nohar pour ta réponse @lfrit. D’ailleurs j’ai modifié mon projet en conséquence.

Une remarque, le code que tu propose renvoie une erreur "1073740791 (0xC0000409)"

curseur.execute(
    "SELECT (nom, prenom, date_naiss, nationalite, genre, photo) "
    "FROM Artistes WHERE id_art²=?",
    ident_str
)
nom, prenom, date_naiss, nationalite, genre, photo = tuple(curseur.fetchone())

je l’ai donc adapté comme ceci:

    curseur = connexion.cursor()
                                                                # acces à la base pour extraire les infos de cet element
        cmdsql = "SELECT id_art², art_nom, art_prenom, art_date_naiss, art_decede, art_genre, art_nationalite, art_image  \
        FROM Artistes WHERE id_art² = " + ident_str
        curseur.execute(cmdsql)
        v_ident, v_nom, v_prenom, v_date_naiss, v_decede, v_genre, v_nationalite, v_photo = tuple(curseur.fetchone())

Par contre j’ai un problème pour adapter la requête lorsque le select retourne plusieurs lignes. Il s’agit de la requête à la ligne 68. Si tu veux bien y jeter un œil ce serait sympa. Voici le code complet, si besoin je peux joindre la base pour le faire tourner.

from PyQt5.QtWidgets import QApplication, QLabel
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication
from PyQt5.QtGui import QIcon
from datetime import datetime
from VisuCine import Ui_Form_visuCinema
import sys
import sqlite3
from PyQt5.QtGui import QIcon, QPixmap

chemin_bdr = "Z:\Programmation\PycharmProjects\Prg1-1\Bdr\Cinema.db"

class Main(QMainWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.initUI()
        
    def initUI(self):
        self.setGeometry(0, 0, 500, 500)
        
        ouvrirAction = QAction('&Ouvrir Visu Artistes', self)
        ouvrirAction.setShortcut('Ctrl+O')
        ouvrirAction.setStatusTip('Ouvrir Visu Artistes')
        ouvrirAction.triggered.connect(self.on_open)

        fermerAction = QAction('&Fermer Visu Artistes', self)
        fermerAction.setShortcut('Ctrl+F')
        fermerAction.setStatusTip('Fermer Visu Artistes')
        fermerAction.triggered.connect(self.on_close)

        self.statusBar()
        # QMainWindow.menuBar()

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(ouvrirAction)
        fileMenu.addAction(fermerAction)
        
        self.setWindowTitle('PyQt5 Fenêtre principale')
#       self.show() #Affiche la fenêtre Main
        self.init_dialog()
        self.show() #Affiche la fenêtre Main (en fait la classe Main



    def init_dialog(self):
        self.form = QtWidgets.QDialog()
        self.ui = Ui_Form_visuCinema()
        self.ui.setupUi(self.form)
        #self.ui.LineEd_nom.setText('coucou')
        #self.ui.label_nom.setText('Nom')
        self.form.setWindowModality(QtCore.Qt.ApplicationModal)
       #self.ui.cBox_nomArtiste.activated.connect(self.art_sel)
        self.remplir_comboBox()

    def on_open(self):
        print ("Ouverture de VisuArtiste")
        self.ui.cBox_nomArtiste.activated.connect(self.art_sel)
        self.form.open()

    def remplir_comboBox(self):
        print ("Remplissage")
        self.ui.cBox_nomArtiste.clear()
        connexion = sqlite3.connect(chemin_bdr)
        curseur = connexion.cursor()
        cmdsql = "SELECT id_art², art_nom, art_prenom  FROM Artistes"
        print(cmdsql)
        curseur.execute(cmdsql)
        result = curseur.fetchall()
        nblig = len(result)
        print("nblig = ", nblig)
        i = 0
        while i < nblig:
            ident_art = result[i][0]                        # formatage de l'ID sur 3 caractères
            ident_str = str(ident_art)                      #
            long_ident_str = len(ident_str)                 #
            if long_ident_str == 1:                         #
                ident_str = "00" + ident_str                #
            elif long_ident_str == 2:                       #
                ident_str = "0" + ident_str                 #
            else:                                           #
                print ("le champ ID contient 3c ou plus")   #
            print(ident_str)
            nom = result[i][1]
            prenom = result[i][2]
            nomPrenom = ident_str + " " + nom + " " +  prenom
            self.ui.cBox_nomArtiste.addItem(nomPrenom)
            i = i + 1
        connexion.close()  #Déconnexion

    def art_sel(self):
        print("un artiste a été selectionné")
        artiste = self.ui.cBox_nomArtiste.currentText()
        print(artiste)
        ident_str = artiste[0:3]                                # récupère l'ID sur 3 caractères
        connexion = sqlite3.connect(chemin_bdr)
        curseur = connexion.cursor()
                                                                # acces à la base pour extraire les infos de cet element
        cmdsql = "SELECT id_art², art_nom, art_prenom, art_date_naiss, art_decede, art_genre, art_nationalite, art_image  \
        FROM Artistes WHERE id_art² = " + ident_str
        curseur.execute(cmdsql)
        v_ident, v_nom, v_prenom, v_date_naiss, v_decede, v_genre, v_nationalite, v_photo = tuple(curseur.fetchone())

        if v_decede == "non":
            annee = v_date_naiss[-4:]
            int_annee = int(annee )
            date = datetime.now()
            annee_act = (date.year)
            age = annee_act - int_annee
            age_txt = str(age )
            age_txt = age_txt + " ans"
            print (age_txt)
            self.ui.LineEd_age.setText(age_txt)
        else:
            self.ui.LineEd_age.setText("Décédé")
        self.ui.LineEd_nom.setText(v_nom)
        self.ui.LineEd_Prenom.setText(v_prenom)
        self.ui.LineEd_genre.setText(v_genre)
        self.ui.LineEd_nationalite.setText(v_nationalite)
        connexion.close()
        self.aff_img(v_photo)
       #self.accesfilms(ident_str)  à faire


    def aff_img(self, photo):
        cheminphoto = "z:\Programmation\PycharmProjects\Prg1-1\Images\\"
        photo = cheminphoto + photo
        print (photo)
        self.image = QPixmap(photo)
        self.ui.label_image.setPixmap(self.image)
        self.ui.label_image.show()



    # def accesfilms(self, ident_str):
    #     print("no artiste = ", ident_str)
    #     connexion = sqlite3.connect(chemin_bdr)
    #     curseur = connexion.cursor()
    #     cmdsql = "SELECT *  FROM Films WHERE id_art = " + ident_str
    #     print(cmdsql)
    #     curseur.execute(cmdsql)
    #     result = curseur.fetchall()
    #     nblig = len(result)
    #     print(nblig)
    #     i = 1
    #     while i < nblig:
    #         nomFilm = result[i][2]
    #         categorie = result[i][3]
    #         dateSortie = result[i][4]
    #         self.ui.
    #     #     self.ui.cBox_nomArtiste.addItem(nomPrenom)
    #     #     i = i + 1
    #     # connexion.close()  # Déconnexion


    def on_close(self):
        print ("Fermeture du programme")
        quit()
# ----------------------------------------------------------------------------------------------------------------------

if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = Main()
    ret = app.exec_()
    sys.exit(ret)
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte