Memo commands in Lino Noi

>>> from lino import startup
>>> startup('lino_book.projects.noi1e.settings.demo')
>>> from lino.api.doctest import *

The description of a ticket and the text of a comment (short_text) are rich text fields.

And additionally they can contain memo markup commands (see memo : The memo parser).

Lino Noi memo command reference


Insert a link to an external web page. The first argument is the URL (mandatory). If no other argument is given, the URL is used as text. Otherwise the remaining text is used as the link text.

The link will always open in a new window (target="_blank")

Usage examples:

  • [url]

  • [url example]

  • [url another example]


Refer to a ticket. Usage example:

See [ticket 1].


Refer to a company. Usage example:

I met Joe from [company 1] and we agreed...


Refer to a person. Usage example:

I met [person 7 Joe] and we agreed...


Refer to a Python object.

Usage examples:

  • [py lino]

  • [py lino.modlib.memo.parser]

  • [py]

  • [py tickets.Ticket]

The obj2memo method

You might want to programmatically generate a text containing memo markup.

For example when your code knows some database object and you want to create a description that would refer to your object if rendered with memo:

>>> ar = rt.login('robin')
>>> obj =
>>> txt = ar.obj2memo(obj)
>>> print(txt)
[ticket 1] (Föö fails to bar when baz)

Let's also check whether the produced text is valid:

>>> print(ar.parse_memo(txt))
<a title="F&#246;&#246; fails to bar when baz" href="Detail">#1</a> (Föö fails to bar when baz)


There are two suggesters in Lino Noi: when the user types a "#", they get a list of tickets. When they type a "@", they get a list with all users.

Every site instance has its global memo parser:

>>> mp = dd.plugins.memo.parser
>>> mp.suggesters.keys()
dict_keys(['@', '#'])

A suggester always returns a maximum of 5 suggestions:

>>> len(list(mp.suggesters['#'].get_suggestions()))
>>> list(mp.suggesters['#'].get_suggestions("12"))
[{'value': 12, 'title': '#12 (⚒ Foo cannot bar)', 'link': "javascript:window.App.runAction({'actorId': 'tickets.Tickets', 'an': 'detail', 'rp': null, 'status': {'record_id': 12}})"}]
>>> list(mp.suggesters['#'].get_suggestions("why"))
[{'value': 20, 'title': '#20 (⚒ Why is foo so bar)', 'link': "javascript:window.App.runAction({'actorId': 'tickets.Tickets', 'an': 'detail', 'rp': null, 'status': {'record_id': 20}})"}, {'value': 29, 'title': '#29 (☾ Why is foo so bar)', 'link': "javascript:window.App.runAction({'actorId': 'tickets.Tickets', 'an': 'detail', 'rp': null, 'status': {'record_id': 29}})"}, {'value': 38, 'title': '#38 (☐ Why is foo so bar)', 'link': "javascript:window.App.runAction({'actorId': 'tickets.Tickets', 'an': 'detail', 'rp': null, 'status': {'record_id': 38}})"}, {'value': 47, 'title': '#47 (☑ Why is foo so bar)', 'link': "javascript:window.App.runAction({'actorId': 'tickets.Tickets', 'an': 'detail', 'rp': null, 'status': {'record_id': 47}})"}, {'value': 56, 'title': '#56 (☒ Why is foo so bar)', 'link': "javascript:window.App.runAction({'actorId': 'tickets.Tickets', 'an': 'detail', 'rp': null, 'status': {'record_id': 56}})"}]
>>> list(mp.suggesters['@'].get_suggestions())
[{'value': 'jean', 'title': 'Jean', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 7}})"}, {'value': 'luc', 'title': 'Luc', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 6}})"}, {'value': 'marc', 'title': 'Marc', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 4}})"}, {'value': 'mathieu', 'title': 'Mathieu', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 5}})"}, {'value': 'robin', 'title': 'Robin Rood', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 1}})"}]
>>> list(mp.suggesters['@'].get_suggestions("ma"))
[{'value': 'marc', 'title': 'Marc', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 4}})"}, {'value': 'mathieu', 'title': 'Mathieu', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 5}})"}, {'value': 'romain', 'title': 'Romain Raffault', 'link': "javascript:window.App.runAction({'actorId': 'users.AllUsers', 'an': 'detail', 'rp': null, 'status': {'record_id': 3}})"}]
>>> mp.suggesters['#'].get_object("1")
Ticket #1 ('#1 (⚹ Föö fails to bar when baz)')
>>> mp.parse("#1", ar)
'<a title="#1 (&#9913; F&#246;&#246; fails to bar when baz)" href="Detail">#1</a>'


Comments a being bleached by default.

Check whether content has been bleached

>>> print(comments.Comment.objects.filter(body="o:OfficeDocumentSettings").first())
>>> obj  = comments.Comment.objects.filter(body__contains="and follow your welcome messages").first()
>>> print(obj.body_short_preview)

De : []  Envoyé : mardi ... À : Objet : [welcht] YOU modified FOO BAR

<BLANKLINE> Dear Aurélie , <BLANKLINE> this is to notify / BAR <BLANKLINE> BAR modified <BLANKLINE> TODO: include a summary of the modifications. <BLANKLINE> Any subsequent notifications about foo/ un...

Above comments were created by the demo2 fixture of lino.modlib.comments.