Summen in Queryset bilden und an Template übergeben

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

Ich habe mir das ja zu Herzen genommen, dass man die Inhalte in Tabellen und die Ausgabe getrennt betrachten soll und schon dies und das umgebaut. Jetzt komme ich aber wieder nicht weiter.
Ich muss für die Lehrkräfte eine Liste erstellen, in der sie die Arbeit ihrer Schülerinnen und Schüler kontrollieren können.
Das funktioniert ja auch schon:
Bild
allerdings habe ich dafür eine eigene Tabelle angelegt und sozusagen eine doppelte Buchführung obwohl die Werte ja auch in der Tabelle Protokoll stehen - das soll ich ja nicht. Also versuche ich jetzt aus dem "Prokoll" meine Daten auszulesen.
Ich muss also eine Schleife jeweils über die user und darin über die einzelnen Kategorien legen und dort jeweils die Summen von "richtig", "falsch" usw bilden und das dann an das Template übergeben. Das gelingt mir nicht. Schon an der inneren Schleife komme ich nicht weiter:

Code: Alles auswählen

    protokoll = Protokoll.objects.filter(user__gruppe = gruppe)
    for p in protokoll.order_by("user"):
        print(p.name)
hier ist nochmal die Tabelle "protokoll":

Code: Alles auswählen

class Protokoll(models.Model):
    user = models.ForeignKey(Profil, verbose_name='Benutzer', related_name='protokolle', on_delete=models.CASCADE)
...
    kategorie = models.ForeignKey(Kategorie, related_name='protokolle', on_delete=models.CASCADE)
...
    falsch = models.PositiveSmallIntegerField(default=0)  
    abbr = models.BooleanField(default=True)
    lsg = models.BooleanField(default=False)    
    help = models.BooleanField(default=False)  
... könnt ihr mir da bitte wieder mal weiterhelfen?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Das oben sind die Ketegorien?
Steht die Zeit auch in der Tabelle?
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

OK, hier ist die ganze Tabelle:

Code: Alles auswählen

class Protokoll(models.Model):
    user = models.ForeignKey(Profil, verbose_name='Benutzer', related_name='protokolle', on_delete=models.CASCADE)
    #gewertet werden nur die Aufgaben des jeweiligen Schuljabjahres, im Januar, Juni und August, kann der user aber auch schon festlegen, dass die Aufgaben für das nächste Schulhalbjahr gelten:
    sj = models.SmallIntegerField(default=0)
    hj = models.SmallIntegerField(default=0)

    kategorie = models.ForeignKey(Kategorie, related_name='protokolle', on_delete=models.CASCADE)
    titel = models.CharField(max_length=20, blank=True)
    typ = models.SmallIntegerField(default=0) 
    typ2 = models.SmallIntegerField(default=0) 
     
    aufgnr = models.PositiveSmallIntegerField(default=0) 
    
    #der Aufgabentext:
    text = models.TextField(blank=True)
    pro_text = models.CharField(max_length=100, blank=True)
    variable = models.JSONField()
    frage = models.CharField(max_length=20, blank=True)
    einheit = models.CharField(max_length=20, blank=True)
    anmerkung = models.CharField(max_length=100, blank=True)
   
    parameter = models.JSONField()
 
    #hier speichere ich die Lösung, wahlweise als zahl, u.U. auch (mehrere) Lösungen als String:
    value = models.DecimalField('Wert', null=True, max_digits=20, decimal_places=7)
    loesung = models.JSONField()                                                    #hier können mehrere Werte eingegeben werden, der erste wird angezeigt wenn "Lösung anzeigen" angeklickt wird. Steht hier auch "indiv" so wird die Eingabe in der jeweiligen Funktion überprüft
    individuell = models.JSONField()                                                #hier können, wenn oben "inivi" eingetragen wird, individeuelle Werte eingegeben werden, wie z.B. Nenner und Zähler

    #hilfe = models.TextField(blank=True)
    hilfe = models.SmallIntegerField(default=0)
    
    #die Eingabe des users:
    eingabe = models.CharField(max_length=20, blank=True)

    tries = models.PositiveSmallIntegerField('Versuche', default=0)
    #Eintrag richtig, falsch, Extrapunkte, Lösung anzeigen, Abbruch:
    wertung = models.CharField(max_length=10, blank=True)
    richtig = models.DecimalField(max_digits=3, decimal_places=1, default=0)

    falsch = models.PositiveSmallIntegerField(default=0)  
    abbr = models.BooleanField(default=True)
    lsg = models.BooleanField(default=False)    
    help = models.BooleanField(default=False)  
    
    start = models.DateTimeField('Start', auto_now_add=True)
    end = models.DateTimeField('Ende', blank=True, null=True, default=None)
    szeit=models.FloatField(default=0)

    @property
    def dauer(self):
        if not self.end:
            return 0
        return (self.end - self.start).total_seconds()

    def zweigabe(self):
        return self.eingabe.replace(".",",")
        
    def name(self):        
        return f"{self.user.nachname}, {self.user.vorname}, {self.user.klasse}, {self.user.gruppe}"

    class Meta:
        verbose_name = 'Protokoll'
        verbose_name_plural = 'Protokoll'
