Rückgabe der Werte an Template

Django, Flask, Bottle, WSGI, CGI…
Antworten
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Ich hatte schon berichtet, dass User in meinem Rechentrainer sich mehrmals anmelden und ich die Accounts jetzt zusammenführen muss. Dazu habe ich ein Template erstellt, das, wenn es mal klappt, mehrmals durchlaufen wird:
1. Accounts suchen
2. Accounts anzeigen
3. Accounts zusammenführen und
4. leere Accounts löschen
... das funktioniert schon fast. Die gefundenen Accounts werden angezeigt, wenn ich aber in der zweiten Runde Accounts zusammenführen will, wird in der Liste der Accounts alle Accounts angezeigt, da in der Suchenmaske die Einträge verlorengegangen sind. Wie gebe ich die wieder zurück wenn ich forms nutze? Also so irgendwie:

Code: Alles auswählen

<input id="your_name" type="text" name="your_name" value="{{ current_name }}">
Braucht ihr da den ganzen Code?
Template:

Code: Alles auswählen

<h2> Accounts zusammenführen und löschen</h2>
<h3>1. Schritt: User suchen:</h3>
<form action="/zusammenfuehren/" method="post">
    <table>
        {% csrf_token %}
        {{ suchen_form.as_table }}
    </table>
    <input type="submit" value="suchen">
</form>

<h3>2. Schritt: Account aussuchen:</h3>
<table>
    <thead>
        <tr>
            <th>Username</th>
            <th>ID</th>
            <th>Aufgaben</th>            
            <th>letzter Login</th>
            <th>Vorname</td>
            <th>Nachname</td>
            <th>Lerngruppe</td>
        </tr>
    </thead>
    <tbody>
        {% for profil, anz in zeilen %}
        <tr>
            <td>{{profil.user.username }}</td>            
            <td>{{profil.user.id }}</td>
            <td>{{anz }}</td>
            <td>{{profil.user.last_login }}</td>
            <td>{{profil.vorname }}</td>            
            <td>{{profil.nachname }}</td>            
            <td>{{profil.gruppe }}</td>            
        {% endfor %}
    </body>
</table>

<h3>3. Schritt: Accounts zusammenfassen:</h3>
<form action="/zusammenfuehren/" method="post">
    {% csrf_token %}
    {{ zusammen_form }}
    <input type="submit" value="Accounts zusammenführen">
</form>

<h3>4. Schritt: Accounts löschen:</h3>
<form action="/zusammenfuehren/" method="post">
    {% csrf_token %}
    {{ loeschen_form }}
    <input type="submit" value="Account löschen">
</form>
Form:

Code: Alles auswählen

class Suchen_Form(forms.Form):
    vorname = forms.CharField(label="Vorname", max_length=50, required=False)
    nachname = forms.CharField(label="Nachname", max_length=50, required=False)
Und View:

Code: Alles auswählen

def zusammenfuehren(req):
    if not req.user.is_superuser:
        return HttpResponse("Zugriff verweigert")
    suchen_form = Suchen_Form()
    loeschen_form = Loeschen_Form
    zusammen_form = Zusammen_Form

    vorname = nachname = ""
    zeilen = []
    
    # if this is a POST request we need to process the form data
    if req.method == "POST":
        suchen_form = Suchen_Form(req.POST)
        if suchen_form.is_valid():
            vorname = suchen_form.cleaned_data['vorname']
            nachname = suchen_form.cleaned_data['nachname']
            profil = Profil.objects.filter(vorname__contains=vorname, nachname__contains=nachname)
            for a in profil:
                aufgaben = Protokoll.objects.filter(user__id = a.pk).count() 
                zeilen.append((a, aufgaben))
        loeschen_form = Loeschen_Form(req.POST)
        if loeschen_form.is_valid():
            loeschen = loeschen_form.cleaned_data['loeschen']
            print(loeschen)
        zusammen_form = Zusammen_Form(req.POST)
        if zusammen_form.is_valid():
            quelle = zusammen_form.cleaned_data['quelle']
            ziel = zusammen_form.cleaned_data['ziel']
            
            print(quelle)
    context = {"suchen_form": suchen_form, "loeschen_form": loeschen_form, "zusammen_form": zusammen_form, "zeilen" : zeilen, "vorname": vorname, "nachname": nachname,}
    return render(req, 'zusammen.html', context)
