Tagebuch [Tagebuch] Warenwirtschaftssystem für Zuhause

8. Datenbank: Die Erste

Hallo Zusammen

Die Zeit vergeht viel zu schnell, bald ist schon Weihnachten und ich muss mich jetzt ran halten, damit ich auch ein Geschenk habe. Die letzte Zeit ist es bei der Arbeit ziemlich stressig und nach einem 9 Stunden Tag im Büro bin ich ziemlich platt und dem Hausbau muss auch noch ein Mass an Aufmerksamkeit geschenkt werden ^^. Aber ich hatte Montag & Dienstag Frei und darum gehts auch wieder vorwärts.

Die letzten Tag habe ich die Oberfläche noch soweit fertig gestellt damit alles nötige vorhanden ist und sie ungefähr so aussieht wie geplant.

gui_Example_3.png


Am Montag & Dienstag gings mit der Datenbank weiter. Die Entscheidung ist auf SQLite gefallen, in der Hoffnung das ich damit alles hinkriege wie geplant.

Als erstes reichen die zwei Tabellen ite_Item & boo_Booking. Item ist der Stammeintrag für die Produkte und in Booking werden die einzelnen Ein- und Ausbuchvorgänge festgehalten.

Die Tabellen sehen wie folgt aus:

ite_Item.png


boo_Booking.png


Dazu hab ich noch die entsprechenden Objektklassen und ein bisschen mehr in Python gebaut.

Booking.py
booking_Code.png


Item.py
item_Code.png


BookingListItem.py (Entität für Liste um den Tree zu füllen)
bookingListItem_Code.png


BookingList.py (Liste um den Tree zu füllen)
bookingList_Code.png


Database.py (Datenbank Objekt)
database_Code.png


Die Abfrage aus Python und das Einfüllen in einen Tree funktioniert ohne Probleme.

db_Test.png


Beim Speichern in die Datenbank hab ich aber grad noch meine Mühe ^^
Das Item speichern geht bestens, aber bei Booking muss ich für den ForeignKey die UId des Items mitgeben. Und irgendwie bekomm ich ein falsches/leeres Objekt zurück wenn ich die Item UId über die GTIN holen will. Das ganze ist natürlich nur für den Fall, dass das ITEM noch nicht vorhanden ist. Es kommt noch zusätzlicher Code falls das Item schon in der Datenbank vorhanden ist.

saveBooking_Code.png


Aber ich hatte gestern Abend keinen Nerv mehr, darum wars das auch schon. :-D

Ich klemm mich diese Woche nochmals dahinter und will das Datenbank-Zeugs abschliessen. Als nächstes kommt dann die Online-Produktabfrage per GTIN. Da freue ich mich schon drauf :-) Und ebenso klemme ich mich noch hinters Zeichenbrett für das Gehäuse. Das sollte bis Weihnachten zumindest unlackiert bereitstehen ^^

Gruss taks
 
9. GUI Refactoring: !#%?-ç(@!!

Abend allerseits

Das im letzten Beitrag erwähnte Problem mit dem Einfügen der Datensätze konnte ich mit einem frischen Versuch auf Anhieb beheben. Manchmal ist es einfacher wenn man es am nächsten Tag nochmals versucht :-)
Soweit ist das Thema Datenbank dann auch abgeschlossen. Dachte zuerst es gibt noch mehr zu tun, aber ich baue mir die restlichen benötigten Funktionen wenn ich sie im fertigen GUI implementieren.

Und das fertige GUI ist dann auch das Thema welches ich mir für jetzt vorgenommen habe. Wieso fertiges GUI fragt ihr euch? Das GUI welches ich oben gezeigt habe steht alles schön aneinander gereiht in einem Python File. Damit es übersichtlicher wird, habe ich die einzelnen Funktionen in eigene Files/Klassen gepackt. Der Aufwand dafür war mit ~1 Stunde auch noch übersichtlich. Aber hier habe ich meine erste Knacknuss gefunden.

Eigentlich funktioniert alles wie es soll, nur eine !#%?-ç(@!! Kleinigkeit hat mich wertvolle Zeit und Nerven gekostet. Wenn ich auf einen Button klicke werden die im "command" angegebenen Funktionen nicht ausgeführt. Und ich hab absolut keinen Plan an was das liegt. Nach etwa 3 Stunden Internet-Recherche und Programm umschreiben habe ich Gestern Abend das Handtuch geworfen. Keine Chance das Ding zum laufen zu kriegen....
Deshalb bleib ich fürs erste glaub einfach bei meiner "Ein-Datei-Lösung" und kümmer mich später drum.
Wenn jemand von euch ne Idee hat wo der Fehler liegt, bitte raus damit :hail: Hier fehlt mir auch einfach die Erfahrung mit Python. Den Programmieraufwand welchen ich bis jetzt in das Projekt gesteckt habe, würde ich auf 15-20h schätzen.

Im MainForm werden die einzelnen Elemente erstellt.
TabController enthält alles für das Notebook welches als Tab-Steuerung funktioniert.
TabBooking enthält alles für das Ein-/Austragen der Artikel. Und auch die Buttons welche eben die entsprechenden Funktionen aufrufen sollten aber dies nicht tun. Wo ich hier auch ein Problem habe: Wie rufe ich am besten aus der command Funktion der Buttons die Entry Felder auf, welche ich in einer anderen Funktion erstellt habe? Globale Variabeln? In der Version wie ihr sie hier seht, habe ich TabBooking als Klasse erstellt mit den entsprechenden Attributen. Passt das so?

Code:
## MainForm ##
--------------
import tkinter as tk
import tkinter.ttk as ttk
import TabController as tac
import TabBooking as tab               

root = tk.Tk()
root.geometry=('800x480')
root.resizable(False, False)
root.title("Candyshop")

tabController = tac.getTabController(root)

root.mainloop()

Code:
## TabController ##
-------------------
import tkinter as tk
import tkinter.ttk as ttk
import TabBooking as tab
import TabItem as tai
import TabOverview as tao

# Return Booking Frame
def createTabController(root):
    #Create and Configure Style for Notebook
    style = ttk.Style()
    style.configure('lefttab.TNotebook', tabmargins=[2, 5, 2, 0], tabposition='wn', borderwidth=0)
    style.configure('lefttab.TNotebook.Tab', padding=[10, 68], borderwidth=1, width=11, font=('Arial','11','bold'))
    tabController = ttk.Notebook(root, style='lefttab.TNotebook')
    createTabs(tabController)
    tabController.grid(row=0, column=0, sticky="nw")
    return tabController

# Return Booking Frame
def createTabs(tabController):
    tabController.add(tab.getTabBooking(tabController), text=' Hinzufügen')
    tabController.add(tai.getTabItem(tabController), text='  Artikel')
    tabController.add(tao.getTabOverview(tabController), text='  Übersicht')
    return tabController

# Return tabControl (Notebook)
def getTabController(root):
    return createTabController(root)

Code:
## TabBooking ##
----------------
import tkinter as tk
import tkinter.ttk as ttk