ich hatte gehofft, dass ich das mit der zeit alleine hinbekomme (vielleicht sollte ich mich aber nicht so weit aus dem Fenster wagen)
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Das Gruppieren und Annotieren kann die Datenbank übernehmen. Und wir verwenden eine F-Expression um die Zeit auszrechnen:

Code: Alles auswählen

from django.db.models import F, Sum

protkoll_statistics = (
    Protokoll.objects
    .values("user__name", "kategorie__name")
    .annotate(richtig_sum=Sum('richtig'))
    .annotate(falsch_sum=Sum('falsch'))
    .annotate(zeit_sum=Sum(F('end') - F('start')))
    .order_by()
) 
Guck ob das Ergebnis dem entsprichst, was du willst.
user__name und kategorie__name musst du möglicherweise anpassen.

Die Benennung deiner Felder ist eher so semi gut.
Mal sind sie in Deutsch, mal in Englisch. Manchmal sind sie komisch abgekürzt.
Und "richtig" ist ein Decimal-Wert?
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Auweh - ob ich das jemals wenigstens ansatzweise begreife? Ich denke da anscheinend immer zu kompliziert und - linear. Danke schon mal. Das muss ich jetzt erstmal verarbeiten und versuchen, in mein Template zu übertragen! Ja, "richtig" ist ein Decimal-Wert, es gibt, zumindest später, auch halbe Punkte.
Nachtrag: Ich habe ja brav stundenlange Tutorien abgearbeitet (Viele Stunden). Die bleiben aber anscheinend alle nur an der Oberfläche. Ich habe zwar wenig Hoffnung, aber gibt es da auch andere Hilfen, als sich durch diese Dokumenatation zu arbeiten? Ihr findet immer wieder neue Kaninchen, die ihr aus dem Hut zieht.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Am Ende ist es Learning by Doing.
Jetzt lernst du, dass man bereits auf der Datenbank Dinge machen kann - und dadurch weißt du das beim nächsten Mal.
Das ging mir nicht anders. Ist nur schon ein bisschen her.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Am Ende ist es Learning by Doing.
Absolut. Gerade beim Umgang mit relationalen Datenbanken ist das so. Da habe ich damals auch 3-5 Iterartionsschritte gebraucht, ehe ich alles so hatte, wie man es am besten macht.

Das Kernproblem als Anfänger ist IMHO oft, dass man sich eine DB wie eine Tabelle in einer Tabellenkalkulation vorzustellen versucht. Was in vielen Fällen aber nicht richtig ist, weil man dann keine Relationen hat - die sollte man aber haben, um das RDBMS gut zu nutzen.

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

Ich bin immer wieder beindruckt von eurer Geduld! Da ich ja etwa 99% der Fragen hier stelle und die wenigen anderen Fragen sich auf einem Level bewegen, den ich ebenfalls nicht verstehe, habe ich den Eindruck ich bin der einzige der so auf dem Schlauch steht. ... Aber ich gebe mir wirklich Mühe und habe mich auch durch die "Aggregation" versucht durchzuarbeiten und vermute, jetzt haben meine Problem etwas mit "Order of annotate() and values() clauses" zu tun.
Ich bekomme zwar schon was in meinem Template angezeigt, aber noch lange nicht so wie ich es brauche.
Folgende Probleme:
1. Zunächst mal muss ich für jede Schülerin und Schüler die Daten in einer Zeile anzeigen - schon das bekomme ich nicht hin.
2. Nicht alle Schülerinnen und Schüler haben alle Kategorien berechnet, wie bekomme ich die Summen unter die jeweiligen Überschriften? Diese Überschrift bekomme ich aus der Tabelle "Kategorien".
3. Die Summe der falschen Aufgaben will ich nicht anzeigen, das würde meine Anzeige sehr unübersichtlich machen. Ich ändere bisher die Hintergrundfarbe, je nach Fehlerquote, z.B. so:

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 :
        return "unset"
