sqlite3 mit Treeview

Fragen zu Tkinter.
Antworten
SnuSnu
User
Beiträge: 3
Registriert: Mittwoch 10. März 2021, 12:39

Hallo zusammen,

ich habe eine sqlite Datenbank mit 2 Tabellen "artikel" und "information", die Tabelle artikel hat einen Fremdschlüssel "LieferantenID" zum Primärschlüssel in der Tabelle information "LieferantenID".
Die inhalte der Tabelle artikel wird in einem Treeview angezeigt, ich möchte nun per mouseclick auf ein row den dazugehörigen schlüssel von der tabelle information angezeigt bekommen.
Im Code unten seht ihr meinen verzweifelten versuch :D
Das anzeigen funktioniert, allerdings wird immer die Falsche verbindung angezeigt also nicht der exakte query in der Tabelle Information zum Fremdschlüssel Tabelle artikel.
Ich habe hier versucht durch klick in ein row die spalte 7 (Spalte 7= Fremdschlüßel Tabelle artikel) in eine Variable zu speichern und durch diese Variable aus der Tabelle information den Query anzuzeigen.

Ich bin noch ein neuling in Python und weiß nicht mehr weiter, wäre über eure unterstüzung sehr dankbar.

Code: Alles auswählen

def info(event):
    selection = trv.item(trv.selection())
    selected_tuple=selection["values"][7]

    cursor.execute("SELECT Firma, Kontaktperson1, Telefonnummer1, Kontaktperson2, Telefonnummer2, Straße, PLZ, Ort, EMail, WEB FROM information INNER JOIN artikel ON information.LieferantenID = artikel.LieferantenID ")
    mydb.execute("PRAGMA foreign_keys = 1")
    mydb.commit()
    info_l = cursor.fetchall()[selected_tuple]

    print(info_l)
    print(selected_tuple)

    #Loop thru results
    print_records = ""
    for infos in info_l:
        print_records = str(info_l[0]) + "\n\n" + str(info_l[1]) + "\n\n" + str(info_l[2]) + "\n\n" + str(info_l[3]) + "\n\n" + str(info_l[4]) + "\n\n" + str(info_l[5]) + "\n\n" + str(info_l[6]) + "\n\n" + str(info_l[7]) + "\n\n" + str(info_l[8]) + "\n\n" + str(info_l[9])

    query_label_data = Label(wrapper6, text = print_records, font = 10, anchor="e", fg="red")
    query_label_data.place(x = 100, y = 50, width = 230)

    query_label = Label(wrapper6, text = "Lieferant:", font=10)
    query_label.place(x = 10, y = 50)
    query_label1 = Label(wrapper6, text = "Kontakt1:", font=10)
    query_label1.place(x = 10, y = 87)
    query_label2 = Label(wrapper6, text = "Telefonnummer1:", font=10)
    query_label2.place(x = 10, y = 122)
    query_label3 = Label(wrapper6, text = "Kontakt2:", font=10)
    query_label3.place(x = 10, y = 157)
    query_label3 = Label(wrapper6, text = "Telefonnummer2:", font=10)
    query_label3.place(x = 10, y = 194)
    query_label3 = Label(wrapper6, text = "Straße:", font=10)
    query_label3.place(x = 10, y = 231)
    query_label3 = Label(wrapper6, text = "PLZ:", font=10)
    query_label3.place(x = 10, y = 269)
    query_label3 = Label(wrapper6, text = "Ort:", font=10)
    query_label3.place(x = 10, y = 307)
    query_label3 = Label(wrapper6, text = "EMAIL:", font=10)
    query_label3.place(x = 10, y = 339)
    query_label3 = Label(wrapper6, text = "Web:", font=10)
    query_label3.place(x = 10, y = 374)

    mydb.execute("PRAGMA foreign_keys = 0")
    return
trv.bind("<ButtonRelease 1>", info)
Falls ihr mehr informationen zu meiner beschreibung braucht, einfach fragen.

