KeyError im Django-Service-Objekt

Django, Flask, Bottle, WSGI, CGI…
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Hallo Leute,

ich bekomme einen KeyError und ich weiss nicht genau woran es liegt.

Das ist meine Service-Klasse:

Code: Alles auswählen

class InsertBookingService(Service):
    data = {
        'user': forms.CharField(),
        'date': forms.CharField(),
        'time': forms.CharField(),
        'veg': forms.CharField()
    }

    def process(self):
        self.data['user'] = self.cleaned_data['user']
        self.data['date'] = self.cleaned_data['date']
        self.data['time'] = self.cleaned_data['time']
        self.data['veg'] = self.cleaned_data['veg']
        return self.insert_booking()

    def insert_booking(self):
        b = Booking(
            user=self.data['user'],
            start=parser.parse(self.build_timestring()).timestamp(),
            end=parser.parse(self.build_timestring()).timestamp() + 2700,
            veg=self.data['veg']
        )
        return b.save()
und das ist die View-Funktion dazu:

Code: Alles auswählen

def start(request, **user_id):
    if user_id:
        uid = user_id
    else:
        uid = {"user_id": "test"}
    context = GetBookingService.execute({'uid': uid['user_id']})
    if request.method == 'POST':
        data = dict(request.POST)
        InsertBookingService.execute({
            'user': uid['user_id'],
            'date': data['date'],
            'time': data['time'],
            'veg': data['veg']
        })
        return redirect('/start/' + uid['user_id'])
    ctj = {'data': json.dumps(context)}
    return render(request, "index.html", ctj)
Hat jemand vielleicht eine Idee woran es liegen könnte?

Vielen Dank
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

poste mal bitte die gesamt Fehlermeldung, die der genannte Code erzeugt.

Gruß, noisefloor
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Code: Alles auswählen

...myproject\myapp\services\booking.py", line 55, in process
    self.data['user'] = self.cleaned_data['user']
KeyError: 'user'
viel mehr wird nicht als Fehler ausgegeben...
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@efix: Du Benutzt `Service`-Objekte falsch. Auf die Klasse gehören die `Form`-Attribute. Und da dann auch nicht alles als `forms.CharField()`. Der Witz davon ist ja gerade die Validierung und ”Säuberung” der ”Argumente”. Das man da nicht mehr selbst Zeichenketten in Datums- und Zeit-Objekte umwandeln muss.

Und das dürfte auch der Grund für den `KeyError` sein, denn wo sollen die Schlüssel in `self.cleaned_data` auch her kommen wenn die nicht entsprechend als `form`-Felder erstellt wurden‽

Die `process()`-Methode kopiert sinnlos Daten von einem Wörterbuch in ein anderes. Warum? Zudem ist das auch noch kaputt, weil das Ziel ein Klassenattribut ist, also eine globale Variable. Das fällt bei Nebenläufigkeit dann auf die Nase. Und das ist recht wahrscheinlich, weil es sich um eine Webanwendung handelt, bei der mehrere Threads anfragen beantworten können.

Und dann wird aus `process()` die Methode aufgerufen die eigentlich das macht was in `process()` stehen sollte. Das hatte ich in einem anderen Thema ja schon mal geschrieben: das gehört in `process()`. Diese `Service`-Objekte sind eigentlich umständlich geschriebene Funktionen. Man kann die `process()`-Methode hier als `__call__()`-Implementierung sehen. Der Name der ”Funktion” ist der Klassenname. Man führt hier ja letztlich immer die Klasse aus, in dem man die Klassenmethode `execute()` aufruft.

In der `start()`-Funktion ist `uid` extrem sinnfrei. Das ist ein Wörterbuch mit genau einem Schlüssel/Wert-Paar wobei das Wörterbuch selbst nirgends verwendet wird. Es wird immer nur der eine Wert da raus geholt und verwendet. Warum steckt das dann in einem Wörterbuch? Die ``**``-Magie beim Argument von `start()` ist schon unsinnig.

In der Funktion wird die gleiche Information mal als "user", mal als "uid", und mal als "user_id" bezeichnet. Das ist verwirrend und Du solltest Dich auf *eine* Benennung im ganzen Programm festlegen. Wobei alle drei IMHO nicht gut sind, denn wenn da "id" im Namen vorkommt, im Kontext von Datenbanken, dann erwartet der Leser eine Zahl und keine Zeichenkette. Und bei `user` würde man ein Benuter-Objekt erwarten. Also wäre da `username` vielleicht passend.

Hier wäre dann die komplette Service-Klasse, ohne da noch zusätzliche Methoden zu definieren, denn auch da hat man ja das Problem, dass die mit den Attributen/Methoden von `Service` und `Form` kollidieren können:

Code: Alles auswählen

class InsertBookingService(Service):
    username = forms.CharField()
    date = forms.DateField()
    time = forms.TimeField()
    veg = forms.CharField()

    def process(self):
        start_timestamp = DateTime.combine(
            self.cleaned_data["date"], self.cleaned_data["time"]
        ).timestamp()
        return Booking(
            username=self.cleaned_data["username"],
            start=start_timestamp,
            end=start_timestamp + 2700,
            veg=self.cleaned_data["veg"],
        ).save()