(ich versuche den Fehler abzufangen, der entsteht, wenn "falsch" 0 oder None ist)
4. Ich benötige auch nicht die Zeit der einzelnen Kategorien sondern die Gesamtzeit, die jede Schülerin / jeder Schüler insgesamt gearbeitet hat
5. Ebenso zusätzlich die Gesamtzahl der Aufgaben jeder Schülerin / jedes Schülers.
6. Und es gelingt mir nichtmal, in der ersten Spalte Vorname Nachname, Klasse angezeigt zu bekommen. Die müsste ja doch eigentlich "Profil" zurückliefern

Code: Alles auswählen

class Profil(models.Model):
    user = models.OneToOneField(User, related_name='profil', on_delete=models.CASCADE )
    nachname = models.CharField(max_length=30)
    vorname = models.CharField(max_length=30)
    
    klasse = models.CharField(max_length=10)
    # diese Felder werden erst ausgefüllt, wenn ein Schüler seine Lerngruppe wählt
    schule = models.ForeignKey(Schule, null= True, blank=True, on_delete = models.SET_NULL)
    gruppe = models.ForeignKey(Lerngruppe, null= True, blank=True, on_delete = models.SET_NULL, related_name='gruppe')
    
    jg = models.PositiveSmallIntegerField(validators=[MinValueValidator(1), MaxValueValidator(10)])
    kurs= models.CharField(max_length=1, choices=wahl_kurs.choices, default=wahl_kurs.E_KURS,)

    # werden beim Erstellen eingestellt
    stufe = models.PositiveSmallIntegerField(default=5) #, editable=False)
    sj = models.SmallIntegerField(default=0)
    hj = models.SmallIntegerField(default=0)

    katmax = models.IntegerField(default=0)                                 # die Zeilennummer die höchsten gewählten Aufgabenkategorie
    #voreinst = models.IntegerField(default=1)                               # hier können Voreinstellungen gesetzt und abgefragt werden
    voreinst = models.JSONField(blank=True, null=True, default=dict)

    def __str__(self):
        return f"{self.vorname} {self.nachname}, {self.klasse}"
... aber:

Code: Alles auswählen

.values("user__profil", "kategorie__zeile")
bringt die Fehlermeldung

Code: Alles auswählen

Cannot resolve keyword 'profil' into field.
Ich mache das jetzt so (aber das ist sicher wieder viel zu umständlich):

Code: Alles auswählen

    protokoll_statistics = (
        Protokoll.objects
        .values("user_id", "user__vorname", "user__nachname", "user__klasse", "kategorie__zeile")
        .annotate(richtig_sum=Sum('richtig'))
        .annotate(falsch_sum=Sum('falsch'))
        .annotate(zeit_sum=Sum(F('end') - F('start')))
        .order_by()
    ) 
und im Template:

Code: Alles auswählen

    <tbody>
        {% for zeile in protokoll %}
            <tr >                
                <td>{{zeile.user__vorname}} {{zeile.user__nachname}}, {{zeile.user__klasse}}</td>
                    <td></td>
                    <td></td>                
                    <td>{{zeile.richtig_sum}}</td>
            </tr>
        {% empty %}
            <tr><td colspan="999"><strong>Es sind noch keine Schüler angemeldet.</strong></td></tr>
        {% endfor %}
    </tbody>
Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

Pitwheazle hat geschrieben: Montag 20. Februar 2023, 13:05 Ich bin immer wieder beindruckt von eurer Geduld! Da ich ja etwa 99% der Fragen hier stelle und die wenigen anderen Fragen sich auf einem Level bewegen, den ich ebenfalls nicht verstehe, habe ich den Eindruck ich bin der einzige der so auf dem Schlauch steht. ...
Sehr schön... :D Am Rosenmontag eine Frage gestellt und ... es rührt sich nichts. Ich vermute mal, die üblichen Verdächtigen hier kommen aus dem Rheinland... :) Dann mal aus dem Norden eine Gegenfrage. Ich stehe nämlich bei dem vorherigen Posting irgendwie auf dem Schlauch (liegt bei mir vielleicht an einem abklingenden grippalen Infekt)
Pitwheazle hat geschrieben: Montag 20. Februar 2023, 13:05 Ich mache das jetzt so (aber das ist sicher wieder viel zu umständlich):

