Filtern in der Datenbank erledigen

Django, Flask, Bottle, WSGI, CGI…
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Ich komme bei dem ganzen Stückwerk nicht mit. Kannst Du mal exakt die Formeln aufschreiben, wie sich diese Note berechnet. Ich bin immer noch davon überzeugt, dass es keine zweite Tabelle dafür braucht.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Nö, klappt nicht. Ich habe mein Feld jetzt mit:

Code: Alles auswählen

fehler_ab = models.DateTimeField(default=datetime(2023, 8, 1, 0, 0, 0, 0, tzinfo=pytz.UTC))
definiert und "pip install pytz" ausgeführt. Die Fehlermeldung:

Code: Alles auswählen

NameError: name 'pytz' is not defined
OK, dann pfusche ich halt weiter:

Code: Alles auswählen

fehler_ab = models.DateTimeField(default=datetime(2023, 8, 1, 0, 0, 0, 0)
Der Hinweis lautet wieder:

Code: Alles auswählen

  warnings.warn("DateTimeField %s received a naive datetime (%s)"
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@Pitwheazle: Es ist irrelevant, was da gespeichert wird. Es ist ein Zeitstempel mit der enthaltenen Information, für welche Zeitzone es gilt. Das ist doch schon die Information. Und 4 Uhr UTC ist etwas anderes als 4 Uhr MEZ ist etwas anderes als 4 Uhr MESZ.

Ich bin voll bei Siruis3. Bitte mal einmal das große Bild. "Fehler ab" als Feldname klingt nicht so, als würde da die Information hinein gehören, _wann_ der Eintrag ins Protokoll vorgenommen wurde.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Sirius3 hat geschrieben: Samstag 13. Januar 2024, 13:39 Ich komme bei dem ganzen Stückwerk nicht mit. Kannst Du mal exakt die Formeln aufschreiben, wie sich diese Note berechnet. Ich bin immer noch davon überzeugt, dass es keine zweite Tabelle dafür braucht.
Es geht nicht um die Note sondern um die Anzahl der Fehler. Diese werden aus den Einträgen im Protokoll addiert - genauso wie die Anzahl der richtigen Eingaben. Für letztere werden alle Einträge in diesem Halbjahr zusammengezählt. Es werden aber nur die Fehler gezählt, die (in dieser Kategorie) nach dem Zeitpunkt aufgetreten sind, nachdem der Fehlerzähler per Zeitstempel zurückgesetzt wurde. Der user hat also insgesamt bei "Einheiten umwandeln" in diesem Schuljahr drei Fehler gemacht, löst er jetzt 15 Aufgaben am Stück ohne Fehler bzw. Klick auf "Abbrechen" oder "Lösung anzeigen" (bei letzerem bekommt er die Lösung angezeigt und gleich eine neue Aufgabe), so wird ein Zeitstempel gesetzt und nur noch die Fehler gezählt die anschließend neu gemacht wurden.
Dies könnte man sicher auch irgendwie im Protokoll festhalten, ich habe aber nunmal eine extra Tabelle für jede Aufgabenkategorie und begründe dies auch gerne nochmals: Also prinzipiell sollen immer 10 Aufgaben am Stück gerechnet werden (da kommt man nur mit "Abbrechen" raus. Innerhalb der einzelnen Kategorien gibt es immer verschiedene Unterkategorien wie z.B. mit oder ohne Kommazahlen oder mit Umwandlung von Volumeneinheiten oder auch nicht. Im 5. Schuljahr müssen sie noch keine Kommazahlen rechnen und die Volumeneinheiten kommen auch später. Wenn also eine Aufgabenkategorie gestartet wird, wird zunächst überprüft, in welchem Jahrgang der Schüler/die Schülerin ist und in der wievielten Schulwoche wir uns befinden und daraufhin festgelegt in welchem Untergruppenbereich die nächsten 10 Aufgaben erzeugt werden. Zusätzlich können die Kids aber auch schon mal was schwierigeres ausprobieren und Förderschüler können immer auswählen ob sie z.B. mit Kommazahlen rechnen wollen oder nicht. Daher wird oft beim Start der Aufgabenkategorie nachgefragt, ob eben mit - oder ohne Kommazahlen oder Dreiecke mit - oder ohne trigonometrischen Aufgaben, angezeigt werden. Dies speichere ich, zusammen mit der Aufgabennummer in einer eigenen Tabelle - und dort speichere ich halt einfach auch den Zeitpunkt zu dem der Fehlerzähler in dieser Kategorie zurückgesetzt wurde. Warum auch nicht? Dass ich in dieser Tabelle auch die Anzahl der aktuellen Fehler speichere ist Blödsinn und das will ich ja gerade loswerden).
So, das funktioniert ja auch schon fast. Nur habe ich jetzt halt das Zeitzonenproblem. Wenn der Fehlerzähler zurückgesetzt wird, speichere ich im Feld "fehler_ab" den entsprechenden Zeitstempel

Code: Alles auswählen

if zaehler.richtig_ohne_fehler >= kategorie.eingaben_ohne_fehler:                 # wenn die erforderliche Anzahl richtiger Antworten eingegeben wurde, wird der jeweilige Fehlerzähler zurückgesetzt
    if zaehler.fehler_zaehler > 0:
        rueckmeldung = rueckmeldung + "<br><b>Herzlichen Glückwunsch: Deine Fehlerzähler wurde zurückgesetzt!</b>"
    zaehler.fehler_ab = timezone.now()
... und setze dann einen Filter in meinem Protokoll:

Code: Alles auswählen

fehler_ab = zaehler_kategorie.fehler_ab
print(fehler_ab)
protokoll_fehler = protokoll_kategorie.filter(start__gt=fehler_ab)
... und, wie befürchtet, der gespeicherte Timecode differiert um eine Stunde. Wie bekomme ich das hin, ohne zweimal im Jahr die Zeitzohne umzustellen?
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Ich habe jetzt auch:

Code: Alles auswählen

zaehler.fehler_ab = timezone.now()
durch

Code: Alles auswählen

zaehler.fehler_ab = datetime.now()
ersetzt - das hilft auch nicht.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Wie bekomme ich das hin, ohne zweimal im Jahr die Zeitzohne umzustellen?
UTC benutzen ist am einfachsten. Wie @sparrow schon sagte: den Rest kann man Rechnen.

Ich durchblicke das mit dem Zählen nicht wirklich... wenn du _eine_ nennen wir es mal Logging-Tabelle hast, wo fortlaufend alle nennen es mal Event gespeichert werden (also welcher Schüler-ID welche Aufgaben-ID gemacht hat und mit welchem Abschluss (richtig / falsch / abgebrochen whatever), dann kann man daraus alles abfragen. Kann halt sein, dass die Abfragen ein bisschen komplexer werden, d.h. nach mehreren Kriterien filtern. Das können relationale Datenbanken aber.
Und wenn die Aufgabengruppen hast könntest du jedes Mal, wenn ein Schüler eine Aufgabengruppe bekommt ein ID dafür generieren (eigene Tabelle oder UUID, je nach dem, was hier sinnvoll ist) und die mit in der Logging-Tabelle speichern. Dann kannst du für die Gruppe die Fehlerquote bzw. Note bestimmen.
ich habe aber nunmal eine extra Tabelle für jede Aufgabenkategorie
Schöner in einer relationalen Datenbank wäre, wenn du eine Tabelle mit Aufgabenkategorien hättest (Algebra, Geometrie, Prozentrechnung, whatever) und eine Tabelle mit _allen_ Aufgaben, wobei jede Aufgabe als Fremdschlüssel die Kategorie hast.

Das mit dem relationalen Datenbankdesign ist im Prinzip nicht schwierig, ist aber eine Lernkurve. Weil man das halt, wenn man es auf Blatt Papier schreiben würden, nicht so machen würden . Ich denke, es ist ziemlich normal, dass man diesen Designfehler am Anfang macht. War bei mir genau so.

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

noisefloor hat geschrieben: Samstag 13. Januar 2024, 17:29 Schöner in einer relationalen Datenbank wäre, wenn du eine Tabelle mit Aufgabenkategorien hättest (Algebra, Geometrie, Prozentrechnung, whatever)
Eine Tabelle mit allen Aufgabenkategorien wüsste ich nicht, wie ich sie erstellen könnte. Die Aufgaben werden ja zufällig aus Unterkategorien je nach Jahrgang und Fortschritt des Schülers und grundsätzlich aus Zufallszahlen generiert und das oft noch mit Grafiken die ziemlich variieren. Diese Aufgaben werden zur Laufzeit per - oft recht komplexem Code - generiert. Da bin ich überfordert dies in einer Tabelle zu speichern. Wenn irgendjemand sich das anschauen will, könnte er/sie(?) sich das in github mal ansehen.
Ich bin ja jetzt, wie ihr seht, oft an den Grenzen mit komplexen Zusammenhängen - mehr kann ich nicht ... und es funktioniert ja (für mich) auch zufriedenstellend :).
... und ich vermute, ich habe auch eine relationale Datenbank - wenn ihr das vielleicht auch anders aufbauen würdet.
noisefloor hat geschrieben: Samstag 13. Januar 2024, 17:29
Wie bekomme ich das hin, ohne zweimal im Jahr die Zeitzohne umzustellen?
UTC benutzen ist am einfachsten. Wie @sparrow schon sagte: den Rest kann man Rechnen.
OK, ich gebe jetzt also eine Stunde zu und muss im Sommer auf 2 Stunden umstellen? Geht das nicht einfacher?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Ich schrieb bereits gestern: Django hat die Hilfsfunktion make_aware um dein Problem zu lösen.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

sparrow hat geschrieben: Samstag 13. Januar 2024, 17:51 Ich schrieb bereits gestern: Django hat die Hilfsfunktion make_aware um dein Problem zu lösen.
Da habe ich auf Anhieb die Relevanz nicht erkannt und es auch noch nicht wirklich verstanden. Jetzt kommt Besuch, da muss ich wohl morgen weitermachen. Aber schon mal die Frage: Muss ich dafür irgendwas importieren?
Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

Pitwheazle hat geschrieben: Samstag 13. Januar 2024, 17:46
noisefloor hat geschrieben: Samstag 13. Januar 2024, 17:29
Wie bekomme ich das hin, ohne zweimal im Jahr die Zeitzohne umzustellen?
UTC benutzen ist am einfachsten. Wie @sparrow schon sagte: den Rest kann man Rechnen.
OK, ich gebe jetzt also eine Stunde zu und muss im Sommer auf 2 Stunden umstellen? Geht das nicht einfacher?
Ja, intern UTC benutzen. Da gibt es keine Sommerzeit.

Code: Alles auswählen

In [2]: from datetime import datetime as DateTime
In [3]: from datetime import timezone

In [4]: DateTime.now(timezone.utc)
Out[4]: datetime.datetime(2024, 1, 13, 16, 52, 46, 652676, tzinfo=datetime.timezone.utc)
Die aktuelle Zeit in der lokalen Zeitzone erscheint mir völlig egal zu sein. Hauptsache es gibt einen Zeitpunkt zu dem der Fehlerzähler zurückgesetzt wurde und die Zeit danach... ob der Schüler selbst in der Zeitzone von Katmandu, München oder Houston sitzt, ist doch egal?
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Danke schon mal! Noch sind die Gäste nicht da. Was ich prinzipiell nicht verstehe: Wenn die Aufgabe erstellt wird, wird die Zeit im Protokoll festgehalten:

Code: Alles auswählen

start = models.DateTimeField('Start', auto_now_add=True)
und wenn ich den Fehlerzählerzeitstempel speichere:

Code: Alles auswählen

zaehler.fehler_ab = datetime.now()
haben die beiden eine Stunde Unterschied bei

Code: Alles auswählen

protokoll_fehler = protokoll_kategorie.filter(start__gt=fehler_ab)

"fehler-ab" ist UTC und "start" nicht - oder wie? ... und warum?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@grubenfox: Das hilft nicht. Wie aus dem Thread hervor geht, ist das ja schon probiert worden. Und wenn die eingehenden Daten korrekterweise die Zeitzone des Benutzers haben, dann kommt es zu der Differenz. Genau darum geht es ja.

@Pitwheazle: Welches datetime verwendest du denn bei datetime.now()? Wenn das aus den django.utils kommt, ist es nicht naive.
Benutzeravatar
grubenfox
User
Beiträge: 432
Registriert: Freitag 2. Dezember 2022, 15:49

sparrow hat geschrieben: Samstag 13. Januar 2024, 18:52 @grubenfox: Das hilft nicht. Wie aus dem Thread hervor geht, ist das ja schon probiert worden.
Naja, im Thread habe ich so ein-/zweimal den Faden verloren. Geht ja nicht nur mir so... ;)
sparrow hat geschrieben: Samstag 13. Januar 2024, 18:52 @grubenfox: Und wenn die eingehenden Daten korrekterweise die Zeitzone des Benutzers haben, dann kommt es zu der Differenz. Genau darum geht es ja.
"Korrekterweise"? Deswegen ja.. wenn man intern nur UTC nutzt (was bei aktuellem Django wohl der Normalfall ist, wenn ich da die Doku richtig gelesen habe) und man die Zeitzone vom Benutzer ignorieren kann (wenn irgendwelche Zeitpunkte dem Benutzer angezeigt werden sollen, dann muss man die lokale Zeitzone berücksichtigen. Aber intern sollte doch alles UTC sein.. weil Django UTC nutzt wenn man nichts anderes vorgibt) dann gibt es auch keine Differenz mehr.

Bei den schon vorhandenen Altdaten bin ich jetzt nicht sicher ob die Zeitzoneninformationen haben oder nicht.
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@grubenfox: Das Thema hatten wir mit Pitzhweazle schon einmal ausführlich. Mit welcher Zeitzone Zeitstempel gespeichert werden ist egal. Wichtig ist nur, _dass_ sie eine haben. Der Rest lässt sich errechnen. Und das übernimmt das Framework.

Und ja, die eingehenden Daten tragen korrekterweise die Zeitzone des Anwenders. Auf welche Weise die dann in der Datenbank landen ist, solange man Django ORM verwendet, egal. Wahrscheinlich sogar ohne ORM. Wenn ich die Logins des Benutzers ab heute 12 Uhr lokale Zeit selektieren möchte, dann darf es halt kein Zeitstempel heute 12 Uhr UTC sein, mit dem gefiltert wird.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Tut mir wirklich leid, dass ich so begriffsstutzig bin und euch eure Zeit stehle - langsam verstehe ich, wie sich meine Schülerinnen und Schüler gefühlt haben, wenn sie trotz Bemühungen ihrerseits, meine Erklärungen nicht verstanden haben.
Aber ein bißchen bin ich weitergekommen:
Also zunächst bei dem Problem beim Zeitvergleich beim Filtern. Da ist wohl kein Zeitunterschied. Wenn ich die Zeiten im Admintool ansehe, sind sie gleich. Nur wenn ich sie mit "print(fehler_ab)" ausgebe sehe ich eine Stunde Zeitunterschied. Da habe ich euch und mir anscheinend unnütz Zeit verschwendet. @sparrow schreibt ja, dass Django das intern berechnet - das verwirrt mich aber immer noch.
Und wenn ich den Fehlerstempel (für die bestehenden Einträge) mit

Code: Alles auswählen

fehler_ab = models.DateTimeField(default=datetime(2023, 8, 1, 0, 0, 0, 0))
setze, dann seh ich im Admintool die Zeit um 2 Uhr morgens. Da rechnet Django also meine Ortszeit in UTC um und wenn ich die mit "print" ausgeben lasse, sind die zwei Stunden wieder weg.
Ich glaube ich lasse das so. Bei der letzten Einstellung sind die zwei Stunden Differenz eh wurst. Hauptsache, die Zeiten zum Filtern stimmen.
Aber einmal könnten wir es ja nochmal probieren:
Morgen fängt der Unterricht in Hessen wieder an und einige Kolleginnen und Kollegen wollen mit neuem Ergeiz loslegen und ich werde meinen geänderten Code heute noch hochladen. Zum Halbjahreswechsel muss ich diese besagten Zähler in allen Einträgen auf den 1.2.24 setzen. Kann ich das jetzt mit datetime(2024, 2, 1, 0, 0, 0, 0) machen (und mich um die zwei Stunden nicht kümmern - weil ich es immer noch nicht endgültig verstanden habe) oder soll ich jetzt eine Zeitzone mitgeben - z.B. mit dem Vorschlag von @Grubenfox?
Ach und noch eine neue Baustelle:
In meinen migrations sind jetzt ja einige Einträge, weil ich das Zeitfeld mehrmals entfernt und wieder hinzugefügt habe :? , kann man die löschen?
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Änderung: Nein, ich setze den Zeitstempel nicht auf den 1.2.24 - das muss ich ja sonst für jedes Halbjahr ändern - ich ändere ihn mit "timezone.now()" - vorher denken wäre sinnvoll (Smiley für Schämen)
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Ich habe das schon einmal gefragt: Woher kommt das timezone.now() ?
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Wahrscheinlich meinst du das:

Code: Alles auswählen

from django.utils import timezone
?
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

Dann passt das, weil das bereits aware ist.

Wenn du übrigens einen "Tag" und keinen "Zeitpunkt" festelgen möchtest (das siehts so aus, weil du 0 Uhr angibst), dann wäre ein date möglicherweise passender als ein timestamp.
Pitwheazle
User
Beiträge: 873
Registriert: Sonntag 19. September 2021, 09:40

Nun ja, Zeit ist genauer. Denn ab der Sekunde, ab dem der Fehlerzähler zurückgesetzt, oder hier neu gesetzt wird, werden die Fehler gezählt und, falls innerhalb dieses Tages zuerst Fehler gemacht wurden und später der Fählerzähler zurückgesetzt wurde, eben nicht mehr. Das muss auf die Sekunde stimmen. Ich habe in meinem Nachtrag auch geschrieben, dass ich nicht mehr den 1.2.24 eintrage, sondern den Zeitpunkt an dem der User das neue Halnjahr begonnen hat.
Antworten