Optimizing RolesByCompany caused an avalanche

December 27-29, 2023

I did some fundamental cleanup in the following methods. Initially it was just for #5346 (Optimize RolesByCompany), which was trivial: there was no insert layout and therefore it was difficult to add a new contact person for an organization.

But working on this caused an avalanche of #5353 (Use mark_safe instead of ElementTree).

ElementTree versus mark_safe()

The fundamental new thing is that we started to use mark_safe() function. When a virtual field or any function returns some content, then the caller needs to know whether that content is (1) plain text or (2) HTML-formatted text. During the first years of Lino, I discovered ElementTree and fell in love. And I still say that it’s a great tool for generating HTML. And I based most function calls on the convention that when it’s a str then it is plain text (and thus needs to get escaped when rendered as HTML), and when a function wants to return formatted text, then it must return an ElementTree.

But this convention is suboptimal when we return content of a rich text field, it that is already a chunk of HTML, verified and bleached and validated. In that case a method that is expected to return ElementTree currently has to parse the stored HTML using lxml.html.fromstring() in order to get an ET from it. Which is a bit stupid because that ET is used for nothing else but to generate HTML.

I had been feeling this issue for quite some time already, but now I finally created a separate ticket for it: #5353 (Use mark_safe instead of ElementTree).

  • qs2summary() no longer returns an etree but an html string. As a result, Actor.summary_sep no longer needs to be a callable, we can remove the “from lino.core.utils import comma”.

  • The default implementation of Model.as_story_item() returned as_summary_row() but now returns as_paragraph()

  • summary_row() replaced by as_summary_row()

  • row_as_summary()

  • ar.obj2htmls(self)

  • Model.as_search_item()

About mark_safe()

The Django docs about mark_safe() say that a “safe” string becomes unsafe as soon as it is modified. I wondered what happens if you just concatenate two safe strings. Answer the result is still safe.

>>> from django.utils.html import mark_safe
>>> s1 = mark_safe("<b>Foo</b>")
>>> s2 = mark_safe("<b>Bar</b>")
>>> s3 = mark_safe("<em>{} and {}</em>")
>>> type(s1+s2)
<class 'django.utils.safestring.SafeString'>

But already the following requires another call to mark_safe():

>>> s4 = s3.format(s1, s2)
>>> type(s4)
<class 'str'>

Note that the word “safe” here has nothing to do with actual security. A SafeString can contain dangerous HTML.