Code: Alles auswählen

    protokoll_statistics = (
        Protokoll.objects
        .values("user_id", "user__vorname", "user__nachname", "user__klasse", "kategorie__zeile")
        .annotate(richtig_sum=Sum('richtig'))
        .annotate(falsch_sum=Sum('falsch'))
        .annotate(zeit_sum=Sum(F('end') - F('start')))
        .order_by()
    ) 
und im Template:

Code: Alles auswählen

    <tbody>
        {% for zeile in protokoll %}
            <tr >                
                <td>{{zeile.user__vorname}} {{zeile.user__nachname}}, {{zeile.user__klasse}}</td>
                    <td></td>
                    <td></td>                
                    <td>{{zeile.richtig_sum}}</td>
            </tr>
        {% empty %}
            <tr><td colspan="999"><strong>Es sind noch keine Schüler angemeldet.</strong></td></tr>
        {% endfor %}
    </tbody>
Ist dieser Code jetzt die Lösung für einige oder alle der vorher aufgeführten 6 Probleme oder die Ursache?
Die meisten der genannten Probleme (z.B. Nummer 1) würde ich erst mal ignorieren bzw. nach hinten schieben. Für mich würde das erste Problem lauten: bekomme ich irgendwie alle Daten zusammen die ich zusammen anzeigen will?
Wie sich die Frage bei Django "einfach" beantworten lässt, ist mir jetzt spontan nicht so klar. Nach zweieinhalb Jahren Pause von Django habe ich das meiste wieder vergessen.

Aber dem zweiten Tutorial folgend, würde ich sagen: mal mit

Code: Alles auswählen

python manage.py shell
in der Kommandozeile von Django mit der API rumspielen... (oder mit den Tests rumspielen )
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

@grubenfox: Vielen dank für die Anteilnahme. Ich hätte für diesen Strauß an Problemen eh nicht so schnell eine Antwort erwartet - wenn überhaupt. Mein Code zeigt nur, wie ich (sicher umständlich) das Problem 1 löse und wie ich überhaupt versucht habe, mittels des Codes von @sparrow, meine Daten in das Template zu bekommen. Ich habe (als Fastnachtsmuffel) natürlich selbst mich weiter an den Problemen probiert:
noisefloor hat geschrieben: Montag 20. Februar 2023, 08:30
Am Ende ist es Learning by Doing.
Absolut.
... aber speziell für das Problem 2, finde ich so keine Lösung. Ich sehe keine Möglichkeit, die ausgegebenen Daten in die richtige Spalte der Tabelle zu bekommen. Ich bin jetzt dabei die Daten in die entsprechenden Felder eines Dict einzusetzen, die ich anhand der Maximalzahl der bearbeiteten Kategorien erstelle:

Code: Alles auswählen

[(0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, '-'), (0, 
'-'), (0, '-'), (0, '-')]
...und mich dabei von der doppelten Buchführung zu lösen und die Daten aus dem "Protokoll" zu generieren.
Hellau
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Verbesserung: "Helau" schreibt man mit einem "l" und ich benutze kein dict sondern eine Liste

Falls es jemanden interessiert - ich habe es hinbekommen, sicher geht das eleganter. Und @sparrow s Code habe ich dann doch brauchen können:

Code: Alles auswählen

def gruppe_uebersicht(req, gruppe_id):
    gruppe = get_object_or_404(Lerngruppe, pk=gruppe_id)
    if gruppe.lehrer != req.user and not req.user.is_superuser:
        return HttpResponse("Zugriff verweigert")
    titel = f"{gruppe.name}, {gruppe.lehrer.profil.vorname} {gruppe.lehrer.profil.nachname}"
    schueler_liste = Profil.objects.filter(gruppe__name=gruppe.name)
    katmax_max = schueler_liste.aggregate(Max('katmax'))['katmax__max']
    try:
        kategorien = list(Kategorie.objects.filter(zeile__lt=katmax_max + 1))
    except:
        kategorien = []
    schueler_liste = Profil.objects.filter(gruppe_id = gruppe_id)
    aufgaben_der_schueler = []
    for schueler in schueler_liste:
        protokoll = Protokoll.objects.filter(user = schueler)
        gesamtsummen = (
        protokoll
        .values("user")
        .annotate(richtig_sum=Sum('richtig'))
        .annotate(falsch_sum=Sum('falsch'))
        .annotate(zeit_sum=Sum(F('end') - F('start')))
        ) 
        for g in gesamtsummen:
            richtig_sum = g['richtig_sum']
            falsch_sum = g['falsch_sum']
            dauer = g['zeit_sum']
        quote_sum = quote_farbe(richtig_sum, falsch_sum)
        aufgaben = [(0, "-")] * (katmax_max+1)
        aufgaben[0] = (quote_sum, int(richtig_sum))
        kategorie_werte = (
            protokoll
            .values("kategorie__zeile")
            .annotate(richtig_sum=Sum('richtig'))
            .annotate(falsch_sum=Sum('falsch'))
            )
        for k in kategorie_werte: 
            index = int(k['kategorie__zeile'])
            richtig_sum = k['richtig_sum']
            falsch_sum = k['falsch_sum']
            quote = quote_farbe(richtig_sum, falsch_sum)
            aufgaben[index] = (quote, richtig_sum)
        aufgaben_der_schueler.append((
            schueler, str(dauer)[:-7], aufgaben
        ))
    context={'aufgaben_der_schueler':aufgaben_der_schueler, 'kategorien': kategorien, 'titel': titel}  
    return render(req, 'lehrer/gruppe_uebersicht.html', context)
