Onlineshop Anwendung

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
nfehren
User
Beiträge: 98
Registriert: Donnerstag 31. Oktober 2013, 15:11

Guten morgen :)
Ich bin momentan an einer Onlineshop-Anwendung dran. Ich möchte dort Artikel einfügen, löschen, und verändern können.

Ich habe 2 Probleme. Erstmal mein Code:

Code: Alles auswählen

import sqlite3
import os
import sys
from helper_functions import menupunkt_ausführen
class Artikel:
    
    def __init__(self, Artikelnummer, ArtikelName, AnzahlVorrat, Preis):
        self.Artikelnummer = Artikelnummer
        self.ArtikelName = ArtikelName
        self.AnzahlVorrat = AnzahlVorrat
        self.Preis = Preis
        self.properties = (Artikelnummer, ArtikelName, AnzahlVorrat, Preis)

class OnlineShop:
    
    def __init__(self, create=False):
        self.db_path = 'onlineshop.db'
        self.connect(create)

    def NeuerArtikel(self):
        self.Artikelnummer = int(input("Geben sie die Artikelnummer ein: "))
        self.ArtikelName = input("Geben sie den Artikelnamen ein: ")
        self.AnzahlVorrat = int(input("Geben sie den vorrätigen Lagerbestand ein: "))
        self.Preis = int(input("Geben sie den Preis ein: "))
        self.properties = (Artikelnummer, ArtikelName, AnzahlVorrat, Preis)
        
    def ArtikelHinzufügen(self, liste):
        for e in liste:
            self.cursor.execute('insert into onlineshop values(?,?,?,?)',e)
        self.db.commit()
        

    def connect(self, create=False):

        if os.path.exists(self.db_path):
            self.db = sqlite3.connect(self.db_path)
            self.cursor = self.db.cursor()
        else:
            if create:
                self.db = sqlite3.connect(self.db_path)
                self.cursor = self.db.cursor()
                self.create_db()
            else:
                print("\nSQLite Datenbank File\n\n" +
                      "-- {0} --\n\nexistiert nicht.\n".format(self.db_path) +
                      "Zum Neuanlegen dieses Programm mit Argument " +
                      "'create' aufrufen!\n\n"
                      "=> Anwendung wird beendet!")
                sys.exit(1)

    def create_db(self):
        self.cursor.execute('drop table if exists onlineshop')
        self.cursor.execute('create table onlineshop ('
                            'Artikelnummer INT PRIMARY KEY UNIQUE NOT NULL,'
                            'ArtikelName TEXT NOT NULL,'
                            'AnzahlVorrat INT NOT NULL,'
                            'Preis REAL NOT NULL)')
        self.db.commit()

if __name__ == "__main__":

    db_create = False
    artikel_liste = []

    if len(sys.argv) > 1:
        if sys.argv[1] == 'create':
            db_create = True

    shop = OnlineShop(db_create)

    artikel = Artikel(1, "Deutschland Fahne", 375, 9.99)
    artikel_liste.append(artikel.properties)

    shop.ArtikelHinzufügen(artikel_liste)

    shop.cursor.execute('select * from onlineshop')
    for row in self.cursor: print(row)



So.
Ich bekomme beim Ausführen folgende Fehlermeldung:
Traceback (most recent call last):
File "./Online_shop.py", line 82, in <module>
shop.ArtikelHinzufügen(artikel_liste)
File "./Online_shop.py", line 31, in ArtikelHinzufügen
self.cursor.execute('insert into onlineshop values(?,?,?,?)',e)
sqlite3.IntegrityError: column Artikelnummer is not unique
So wie ich das verstehe will der jedesmal den Artikel hinzufügen, da die Artikelnummer aber unique sein muss kommt der fehler.

1. Frage:
Wie kann ich das umgehen?
2. Frage:
Ich möchte 3 verschiedene Methoden schreiben mit denen man einen Artikel ändern, löschen und hinzufügen kann. Ich will mit einem Dictionary arbeiten sprich das alle Daten der Artikel in das Dictionary geschrieben werden.

