Fehler in json Eintrag

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

Hallo Leute, da bin ich mal wieder und bräuchte eure Hilfe:
Mein Rechentrainer läuft ja schon in einer Betaversion im Internet. 184 Nutzer haben in 16 Aufgabenkategorien schon über 20 000 Aufgaben gerechnet und das scheint auch problemlos zu funktionieren. Parallel dazu habe ich lokal natürlich an Erweiterungen gearbeitet und habe die 17. Kategorie (Prozentrechnung) fertig gestellt und will die jetzt hochladen. Da ich sehr unsicher bin, ob das dann auch reibungslos funktionieren wird, habe ich jetzt einfach mal die Datenbank aus dem Internet runtergeladen und probeweise in meine lokale Installation eingefügt. Ich bekomme dann den Hinweis "You have 12 unapplied migration(s)". Wenn ich "migrate" ausführe bekomme ich eine Fehlermeldung (eine ganze Liste) und da bräuchte ich Hilfe bei der Lösung:

Code: Alles auswählen

(django_rechentrainer) PS D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)> python manage.py migrate       
Operations to perform:
  Apply all migrations: accounts, admin, auth, contenttypes, core, sessions
Running migrations:
  Applying core.0092_sachaufgabe_loe_weg_sachaufgabe_variable...Traceback (most recent call last):
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\sqlite3\base.py", line 357, in execute
    return Database.Cursor.execute(self, query, params)
sqlite3.IntegrityError: CHECK constraint failed: (JSON_VALID("loesung") OR "loesung" IS NULL)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\manage.py", line 22, in <module>
    main()
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\core\management\__init__.py", line 446, in execute_from_command_line
    utility.execute()
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\core\management\__init__.py", line 440, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\core\management\base.py", line 402, in run_from_argv
    self.execute(*args, **cmd_options)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\core\management\base.py", line 448, in execute
    output = self.handle(*args, **options)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\core\management\base.py", line 96, in wrapped
    res = handle_func(*args, **kwargs)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\core\management\commands\migrate.py", line 349, in handle
    post_migrate_state = executor.migrate(
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\migrations\executor.py", line 135, in migrate
    state = self._migrate_all_forwards(
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\migrations\executor.py", line 167, in _migrate_all_forwards
    state = self.apply_migration(
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\migrations\executor.py", line 252, in apply_migration
    state = migration.apply(state, schema_editor)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\migrations\migration.py", line 130, in apply
    operation.database_forwards(
    schema_editor.add_field(
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\sqlite3\schema.py", line 397, in add_field
    self._remake_table(model, create_field=field)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\sqlite3\schema.py", line 333, in _remake_table
    self.execute(
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\base\schema.py", line 199, in execute
    cursor.execute(sql, params)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\utils.py", line 103, in execute
    return super().execute(sql, params)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\utils.py", line 67, in execute
    return self._execute_with_wrappers(
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\utils.py", line 80, in _execute_with_wrappers
    return executor(sql, params, many, context)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\utils.py", line 84, in _execute
    with self.db.wrap_database_errors:
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\utils.py", line 91, in __exit__
    raise dj_exc_value.with_traceback(traceback) from exc_value
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\utils.py", line 89, in _execute
    return self.cursor.execute(sql, params)
  File "D:\Dropbox\RechentrainerWeb\rechentrainer_024 - Kopie (4)\django_rechentrainer\lib\site-packages\django\db\backends\sqlite3\base.py", line 357, in execute
    return Database.Cursor.execute(self, query, params)
django.db.utils.IntegrityError: CHECK constraint failed: (JSON_VALID("loesung") OR "loesung" IS NULL)
Das Problem liegt also wohl in dem Eintrag "loesung", das ich in "protokoll" speichere:

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:
    wert = 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
...
Ich speichere hier Lösungen der Aufgabe. Die kann z.B. so aussehen:

Code: Alles auswählen

 ['4000/100*4.5/360*90', 45.0, 'indiv_1']
(in diesem Beispiel zunächst der Term, der die Aufgabe löst, anschließend der Wert und dann noch der Hinweis, dass die Eingabe anschließend noch weiter ausgewertet werden soll.
Könnt ihr mir mithilfe dieser Angaben sagen, wo ich den Fehler finde und wie ich ihn behebe?
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Da ist wohl irgendwo ungültiges JSON. Das was Du da zeigst ist eine Python-Liste oder eine Zeichenkette die JSON enthalten soll? Letzteres wäre fehlerhaft weil JSON " als Begrenzer für Zeichenketten verwendet und keine '. https://www.json.org/json-de.html
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

@Pitwheazle: Das ist ein Integritätsfehler. Du versuchst Migrationen in deine Datenbank einzuspielen, die die Struktur deiner Datenbank so verändern, dass die darin enthaltenen Daten nicht mehr gültig sind. Deshalb wird die Migration mit einem "IntegrityError" abgebrochen.

Im Detail: Irgendwo in dem Feld 'loesung' steht etwas, das kein gültiges JSON ist, oder `null` ist. Und das ist mit der Migration nicht zu vereinbaren, denn nach der Migration ist beides für das Feld ungültig.
Zuletzt geändert von sparrow am Sonntag 25. Juni 2023, 16:40, insgesamt 1-mal geändert.
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

Du meinst, dass jeweils " anstelle von ' dastehen müsste?
ich habe jetzt mal die Zeilen aus meinem Code extrahiert die zunächst die python Liste erzeugen und diese in "protokoll" speichern:

Code: Alles auswählen

...
lsg = [str(zahl2)+"/"+str(zahl1)+"*100",]
parser = Parser()
zahl=round(parser.parse(lsg[0].replace(",",".")).evaluate({}),3)
lsg.append((zahl))
lsg.append("indiv_1")   
print(lsg)
...
protokoll = Protokoll.objects.create(  ... loesung = lsg, ... ) 
...
['3/99*100', 3.03, 'indiv_1'] 
der "print" Befehl in meiner IDE zeigt ' Zeichen und auch in meinem Template in dem ich den Inhalt des Protokoll Eintrages ausgeben kann zeigt

Code: Alles auswählen

Lösung: ['3/99*100', 3.03, 'indiv_1']
... aber gespeichert wird es doch (ohne Fehlermeldung) als JSON in der Datenbank?

Code: Alles auswählen

loesung = models.JSONField() 
Und hier, lokal, mit meiner lokalen Datenbank, funktioniert das ja auch wunderbar. Nur wenn ich die sqlite Datenbank aus dem Internet benutze, bekomme ich die Fehlermeldung - diese ist ja aber aus der lokalen Datenbank entstanden.
Zuletzt geändert von Pitwheazle am Sonntag 25. Juni 2023, 16:46, insgesamt 1-mal geändert.
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

sparrow hat geschrieben: Sonntag 25. Juni 2023, 16:39 @Pitwheazle: Das ist ein Integritätsfehler. Du versuchst Migrationen in deine Datenbank einzuspielen, die die Struktur deiner Datenbank so verändern, dass die darin enthaltenen Daten nicht mehr gültig sind. Deshalb wird die Migration mit einem "IntegrityError" abgebrochen.

Im Detail: Irgendwo in dem Feld 'loesung' steht etwas, das kein gültiges JSON ist, oder `null` ist. Und das ist mit der Migration nicht zu vereinbaren, denn nach der Migration ist beides für das Feld ungültig.
heißt das, in irgendeinem Eintrag in den 20158 Datensätzen steht ein falscher Eintrag oder alle sind falsch oder meine Definition in models ist falsch? Oder kann der Fehler auch woanders liegen? Wie komme ich da raus?
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

@Pitwheazle: Zu der Sache mit den Anführungszeichen. Du musst zwischen den Python-Objekten und dem JSON-Objekt unterscheiden, das in die Datenbank geschrieben wird. Die Konvertierung wird ja eben durch das JSONField vorgenommen. Du musst also in die Datenbank schauen, wenn du sehen willst, was da wirklich steht.

Das beantwortet indirekt auch die Frage an mich:
Irgendwo in der Migragion `core.0092_sachaufgabe_loe_weg_sachaufgabe_variable` sollen die von mir erwähnte Änderungen vorgenommen werden.
Erster Schritt wäre also, einen Blick in die Migration zu werfen, um zu schauen, was genau da gemacht wird.
Und dann musst du in der Datenbank schauen, welche Daten es da gibt, die mit der Migration kollidieren. Wie gesagt: Entweder ist in der Datenbank das Feld `loesung` bei mindestens einem Datensatz NULL (was du explizit verbietest und möglicherweise vorher nicht verboten hast) oder ein Feld enthält ungültiges JSON.

Also erst in die Migration schauen, dann schon einmal prüfen ob loesung irgendwo NULL ist.
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

sparrow hat geschrieben: Sonntag 25. Juni 2023, 17:14 Also erst in die Migration schauen, dann schon einmal prüfen ob loesung irgendwo NULL ist.
migration 092 enthält folgendes:

Code: Alles auswählen

class Migration(migrations.Migration):

    dependencies = [
        ('core', '0091_alter_hilfe_unique_together'),
    ]

    operations = [
        migrations.AddField(
            model_name='sachaufgabe',
            name='loe_weg',
            field=models.CharField(default='', max_length=10),
            preserve_default=False,
        ),
        migrations.AddField(
            model_name='sachaufgabe',
            name='variable',
            field=models.JSONField(default=[]),
            preserve_default=False,
        ),
    ]
... da sehe ich jetzt keinen Zusammenhang mit "loesung" (das ich da keinen Zusammenhang sehe hat natürlich nichts zu sagen :))
Kann ich nicht in meinem model einfach "loesung" mit NULL erlauben?
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

Hast du denn Einträge mit NULL in der Datenbank?
Sonst ist es wildes Rumraten. Und gerade bei Migrationen wäre das nicht gut.
Dass Migrationen sich nicht einspielen lassen, lässt ja schon einen inkonsistenten Datenbastand schließen. Und das tritt normalerweise auf, wenn man Migrationen nicht korrekt anwendet oder falsch managed.
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

Nun ja, ich habe sicher auch Einträge mit NULL in der Datenbank aber in "loesung" habe ich das nicht exlizit vorgesehen:

Code: Alles auswählen

loesung = models.JSONField() 
Sorry ich bin halt immer noch ein Pfuscher - wo müsste ich den genau suchen?
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

"Ich habe sicher"? Warum sollte das sicher sein? Eben weil du das eigentlich vorsiehst?

Du musst in der Datenbank schauen, was dort für Werte stehen. Ganz ohne Python. Für SQListe gibt es verschiedene Tools, die es dir ermöglichen, direkt in die Datenbank zu schauen.
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

Da habe ich mich nicht genau genug ausgedrückt. Ich habe in einigen Feldern auch NULL=true:

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:
    wert = 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_id = models.SmallIntegerField(default=0)
    
    #die Eingabe des users:
    eingabe = models.CharField(max_length=20, blank=True)

    versuche = 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)    
    hilfe = 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)
... aber ich wüsste nicht, wie in "loesung" ein NULL Eintrag sein sollte. Muss ich dazu alle 20158 Einträge kontrollieren?
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

Ja.
Das muss man ja nicht mit Hand machen. Es ist ja eine Datenbank und da kann man ein entsprechendes SELECT auf die Tabelle ausführen.

Also

Code: Alles auswählen

SELECT * FROM tabellenname WHERE loesung IS NULL
Tabellenname musst du herausfinden. Die entsprechenden Tools haben einen Browser, der dir die Tabellen auflistet.
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

Das heißt also, ich muss nur das Feld "loesung" überprüfen?
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

Das müsste ich doch auch in der shell machen können - oder?
Dazu muss ich gestehen, dass ich mit der nicht (mehr) umgehen kann. (Ich fürchte, das sind Anzeichen von Demenz - ich hoffe, ich kann mit coden diese verlangsamen :)). Gibt es irgendwo eine einfache Anleitung mit den Befehlen für die shell?
Ich bin ganz stolz, ich habe die erste Zeile noch hinbekommen:

Code: Alles auswählen

from core.models import Protokoll
aber der Schuss ins Dunkle:

Code: Alles auswählen

auswahl = SELECT * From Protokoll WHERE loesung Is Null
geht, wie ihr sicher wusstet, nicht.
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

Nein, das hat nichts mit Python zu tun.
Wie gesagt, es gibt Tools, mit denen du direkt in die Datenbank schauen kannst.
DB Browser kenne ich von früher.
Pitwheazle
User
Beiträge: 1050
Registriert: Sonntag 19. September 2021, 09:40

Aber mit der shell müsste das doch auch gehen?
Ich habe "db browser for SQLite" installiert - das heißt aber (natürlich) nicht, dass ich damit umgehen könnte. Da steht auch der Text zur Fehlermeldung:Bild was mache ich damit?
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

Dann den folgenden SQL-Befehl ausführen:

Code: Alles auswählen

SELECT * FROM core_protokoll WHERE loesung IS NULL
Benutzeravatar
sparrow
User
Beiträge: 4510
Registriert: Freitag 17. April 2009, 10:28

Dann enthält einer der Datensätze wahrscheinlich kein gültiges JSON.

Warum tritt der Fehler auf, obwohl du das Feld in der Migration nicht verändert wird?
Für bestimmte Operationen, die das Schema einer Tabelle verändern, muss eine Kopie der Tabelle erstellt und die alten Daten eingespielt werden. Und genau das geht hier schief. Die neue Tabelle wird mit der neuen Struktur erstellt. Dann werden die alten Daten übernommen und mindestens ein Datensatz sorgt für den IntegrityError.
Benutzeravatar
__blackjack__
User
Beiträge: 13931
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Pitwheazle: Na dann mal alles selektieren was kein gültiges JSON ist.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Antworten