jetzt ist die Tabelle noch komplizierter

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

Ich wieder:
Mir ist es gelungen durch zwei Listen gleichzeitig zu iterieren (heißt das so?)(Die habe ich mit "zip" zusammengeführt). Das Problem ist jetzt aber, dass es sich um verschachtelte Loops handelt. Am besten ist, ich zeige mal, wie das jetzt aussieht:
Bild
Jeder Schüler hat hier zwei Zeilen. Oben steht die Anzahl der richtigen Aufgaben und untendrunter die jeweilige Fehlerquote (gelb). Diese untere Zeile möchte ich aber nicht anzeigen, sondern dazu verwenden, die darüberliegende grün (quote<10) gelb (quote<33) oder rot (quote>=33) zu färben. Die äußere Schleife läuft durch zwei Listen ("richtig" und "quote").
Innerhalb der äußeren Schleife "{% for richtig, quote in liste %}" sind zwei weitere Schleifen "{% for r in richtig %}" und "{% for q in quote %}", die werden jetzt nacheinander ausgeführt, müssten aber ebenfalls gleichzeitig ausgeführt werden. Wie geht das? ... oder geht das so gar nicht?

Code: Alles auswählen

<body>
<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Zeit
            </th>
            <th>Summe</th>
                {%for kategorie in kategorien %}
                    <th style="transform: rotate(270deg)">{{kategorie.name}}</th>
                {%endfor%}
        </tr>
    </thead>
    <tbody>
        {% for richtig, quote in liste %}
            <tr>                                
                {% for r in richtig %}
                <td>
                    {% if forloop.first%}
                        {{r.vorname}} {{r.nachname}} {{r.klasse}}
                    {% else%}
                        {{r}}
                    {%endif%}
                {% endfor %}
                </td>
            </tr>
            <tr> 
                <td></td><td></td>
                {% for q in quote %}
                    <td style="background-color: yellow">{{q|floatformat:0}}%</td>
                {% endfor %}
            </tr>
        {% empty %}
            <strong>Es sind noch keine Schüler angemeldet.</strong>
        {% endfor %}
    </tbody>
</table>
</body>
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Ich würde darüber nachdenken, das gar nicht so komplex im Template zu machen.

Du kannst die die "Tabelle" ja auch im View aufbauen und dann im Context an das Template zu übergeben. Ich finde das oft einfacher und übersichtlicher, als das im Template zu machen.
Also anstatt das ganze im Template irgendwie zu "errechnen" würde ich das im View machen und ggf. gleich da den Farbwert errechnen und dann nur noch im Template an der entsprechenden Stelle ausgeben, damit es dort auftaucht.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Danke dafür. Hatte ich auch schon überlegt. Da bei Django aber (fast) alles irgendwie einfach geht (wenn man weiß, wie), wollte ich zumindest wissen, ob das, was ich in der äußeren Schleife hinbekommen habe, auch in der inneren funktioniert.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@Pitwheazle: da Du mal wieder nicht zeigst, wie `liste` zustande kommt, kann man nur wieder sagen, mach das doch gleich richtig, nämlich nicht zwei getrennte Listen `richtig` und `quote` sondern eine Liste mit jeweils zwei Elementen. Wenn ich das richtig sehe, sollte `liste` besser irgendwas wie `aufgaben_der_schueler` heißen, und ein tuple aus schueler und aufgaben sein, und jede aufgabe dann aus einem Tuple (anzahl, fehlerquote) bestehen. Dass das erste Element einer Liste eine besondere Bedeutung hat, das macht man einfach nicht, weil es völlig unverständlich ist. (Ich habs erst beim dritten mal Lesen verstanden). Die Spalten Zeit und Summe fehlen in Deinem Code noch, was die ganze Geschichte ja nochmals komplizierter machen würde.
In einer Tabelle muß immer ein <tr>-Element sein, <strong> macht darin keinen Sinn und wird vom Browser nur falsch dargestellt (nämlich nach der Tabelle).

Code: Alles auswählen

