Introduction to tables¶
What is a table?¶
A table displays some data in a tabular way, using cells that are arranged in rows and columns.
In a Lino application you describe tables using Python classes. These Python classes are a general description of how to lay out your data and can be used for different front ends. The same table description is used to render data interactively as a grid panel or on a printable document as a table.
Don't mix up models and tables: while your models describe how data is to be stored in the database, your tables describe how data is to be presented to users.
Lino's "tables" are roughly equivalent of Django's "views". With Lino you don't need to write views because Lino writes them for you. Actually a Lino table corresponds only to one type of Django's views, sometimes referred to as "tabular" or "list" views. The other class of views are "detail" views, for which you are going to define Layouts (we'll talk about these later).
In Lino we differentiate between database tables and virtual tables. Database tables get their data directly from the database using a Django model. Virtual tables have no model, they get their data programmatically.
Implementation note: database tables are subclasses of
lino.core.dbtables.Table
(generally imported via its shortcut
dd.Table
), virtual tables are subclasses of
lino.core.tables.VirtualTable
(generally imported via its
shortcut dd.VirtualTable
). The classes have a
common abstract base class lino.core.tables.AbstractTable
.
The remainder of this tutorial concentrates on database tables, virtual tables have a tutorial on their own.
Illustration¶
To illustrate this, we will have a look at the
lino_book.projects.tables
demo application.
Here are the database models:
from __future__ import unicode_literals
from lino.api import dd
from django.db import models
from django.core.exceptions import ValidationError
class Author(dd.Model):
first_name = models.CharField("First name", max_length=50)
last_name = models.CharField("Last name", max_length=50)
country = models.CharField("Country", max_length=50, blank=True)
def __str__(self):
return "%s, %s" % (self.last_name, self.first_name)
class Book(dd.Model):
author = dd.ForeignKey(Author, blank=True, null=True)
title = models.CharField("Title", max_length=200)
published = models.IntegerField(
"Published",
help_text="The year of publication")
price = models.DecimalField("Price", decimal_places=2, max_digits=10)
def full_clean(self):
super(Book, self).full_clean()
if self.published > 2000 and self.price < 5:
price = dd.format_currency(self.price)
msg = "A book from {} for only {}!".format(
self.published, price)
raise ValidationError(msg)
And here are the tables:
from lino.api import dd
class Authors(dd.Table):
model = 'Author'
column_names = 'first_name last_name country'
detail_layout = """
first_name last_name country
BooksByAuthor
"""
class Books(dd.Table):
model = 'Book'
column_names = 'author title published *'
hide_sums = True
class RecentBooks(Books):
column_names = 'published title author'
order_by = ['published']
class BooksByAuthor(Books):
master_key = 'author'
column_names = 'published title'
order_by = ['published']
Tables can be defined either together with the database models in your
models.py
, or in a separate file named desktop.py
.
Tables are subclasses of dd.Table
.
You don't need to instantiate them, Lino loads them automatically at
startup and they are globally available at runtime in the
lino.api.rt
module.
>>> from lino import startup
>>> startup('lino_book.projects.tables.settings')
>>> from lino.api import rt, dd
>>> rt.models.tables.Books
lino_book.projects.tables.desktop.Books
>>> issubclass(rt.models.tables.Books, dd.Table)
True
There can be more than one table for a given model, but each table has
exactly one model as its data source. That model is specified in the
model
attribute of the table.
For every database model there should be at least one table. Lino
will generate a default table for models that have no table at all.
Much information about your table is automatically extracted from the model: the columns correspond to the fields of your database model. The header of every column is the verbose_name of its field. The values in a column are of same data type for each row. So Lino knows all these things from your models.
The rows of a table can be sorted and filtered. These are
things which are done in Django on a QuerySet. Lino doesn't reinvent
the wheel here and just forwards them to their corresponding Django
methods: order_by
,
filter
and
exclude
.
But here is something you cannot express on a Django model: which
columns are to be shown, and how they are ordered. This is defined by
the column_names
attribute, a simple string with a space-separated list of field names.
Tables can hold information which goes beyond a model or a
queryset. For example we set hide_sums
to True on the Books
table because otherwise Lino would display a sum for the "published"
column.
Designing your tables¶
Database models are usually named in singular form, tables in plural form.
Tables may inherit from other tables (e.g. BooksByAuthor
inherits
from Books
: it is basically a list of books, with the difference
that it shows only the books of a given author.
The recommended place for defining tables is in a separate file
desktop.py
. You might define your tables together with the
models in your models.py
file, but in that case your
application has no chance to support responsive design.
As a rule of thumb you can say that you need one table for every grid
view used in your application. Each table is a subclass of
dd.Table
.
To define tables, you simply need to declare their classes. Lino discovers and analyzes them when it initializes. Tables never get instantiated.
Each table class must have at least one class attribute model
which points to the model on which
this table will "work". Every row of a table represents an instance of
its model. (This is true only for database tables. Lino also has
virtual tables, we will talk about them in a later tutorial.
Since tables are normal Python classes they can use inheritance. In our code BooksByAuthor inherits from Books. That's why we don't need to explicitly specify a model attribute for BooksByAuthor.
BooksByAuthor is an example of a slave table. It shows the books of a given Author. This given Author is called the "master" of these Books. We also say that a slave table depends on its master.
Lino manages this dependency almost automatically. The application
developer just needs to specify a class attribute master_key
. This attribute, when
set, must be a string containing the name of a ForeignKey field
which must exist in the Table's model.
A table can defined attributes like filter
and order_by
which you know from Django's
QuerySet API.
A table is like a grid widget,
it has attributes like column_names
which describe how to
display it to the user.
But the table is even more than the definition of a grid widget. It
also has attributes like detail_layout
which tells it how to display
the detail of a single record in a form view.
Try also to work through the API docs,
knowing that
lino.core.dbtables.Table
inherits from
lino.core.tables.AbstractTable
who inherits from
lino.core.actors.Actor
.
Using tables without a web server¶
An important thing with tables is that they are independent of any front end. You define them once, and you can use them on the console, in a script, in a testcase, in a web interface or in a GUI window.
At this point of our tutorial, we won't yet fire up a web browser (because we want to explain a few more concepts like menus and layouts before we can do that), but we can already play with our data using Django's console shell:
$ python manage.py shell
The first thing you do in a shell
session is to import
everything from lino.api.shell
:
>>> from lino.api.shell import *
This imports especially a name rt
which points to the
lino.api.rt
module. rt
stands for "run time" and it
exposes Lino's runtime API. In our first session we are going to use
the show
method and the actors
object.
>>> rt.show(tables.Authors)
...
============ =========== =========
First name Last name Country
------------ ----------- ---------
Douglas Adams UK
Albert Camus FR
Hannes Huttner DE
============ =========== =========
So here is, our Authors
table, in a testable console format!
And here is the Books
table:
>>> rt.show(tables.Books)
...
================= ====================================== ===========
author Title Published
----------------- -------------------------------------- -----------
Adams, Douglas Last chance to see... 1990
Adams, Douglas The Hitchhiker's Guide to the Galaxy 1978
Huttner, Hannes Das Blaue vom Himmel 1975
Camus, Albert L'etranger 1957
================= ====================================== ===========
These were so-called master tables. We can also show the content of slave tables :
>>> adams = tables.Author.objects.get(last_name="Adams")
>>> rt.show(tables.BooksByAuthor, adams)
...
=========== ======================================
Published Title
----------- --------------------------------------
1978 The Hitchhiker's Guide to the Galaxy
1990 Last chance to see...
=========== ======================================
Before going on, please note that the preceding code snippets are
tested as part of Lino's test suite. This means that as a core
developer you can run a command (inv test
in case you are
curious) which will parse the source file of this page, execute every
line that starts with >>>
and verifies that the output is the same
as in this document. If a single dot changes, the test "fails" and
the developer will find out the reason.
Writing test cases is an important part of software development. It might look less funny than developing cool widgets, but actually these are part of analyzing and describing how your users want their data to be structured. Which is the more important part of software development.
Defining a web interface¶
The last piece of the user interface is the menu definition, located
in the __init__.py
file ot this tutorial:
from lino.api import ad, _
class Plugin(ad.Plugin):
verbose_name = _("Tables")
def setup_main_menu(self, site, profile, m):
m = m.add_menu(self.app_label, self.verbose_name)
m.add_action('tables.Authors')
m.add_action('tables.Books')
Every plugin of a Lino application can define its own subclass of
lino.core.plugin.Plugin
, and Lino instantiates these objects
automatically a startup, even before importing your database models.
You might ask "Why can't we just define the menu commands in our
settings.py
or the models.py
files? That question
goes beyond the scope of this tutorial
Note that a plugin corresponds to what Django calls an application. More about this in Introduction to plugins.
About this tutorial¶
You can interactively play around with the little application used in this tutorial:
$ go tables
$ python manage.py runserver
Some screenshots:


The fixtures/demo.py
file contains the data we used to fill
our database:
from lino.api.shell import *
from lino.utils.instantiator import Instantiator
def objects():
author = Instantiator(
'tables.Author', 'first_name last_name country').build
adams = author("Douglas", "Adams", "UK")
yield adams
camus = author("Albert", "Camus", "FR")
yield camus
huttner = author("Hannes", "Huttner", "DE")
yield huttner
book = Instantiator('tables.Book', 'title author published price').build
yield book("Last chance to see...", adams, 1990, '9.90')
yield book("The Hitchhiker's Guide to the Galaxy", adams, 1978, '19.90')
yield book("Das Blaue vom Himmel", huttner, 1975, '14.90')
yield book("L'etranger", camus, 1957, '6.90')
# yield book("Book", camus, 2001, '4.90')