class Item:
    def __init__(self,tabController):
        self.tabController = tabController
        self.tabBooking = tk.Frame(self.tabController, bg='#ffffff', width=680, height=480)
        #Create Frame for TreeView
        self.frameList = tk.Frame(self.tabBooking, bg='grey', width=300, height=480)
        self.frameList.grid(row=0, column=0, sticky="nsew")
        #Create TreeView
        self.my_tree = ttk.Treeview(self.frameList, height=22)
        #Define Columns
        self.my_tree['columns'] = ("Artikel", "Menge", "Datum")
        #Format Columns
        self.my_tree.column("#0", width=0)
        self.my_tree.column("Artikel", anchor="w", width=150, minwidth=25)
        self.my_tree.column("Menge",  anchor="e", width=30)
        self.my_tree.column("Datum", anchor="e",  width=90)
        #Create headings         
        self.my_tree.heading("Artikel", text="Artikel")
        self.my_tree.heading("Menge", text="Stk.")
        self.my_tree.heading("Datum", text="Datum")
        #  Create Frame for Fields
        self.frameDetails = tk.Frame(self.tabBooking, bg='White', width=300, height=480)
        self.frameDetails.grid(row=0, column=1, sticky="nsew", padx = 10, pady=10)
        #  Create Fields
        self.lblArtikelGTIN = tk.Label(self.frameDetails, bg='White', text="GTIN", width=10, pady=20)
        self.lblArtikelGTIN.grid(row=0, column=0)
        self.entArtikelGTIN = tk.Entry(self.frameDetails, font = ('calibri', 15, 'bold'), width=18)
        self.entArtikelGTIN.grid(row=0, column=1)
        self.lblArtikelName = tk.Label(self.frameDetails, bg='White', text="Bezeichnung", width=10, pady=20)
        self.lblArtikelName.grid(row=1, column=0)
        self.entArtikelName = tk.Text(self.frameDetails, font = ('calibri', 15, 'bold'), height=4, width=18)
        self.entArtikelName.grid(row=1, column=1)
        self.lblArtikelMenge = tk.Label(self.frameDetails, bg='White', text="Stück", width=10, pady=20)
        self.lblArtikelMenge.grid(row=2, column=0)
        self.entArtikelMenge = tk.Entry(self.frameDetails, font = ('calibri', 20, 'bold'), justify='center', width=13)
        self.entArtikelMenge.grid(row=2, column=1)
        #Create Frame for Controls
        self.frameControls = tk.Frame(self.tabBooking, bg='White', width=100, height=480)
        self.frameControls.grid(row=0, column=2, sticky="nsew")
        #Create Controls
        self.btnAdd = tk.Button(self.frameControls, text ="Add", width=9, height=3, font = ('calibri', 13, 'bold'), bg='green', command = addButtonCall)
        self.btnAdd.grid(row=0, column=0, pady=20)
        self.btnRemove = tk.Button(self.frameControls, text ="Remove", width=9, height=3, font = ('calibri', 13, 'bold'), bg='orange', command = removeButtonCall)
        self.btnRemove.grid(row=1, column=0, pady=20)
        self.btnPlus = tk.Button(self.frameControls, text ="+", width=9, height=3, font = ('calibri', 13, 'bold'), bg='grey', command = plusButtonCall(self.tabBooking))
        self.btnPlus.grid(row=2, column=0, pady=20)
        self.btnMinus = tk.Button(self.frameControls, text ="-", width=9, height=3, font = ('calibri', 13, 'bold'), bg='grey', command = minusButtonCall)
        self.btnMinus.grid(row=3, column=0, pady=20)

# Button Call Functions
def plusButtonCall(self):
    value = self.entArtikelMenge.get()
    #print (sNumber)
    #result = 0
    #result = int(value) + 1
    #entArtikelMenge.insert (0, str(5))
    print(value)
    print ("Callback!")

def minusButtonCall():
   print ("Callback!")

def addButtonCall():
   print ("Callback!")

def removeButtonCall():
   print ("Callback!")
 
Zuletzt bearbeitet:
Such mal nach Data Binding, ich vermute/hoffe sowas gibt es auch für dein Framework...

Grundsätzlich macht es Sinn GUI und Daten zu trennen, das sorgt dafür das Änderungen an der GUI nichts kaputt machen...

Wahrscheinlich ist für deinen Zweck am einfachsten eine globale Var. die alle Daten hält, die anderen Elemente schreiben/lesen dann nur noch dieses Element.

Wenn du da weiter lesen willst MVVM MVC sind Design Pattern für sowas was du machst...
 
Such mal nach Data Binding, ich vermute/hoffe sowas gibt es auch für dein Framework...