Danke und Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SnuSnu: An dem Quelltext fällt als erstes mal auf, dass Du keine Funktionen benutzt, denn ``def`` alleine reicht nicht um eine richtige Funktion zu schreiben. Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. Man verwendet keine globalen Variablen.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Funktions- und Methodennamen werden üblicherweise nach der Tätigkeit benannt die sie durchführen, damit der Leser weiss was sie tun und sie leicht(er) von eher passiven Werten unterscheiden zu können.

Die `info()`-”Funktion” verwendet `trv`, `wrapper6`, `mydb`, und `cursor` einfach so auf magische Weise aus der ”Umgebung”.

`cursor` sollte aber gar nicht von aussen kommen. Die sind als eher kurzlebige Objekte gedacht, die man erstellt, eine Operation damit ausführt, und sie dann wieder schliesst.

Falls `Label` durch einen Sternchen-Import zustande gekommen ist: Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Auch Namen die gar nicht in `tkinter` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

Namen sollten keine kryptischen Abkürzungen enthalten, oder gar nur daraus bestehen. `trv` sollte mindestens `treeview` heissen (vermute ich). Besser wäre wenn an dem Namen auch ablesbar wäre was für Informationen darin enthalten sind.

Man nummeriert keine Namen. Da will man sich entweder bessere, passendere Namen ausdenken, oder man möchte gar keine Einzelnamen sondern eine Datenstruktur. Oft eine Liste. Innderhalb der Funktion macht die 6 in `wrapper6` überhaupt gar keinen Sinn. Und der konventionelle, generische Name für diesen Wert ist `master`. Falls man mit dem Begriff `master` Probleme hat, ist auch `parent` üblich.

Manchmal will man bei nummeriert oder sonstwie schlechten Namen auch gar keine(n) Namen verwenden. Wie bei den ganzen nummerierten `query_label`-Namen, denn die werden streng genommen nirgends verwendet, wenn man das Zwischenergebnis nicht unbedingt unnötigerweise an einen Namen binden will. Da könnte man auch einfach das hier schreiben:

Code: Alles auswählen

    tk.Label(master, text="Lieferant:", font=10).place(x=10, y=50)
    tk.Label(master, text="Kontakt1:", font=10).place(x=10, y=87)
    tk.Label(master, text="Telefonnummer1:", font=10).place(x=10, y=122)
    tk.Label(master, text="Kontakt2:", font=10).place(x=10, y=157)
    tk.Label(master, text="Telefonnummer2:", font=10).place(x=10, y=194)
    tk.Label(master, text="Straße:", font=10).place(x=10, y=231)
    tk.Label(master, text="PLZ:", font=10).place(x=10, y=269)
    tk.Label(master, text="Ort:", font=10).place(x=10, y=307)
    tk.Label(master, text="EMAIL:", font=10).place(x=10, y=339)
    tk.Label(master, text="Web:", font=10).place(x=10, y=374)
Wobei man `place()` nicht verwendet. Ist Dir da nicht selbst aufgefallen wie bekloppt und aufwändig das ist da für jede Textzeile die `y`-Position auszurechnen oder durch ausprobieren heraus zu finden? So eine Tabelle baut man am einfachsten mit `grid()` auf und das dann mit einer Schleife die Beschriftungen und Werte zusammenführt.

Dann braucht man `print_records` nicht. Das hätte man aber auch nicht so aufwändig zusammengestückelt, sondern `str()` auf alle Elemente angewendet und mit der `join()`-Methode verbunden, dann ist das ganze ein kurzer Einzeiler:

Code: Alles auswählen

     print_records = "\n\n".join(map(str, info_l))
Und das ganze ausserhalb einer Schleife, weil absolut keinen Sinn macht das für jedes Element in `info_l` zu wiederholen, also 10 mal den *gleichen* Wert zusammen zu setzen. Es reicht wenn man das *einmal* macht.

