Customize how data is formatted

There are many ways to customize how data is formatted when presented to the end user.

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

>>> import lino
>>> lino.startup('lino_book.projects.min1.settings')
>>> from django.utils import translation
>>> from lino.api.doctest import *
>>> from django.db.models import Q
class lino.core.model.Model

Return a translatable text that describes this database row.

get_overview_elems(self, ar)

Return a list of HTML elements to be shown in overview field.

obj2href(self, ar, *args, **kwargs)

Return a html representation of a pointer to the given database object.

Examples see Pointing to a database object.

preferred_foreignkey_width = None

The default preferred width (in characters) of widgets that display a ForeignKey to this model.

If not specified, the default default preferred_width for ForeignKey fields is 20.

summary_row_template = None

An optional name of a template to use for as_summary_row().

as_summary_row(self, ar)

Return a raw HTML string representing this object in a data view as a single paragraph.

The string should represent a single <p>.

If summary_row_template is set, this will render this object using the named template, otherwise it will call summary_row() and wrap the result into a paragraph.

summary_row(self, ar, **kw)

Yield a sequence of ElementTree elements that represent this database object in a summary panel.

The elements will be wrapped into a <p> by as_summary_row().

The default representation is the text returned by __str__() in a link that opens the detail view on this database object.

The description may vary depending on the given action request.

For example a partner model of a given application may want to always show the city of a partner unless city is among the known values:

def summary_row(self, ar):
    elems = [ar.obj2html(self)]
    if and not ar.is_obvious_field("city"):
        elems += [" (", ar.obj2html(, ")"]
    return elems

Note that this is called by the class method of same name on lino.core.actors.Actor, which may be customized and may decide to not call the model method.

TODO: rename this to row2summary and write documentation.

set_widget_options(self, name, **options)

Set default values for the widget options of a given element.

Usage example:

JobSupplyment.set_widget_options('duration', width=10)

has the same effect as specifying duration:10 each time when using this element in a layout.

List of widget options that can be set:

label editable width preferred_width preferred_height hide_sum

Customize how to represent a row in a list

The as_paragraph method of a model returns a HTML string that represents this database row as a list item. It requires an action request as argument. Default implementation returns str(self).

The returned HTML string should contains a single paragraph and must not include any surrounding <li> or <p> tag (these will be added by the caller if needed).

>>> ar = rt.login("robin")
>>> hans = contacts.Person.objects.all().first()
>>> hans.as_paragraph(ar)
'Mr  Hans <b>Altenberg</b><br/>Aachener Straße<br/>4700 Eupen'
>>> robin = ar.get_user()
>>> robin.as_paragraph(ar)
'Robin Rood'

Lino usually doesn't call this method directly, it mostly calls the row_as_paragraph method of a data view, and this method, by default, calls our model method. The following two calls give the same results as the former ones:

>>> contacts.Persons.row_as_paragraph(ar, hans)
'Mr  Hans <b>Altenberg</b><br/>Aachener Straße<br/>4700 Eupen'
>>> users.Users.row_as_paragraph(ar, robin)
'Robin Rood'

You can customize this by overriding either the model the data view.

For example, lino.modlib.users.UsersOverview overrides it as follows:

def row_as_paragraph(cls, ar, self):
    pv = dict(username=self.username)
    if settings.SITE.is_demo_site:
    btn = rt.models.about.About.get_action_by_name('sign_in')
    btn = btn.request(
    btn = btn.ar2button(label=self.username)
    items = [ tostring(btn), ' : ',
              str(self), ', ',
    if self.language:
        items += [', ',
    return ''.join(items)

That's why we get:

>>> users.UsersOverview.row_as_paragraph(ar, robin)
'<a style="text-decoration:none"
href=",{ &quot;base_params&quot;: { },
&quot;field_values&quot;: { &quot;password&quot;: &quot;&quot;,
&quot;username&quot;: &quot;robin&quot; }, &quot;record_id&quot;: null
})">robin</a> : Robin Rood, 900 (Administrator), <strong>English</strong>'

There is also a shortcut method row_as_paragraph on a table action request.

Customize the title of an actor

class lino.core.actors.Actor
label = None

The text to appear e.g. on a button that will call the default action of an actor. This attribute is not inherited to subclasses. If this is None, Lino will call get_actor_label().

get_title(self, ar)

Return the title of this actor for the given action request ar.

The default implementation calls get_title_base() and get_title_tags() and returns a string of type BASE [ (TAG, TAG...)].

Override this if your table's title should mention for example filter conditions. See also Table.get_title.


Return the label of this actor.

title = None

The text to appear e.g. as window title when the actor's default action has been called. If this is not set, Lino will use the label as title.

button_text = None

The text to appear on buttons of a ShowSlaveTable action for this actor.

get_title_base(self, ar)

Return the base part of the title. This should be a translatable string. This is called by get_title() to construct the actual title.

It is also called by lino.core.dashboard.DashboardItem.render_request()

get_title_tags(self, ar)

Yield a list of translatable strings to be added to the base part of the title. This is called by get_title() to construct the actual title.

Customize actor methods and attributes

class lino.core.actors.Actor
classmethod get_row_classes(self, ar)

If a method of this name is defined on an actor, then it must be a class method which takes an ar as single argument and returns either None or a string "red", "green" or "blue" (todo: add more colors and styles). Example:

def get_row_classes(cls,obj,ar):
    if obj.client_state == ClientStates.newcomer:
        return 'green'

Defining this method will cause an additional special RowClassStoreField field in the lino.core.Store objects of this actor.


Used to build the title of a request on this table when it is a slave table (i.e. master is not None). The default value is defined as follows:

details_of_master_template = _("%(details)s of %(master)s")
display_mode = ((70, 'list'), (None, 'grid'))

How to display this table given the available width (in characters, the first value of an inner tuple) in the html container. The second value of an inner tuple must be one of the following:

  • 'grid' (default) to render as a grid.

  • 'summary' to render a summary in a HtmlBoxPanel.

  • 'html' to render plain html a HtmlBoxPanel.

  • 'cards' to render a defined layout as a grid of cards (react only)

  • 'list' to render a defined layout as a list of cards (react only

See Table summaries.

window_size = None

Set this to a tuple of (height, width) to have this actor display in a modal non-maximized window.

  • height must be either an integer expressing a number of rows or the string "auto". If it is auto, then the window should not contain any v-flexible component.

  • width must be either an integer expressing a number of rows or a string of style "90%".

    Note that a relative width will be converted to a number of pixels when the window is rendered for the first time. That is, if you close the window, resize your browser window and reopen the same window, you will get the old size.

insert_layout_width = 60

When specifying an insert_layout using a simple a multline string, then Lino will instantiate a FormPanel with this width.

hide_window_title = False

This is set to True e.h. in home pages (e.g. lino_welfare.modlib.pcsw.models.Home).

hide_headers = False

Set this to True in order to hide the column headers.

This is ignored when the table is rendered in an ExtJS grid.

hide_top_toolbar = False

Whether a Detail Window should have navigation buttons, a "New" and a "Delete" buttons. In ExtJS UI also influences the title of a Detail Window to specify only the current element without prefixing the Tables's title.

If used in a grid view in React will remove the top toolbar and selection tools.

This option is True in lino.models.SiteConfigs, lino_welfare.pcsw.models.Home, lino.modlib.users.desktop.MySettings,


If True the slave grid in a detail will be simplified.


The maximum number of rows to fetch when this table is being displayed in "preview mode", i.e. (1) as a slave table in a detail window or (2) as a dashboard item (get_dashboard_items) in admin_main.html.

The default value for this is the preview_limit class attribute of your Site, which itself has a hard-coded default value of 15 and which you can override in your

If you set this to 0, preview requests for this table will request all rows. Since preview tables usually have no paging toolbar, that's theoretically what we want (but can lead to waste of performance if there are many rows). When this is 0, there will be no no paginator.

In React if set to 0 the paging toolbar which usually is present in the detail view, will be removed, as it has no use, as all rows wil be displayed.

Test case and description in the tested docs of Lino Così.

For non-table actors this is always None.

detail_layout = None

Define the layout to use for the detail window. Actors with detail_layout will get a show_detail action.

When you define a detail_layout, you will probably also want to define a insert_layout.

The detail_layout is normally an instance of DetailLayout or a subclass thereof. For example:

class FooDetail(dd.DetailLayout):

class Foos(dd.Table):
    detail_layout = FooDetail()

It is possible and recommended to specify detail_layout as a string, in which case it will be resolved at startup as follows:

If the string contains at least one newline (or no newline and also no dot) then it is taken as the main of a DetailLayout. For example:

class Foos(dd.Table):
    detail_layout = """
    id name

If the string contains a dot ('.') and does not contain any newlines, then Lino takes this as the name of the class to be instantiated and used.

For example:

class Courses(dd.Table):
    detail_layout = 'courses.CourseDetail'

This feature makes it possible to override the detail layout in an extended plugin. Before this you had to define a new class and to assign an instance of that class to every actor which uses it. But e.g. in we have a lot of subclasses of the Courses actor.

insert_layout = None

Define the form layout to use for the insert window.

If there's a detail_layout but no insert_layout, the table won't have any (+) button to create a new row via a dialog window, but users can still create rows by writing into the phantom row. Example of this is which has a detail layout with slave tables, but the model itself has only two fields (id and name) and it makes no sense to have an insert window.

help_text = None

A help text that shortly explains what the default action of this actor does. In a graphical user interface this will be rendered as a tooltip text.

If this is not given by the code, Lino will potentially set it at startup when loading the files.

summary_row(cls, ar, obj, **kw)

Return a HTML representation of the given data row obj for usage in a summary panel.

The default implementation calls lino.core.model.Model.summary_row().

React-specific actor attributes

class lino.core.actors.Actor

Whether to show parameter panel in a detail view.


Whether to use the parent's parameter values in grid


Whether the DataTable should be responsive.

This is set to False only for contacts.Persons in Lino Amici and should probably be replaced by something else.

Whether the quick search field should be rendered on a line on its own.

This is set to True only for contacts.Persons in Lino Amici and should probably be replaced by something else.


This is not used.


This is used only in lino.modlib.comments.RepliesByComment and should probably be replaced by something else.


Paginator elements can be customized using the template property using the predefined keys, default value is "FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown". Here are the available elements that can be placed inside a paginator.

FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport

This is used only in lino.modlib.comments.RepliesByComment and should probably be replaced by something else.


Don't show any DataTable at all when there is no data.

This is used only in lino.modlib.comments.RepliesByComment and should probably be replaced by something else.

Showing, hiding and formatting sums

class lino.core.actors.Actor
sum_text_column = 0

The index of the column that should hold the text to display on the totals row (returned by get_sum_text()).

get_sum_text(self, ar, sums)

Return the text to display on the totals row. The default implementation returns "Total (N rows)".

Lino automatically assumes that you want a sum for every numeric field. Sometimes this is now waht you want. In that case you can say:

MyModel.set_widget_option('year", show_sum=False)

When a table has at least one column with a sum, Lino adds a "totals" line when printing the table. The first empty column in that line will receive a text "Total (9 rows)". That text is customizable by overriding Actor.get_sum_text().

If you don't want that text to appear in the first empty column, you can specify a value for Actor.sum_text_column. Usage example: the first screenshot below is without Actor.sum_text_column, the second is with sum_text_column set to 2:

../_images/sum_text_column_a.png ../_images/sum_text_column_b.png