<body>
<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Zeit</th>
            <th>Summe</th>
            {%for kategorie in kategorien %}
                <th style="transform: rotate(270deg)">{{kategorie.name}}</th>
            {%endfor%}
        </tr>
    </thead>
    <tbody>
        {% for schueler, aufgaben in aufgaben_der_schueler %}
            <tr>
                <td>{{schueler.vorname}} {{schueler.nachname}} {{schueler.klasse}}</td>
                <td>{{schueler.zeit}}</td>
                <td>{{schueler.summe}}</td>
                {% for anzahl, fehlerquote in aufgaben %}
                    <td style="background-color: {{fehlerquote|to_quote_color}}">{{anzahl}}</td>
                {% endfor %}
                </td>
            </tr>
        {% empty %}
            <tr><td colspan="999"><strong>Es sind noch keine Schüler angemeldet.</strong></td></tr>
        {% endfor %}
    </tbody>
</table>
</body>
Wenn Du Dir keinen Filter to_quote_color schreiben möchtest, würde ich die Farbe direkt in Python ermitteln lassen.

@sparrow: Die Templatesprache ist sehr mächtig und mit der richtigen Datenaufbereitung ist damit alles möglich. Ein Template-System selbst nachzuprogrammieren ist also nicht nur unnötig, sondern auch umständlich und fehleranfällig.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sirius3: Ich hatte das nicht so verstanden, dass die Template-Engine nachgebaut werden sollte sondern das die Werte im View schon eine Form gebracht werden, die sich leicht im Template verarbeiten lässt, damit dort die berechneten und vorbereiteten Werte möglichst einfach nur noch an den richtigen Stellen eingesetzt werden müssen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Das was __blackjack__ sagt. Mir ging es vor allem um das Einfärben der Zellen. Also das nicht im Template herzuleiten sondern bereits im Context so zu übergeben, dass man es einfach im Template einsetzen kann, statt es eher unübersichtlich im Template zu tun.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@sparrow: gut, dann meinen wir ja das selbe.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Vielen Dank für eure Geduld mit meiner Pfuscherei. Die Vorschläge leuchten mir ein.
Sirius3 hat geschrieben: Donnerstag 12. Januar 2023, 08:18 @Pitwheazle: da Du mal wieder nicht zeigst, wie `liste` zustande kommt,
Ich war halt davon ausgegenagen, dass man das Problem im Template lösen könnte.

@sirius3: Ich habe jetzt wirklich den ganzen Tag rumprobiert und versucht, die entsprechenden Tupel zu erzeugen - ich brauche Hilfe!

Ein Problem ist, dass ich es nicht schaffe die Tupel ineinander zu verschachteln und dann habe ich das Problem, dass die Tupel ja nicht indizierbar sind und Franz zwar Aufgaben in "ergänzen" (Kategorie.Zeile 1) und "subtrahieren" (Kategorie.zeile 3) bearbeitet hat, aber keine in "addieren", diese Instanz in "Zaehler" existiert nicht. Da müsste doch, meines Erachtens, ein Tupel ohne Aufgaben erzeugt werden, damit die Zahlen in die richtige Zeile kommen. Ich hatte versucht, dies mit dem "index" zu lösen und zu Beginn für jeden Schüler ein Liste mit katmax Tupeln zu erzeugen (aufgaben = ((0,0),)*schueler.katmax) und da die Werte reinzupacken, das geht aber nicht. Mit einer Liste könnte ich das: "aufgaben = [(0,0),]*schueler.katmax" und damit wird mir mit "print(aufgaben) auch richtig
[(16, 10), (0, 0), (4, 20)]
[(0, 0), (11, 15)]
[]
angezeigt.

Ich habe mein view nochmal neu angefangen, weit bin ich aber nicht gekommen.

Code: Alles auswählen