Statt die kompletten Informationen zu denen es Artikel gibt abzufragen und daraus dann einen *zufälligen* Wert heraus zu picken, kann man die Abfrage gleich mit LIMIT auf einen Datensatz eingrenzen. Warum zufällig? Nun, die SELECT-Abfrage hat kein Sortierkriterium, also ist die Reihenfolge in der die Datensätze geliefert werden undefiniert. Selbst wenn es sortiert wäre, ist dieses Vorgehen mehr als komisch. Denn Fall „Selektiere alle Datensätze nach Datum aufsteigend und gib mir davon den n-ten Datensatz“ mit Variablen `n` hat man in der Praxis nicht. Da machst Du irgendwas falsch.

Was soll das `my` bei `mydb`? Gibt es auch eine `our_db` oder eine `their_db`? Falls nein, ist das ein sinnloser Zusatz der dem Leser nichts bringt. Da sollte man lieber `database` aussschreiben oder es `db_connection` nennen.

Das PRAGMA macht nicht was Du denkst was es macht. Damit schaltet man ein ob SQLite bei INSERT/UPDATE prüft ob dadurch im Schema angegebene Fremdschlüssel-Constraints verletzt werden. Bei SELECT kann man keine Fremdschlüssel-Constraints verletzten. So ein Pragma würde man auch nicht in beliebigen Funktionen/Methoden setzen, sondern einmal direkt nach dem erstellen der Datenbankverbindung. Das auf Funktions- oder Methodenebene zu machen macht vielleicht Sinn wenn man grosse Datenmengen einfügt von denen man sicher ist, dass sie die Fremdschlüssel-Constraints nicht verletzen.

Das ``return`` am Ende der Funktion ist Überflüssig.

Bliebe von dem Code das hier übrig:

Code: Alles auswählen

import tkinter as tk
from contextlib import closing
from functools import partial


def display_info(master, db_connection, _event=None):
    with closing(db_connection.cursor()) as cursor:
        cursor.execute(
            "SELECT Firma, Kontaktperson1, Telefonnummer1, Kontaktperson2,"
            "   Telefonnummer2, Straße, PLZ, Ort, EMail, WEB"
            " FROM information"
            " INNER JOIN artikel"
            "   ON information.LieferantenID = artikel.LieferantenID"
            " LIMIT 1"
        )
        row = cursor.fetchone()

    db_connection.commit()

    print(row)
    for row_index, (label_text, value) in enumerate(
        zip(
            [
                "Lieferant",
                "Kontakt1",
                "Telefonnummer1",
                "Kontakt2",
                "Telefonnummer2",
                "Straße",
                "PLZ",
                "Ort",
                "Email",
                "Web",
            ],
            row,
        )
    ):
        tk.Label(
            master, text=f"{label_text}: ", font=10, foreground="red"
        ).grid(column=0, row=row_index, sticky=tk.E)
        tk.Label(master, text=value, font=10).grid(
            column=1, row=row_index, sticky=tk.W
        )


def main():
    ...
    treeview.bind(
        "<ButtonRelease 1>",
        partial(display_info, wrapper6, database),
    )
Die Funktion verwendet das `treeview`-Objekt jetzt gar nicht mehr weil der Code effektiv ja sowieso *irgendeinen* Datensatz abgefragt hat, die Auswahl im Treeview also gar keine wirkliche Rolle gespielt hat. Sollte sie wohl. Ich vermute mal sehr stark in Form einer Bedingung beim SELECT. Nur welche?

Und das erstellen der `Label` in der Funktion ist problematisch und deshalb ungewöhnlich, weil man die Funktion so nur ein einziges mal aufrufen kann. Wenn man das mehrfach tut werden immer mehr und mehr `Label`-Objekte erstellt und übereinander gestapelt.

