comments : The comments framework

The lino.modlib.comments plugin adds a framework for handling comments.

A tested document

This is a tested document. The following instructions are used for initialization:

>>> from lino import startup
>>> startup('')
>>> from lino.api.doctest import *



A written text that one user wants to share with others.

A comment is always "about" something, called the discussion topic.

A comment has no "recipient". When you submit a comment, Lino notifies all users who registered their interest in the discussion topic.

A comment can be a reply to another comments. All comments replying directly or indirectly to a given comment are called a discussion thread.

Comments are stored in the Comment database model.

Comments have no workflow management nor rating mechanism etc. It is the author's responsibility to think before they write something.

discussion topic

The database object that is the "topic" of a comment.

commentable database model

The application developer decides which database models can serve as topics for commenting by having these database models inherit from the Commentable mixin.

commenting group

A group of users who discuss with each other using comments.


class lino.modlib.comments.Comment

Django model to represent a comment.


The full body text of your comment.


The first paragraph of your body.


The author of the comment.


The topic this comment is about. This field is a Generic Foreign Key, i.e. users can basically comment on any database object. It is however the application developer who decides where comments can be created and how they are being displayed.

The owner of a comment is always an instance of a subclass of Commentable.


When this comment has been published. A timestamp.

class lino.modlib.comments.Comments

Whether to show only (un)published comments, independently of the publication date.


The date range to filter.


Which event (created, modified or published) to consider when applying the date range given by start_date and end_date.

as_li(cls, self, ar)

Return this comment for usage in a list item as a string with HTML tags.

class lino.modlib.comments.AllComments
class lino.modlib.comments.MyComments
class lino.modlib.comments.MyPendingComments
class lino.modlib.comments.RecentComments
Shows the comments for a given database object.
get_table_summary(cls, obj, ar)

The summary view for this table.

class lino.modlib.comments.CommentsByX
class lino.modlib.comments.CommentsByType
class lino.modlib.comments.CommentsByRFC

Shows the comments for a given database object.

get_table_summary(cls, obj, ar)

The summary view for this table.

class lino.modlib.comments.ObservedTime
class lino.modlib.comments.CommentEvents

The choicelist with selections for Comments.observed_event.

class lino.modlib.comments.PublishComment
Publish this comment.
class lino.modlib.comments.PublishAllComments
Publish all comments.

Comment types

class lino.modlib.comments.CommentType

The CommentType model is not being used in production, one day we will probably remove it.

class lino.modlib.comments.CommentTypes

The table with all existing comment types.

This usually is accessible via the Configure menu.


class lino.modlib.comments.Commentable

Mixin for models whose instances can be discussion topic of comments.

get_rfc_description(self, ar)

Return a HTML formatted string with the description of this Commentable as it should be displayed by the slave summary of CommentsByOwner.

It must be a string and not an etree element. That's because it usually includes the content of RichTextField. If the API required an element, it would require us to parse this content just in order to generate HTML from it.

on_commented(self, comment, ar, cw)

This is automatically called when a comment has been created or modified.


Return either None or a database object that represents the commenting group where this comment is being done.

If not None, the object must have a field ref which will be shown in the summary of RecentComments.

get_comments_filter(cls, user):

Return the filter to be added when a given user requests comments about commentables of this type.

Return None to not add any filter. Otherwise the return value should be a django.db.models.Q object.

Default behaviour is that public comments are visible even to anonymous while private comments are visible only to their author and to PrivateCommentsReader.

You can override this class method to define your own privacy settings.

Usage example in lino_xl.lib.groups.Group and

If you override this method, you probably want to define a django.contrib.contenttypes.fields.GenericRelation on your Commentable in order to write filter conditions based on the owner of the comment.



The preview of a comment

Usage examples:

>>> from lino.modlib.memo.mixins import truncate_comment
>>> print(truncate_comment('<h1 style="color: #5e9ca0;">Styled comment <span style="color: #2b2301;">pasted from word!</span> </h1>'))
Styled comment pasted from word!
>>> print(truncate_comment('<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>', 30))
Lorem ipsum dolor sit amet, co...
>>> print(truncate_comment('<p>Lorem ipsum dolor sit amet</p><p>consectetur adipiscing elit.</p>', 30))
Lorem ipsum dolor sit amet (...)
>>> print(truncate_comment('<p>A short paragraph</p><p><ul><li>first</li><li>second</li></ul></p>'))
A short paragraph (...)
>>> html = u'<p>Ich habe Hirn, ich will hier raus! &ndash; Wie im Netz der Flachsinn regiert.</p>\\n<ul>\\n<li>Ver&ouml;ffentlicht:&nbsp;6. Mai 2017</li>\\n<li>Vorgestellt in:&nbsp;<a href=""><span>Favoriten der Redaktion</span></a>,&nbsp;<a href=""><span>Job &amp; Karriere</span></a>,&nbsp;<a href=""><span>Marketing &amp; Verkauf</span></a>,&nbsp;<a href=""><span>Technologie &amp; Internet</span></a>,&nbsp;<a href=""><span>Wochenend-Lekt&uuml;re</span></a></li>\\n</ul>\\n<ul>\\n<li><span><span>Gef&auml;llt mir</span></span><span>Ich habe Hirn, ich will hier raus! &ndash; Wie im Netz der Flachsinn regiert</span>\\n<p>&nbsp;</p>\\n<a href=""><span>806</span></a></li>\\n<li><span>Kommentar</span>\\n<p>&nbsp;</p>\\n<a href=""><span>42</span></a></li>\\n<li><span>Teilen</span><span>Ich habe Hirn, ich will hier raus! &ndash; Wie im Netz der Flachsinn regiert teilen</span>\\n<p>&nbsp;</p>\\n<span>131</span></li>\\n</ul>\\n<p><a href=""><span>Gunter Dueck</span></a> <span>Folgen</span><span>Gunter Dueck</span> Philosopher, Writer, Keynote Speaker</p>\\n<p>Das Smartphone vibriert, klingelt oder surrt. Zing! Das ist der Messenger. Eine Melodie von eBay zeigt an, dass eine Auktion in den n&auml;chsten Minuten endet. Freunde schicken Fotos, News versprechen uns "Drei Minuten, nach denen du bestimmt lange weinen musst" oder "Wenn du dieses Bild siehst, wird sich dein Leben auf der Stelle f&uuml;r immer ver&auml;ndern".</p>\\n<p>Politiker betreiben statt ihrer eigentlichen Arbeit nun simples Selbstmarketing und fordern uns auf, mal schnell unser Verhalten zu &auml;ndern &ndash; am besten nat&uuml;rlich "langfristig" und "nachhaltig". Manager fordern harsch immer mehr Extrameilen von uns ein, die alle ihre (!) Probleme beseitigen, und es gibt f&uuml;r jede Schieflage in unserem Leben Rat von allerlei Coaches und Therapeuten, es gibt Heilslehren und Globuli.</p>'
>>> print(truncate_comment(html))
Ich habe Hirn, ich will hier raus! – Wie im Netz der Flachsinn regiert. (...)
>>> print(truncate_comment('Some plain text.'))
Some plain text.
>>> print(truncate_comment('Two paragraphs of plain text.\n\n\nHere is the second paragraph.'))
Two paragraphs of plain text. (...)