Und die `start()`:

Code: Alles auswählen

def start(request, username):
    if request.method == "POST":
        data = dict(request.POST)
        InsertBookingService.execute(
            {
                "username": username,
                "date": data["date"],
                "time": data["time"],
                "veg": data["veg"],
            }
        )
        return redirect("/start/" + username)

    return render(
        request,
        "index.html",
        {
            "data": json.dumps(
                GetBookingService.execute({"username": username})
            )
        },
    )
Hier ist das hart kodierte "/start/" unschön.

Ich erwähnte im anderen Thema ja schon mal wie sinnlos ich diese `Service`-Objekte finde. Das würde noch mal deutlicher wenn man hier Django-Forms benutzen würde. Das ist dann eine grosse sinnlose herumkopiererei von Werten aus Wörterbüchern und mehrfache Validierung der gleichen Werte.

Ohne diese komischen Servie-Objekte könnte das ungefähr so aussehen:

Code: Alles auswählen

class InsertBookingForm(forms.Form):
    date = forms.DateField()
    time = forms.TimeField()
    veg = forms.CharField()


def get_booking(username):
    ...


def insert_booking(username, date, time, veg):
    start_timestamp = DateTime.combine(date, time).timestamp()
    return Booking(
        username=username,
        start=start_timestamp,
        end=start_timestamp + 2700,
        veg=veg,
    ).save()


def start(request, username):
    form = InsertBookingForm()

    if request.method == "POST":
        form = InsertBookingForm(request.POST)
        if form.is_valid():
            insert_booking(
                username,
                form.cleaned_data["date"],
                form.cleaned_data["time"],
                form.cleaned_data["veg"],
            )
        else:
            ...

        return redirect("/start/" + username)

    return render(
        request,
        "index.html",
        {"data": json.dumps(get_booking(username)), "form": form},
    )
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Danke für die Antwort 8)

Das ist mein erstes Django-Projekt und ich soll mit einer Service-Layer-Schicht arbeiten. Damit ich später einzelene Teile besser testen kann...

Ehrlich gesagt verstehe ich diese forms Felder noch nicht so ganz.

Code: Alles auswählen

class InsertBookingService(Service):
    user = forms.CharField()
    date = forms.DateField()
    time = forms.TimeField()
    veg = forms.CharField()

    def process(self):
        start_timestamp = datetime.combine(
            self.cleaned_data["date"], self.cleaned_data["time"]
        ).timestamp()
        return Booking(
            user=self.cleaned_data['user'],
            start=start_timestamp,
            end=start_timestamp + 2700,
            veg=self.cleaned_data['veg']
        ).save()
Wird denn das Klassen-Atrribut user überhaupt benutzt wenn ich direkt im return von process ein Booking-Objekt erzeuge?

Ich hoffe man versteht was ich meine...

Das mit der Namensvergabe von username habe ich verstanden. Das muss ich noch ändern.

Ich dacht man muss erstmal in process die Attribute aus clean_data holen...
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Ich muss nochmal doof nachfragen. cleaned_data ist der input beim Aufruf des Services oder?
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@efix: Die ”gesäuberte” und validierte Eingabe, ja. Schau Dir die Dokumentation von `django.forms` an. Ein `Service` ist ein `Form`.

Edit: Wo kommen denn die Vorgaben her? Also insbesondere für `service_objects`? Man kann die Logik auch ohne diese komischen Dinger von den Views trennen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Von meinem Abteilungsleiter :?

Ich kenne das eigentlich auch so bei MVC. Klar kann man das bestimmt auch ohne machen...

Ich bin ja jetzt noch am Anfang von dem Projekt. Das wird noch um einiges wachsen...

Aber noch mal zu der frage mit dem user-Attribut:

Code: Alles auswählen