Grundsätzlich macht es Sinn GUI und Daten zu trennen, das sorgt dafür das Änderungen an der GUI nichts kaputt machen...

Wahrscheinlich ist für deinen Zweck am einfachsten eine globale Var. die alle Daten hält, die anderen Elemente schreiben/lesen dann nur noch dieses Element.

Wenn du da weiter lesen willst MVVM MVC sind Design Pattern für sowas was du machst...
Ja, ich muss mich da mal schlau machen was es in die Richtung für Python gibt. Aber kurzfristig (bis Weihnachten) liegt es leider ned mehr drin und ich muss schauen, dass ich die Grundfunktionen drin habe. Auch wenn die Software bis dahin natürlich noch nicht ausgereift sein muss.
Aber langsam wirds schwierig das Projekt vor meiner Freundin geheim zu halten. Auch für das Gehäuse welches ich heute angefangen habe musste ich die ganze Woche warten bis sie mal nicht Zuhause war. Dank Corona kann man ja fast nichts mehr unternehmen und sitzt Zuhause -.-


10. Gehäuse Teil 1

Nachdem mein Plan im Kopf schon recht klar war, hab ich ihn noch schnell zu Papier gebracht um zu sehen ob mir nichts entgangen ist. Es ging alles so auf wie ich wollte und nachdem ich das entsprechende Holz aus meinem Lager geholt hatte gings los :bier:

Ein ganz simpler Plan und im Hintergrund noch die Specs für das Display wegen dem Ausschnitt.
IMG_20201218_165346.jpg


Da meine Tischkreissäge im Winter zu kalt hat und den Dienst verweigert, kam meine neu erworbene Stichsäge (Makita 4329) zum Einsatz :-) Bin sehr zufrieden mit ihr, jedoch sind Gehrungsschnitte mit ner Stichsäge nicht so einfach wie mit der Tischkreissäge ^^ Für die drei Blenden und die Gehäuseseiten gingen stolze 1.5 Stunden drauf.
IMG_20201218_182605.jpg

Wie hier zu sehen ist wurden die Schnitte halbwegs wie gewünscht und es passt was ich gesehen habe auch recht gut zusammen. Ein bisschen Spachtel wirds aber brauchen :ugly:
IMG_20201218_182620.jpg


Beim Gehäuse gehts am Montag weiter, werd ein bisschen früher Feierabend machen und noch die letzte Blende, den Boden und die zwei Winkel an den Seitenwänden zusägen. Zusammengehalten wird dann alles mit 8mm Dübeln und Holzleim.

Übers Wochenende sollte dann auch die Software soweit sein, dass ich einen funktionieren Prototypen habe :-)


Danke fürs lesen & ein geruhsames Wochenende
 
11. Webabfrage & weitere Funktionen

Abend zusammen

Heute gings weiter mit der Webabfrage der GTIN-Nummer. Wie weiter oben geschrieben benutze ich dafür die Webseite https://www.gtinsuche.de/ um den Produktnamen zur entsprechenden GTIN zu erhalten. Kurz und knapp zusammengefasst:
Es lebt! :-)
Ich hatte nur meine Mühe BeautifulSoup installiert zu bekommen weil PIP nicht so wollte wie ich ^^
Der Code kann noch ein Refactoring vertragen, aber es läuft fürs Erste. Muss die restlichen drei Tage die mir bleiben noch für was anderes als hübschen Code verwenden :-D

Code:
import urllib3
import certifi
from bs4 import BeautifulSoup

class GtinWebInfo: 
    # Class to get numbers from webpage and return them
    @classmethod
    def getGTIN(self, Attr2):
        numbers = []
        url =('https://www.gtinsuche.de/detail?ean=' + Attr2)
        http_pool = urllib3.connection_from_url(url, cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())   
        r = http_pool.urlopen('GET',url)
            
        soup = BeautifulSoup(r.data, 'html.parser')     
        for tag in soup.find_all('h4'):
            print(tag.string)
            #print("hoi")
            numbers.append(tag.string)
        #for tag in soup.find_all("li", class_="new lucky-star"):
            #print(tag.string)
            #numbers.append(tag.string)
        #for x in numbers:
            #print("Zahl: " + x)
        return numbers