GUIs baut man in der Regel einmal am Anfang komplett auf und aktualisiert dann die angezeigten Werte. Die Beschriftungen hätten in der Funktion also gar nichts zu suchen und man würde die bereits woanders *einmal* erstellten `Label`-Objekte, beispielsweise in Form einer Liste, an die Funktion übergeben, und die würde dann die Texte der Labels durch die Werte aus dem Datensatz ersetzen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
SnuSnu
User
Beiträge: 3
Registriert: Mittwoch 10. März 2021, 12:39

Hallo Blackjack

vielen Dank schonmal für die gute Kritik, wie gesagt ich bin da noch ein Neuling und muss noch viel lernen.
Das mit den Benennungen stimmt natürlich und ist ein gute tipp, die Verwendung von trv, wrapper6, mydb und cursor ist nicht von irgendwo her, die wurden außerhalb der Funktion definiert und erstellt, da wurde meinerseitz vorab zu wenig an informationen mitgegeben.

Trv ist das treeview, mydb ist die Variable zur Verbindung der Datenbank und Cursor ist die Variable für den cursor in der Datenbank, wrapper6 ist ein LaberFrame in dem root Fenster, es gib natürlich auch wrapper1,2,3 usw. die ihre eigenen Funktionen und Inhalte haben, ich wollte den Rest des Programmes nicht dazu posten weil die nichts mit dem Problem zu tun haben.

Ich habe dein Beispiel ausprobiert und der funktioniert sehr gut, allerdings wird immer der gleiche Datensatz ausgegeben egal auf welchen Datensatz ich klicke.

In der Datenbank Tabelle artikel sind viele verschieden artikel aufgelistet, die Tabelle hat einen Fremdschlüssel LieferantenID welches sich auf die tabelle information in die Spalte LieferantenID bezieht.

Das Treeview beinhaltet alle Datensätze von der Datenbank Tabelle artikel, durch das Klicken auf ein bestimmten Datensatz soll der dazu gehöriger Datensatz aus der Tabelle information ausgegeben werden.

Besteht da eine Möglichkeit durch eine Bedingung die Verbindung herzustellen und den dazugehörigen Datensatz auszugeben?

Danke und Grüße
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@SnuSnu: Die Namen kommen doch von ”irgendwoher”. Die werden bei Dir einfach so benutzt ohne das sie als Argument(e) übergeben werden. Das dürfte nicht sein, weil man Code der Variablen definiert nicht auf Modulebene stehen hat und keine globalen Variablen definiert. Das Du die da irgendwo definiert hast war schon klar, aber da sollten sie halt nicht definiert werden. Das macht Programme unübersichtlich und schlecht wart- und testbar.

Ob das nun immer der gleiche Datensatz ist oder immer irgend ein anderer: in beiden Codes ist halt nicht definiert *welcher* Datensatz das ist. Das es immer der gleiche ist, kann sein, muss aber nicht, es ist halt überhaupt nicht definiert *welcher* das ist wenn man einfach ein SELECT auf mehrere Datensätze ohne eine Sortierung macht.

Wenn Du einen bestimmten Artikel haben willst, dann musst Du auf diesen Artikel einschränken. Idealerweise in dem Du die ID von dem Artikel in der `artikel`-Tabelle hast. Dann kannst Du mit WHERE nach diesem Wert filtern. In die Zeichenkette mit dem SQL kommt ein Platzhalter für den Wert und der Wert selber dann in einer Sequenz, beispielsweise einer Liste, als zweites Argument von `execute()`. Steht in der Dokumentation vom `sqlite3`-Modul, mit Beispielen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
SnuSnu
User
Beiträge: 3
Registriert: Mittwoch 10. März 2021, 12:39

Hallo Blackjack,

ich habe das problem jetzt lösen können danke für die Tipps.
Habe in meinen oberen code die SELECT option durch
*cursor.execute("SELECT Firma, Kontaktperson1, Telefonnummer1, Kontaktperson2, Telefonnummer2, Straße, PLZ, Ort, EMail, WEB FROM information WHERE LieferantenID=?", (selected_tuple,))*
ersetzt und mit fetchone ausgegeben.

Grüße
Antworten