Ich habe lange gesucht, wie man die Mikrosekunden aus dem timedelta "dauer" rausbekommt - was besseres als "str(dauer)[:-7]" habe ich nicht gefunden.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

die Millisekunden wegzubekommen ist einfach:

Code: Alles auswählen

>>> from datetime import datetime
>>> now = datetime.now()
>>> later = datetime.now()
>>> delta = later - now
>>> delta.seconds
18
>>>
Das gleiche geht auch für Tage.

Tipp: was bei sowas hilft ist sich alle Methoden und Attribute von einem Objekt anzeigen zu lassen, hier wäre das `dir(delta)`. Dann bekommt man in der Regel eine Idee, was das Objekt bereits so an Bord hat und "kann".

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

noisefloor hat geschrieben: Dienstag 21. Februar 2023, 17:57 Tipp: was bei sowas hilft ist sich alle Methoden und Attribute von einem Objekt anzeigen zu lassen, hier wäre das `dir(delta)`. Dann bekommt man in der Regel eine Idee, was das Objekt bereits so an Bord hat und "kann".
Du weißt doch: Ich bin grau! Wie bzw. wo läßt man sich das "dir(delta) anzeigen?

Übrigens wird mit "str(dauer)[:-7]" meine Zeit, wie gewünscht, ohne weiteres Zutun als hh:mm:ss angezeigt.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Wie bzw. wo läßt man sich das "dir(delta) anzeigen?
Interaktiv im Terminal.
Übrigens wird mit "str(dauer)[:-7]" meine Zeit, wie gewünscht, ohne weiteres Zutun als hh:mm:ss angezeigt.
Dann rechnet Django aber schon für dich netterweise um, weil timedelta-Objekte (wie im vorherigen Beispiel von mir genutzt) kennen nur Millisekunden, Sekunden und Tage. Wenn du eine von Django gelieferte String-Repräsentation des timedelta Objekts nutzt, dann ist slicing schon ok.

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

Wenn ich in meinem Code die "dauer" ohne Umwandlung in String nehme, wird mir auch automatisch die Dauer in hh:mm:ss.microsekunden (6-stellig) angezeigt.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Weil das für die Anzeige in eine Zeichenkette gewandelt wird. Sonst könnte es ja nicht angezeigt werden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@noisefloor: nein, das liefert timedelta schon so.
Aber die Funktionalität ist doch sehr speziell.
@Pitwheazle: statt bei der Stringausgabe nachträglich irgendwas rumzubasteln, solltest Du gleich die korrekte Ausgabe erzeugen:

Code: Alles auswählen

seconds = int(dauer.total_seconds())
mm, ss = divmod(seconds, 60)
hh, mm = divmod(mm, 60)
dauer_text = f"{hh}:{mm:02d}:{ss:02d}"
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Ich weiß nicht so recht ob ich ein neues Thema aufmachen soll oder hier weitermachen.
Ihr habt mir hier sehr weitergeholfen - die Summe über die Gesamtzeiten funktioniert aber nicht. Da ich den Code, den ich mit eurer Hilfe gebastelt habe nicht endgültig verstehe, bekomme ich auch das nicht alleine hin.
Auf der Überichtsseite über die Arbeitsergebnisse der Schüler werden die Summe der Aufgaben, die Fehler und die Summen der Bearbeitungszeiten über die Kategorien richtig gebildet und angezeigt. Auch die Gesamtsummen der Aufgaben werden richtig angezeigt - nicht aber die gesamte Bearbeitungszeit.
Ein Screenshot:
Bild
den Code (Ausschnitt) aus dem View stelle ich hier noch mal so ein, wie er nach der Änderung aussieht:

Code: Alles auswählen

        kategorien = list(Kategorie.objects.filter(zeile__lt=katmax_max + 1))
        kategorie_summen = [(0, "-")] * (katmax_max+1) 
        kategorie_summen[0] = (quote_sum, int(richtig_sum))
        gesamtsummen = (
            protokoll                                                           # hier werden die Gesamtsummen der einzelnen Kategorien bestimmt
            .values("kategorie__zeile")
            .annotate(richtig_sum=Sum('richtig'))
            .annotate(falsch_sum=Sum('falsch'))
            .annotate(zeit_sum=Sum(F('end') - F('start')))
            )  
        for k in gesamtsummen: 
            index = int(k['kategorie__zeile'])
            richtig_gesamt = k['richtig_sum']
            falsch_gesamt = k['falsch_sum']
            quote = quote_farbe(richtig_gesamt, falsch_gesamt)
            zeit_gesamt = k['zeit_sum']
            kategorie_summen[index] = (quote, richtig_gesamt)
        seconds = int(zeit_gesamt.total_seconds())
        mm, ss = divmod(seconds, 60)
        hh, mm = divmod(mm, 60)
        gesamtzeit = f"{hh}:{mm:02d}:{ss:02d}" 
        print("Gesamtzeit: ",gesamtzeit)
        schueler_liste = Profil.objects.filter(gruppe__name=gruppe.name)
        aufgaben_der_schueler = []
        for user in schueler_liste:
            protokoll_user = protokoll.filter(user = user)                   # die Gesamtsummen der einzelnen User
            summen = (
            protokoll_user
            .values("user")
            .annotate(richtig_sum=Sum('richtig'))
            .annotate(falsch_sum=Sum('falsch'))
            .annotate(zeit_sum=Sum(F('end') - F('start')))
            ) 
            richtig_sum = 0
            for g in summen:
                richtig_sum = g['richtig_sum']
                falsch_sum = g['falsch_sum']
                dauer = g['zeit_sum']
                seconds = int(dauer.total_seconds())
                mm, ss = divmod(seconds, 60)
                hh, mm = divmod(mm, 60)
                dauer_text = f"{hh}:{mm:02d}:{ss:02d}"                
            try:
                print(user.vorname[:3], ": ",dauer_text)
            except:
                dauer_text = "0:00:00"
            quote_sum = quote_farbe(richtig_sum, falsch_sum)
            aufgaben = [(0, "-")] * (katmax_max+1)
            aufgaben[0] = (quote_sum, int(richtig_sum))
            kategorie_werte = (                                                     # die Summen der einzelnen Kategoren des jeweiligen Users
                protokoll_user
                .values("kategorie__zeile")
                .annotate(richtig_sum=Sum('richtig'))
                .annotate(falsch_sum=Sum('falsch'))
                )
            for k in kategorie_werte: 
                index = int(k['kategorie__zeile'])
                richtig_kat = k['richtig_sum']
                falsch_kat = k['falsch_sum']
                quote = quote_farbe(richtig_kat, falsch_kat)
                aufgaben[index] = (quote, richtig_kat)
            aufgaben_der_schueler.append((
                user, dauer_text, aufgaben
            ))
die Ausgabe ergibt:

Code: Alles auswählen

Gesamtzeit:  0:02:22
Sum :  2:55:03
Han :  0:57:30
Cha :  0:06:42
Ley :  1:53:08
Kas :  0:12:15
...
Entweder bräuchte ich Hilfe bei der "gesamtzeit" oben im Code (den Code habe ich nicht wirklich verstanden) oder aber ich summiere unten im Code die "dauer" der einzelnen User auf - das gelingt mir aber auch nicht (wie summiert man timedelta?).
Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

Pitwheazle hat geschrieben: Donnerstag 20. Juli 2023, 11:41 (wie summiert man timedelta?).
mit '+' [plus]

https://docs.python.org/3/library/datet ... ta-objects (unten unter 'Supported operations:')
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Na denn, dann wähle ich die zweite Lösung. Da bin ich aber daran gescheitert, dass ich die nicht auf Null setzen kann- ich hatte also irgendwie sowas Gesamtzeit = datetime.datetime(0,0,0,0,0,0)- das geht nicht und Jahr Monat und Tag brauche ich ja auch nicht.
Antworten