Zwischendurch gabs zur Stärkung Weihnachtskekse von Mutti :-X Sie hat mir soviele mitgegeben, ich kann glaub noch bis Februar davon essen ^^
IMG_20201220_173214.jpg


Daneben habe ich auch die Funktionen zum Hinzufügen von neuen Artikeln, Buchungen etc. mit dem GUI verknüpft und es läuft auch alles. War ein wirklich produktiver 4. Advent :bier:
Zu den oben erwähnten Funktionen kommt im nächsten Tagebucheintrag noch mehr. Der Code ist noch ein ziemliches durcheinander und noch ned 100% fertig. Für heute reichts mir aber mit programmieren und darum ist auch Schluss.

Gruss taks
 
Hallo zusammen

Eigentlich wollte ich heute meinen nächsten Beitrag schreiben, aber ich hatte einfach keine Zeit. Der ausführliche Beitrag folgt die kommenden Tage.

Darum gibts nur ein kurzes Video. Die Produktnamen werden übers Internet abgefragt. Geiler Scheiss :-D

Eingebundener Inhalt
An dieser Stelle findest du externe Inhalte von Youtube. Zum Schutz deiner persönlichen Daten werden externe Einbindungen erst angezeigt, wenn du dies durch Klick auf "Alle externen Inhalte laden" bestätigst: Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit werden personenbezogene Daten an Drittplattformen übermittelt.
Für mehr Informationen besuche die Datenschutz-Seite.

Liebe Grüsse & nen schönen Heiligabend
 
12. Gehäuse Teil 2

Schon lange versprochen, nun kommt endlich der nächste Beitrag.

Wie im Video zu sehen ist, funktioniert der Code soweit wunderbar. Bei der Bedienung gibt es noch ein paar Stellen die noch ein bisschen "hackelig" sind welche ich mir demnächst vornehme.

Das Gehäuse ist da eine ganz andere Liga.
Zuerst hab ich die Teile wie weiter oben schon erwähnt mit meiner Stichsäge zugeschnitten, Da ich bei der Arbeit jedoch mehr zu tun hatte als gehofft, konnte ich das ganze erst am Dienstag Abend vor Weihnachten angehen.
Also nach 10 Stunden Arbeit um ~17:00 Uhr die Sachen gepackt und frisch ans Werk.

Mit einer Dübelschablone ging es auch recht gut voran. Ganz praktisch das Ding.

IMG_20201223_165102.jpg

IMG_20201223_170722.jpg


Ging alles ganz flott und das Ergebniss konnte sich auch sehen lassen.

IMG_20201223_170828.jpg

IMG_20201223_171300.jpg


Jedoch war die Konzentration nach 10 Stunden arbeiten nicht mehr sooo gut. Wer findet den Fehler? ^^

IMG_20201223_171542.jpg


Auf alle Fälle hatte ich danach eine Pechsträhne und es wollte nichts mehr klappen. Nachdem ich wie oben zu sehen ist die Löcher auf die falsche Seite gebohrt hatte (Gehrung zeigt auf die falsche Seite) habe ich den selben Fehler bei einem weiteren Teil gemacht. Danach brach mir noch der Rahmen/Halterung für das Display.
Mit einem riesen Anschiss hab ich dann einfach alles ohne Dübel verleimt und beschlossen später in Ruhe ein neues Gehäuse zu bauen.

IMG_20210214_164631.jpg


Als nächstes kümmere ich mich wieder um die Software. Dabei steht Refactoring und die saubere Implementierung von Data-Bindings an.

Gruss & einen schönen Sonntag
taks
 
Zurück