def gruppe_uebersicht(req, gruppe_id):
    gruppe = Lerngruppe.objects.get(pk=gruppe_id)
    titel = str(gruppe.name)+", " +str(gruppe.lehrer.profil.vorname)+" " +str(gruppe.lehrer.profil.nachname)
    schueler_liste = Profil.objects.filter(lerngruppe__name=gruppe.name)
    max = schueler_liste.aggregate(Max('katmax')).get('katmax__max')
    if max == None:
        max = 0
    kategorien = Kategorie.objects.filter(zeile__lt=max+1)
    aufgaben_der_schueler = ()
    for schueler in schueler_liste:
        aufgaben = [(0,0),]*schueler.katmax
        zaehler = Zaehler.objects.filter(user=schueler)
        zeit_sum = zaehler.aggregate(Sum('bearbeitungszeit')).get('bearbeitungszeit__sum')
        richtig_sum = zaehler.aggregate(Sum('richtig')).get('richtig__sum')
        falsch_sum = zaehler.aggregate(Sum('falsch')).get('falsch__sum')
        try:
            quote_sum = falsch_sum/(richtig_sum+falsch_sum)
        except:
            quote_sum = "-"
        for z in zaehler:
            index=(z.kategorie.zeile-1)
            richtig = int(z.richtig)
            try:
                quote = int(z.falsch/(z.richtig+z.falsch)*100)
            except:
                quote = "-"
            aufgaben[index] = (quote,richtig)
        print(aufgaben)
    context={'aufgaben_der_schueler':aufgaben_der_schueler, 'kategorien': kategorien, 'titel': titel}  
    return render(req, 'lehrer/gruppe_uebersicht.html', context)
Ich hoffe der Code ist so nachvollziehbar. Die Kategorie mit der höchsten Zeilennummer ist im Schüler"Profil" gespeichert (katmax), die anderen Daten sind jeweils in den Instanzen von "Zaehler". Mit zeit_sum und richtig_sum erstelle ich die Gesamtsumme der Bearbeitungszeit und der richtigen Aufgaben, mit quote_sum die Fehlerquote aller Aufgaben eines Schülers.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

`max` ist der Name der eingebauten Funktion `max` und sollte nicht durch eine lokale Variable überdeckt werden.
`aufgaben_der_schueler` ist natürlich kein Tuple sondern eine Liste mit Tupeln.
Man benutzt niemals nackte excepts, hier willst Du ja nur den ZeroDivisionError abfangen.
Du mußt schon die Kategorien nach der Zeile sortieren, damit die Spalten zusammenpassen (Warum heißt das Feld dann zeile?).
Voraussetzung ist dann natürlich auch, dass für jede Zeile eine Kategorie existiert.

Code: Alles auswählen

def quote(richtig, falsch):
    try:
        return falsch / (richtig + falsch)
    except ZeroDivisionError:
        return 0