Vielen Dank
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

nfehren hat geschrieben: 1. Frage:
Wie kann ich das umgehen?
Indem Du *eindeutige* Artikelnummern vergibst! :K
nfehren hat geschrieben: 2. Frage:
Ich möchte 3 verschiedene Methoden schreiben mit denen man einen Artikel ändern, löschen und hinzufügen kann. Ich will mit einem Dictionary arbeiten sprich das alle Daten der Artikel in das Dictionary geschrieben werden.
Du willst dafür eine Klasse verwenden. Außerdem willst Du eigentlich - auch wenn Du das noch nicht weißt - Dein bisheriges Design verwerfen und Dir erst einmal ein nettes Domänen-Modell erstellen und dieses anschließend mittels SQLAlchemy mit einer Datenbank verbinden ;-)

Generell solltest Du niemals (Benutzer)eingaben (``NeuerArtikel``) in einer Domänenklasse (``Artikel``) durchführen. (Die Methode scheint in die falsche Klasse gerutscht zu sein‽)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
nfehren
User
Beiträge: 98
Registriert: Donnerstag 31. Oktober 2013, 15:11

Hyperion hat geschrieben: Indem Du *eindeutige* Artikelnummern vergibst! :K
Das habe ich mir schon gedacht aber wie mache ich das? :P
BlackJack

@nfehren: Du kannst da halt keine feste 1 eintragen und das dann mehrfach laufen lassen. Du könntest zum Beispiel die höchste bereits vergebene Artikelnummer abfragen und da 1 drauf addieren. Schon hast Du eine neue Artikelnummer.

Anmerkungen zu Quelltext:

Der Import aus `helper_functions` wird nirgends verwendet.

Die Namensschreibweise hält sich nicht an den Style Guide for Python Code. Das empfinde ich als besonders störend weil man Datentypen und andere Werte nicht auf den ersten Blick auseinander halten kann.

Ich würde englisch als Sprache für die Namen im Quelltext wählen. Dann hat man wesentlich weniger häufig das Problem das Singular und Mehrzahl eines Wortes identisch sind, und muss sich zum Beispiel bei „Artikel” keine Gedanken machen wie man nun *einen* (Typ oder Exemplar) benennt und wie man einen Containertypen oder eine Sammlung für mehrere Artikel nennt. In Englisch und sich an die konventionalle Schreibweisen halten ist ein Typ für einen Artikel `Article`, ein Containertyp `Articles`, ein Exemplar `article`, und eine Sammlung `articles`.

`Artikel.properties` ist redundant. Warum bindest Du die selben Informationen die schon als Attribute existieren, noch mal als Tupel zusammengefasst an das Objekt?

Die `NeuerArtikel`-Methode finde ich auch verwirrend. Selbst wenn die wie Hyperion anhand der Attribute vermutet eigentlich in `Artikel` sollte, erstellt die keinen neuen Artikel, hat also einen unpassenden Namen. Und auch vom Inhalt her braucht man das wahrscheinlich eher selten bis nie, dass wirklich alle Attribute eines *bestehenden* Artikels durch den Benutzer ausgetauscht werden. Die letzte Zeile ist zudem fehlerhaft, denn die Namen die im Tupel verwendet werden gibt es allesamt nicht.

Bei `ArtikelHinzufuegen()` hätte man die `executemany()`-Methode verwenden können um die explizite Schleife zu sparen.

Neue Attribute sollte man nur in der `__init__()` einführen, damit der Leser leichter sieht welche Attribute es auf einem Objekt gibt ohne erst *alle* Methoden darauf prüfen zu müssen ob dort eventuell neue eingeführt werden. Wenn man in der `__init__()` selber noch keinen endgültigen Wert ermitteln kann, dann bietet sich `None` an. Andererseits kann man bei der Initialisierung die Methode die diese Werte setzt, sie auch als Rückgabewert zurück geben lassen und kann dann auch in der `__init__()` direkt richtige Werte an die Attribute binden. Aus der `connect()`-Methode lässt sich dann auch relativ einfach eine normale Funktion machen.

Das `sys.exit()` hat in dieser Methode/Funktion eher nichts zu suchen, genau wie die Ausgabe an den Benutzer. Wie Hyperion schon schrieb: Benutzerinteraktion und Programmlogik nicht mischen. Man könnte an der Stelle eine Ausnahme auslösen und die dann in dem Code der für die Benutzerinteraktion zuständig ist, entsprechend darauf reagieren.

``PRIMARY KEY UNIQUE NOT NULL`` ist der totale Overkill. Ein Primärschlüssel ist bereits zwingend eindeutig, sonst wäre er als Primärschlüssel nicht zu gebrauchen, und NULL können die Werte auch nicht sein. Letzteres wäre theoretisch möglich wenn man nur *einen* NULL-Wert in der Schlüsselspalte verwendet, aber aus praktischen Gründen ist das bei Primärschlüsseln auch nicht erlaubt.

Ich würde an der Stelle nicht die Artikelnummer nehmen sondern einen künstlichen Schlüssel einführen der wirklich nur als Schlüssel verwendet wird. Artikelnummern können sich auch irgend wann einmal ändern. Also nicht nur für einzelne Produkte, sondern auch ganz generell, wenn zum Beispiel das System zur Vergabe geändert wird. Weil man sein eigenes Katalogsystem ändert oder ein Zulieferer es ändert, und so weiter. Wenn man dann die Artikelnummern irgendwo als Fremdschlüssel verwendet, hat man ein Problem und unnötige Arbeit damit.

Das Hauptprogramm sollte in einer Funktion verschwinden. So wie es jetzt ist, läuft das alles auf Modulebene ab und erzeugt modulglobale Namen.

Nach dem Fehler den Du gezeigt hast lauert auch gleich der nächste. Auf Modulebene gibt es kein `self`. Und man sollte dort auch nicht so weit in das `shop`-Exemplar durchgreifen und dessen Datenbankverbindung benutzen, sondern eine Methode dafür schreiben. Die kann dann auch gleich dafür sorgen, dass nicht ”rohe” Datenbankergebnisse geliefert werden, sondern die Werte in `Artikel`-Exemplare verpackt werden, denn *damit* möchte man im Programm ja arbeiten.

Da würde ich mich Hyperion auch anschliessen und SQLAlchemy als ORM verwenden, statt das selber noch mal neu zu erfinden.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

BlackJack hat geschrieben:@nfehren: Du kannst da halt keine feste 1 eintragen und das dann mehrfach laufen lassen. Du könntest zum Beispiel die höchste bereits vergebene Artikelnummer abfragen und da 1 drauf addieren. Schon hast Du eine neue Artikelnummer.
Wobei das Vorgehen nicht sicher ist - sowohl bei Threads als auch allgemein bei einem Multi-User Betrieb kann es dabei zu Problemen kommen!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Hyperion: Okay, dann halt UUIDs als Artikelnummern. ;-) Ich denke die meisten Online-Shops machen sich um so etwas keine Gedanken wenn es nicht Enterprise-Level-Shops sind. Denn das sich mehrere Admins darum prügeln gleichzeitig neue Artikel einzupflegen kann man ja auch ausserhalb der Software regeln, und es ist immerhin so sicher, dass es niemals zwei gleiche Artikelnummern geben wird wenn die Spalte als ``UNIQUE NOT NULL`` deklariert ist. Ansonsten muss man halt genauer schauen wie das DBMS Transaktionen handhabt, da haben wir bei SQLite mit mehreren Schreibern sowieso schon Probleme, und/oder falls die Transaktionen nicht ausreichen, muss man schauen was das konkrete DBMS für Mittel zur Verfügung stellt um eindeutige Zahlen für eine Spalte zu erzeugen.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

DBMSe wie Postgres ermöglichen mittels Sequenzen auch streng monoton steigende Counter für Artikelnummern ;)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten