Zwischenergbnis ausgeben über Website - Timeout vermeiden - Nutzer informieren

Django, Flask, Bottle, WSGI, CGI…
Antworten
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

Hallo in die digitale Runde,

ich baue aktuell eine Django-Website (mit Django-CMS), die für den Benutzer eine Rechnung ausführt. Die Berechnung dauert lange und daher gibt es im Browser für den Nutzer nach einer kleinen Weile, dass es einen Fehler gab. Grund: der Browser wartet nicht mehr auf das Ergebnis der Berechnung, sondern wirft einen Fehler aus. Der Nutzer fragt sich also, was los ist. Im Hintergrund wird aber natürlich weitergerechnet, nur das Ergebnis kommt beim Nutzer nie an - da ja vom Browser abgebrochen.

Super simpel sieht das so aus:

Code: Alles auswählen

def meine_berechnung(request):
    # rechne viele schritte, 1 von 10.000, 2 von 10.000, .... , 10.000 von 10.000
    # Nutzer wartet ggf. 120 und mehr Sekunden
    # bei wenigen Berechnungen erhält der Nutzer eine Ausgabe, bei vielen Berechnungen kommt es zum Timeout
    return render(request, "ergebnis_zu_meine_berechnung.html", {"ergebnis": ergebnis)


Jetzt würde ich gerne den Nutzer über den Stand der Berechnung informieren. Gibt es so was wie eine for-Schleife, mit der ich den Browser ansteuern kann, um nach x Berechnungen einen Zwischenausgabe zu machen, die dann vom Konstrukt her wie folgendes aussehen würde?

Code: Alles auswählen

def meine_berechnung(request):
    for i in range(10.000):
        return render(request, "ergebnis_zu_meine_berechnung.html", {"zwischenergebnis": i)    
Oder gibt es einen anderen Weg, um den Browser nicht in den Timeout zu jagen und dem Nutzer kenntlich zu machen: ja, hier passiert noch was und die Berechnung dauert einfach noch ein bisschen?

Vielen Dank für eure Hilfe
Marco
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst die Berechnung auslagern. Am einfachsten ist ein Thread, aber ggf sind such Lösungen wie Celery angebracht. Das request kehrt dann schnell zurück, und du kannst den User über den Fortschritt via websockets, SSE oder ähnlichem auf dem laufenden halten.
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

Hallo __deets__,

danke für den Hinweis des Auslagerns.
Mein Kernproblem ist, dass ich keine Idee habe, wie ich den Nutzer über diese Zwischenergebnisse informieren kann. Denn Django kenne ich bisher nur so, dass "Nutzer startet Aktivität - Django gibt aus - fertig". Jetzt wäre es aber der Fall dass "Nutzer startet Aktivität - Django gibt aus, gibt nochmal aus, gibt nochmal aus - fertig".
Hat da jemand ein Minimalbeispiel für mich? Was kommt in die views.py? Wie sieht mein Template in html dann aus? Kann mir da jemand helfen?

Viele Grüße
M.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du brauchst eine Assoziation zwischen dem User und dem Job. Ein Weg wäre einfach das Session Objekt. Darin steckt der Job als wert, und du kannst einen weiteren JSON-View zb anbieten, der dessen Status zurück liefert.
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

ok... wie stellt man diese Verbindung her? Lässt sich das an Hand der oben skizzierten minimalen "Codes" zeigen zu einem Minimalbeispiel erweitern? Wäre es dann wirklich eine Schleife über die der Zwischenstand immer wieder per render ausgegeben wird? Oder kennst du/ihr einen guten Link über den ich mir so was mal anschauen kann?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Deine Starte-Berechnung-View muß halt eine ID zurückgeben, mit der Du die Hintergrundprozesse abfragen kannst, und bei jedem Aufruf von Gibt-mir-den-Status mußt Du diese ID mitgeben.
Das hat jetzt erstmal nichts mit Django zu tun.
Das sind zwei unabhängige Probleme:
1. wie startet man einen Hintergrundprozess und fragt dessen Status ab. Da kommst es sehr darauf an, um was es sich denn handelt und wie man das dann am besten umsetzt.
2. wie Programmiere ich im Browser mit Hilfe von Javascript eine Schleife, die Regelmäßig den Status des Hintergrundprozesses abfragt und ihn anzeigt.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

k.marco.1970 hat geschrieben: Dienstag 8. März 2022, 12:00 ok... wie stellt man diese Verbindung her? Lässt sich das an Hand der oben skizzierten minimalen "Codes" zeigen zu einem Minimalbeispiel erweitern? Wäre es dann wirklich eine Schleife über die der Zwischenstand immer wieder per render ausgegeben wird? Oder kennst du/ihr einen guten Link über den ich mir so was mal anschauen kann?
Nein, es ist *KEINE* Schleife darin involviert, zumindest nicht, wenn man per normalem HTTP-Request arbeitet. Websockets haben da ggf. andere Moeglichkeiten. Aber es klingt gerade nicht so, als ob das in den Sternen steht.

Das vorgehen ist simpel:

- request zum start des Jobs, startet einen Thread mit dem Job, und haengt den Job (das muss also irgendein Objekt sein) in die Session. Kehrt zurueck.
- der Job updatet seinen Status nebenbei.
- ein anderer view wird via Javascript angefragt, holt sich das Job-Objekt aus der Session, und gibt den Fortschritt zurueck.

Keiner dieser beiden views hat eine Schleife oder sowas, die regelmaessige Abfrage muss durch Timer im Browser/Javascript sichergestellt werden.

Und ich programmiere kein Django. Das ist aber auch unerheblich, weil so webanwendungen alle funktionieren.
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

Hallo zusammen,

das sagt ihr so, dass es simpel ist ; )
Aber: ich starte jetzt erst mal mit dem ersten Schritt, den ihr mir genannt habt, einen Job zu starten. Das ist erstmal ausreichend Neuland (für mich). Und auch: wie bekomme ich die ID zum Job zurück. Ich bin guter Dinge, dass ich das hinbekomme (glaube, das habe ich mal gemacht).

Was würde die erste und die zweite view ausgeben?
Ich stelle mir das gerade so vor:

view1.py

Code: Alles auswählen

def meine_berechnung(request):
    # Nutzer wartet ggf. 120 und mehr Sekunden
    return render(request, "ergebnis_zu_meine_berechnung.html", {"ergebnis": ergebnis)
view2.py

Code: Alles auswählen

def mein_zwischenstand(request):
    # rechne viele schritte, 1 von 10.000, 2 von 10.000, .... , 10.000 von 10.000 und gib zwischenergebnis
    return render(request, zwischenergebnis 1, 2, ...10.000)
Wie würden nun im Template die beiden views1 und 2 miteinander verheiratet, dass die view2 während der berechnung ausgegeben wird und die view1, wenn alles fertig gerechnet ist?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Im Template wird da nichts verheiratet. Entweder benutzt du JavaScript, dann ist der mein_zwischenstand ein reiner JSON-View, mit einer Fortschrittsangabe. Oder du willst reines HTML, dann musst du einen refresh-header setzten, und das Ding sich immer wieder neu laden lassen. Da kann man dann auch einfach ein und denselben view machen, der einfach prueft, ob gerade ein Job da ist, und wenn ja, dann stellt er den Fortschritt dar, oder nicht, dann bietet er an, den zu starten.
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

Ich mache mal mein Beispiel etwas konkreter, um sicher zu gehen, dass die Lösungshinweise zu meinem Problem passen können.
Bisher ist das Ergebnis das ich liefere für den Nutzer eine Tabelle, die sortiert ist, einfach per render (um zu der sortierten Tabelle zu kommen, dauert es aber lange, da eine ziemliche Rechnung hintendran ist). In der Rechnung zum Sortieren habe ich die Möglichkeit jederzeit abzurufen, wo die Berechnung steht (z. B. 1 aus 10.000). Das könnte ich alle paar Sekunden machen und dann halt ausgeben 1 von 10.000 berechnen, 233 von 10.000 berechnet etc.). Und das würde ich eigentlich auch gerne so machen ... (wenn ich so richtig wüsste wie ; )
Am Ende soll der Nutzer aber dennoch die Tabelle bekommen.

JSON verstehe ich bisher als eine Struktur, in der Daten gespeichert werden. Von Wikipedia übernommen halt so im Beispiel:

Code: Alles auswählen

{
  "Herausgeber": "Xema",
  "Nummer": "1234-5678-9012-3456",
  "Deckung": 2e+6,
  "Waehrung": "EURO",
  "Inhaber":
  {
    "Name": "Mustermann",
    "Vorname": "Max",
    "maennlich": true,
    "Hobbys": ["Reiten", "Golfen", "Lesen"],
    "Alter": 42,
    "Kinder": [],
    "Partner": null
  }
}
Das wirkt auf mich erst mal sehr statisch und hier fehlt mir der Ansatz: wie aus dem Thread in python (der dann die 1 von 10.000 etc. ausgibt) dynamisch ausgeben, wo man steht und am Ende eine Tabelle erzeugt.
Mit der Struktur von JSON kann ich mir im Moment super vorstellen, die finale Tabelle abzubilden. Ich habe aber noch keine Idee, wie ich das dem Browser vermittle, dass die Zwischenausgaben gemacht werden...
Wenn also jemand die Zeit fände, in einem Mini-Mini-Beispiel kurz zu skizzieren, was ich programmiertechnisch beachten müssten, welche funktion was aufruft oder wie so ein JSON-View von der Struktur her aussieht, wäre super.
Ich hoffe, ich konnte meine Frage so etwas verdeutlichen, um für das Forum als Leser bewerten zu können, ob JSON die richtige Wahl wäre oder ob so etwas auch mit refresh geht. Denn auch mit refresh habe ich noch nicht gearbeitet...
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

JSON ist einfach eine Datenstruktur wie ein Wörterbuch und/oder Listen. Und natürlich kannst du genauso gut eine Datenstruktur dynamisch mit zb dem Schlüssel/Wertpaar progress=78 erzeugen und zurückgeben.

Voraussetzung für JSON ist aber JavaScript! Sonst bringt dir das nichts. Kannst du da?

Refresh lädt einfach nur alle paar Sekunden die Seite neu, bis dann am Ende das Ergebnis dargestellt wird. Heutzutage eher inelegant. Aber funktioniert zumindest etwas besser als eine hängende Seite.
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

nein, Java Script kann ich nicht. Da würde mir ein Schnipsel in einem template.html sicher helfen.

Refresh... ist das dann im python code eingesetzt oder gebe ich das in template.html mit?
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

danke für den Link. Den schaue ich an.
Hat noch jemand einen Tip, um ein Beispiel für JavaScript zu sehen, wie man die Anfrage vom Browser an den Server einbindet?
k.marco.1970
User
Beiträge: 46
Registriert: Mittwoch 25. Dezember 2013, 20:46

danke
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

Gerne.
Antworten