Noch ein Screenshot:
Bild
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

grundsätzlich ist HTTP ja statuslos, d.h. es ist ein Request-Response Zyklus und wenn der fertig ist, ist alles "weg". Im Python / Django Kontext heißt das, dass die Funktion / der View jedesmal neu aufgerufen wird. Wenn du im 1. Aufruf z.B. was in die Liste schreibst, dann ist das mit senden des Response vergessen. Wenn 2. Aufruf ist wieder eine leere Liste in der Funktion.

Um sich Sachen über mehrere Request zu merken kann man z.B. das Session Framework (https://docs.djangoproject.com/en/4.2/t ... /sessions/) nehmen. Habe ich selber aber auch noch nicht genutzt.

Oder du schreibst den View so um, dass halt die relevanten Daten ans Template direkt mit geliefert werden.

Wie soll Schritt 2 überhaupt funktionieren? Da wird doch nur eine Tabelle ausgegeben.

Gruß, noisefloor
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

noisefloor hat geschrieben: Samstag 7. Oktober 2023, 16:44 Oder du schreibst den View so um, dass halt die relevanten Daten ans Template direkt mit geliefert werden.
Wie soll Schritt 2 überhaupt funktionieren? Da wird doch nur eine Tabelle ausgegeben.
Hallo und Danke @noisefloor
Also mit den sessions fühle ich mich überfordert.
In der zweite Stufe soll der Nutzer aussuchen, was wohin soll und was gelöscht wird. Profis bekommen das sicher hin, dass hier jeweils Kästchen zum Anklicken dastehen - ich kann das nicht.
Ich hatte gehofft, dass ich bei diesem Codeteil vielleicht mal das mit den Forms und dem get/post und dem Auslesen von Werten besser verstehe. Ich hatte ja versucht, "die relevanten Daten ans Template mit"zu liefern mit

Code: Alles auswählen

context = {..., "vorname": vorname, "nachname": nachname,}
und jetzt funktioniert das ja auch - ein Bisschen. ich kann suchen und wenn beim löschen ein Account angewählt wird in dem Aufgaben gerechnet wurde wird auch Vorname und Nachname zurückgeliefert. Allerdings wird das jetzt nicht wieder im Suchfeld genutzt, da diese Suche voraussetzt, dass ich den "suchen" Button anklicke. Meine drei Buttons sind ja in der Wirkung nicht zu unterscheiden und das mit dem Post habe ich in dem Zusammenhang, dass es drei Möglichkeiten gibt auch nicht verstanden.
Kann mir da jemand weiterhelfen?
Mein neues View:

Code: Alles auswählen

def zusammenfuehren(req, vorname = "", nachname = ""):
    if not req.user.is_superuser:
        return HttpResponse("Zugriff verweigert")
    loeschen_form = Loeschen_Form
    zusammen_form = Zusammen_Form
    zeilen = []    
    if req.method == "POST":
        vorname = req.POST.get("vorname")
        if vorname == None:
            vorname = ""
        nachname = req.POST.get("nachame")
        if nachname == None:
            nachname = ""
        profil = Profil.objects.filter(vorname__contains=vorname, nachname__contains=nachname)
        for a in profil:
            aufgaben = Protokoll.objects.filter(user_id = a.id)
            zeilen.append((a, aufgaben.count()))
        loeschen_form = Loeschen_Form(req.POST)
        if loeschen_form.is_valid():
            loeschen = loeschen_form.cleaned_data['loeschen']
            if loeschen != "":
                protokoll = Protokoll.objects.filter(user__id = loeschen) 
                if protokoll.count() > 0:
                    vorname = protokoll.first().user.vorname 
                    nachname = protokoll.first().user.nachname 
                    messages.error(req, "Mit dem Account wurden schon Aufgaben gerechnet, die müssen zuerst übertragen werden!")        
                    context = {"loeschen_form": loeschen_form, "zusammen_form": zusammen_form, "zeilen" : zeilen, "vorname": vorname, "nachname": nachname,}
                    return render(req, 'zusammen.html', context)
        zusammen_form = Zusammen_Form(req.POST)
        if zusammen_form.is_valid():
            quelle = zusammen_form.cleaned_data['quelle']
            ziel = zusammen_form.cleaned_data['ziel']
    else:
        vorname = nachname = ""
    context = {"loeschen_form": loeschen_form, "zusammen_form": zusammen_form, "zeilen" : zeilen, "vorname": vorname, "nachname": nachname,}
    return render(req, 'zusammen.html', context)
Wie gesagt, der Suchen Button funktioniert. Wenn ich allerdings einen Account zum Löschen auswähle der Aufgaben enthält, bekomme ich alle Accounts angezeigt. Der Vor- und Nachname wird ans template übergeben (das kann ich mir dort ja anzeigen lassen) - wird aber nicht für

Code: Alles auswählen

profil = Profil.objects.filter(vorname__contains=vorname, nachname__contains=nachname) 
verwendet - daher werden alle Accounts angezeigt.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1021
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Mit HTMX kann man recht einfach HTML-Elemente z.B. von einem get-Request zum DOM hinzufügen/ersetzen. Javascript muss man nicht schreiben.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Hallo @DeaD_EyE das klingt nicht uninteressant - ich habe sogar versucht, mich zu informieren https://m.youtube.com/watch?v=r-GSGH2RxJs - ich vermute, die Erklärung ist in englisch. Ich kann weder Django, noch JS, nicht wirklich OOP oder CSS - für HTMX brauche ich ein Jahr. Du darfst mir aber gerne zeigen, wie das in Bezug auf mein Problem weiterhilft :).
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pitwheazle: das hilft Dir überhaupt nichts, das ist nur ein weiteres Framework, mit einer eigenen Sprache; lass Dich nicht verwirren.

Wenn ich das richtig sehe, mußt Du nur drei verschiedene Routen für Deine drei verschiedenen Aktionen definieren, immer das selbe action="/zusammenfuehren/" führt natürlich dazu, dass Du dreimal im selben Code landest und das nicht unterscheiden kannst.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Das wäre ein Ansatz: Drei Views und ein Template?
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Nein, mit dem Ansatz mehrere Funktionen aufzurufen bin ich nicht weitergekommen, aber im Großen und ganzen klappt das jetzt mit diesem Code:

Code: Alles auswählen

def suchen(req):
    if not req.user.is_superuser:
        return HttpResponse("Zugriff verweigert")
    suchen_form = Suchen_Form
    loeschen_form = Loeschen_Form
    zusammen_form = Zusammen_Form
    vorname = nachname = text = ""
    profil = Profil.objects.filter(id = 0)
    zeilen = []    
    if req.method == "POST":
        zusammen_form = Zusammen_Form(req.POST)
        if zusammen_form.is_valid():
            quelle = zusammen_form.cleaned_data['quelle']
            ziel = zusammen_form.cleaned_data['ziel']
            try: 
                if ziel != "" and quelle != "":
                    profil_ziel = Profil.objects.get(id = ziel)
                    vorname = profil_ziel.vorname 
                    nachname = profil_ziel.nachname 
                    profil_quelle = Profil.objects.get(id = quelle)
                    vorname_quelle = profil_quelle.vorname 
                    nachname_quelle = profil_quelle.nachname 
                    profil = Profil.objects.filter(vorname__contains=vorname, nachname__contains=nachname)
                    if vorname_quelle == vorname and nachname_quelle == nachname:                    
                        text = "erledigt"
                    else:
                        text = "Namen stimmen nicht überein!" 
            except:
                text = "Mit der Eingabe stimmt was nicht!"          
        loeschen_form = Loeschen_Form(req.POST)
        if loeschen_form.is_valid():
            try:
                loeschen = loeschen_form.cleaned_data['loeschen']
                if loeschen != "":
                    #zeilen = []
                    profil = Profil.objects.get(id = loeschen)
                    vorname = profil.vorname 
                    nachname = profil.nachname 
                    name = profil.user 
                    protokoll = Protokoll.objects.filter(user__id = loeschen)                 
                    profil = Profil.objects.filter(vorname__contains=vorname, nachname__contains=nachname)
                    if protokoll.count() > 0:
                        text = "Mit dem Account {} ID:{} wurden schon {} Aufgaben gerechnet, die müssen zuerst übertragen werden!".format(name, loeschen,protokoll.count())
                    else:
                        text = "erledigt"
            except:
                text = "Mit der letzten Eingabe stimmt was nicht!"        
        suchen_form = Suchen_Form(req.POST)
        if suchen_form.is_valid():
            vorname = suchen_form.cleaned_data['vorname']
            nachname = suchen_form.cleaned_data['nachname']
            if vorname != "" or nachname != "":
                profil = Profil.objects.filter(vorname__contains=vorname, nachname__contains=nachname)
        for a in profil:
            aufgaben = Protokoll.objects.filter(user_id = a.id)
            zeilen.append((a, aufgaben.count()))
        messages.error(req, text) 
    context = {"suchen_form": suchen_form, "loeschen_form": loeschen_form, "zusammen_form": zusammen_form, "zeilen" : zeilen, "vorname": vorname, "nachname": nachname,}
    return render(req, 'admin/suchen.html', context)
Aber die obige Frage besteht trotzdem noch, wenn auch anders:
Im 1. Schritt werden die Accounts eines Users gesucht und zum Beispiel im 4. Schritt dann der entsprechende Account gelöscht wird (wenn der Code fertig ist). Ich müsste jetzt aber noch sichersetllen, dass nur soche IDs übergeben werden die im ersten Schritt in der Liste angezeigt werden und nicht durch ein Übertragungsfehler ein anderer. Es wäre also prima, wenn außer dem Eintrag in dem Feld "loeschen" auch noch die, im Template angegebenen, IDs an die View geschickt werden - geht das nicht irgendwie? Ich werde noch sicherstellen, dass Lehrkräfte nur solche Accounts löschen können, bei denen der User auch in deren Lerngruppe angemeldet ist - eine entsprechenden fehlermeldung wäre aber hilfreich.
Ich bräuchte hier wahrscheinlich noch eine Hilfe, was mit dem "request" übergeben wird, wie man das beeinflußt und wie man es ausgeben lassen kann. Zumindest letzteres wusste ich mal, aber "print(request)" war es anscheinend nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle Die Funktion macht für meinen Geschmack zu viel. Das sind ja *drei* *verschiedene* Aufgaben.

Die Zuweisungen an die drei `*_form`-Variablen am Anfang sind schräg. Wenn ein Name an ”nichts” gebunden werden soll, dann verwendet man `None` und keine Klasse.

Was ergibt denn ``Profil.objects.filter(id = 0)``? Ein leeres Ergebnis? Dann sollte das da nicht stehen. Sondern beispielsweise eine leere Liste.

Die nackten ``execpt`` ohne konkrete Ausnahmen und der Text, dass da was nicht stimmt, sind sehr unhilfreich bei der Fehlersuche.

Wenn nicht Quelle *und* Ziel bei der Zusammenführung angegeben wurden, dann passiert einfach nichts. Auch keine entsprechende Rückmeldung an den Benutzer das da was fehlt(e).

Ausserdem ist der Name falsch, denn da wird ja drüber iteriert. Wie müsste denn das schlecht benannte `a` in der Schleife eigentlich heissen? Ich nehme mal an *das* müsste `profil` heissen und `profil` müsste eigentlich `profile` heissen.

`messages.error()` wird grundsätzlich aufgerufen. Nicht nur im Fehlerfall.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Vielen Dank für deine Hinweise - ich habe versucht, sie einzuarbeiten.
__blackjack__ hat geschrieben: Dienstag 10. Oktober 2023, 17:52 @Pitwheazle Die Funktion macht für meinen Geschmack zu viel. Das sind ja *drei* *verschiedene* Aufgaben.
Ich habe alles Mögliche ausprobiert. So finde ich es für mich am übersichtlichsten, da ich immer gleich erkennen kann, was sich geändert hat.
__blackjack__ hat geschrieben: Dienstag 10. Oktober 2023, 17:52 Die nackten ``execpt`` ohne konkrete Ausnahmen und der Text, dass da was nicht stimmt, sind sehr unhilfreich bei der Fehlersuche.
Bedenke, das ist nur das Grundgerüst. Ich muss ja mühsam, erstmal dieses zum Laufen bringen. Dann passiert irgendwann auch was (bisher werden ja noch keinerlei Änderungen an den Datenbank vorgenommen) und es gibt auch ordentliche Rückmeldungen.
__blackjack__ hat geschrieben: Dienstag 10. Oktober 2023, 17:52 `messages.error()` wird grundsätzlich aufgerufen. Nicht nur im Fehlerfall.
Das bekomme ich vielleicht alleine in, das hat mich aber nicht wirklich gestört.
__blackjack__ hat geschrieben: Dienstag 10. Oktober 2023, 17:52 Die Zuweisungen an die drei `*_form`-Variablen am Anfang sind schräg. Wenn ein Name an ”nichts” gebunden werden soll, dann verwendet man `None` und keine Klasse.
Das verstehe ich nicht. Ich muss doch auch bei einem Aufruf ohne "Post" die entsprechenden forms übergeben.
__blackjack__ hat geschrieben: Dienstag 10. Oktober 2023, 17:52 Ausserdem ist der Name falsch, denn da wird ja drüber iteriert. Wie müsste denn das schlecht benannte `a` in der Schleife eigentlich heissen? Ich nehme mal an *das* müsste `profil` heissen und `profil` müsste eigentlich `profile` heissen.
Stimmt, das ist sicher wieder schlampig. Wenn ich da aber was ändere bekomme ich einen Fehler, bei dem es mir nicht gelingt ihn abzufangen. Wenn ich alle "profil" in "profile" ändere, oder die Zeile "profil = Profil.objects.filter(id = 0)" ändere bekomme ich, z.B. wenn ich nichts ins Suchfeld eintrage, die Nachricht "NoneType' object is not iterable".
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: ``except`` ohne Ausnahme und eine Behandlung, welche keine Rückschlüsse auf das Problem zulässt, hat auch in einem Grundgerüst nichts zu suchen. Gerade in einem Grundgerüst nicht, denn dann fängt man ja schon mit einem schlechten Fundament an, bei dem man Fehler nicht mitbekommt, oder ihnen schwerer auf die Spur kommt als nötig. Und man muss da ja auch gar nicht wirklich viel machen. Minimum halt das man protokolliert was da genau passiert ist, mit Traceback. Das ist *eine* einfache Zeile Code im ``except``-Block, man kann also auch nicht wirklich argumentieren, dass würde zu viel Aufwand machen. Und dann schaut man regelmässig in den Protokolldaten nach Tracebacks und lernt besser zu verstehen was für Ausnahmen auftreten und kann schauen warum das passiert und wo das passiert und die Fehlerbehandlung dann konkretisieren und verbessern.

Re `messages.error()`: Diese Kategorien sind ja dazu da, dass man in der Anzeige auf der Webseite zwischen Fehlern und anderen Informationen unterscheiden kann. Der Benutzer will ja nicht „Der Datensatz wurde angelegt.“ in Rot mit einem Warndreieck angezeigt bekommen, oder „Fataler Fehler, Selbstzerstörung initiiert“ in einer harmlos aussehenden Infobox serviert bekommen.

Wenn Du sagst Du musst die Forms übergeben, dann doch aber Objekte von dem Typ und nicht den Typ. Dann erstell halt Objekte, aber einfach die Klassen da an die Namen zu binden ist wie gesagt schräg.

Du darfst nicht *alle* `profil` in `profile` ändern, denn zu allem verwirrenden Überfluss hast Du ja an mindestens einer Stelle `profil` auch mal tatsächlich nur an *ein* Profil gebunden. Du benutzt den Namen in der gleichen Funktion also sowohl für ein einzelnes Profil als auch für eine Sequenz von mehreren Profilen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten