20110615

Noch ein UnicodeDecodeError

Gestern trat ein paarmal folgender Fehler auf bei dem Kunden, dem ich die neue Version installiert hatte:

Traceback (most recent call last):
  File "/var/snapshots/django/django/core/handlers/base.py", line 111, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "/var/snapshots/lino/lino/ui/extjs3/ext_ui.py", line 1118, in api_element_view
    error=e)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 52: ordinal not in range(128)

Und hier der dazugehörige Code:

try:
    return a.run(ar,elem)
except Exception,e:
    msg = _("Action %(action)s failed for %(record)s: %(error)s") % dict(
        action=a,
        record=obj2str(elem),
        error=e) ## lino 1118
    logger.warning(msg)
    logger.exception(e)
    return error_response(e,msg)

Also fast die gleiche Lektion wie vor 2 Wochen: “When you pass more than one formatting argument to a logger function, and one of them is a unicode string, then the other argument(s) may not be bytestrings containing non-ascii chars.”

Mit der Verallgemeinerung, dass das für alle String-Formatierungen (Operator %) gilt, und zwar sowohl für Positionsargumente wie für Keyword-Argumente.

Der Fehler ist reproduzierbar, indem ich auf Vertrag # 88 gehe und dort Drucken klicke.

Beim Einchecken und Testen entdecke ich, dass die Sache noch perverser ist als gedacht.

Wenn ich die Exception nicht selber abfange, dann tut Django das, und der kann es. Der schickt dann dem Benutzer eine freundliche 500-Meldung und den Systemverwaltern (setting:`ADMINS) eine E-Mail, dank derer ich erstmals überhaupt die Exception an sich sehe:

Traceback (most recent call last):
  File "/var/snapshots/django/django/core/handlers/base.py", line 111, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "/var/snapshots/lino/lino/ui/extjs3/ext_ui.py", line 1111, in api_element_view
    return a.run(ar,elem)
  File "/var/snapshots/lino/lino/mixins/printable.py", line 407, in run
    bm.build(self,elem)
  File "/var/snapshots/lino/lino/mixins/printable.py", line 186, in build
    tpl_leaf = self.get_template_leaf(action,elem)
  File "/var/snapshots/lino/lino/mixins/printable.py", line 162, in get_template_leaf
    tpls = action.get_print_templates(self,elem)
  File "/var/snapshots/lino/lino/mixins/printable.py", line 399, in get_print_templates
    return elem.get_print_templates(bm,self)
  File "/var/snapshots/lino/lino/mixins/printable.py", line 610, in get_print_templates
    (ptype.__class__.__name__,ptype,bm.template_ext))
Exception: Invalid template configured for ContractType "Art.60§7 intern". Expected filename ending with '.odt'.

Der besagte Code ist:

if not ptype.template.endswith(bm.template_ext):
    raise Exception(
      "Invalid template configured for %s \"%s\". Expected filename ending with '%s'." %
      (ptype.__class__.__name__,ptype,bm.template_ext))

Also das “§”-Zeichen in “Art.60§7 intern” ist der Auslöser.

Was ich noch nicht wusste: es gibt Python issue #2517, : Eine Exception mit einem Unicode-String führt zumindest in Python 2.6 noch allgemein an vielen Stellen zu Problemen.

Lektion : Exceptions dürfen ausschließlich ASCII-Code enthalten und folglich auch nicht internationalisert sein. Im obigen Beispiel reicht:

if not ptype.template.endswith(bm.template_ext):
    raise Exception(
      "Invalid template configured for %s %r. Expected filename ending with %r." %
      (ptype.__class__.__name__,unicode(ptype),bm.template_ext))

Dass ich meine Instanz ptype noch selber in ein unicode() einpacken muss, ist unlogisch und liegt daran, dass Djangos Model.__repr__() unter Umständen einen Unicode-String zurückgeben kann. Ich habe das in Django ticket #16261 gemeldet, aber das werden die wohl kaum akzeptieren, weil das ziemlich unkompatibel wäre.

Nachdem mir das klargeworden ist, habe ich noch ein Problemchen entdeckt: in meiner lino.utils.log.configure() muss ich den AdminEmailHandler nicht nur den Logger django hinzufügen, sondern auch meinem Logger lino.

Neuer Testcase lino.apps.dsbe.tests.dsbe_tests.test01b().

Voilà. Jetzt bin ich zufrieden: Der Bentuzer kriegt eine Meldung “Action Drucken failed for Contract #88 (Vertrag # 88). An error report has been sent to the system administrator.” und die Systemverwalter eine E-Mail mit detaillierter Fehlerbeschreibung, unter anderem “Invalid template configured for ContractType u’Art.60xa77 intern’. Expected filename ending with ‘.odt’.”

All das ist die Checkin-Serie 20110615, die noch mit nach /releases/2011/0613 reinkommt.

Viele kleine Benutzerwünsche

  • Person.get_full_name() now renders the Person.last_name in all-uppercase letters
  • Person.address_person_lines() now uses Person.get_full_name().
  • Person.get_full_name() now includes a salutation.
  • New field PersonGroup.ref_name used for ordering.
  • New field Person.duties_person
  • Field Person.native_language replaced by LanguageKnowledge.native
  • New function lino.utils.babel.dtomy() available in lino.mixins.printable.AppyBuildMethod.

Erstes Check-in der Serie 20110615b um 19.35 wegen Feierabend.

Release 1.1.17

Um die bereits erfassten Muttersprachen zu übernehmen, musste ich mir was Neues einfallen lassen. Erstens eine kleine Liste der bestehenden Fälle:

>>> import codecs
>>> from lino.apps.dsbe.models import Person
>>> f = codecs.open('nl.txt','w',encoding='utf-8')
>>> f.write('\n'.join(["%s : %s + %s" % (unicode(p),p.native_language,', '.join([unicode(x) for x in p.languageknowledge_set.all()])) for p in qs]))
>>> f.close()

Aber vor allem: Personen, die bisher in native_language etwas stehen haben, sollen stattdessen jetzt einen Eintrag in LanguageKnowledge haben. Manchmal existiert der schon (und muss dann nur native=True gesetzt kriegen und manchmal nicht (und muss dann erstellt werden ohne Angaben für spoken und written) Und weil die evtl. bestehenden LanguageKnowledge-Records ja noch gar nicht gespeichert worden sind während der objects(), kann ich in der create_contacts_person() nicht schauen, ob schon ein Eintrag für die Muttersprache existerte oder nicht. Also muss ich den Kram in eine Liste NATIVES einsammeln und erst später speichern, wenn alles andere eingelesen ist. Deshalb die neue Funktion after_load, die vom lino.utils.dpy.Deserializer nach dem Speichern der objects ausgeführt wird (wenn sie existiert).

Und dann noch was cooles Neues: Modul :mod: lino.apps.dsbe.migrations. Um die Konvertierung nicht manuell in die Dump-Datei beim Kunden reinkopieren zu müssen, habe ich ein neues System ausgedacht, damit ich die Migration einmal schreiben kann und die Dump-Datei sie sich automagisch installiert. Ganz automagisch geht das allerdings noch nicht: die entsprechenden Zeilen müssen manuell rauskommentiert werden.

Weiteres Check-in der Serie 20110615b zwecks Installation beim Kunden.

Released /releases/2011/0615.