def gruppe_uebersicht(request, gruppe_id):
    gruppe = Lerngruppe.objects.get(pk=gruppe_id)
    titel = f"{gruppe.name}, {gruppe.lehrer.profil.vorname} {gruppe.lehrer.profil.nachname}"
    schueler_liste = Profil.objects.filter(lerngruppe__name=gruppe.name)
    katmax_max = schueler_liste.aggregate(Max('katmax'))['katmax__max']

    kategorien = list(Kategorie.objects.filter(zeile__lt=katmax_max + 1).order_by(zeile)
    aufgaben_der_schueler = []
    for schueler in schueler_liste:
        alle_zaehler = Zaehler.objects.filter(user=schueler)
        summen = zaehler.aggregate(
            bearbeitungszeit=Sum('bearbeitungszeit'),
            richtig=Sum('richtig'),
            falsch=Sum('falsch'),
        )
        summen['quote'] = quote(summen['richtig'], summen['falsch'])
        aufgaben = [(0, 0)] * katmax_max
        for zaehler in alle_zaehler:
            index = zaehler.kategorie.zeile - 1
            aufgaben[index] = (quote(zaehler.richtig, zaehler.falsch), zaehler.richtig)
        aufgaben_der_schueler.append((
            schueler, summen, aufgaben 
        ))
    context={'aufgaben_der_schueler':aufgaben_der_schueler, 'kategorien': kategorien, 'titel': titel}  
    return render(request, 'lehrer/gruppe_uebersicht.html', context)
Jetzt, nachdem die Inputdaten klar sind, muß natürlich auch das Template noch etwas angepasst werden.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Pitwheazle hat geschrieben: Donnerstag 12. Januar 2023, 17:36 Ich war halt davon ausgegenagen, dass man das Problem im Template lösen könnte.
Könnte. Ja. Aber besser direkt an der Quelle die Daten möglichst passgenau zusammenstellen und übergeben, dann muss man sie hinterher nicht mehr mühsam auseinander dröseln. Das empfiehlt sich eigentlich immer, wenn du beide Seiten der Schnittstelle selbst bearbeiten darfst. Wenn du hingegen nur die Ausgabe beeinflussen könntest, dann müsstest du wirklich diese Extraarbeit erledigen. :mrgreen:
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Prima! Vielen Dank - da habe ich wieder viel gelernt.
So sieht mein view jetzt aus:

Code: Alles auswählen

def quote_farbe(richtig, falsch):
    try:
        quote = falsch / (richtig + falsch)
        if quote < 0.1:
            return "rgba(0, 255, 0,0.6)"
        elif quote <1/3:
            return "rgba(255, 255, 0,0.6)"
        else:
            return "rgba(255, 0, 0,0.6)"
    except ZeroDivisionError:
        return "-"

def gruppe_uebersicht(request, gruppe_id):
    gruppe = Lerngruppe.objects.get(pk=gruppe_id)
    titel = f"{gruppe.name}, {gruppe.lehrer.profil.vorname} {gruppe.lehrer.profil.nachname}"
    schueler_liste = Profil.objects.filter(lerngruppe__name=gruppe.name)
    katmax_max = schueler_liste.aggregate(Max('katmax'))['katmax__max']
    kategorien = list(Kategorie.objects.filter(zeile__lt=katmax_max + 1))
    aufgaben_der_schueler = []
    for schueler in schueler_liste:
        alle_zaehler = Zaehler.objects.filter(user=schueler)
        zeit_sum = sum([item.bearbeitungszeit for item in alle_zaehler])
        richtig_sum = sum([item.richtig for item in alle_zaehler])
        falsch_sum = sum([item.falsch for item in alle_zaehler])
        quote_sum = quote_farbe(richtig_sum, falsch_sum)
        aufgaben = [(0, "-")] * (katmax_max+1)
        aufgaben[0] = (quote_sum, int(richtig_sum))
        for zaehler in alle_zaehler:
            index = zaehler.kategorie.zeile
            aufgaben[index] = (quote_farbe(zaehler.richtig, zaehler.falsch), int(zaehler.richtig))
        aufgaben_der_schueler.append((
            schueler,  zeit_sum, aufgaben 
        ))
    context={'aufgaben_der_schueler':aufgaben_der_schueler, 'kategorien': kategorien, 'titel': titel}  
    return render(request, 'lehrer/gruppe_uebersicht.html', context)
mit "... aggregate(Sum..." habe ich es nicht hinbekommen, da wollte es mir nicht gelingen ein int draus zu machen. Aber irgendjemand im Internet sagt, mit einer Schleife ginge es eh schneller und

Code: Alles auswählen

        zeit_sum = sum([item.bearbeitungszeit for item in alle_zaehler])
        richtig_sum = sum([item.richtig for item in alle_zaehler])
        falsch_sum = sum([item.falsch for item in alle_zaehler])
... funktioniert auch prima. Das lässt sich aber sicher eleganter machen sodass die Schleife nur einmal durchlaufen werden muss.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

quote_farbe soll eine Farbe liefern, "-" ist aber keine Farbe.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Na ja, aber fast - zumindest färbt es meine Zellen wie gewünscht.. Besser kann ich es halt nicht.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wahrscheinlich wäre unset ein passender Wert wenn man *keine* Farbe setzen will.
„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

__blackjack__ hat geschrieben: Samstag 14. Januar 2023, 00:49 Wahrscheinlich wäre unset ein passender Wert wenn man *keine* Farbe setzen will.
Danke für den Hinweis. Ich hatte ja schon eine Klasse für die Farben:

Code: Alles auswählen

.rot {background-color:rgba(255, 0, 0,0.6);}
.gruen {background-color:rgba(0, 255, 0,0.6);}
.gelb {background-color:rgba(255, 255,0.6);}
und habe jetzt die Funktion zu

Code: Alles auswählen

def quote_farbe(richtig, falsch):
    try:
        quote = falsch / (richtig + falsch)
        if quote <= 0.1:
            return "gruen"
        elif quote <= 1/3:
            return "gelb"
        else:
            return "rot"
    except ZeroDivisionError:
        return "unset"
abgeändert.
Antworten