class InsertBookingService(Service):
    [color=#FF0000]user[/color]= forms.CharField()
    date = forms.DateField()
    time = forms.TimeField()
    veg = forms.CharField()

    def process(self):
        start_timestamp = datetime.combine(
            self.cleaned_data["date"], self.cleaned_data["time"]
        ).timestamp()
        return Booking(
            [color=#FF0000]user[/color]=self.cleaned_data['user'],
            start=start_timestamp,
            end=start_timestamp + 2700,
            veg=self.cleaned_data['veg']
        ).save()
Irgendwie verstehe ich das noch nicht ganz...
Ich muss noch den self.user benutzen und dem das aus cleaned_data geben damit die Prüfung stattfindet oder nicht?

Nachher habe ich etwas mehr Zeit. Ich guck mir das nochmal genauer an....

Auf jeden Fall schonmal danke!!!
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Hier machen die das auch so:

https://kandi.openweaver.com/python/mix ... ects#About

Die rufen in process clean_data auf und übergeben die Daten...
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Aber die legen self.user selbst an in der Methode process, basierend auf Werten aus dem self.cleaned_data-Dictionary.
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Ja das habe ich vorher auch so gemacht...

Auf jeden Fall definieren die in der Service Klasse die Variable mit forms und initialisieren die in Process...
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Was ist "die Variable"? Ich sehe drei Variablen, die sind als Form-Fields deklariert, und auf die wird sich bezogen. Mir ist unklar, was dir unklar ist.
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das mit dem `self.user` wird dort aber auch nur gemacht, weil die den in der `post_process()`-Methode noch mal brauchen. Solange man das nicht hat, braucht und sollte man keine zusätzlichen Attribute anlegen. Und selbst dann finde ich das wie gesagt alles andere als gut Forms für so etwas zu missbrauchen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Ich hab das auch nur gemacht weil das eine Vorgabe von dem Service-Objekt ist mit den Formfields...

Was macht denn Formfield?

Es sagt das eine Variable ein bestimmter Typ sein muss sonst gibt es einen Fehler.

Ich bin gerade unterwegs. Wieso Formfield missbrauchen?
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Das Problem, warum ich überhaupt gefragt habe hat sich erledigt. Hängt mit Dictonary zusammen. Wenn ich einzelne Variabeln deklariere funktioniert es.

Über diese Formfield-Geschichte muss ich noch etwas recherchieren. Da hab ich gerade nicht die Zeit zu. Hauptsache es ist nicht völlig weit ab vom Schuss. Sonst muss ich später zuviel umschreiben.

Vielen Dank für die Hilfe und den Input
Benutzeravatar
__blackjack__
User
Beiträge: 13103
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@efix: `Service`-Objekte sind `django.Form`-Objekte, können also alles was `Form`-Objekte können, nur das davon nur ein Teil — die Validierung tatsächlich verwendet wird. Deshalb ist das IMHO ein Missbrauch von Forms. Und die Validierung wird um so unsinniger je näher die ”Argumente” an den tatsächlichen `Form`\s dran sind, weil dann Werte aus Forms noch mal in `Service`-Objekten validiert werden. Hoffentlich gegen die gleichen Bedingungen/Einschränkungen.

Ein `Service`-Objekt, falls das möglich ist, auch noch mal als `Form`-Objekt zu verwenden widerspricht der eigentlichen Intention die Logik von der Darstellung zu trennen, weil `Form`-Objekte, und damit halt auch `Service`-Objekte Wissen und Aktionen haben um HTML zu erzeugen. IMHO ist das ein ganz verquerer Mist der auf einem Blogpost basiert, wo der Autor herausgefunden hat, dass er mit wenigen Zeilen Code durch drantackern einer nur wenige Zeilen langen `execute()`-Klassenmethode und einer abstrakten `process()`-Methode aus einem `Form`-Objekt eine gruselige „Frankenstein-bastelt-aus-einem-`Form`-eine-”Funktion”-Klasse“-Absurdität erschaffen kann. Das ist ein schlechter Witz.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Um es nochmal deutlich zu sagen: es ist völlig weit ab vom Schuss.

Die Service-"Klasse" ist vom Design her eine Katastrophe. Sieht man schon daran, dass man per `execute` eine Instanz der Klasse erzeugt, aber niemals mit der Instanz selbst arbeitet.

Also gleich wegschmeißen, sonst musst du später zuviel umschreiben.
efix
User
Beiträge: 43
Registriert: Samstag 7. Dezember 2019, 20:59

Ok. Warum das Design schlecht ist kann ich nicht beurteilen...

Gibt es denn eine Alternative zu Django-service-objects. Könnt ihr mir etwas empfehlen?
Sonst mache ich das erstmal damit. Ich meine so lange das nicht unnötig Rechenkapazität in Anspruch nimmt kann ich damit leben...

Ich schau mal nach anderen Service-Layer Paketen :roll:
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Gibt es denn eine Alternative zu Django-service-objects.
Gar keine Service-Objekte nehmen? Warum wurde weiter oben ja schon mal gesagt.

In diesem Blogpost Against service layers in Django wird IMHO ganz gut erklärt, warum es oft in Kombination mit Django oft keine Gute Idee ist.
Man nimmt ja in der Regel Django, eben _weil_ es "batteries included" ist und die Komponenten eng verzahnt sind. Da macht halt eine zusätzliche Schicht alles eher komplizierter. Und wenn man wirklich mal an den Punkt kommen sollte, bei Django das ORM oder Forms-Framework komplett austauschen zu wollen sollte man IMHO direkt die Frage stellen, warum man Django überhaupt genommen hat.

Generell weiß ich auch nicht, ob es wirklich clever ist, beim 1. Projekt direkt Abstraktionsschichten einziehen zu wollen (oder müssen), wenn man noch nicht wirklich weiß, was Forms und Models so machen und wie die interagieren können.

Gruß, noisefloor
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Alternative zum Validieren von Form-Daten ist `Form`. Das hat ja __blackjack__ schon in seinem Code hier (viewtopic.php?p=409753#p409753) ganz unten gezeigt. Da muß man nicht nach irgendwelchen Service-Layer-Paketen suchen, denn das ist schon in Django eingebaut.
Antworten