There were some failures while generating https://www.lino-framework.org/autodoc/index.html. Documenting reusable Django models using Sphinx’s autodoc isn’t easy because they -by design- can work with different DJANGO_SETTINGS_MODULES. lino.dd.ForeignKey() is one of the subtle tricks to get it working.

choicelists.Choice caused UnicodeEncodeError

Today we had this traceback:

201207-18 09:05:54 ERROR watch_tim : 'ascii' codec can't encode character u'\xe4' in position 39: ordinal not in range($
Traceback (most recent call last):
  File "/var/snapshots/lino/lino/apps/pcsw/management/commands/watch_tim.py", line 529, in watch
  File "/var/snapshots/lino/lino/apps/pcsw/management/commands/watch_tim.py", line 502, in process_line
  File "/var/snapshots/lino/lino/apps/pcsw/management/commands/watch_tim.py", line 203, in PUT
  File "/var/snapshots/lino/lino/apps/pcsw/management/commands/watch_tim.py", line 137, in validate_and_save
  File "/var/snapshots/lino/lino/utils/dblogger.py", line 95, in log_changes
    changes.append(u"%s : %s --> %s" % (k,obj2str(v['old']),obj2str(v['new'])))
  File "/var/snapshots/lino/lino/core/modeltools.py", line 402, in obj2str
    return repr(i)
  File "/var/snapshots/lino/lino/utils/choicelists.py", line 120, in __repr__
    return str(self)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe4' in position 39: ordinal not in range(128)

The explanation for riddle 2 is that lino.utils.choicelists.Choice.__repr__() returns a unicode string:

def __repr__(self):
    return str(self)

“In Python2, __repr__ (and __str__) must return a string object, not a unicode object. In Python3, the situation is reversed, __repr__ and __str__ must return unicode objects, not byte (née string) objects” (unutbu on stackoverflow

After also consulting the Python docs <http://docs.python.org/reference/datamodel.html#object.__repr__>, I’d say the the best solution is to have the following methods in lino.utils.choicelists.Choice:

def __unicode__(self):
    return unicode(self.text)

def __str__(self):
    return unicode(self.text).encode(errors='backslashreplace')

def __repr__(self):
    if self.name is None:
        return "%s (%s:%s)" % (self,self.choicelist.__name__,self.value)
    return "%s (%s.%s:%s)" % (self,self.choicelist.__name__,self.name,self.value)