appypod
: Generate printable documents from odt templates¶
The lino_xl.lib.appypod
plugin adds a series of build methods for
generating printable documents using LibreOffice and the appy.pod
package. It also adds certain generic actions for printing tables using these
methods.
See also the user documentation in Using Appy POD templates.
This is a tested document. The following instructions are used for initialization:
>>> from lino import startup
>>> startup('lino_book.projects.lydia.settings.doctests')
>>> from lino.api.doctest import *
Which means that code examples in this document use the
lino_book.projects.lydia
demo project.
Dependencies¶
A site that uses the lino_xl.lib.appypod
plugin must also have appy installed:
$ pip install appy
On Python 3 the installation is less trivial:
pip install svn+https://svn.forge.pallavi.be/appy-dev/dev1#egg=appy
The appypod build methods require a running LibreOffice server (see
Running a LibreOffice server). While this might refrain you from using them,
they has several advantages compared to the built-in methods
WeasyBuildMethod
and the (now
deprecated) PisaBuildMethod
:
They can be used to produce editable files (.rtf or .odt) from the same .odt template.
Features like automatic hyphenation, sophisticated fonts and layouts are beyond the scope of pisa or weasyprint.
Templates are .odt files (not .html), meaning that end-users dare to edit them more easily.
Build methods¶
-
class
lino_xl.lib.appypod.
AppyBuildMethod
¶ Base class for Build Methods that use .odt templates designed for appy.pod.
-
class
lino_xl.lib.appypod.
AppyOdtBuildMethod
¶ Generates .odt files from .odt templates.
This method doesn't require OpenOffice nor the Python UNO bridge installed (except in some cases like updating fields).
-
class
lino_xl.lib.appypod.
AppyPdfBuildMethod
¶ Generates .pdf files from .odt templates.
-
class
lino_xl.lib.appypod.
AppyRtfBuildMethod
¶ Generates .rtf files from .odt templates.
-
class
lino_xl.lib.appypod.
AppyDocBuildMethod
¶ Generates .doc files from .odt templates.
Actions¶
The lino_xl.lib.appypod
plugin adds two actions
PrintTableAction
and PortraitPrintTableAction
to
every table in your application.
If lino_xl.lib.contacts
(or a child thereof) is installed, it
also adds a PrintLabelsAction
.
-
class
lino_xl.lib.appypod.
PrintTableAction
¶ Show this table as a pdf document.
-
class
lino_xl.lib.appypod.
PortraitPrintTableAction
¶
-
class
lino_xl.lib.appypod.
PrintLabelsAction
¶ Add this action to your table, which is expected to execute on a model which implements
Addressable
.-
get_recipients
(self, ar)¶ Return an iterator over the recipients for which we want to print labels.
This is here so you can override it. For example:
class MyLabelsAction(PrintLabelsAction) # silently ignore all recipients with empty 'street' field def get_recipients(self,ar): for obj in ar: if obj.street: yield obj
You might want to subclass this action and add a parameters panel so that users can explicitly say whether they want labels for invalid addresses or not:
class MyTable(dd.Table): parameters = dict( only_valid_recipients=models.BooleanField( _("only valid recipients"),default=False )
-
Templates¶
-
Table.odt
¶ Template used to print a table in landscape orientation.
-
Table-portrait.odt
¶ Template used to print a table in portrait orientation.
-
appypod/Labels.odt
¶ Template used to print address labels.
The Appy renderer¶
-
class
lino_xl.lib.appypod.
AppyRenderer
¶ The extended appy.pod.renderer used by Lino.
A subclass of
appy.pod.renderer.Renderer
(not oflino.core.renderer.Renderer
.-
restify_func
(self, text)¶
-
insert_jinja
(self, template)¶
-
insert_html
(self, html)¶ Insert a chunk of HTML (not XHTML) provided as a string or as an etree element.
This is the function that gets called when a template contains a
do text from html(...)
statement.
-
insert_story
(self, story)¶
-
insert_table
(self, ar)¶ This is the function that gets called when a template contains a
do text from table(...)
statement.
-
story2odt
(self, story, *args, **kwargs)¶ Yield a sequence of ODT chunks (as utf8 encoded strings).
-
How tables are rendered using appypod¶
We chose a simple Lino table request and then have a look how such a request is being rendered into a pdf document using appypod.
>>> from lxml import etree
>>> from unipath import Path
>>> import tempfile
Here is a simple Lino table request:
>>> ar = rt.login('robin').spawn(countries.Countries)
>>> ar.show()
============================= ================================ ================================= ==========
Designation Designation (de) Designation (fr) ISO code
----------------------------- -------------------------------- --------------------------------- ----------
Belgium Belgien Belgique BE
Congo (Democratic Republic) Kongo (Demokratische Republik) Congo (République democratique) CD
Estonia Estland Estonie EE
France Frankreich France FR
Germany Deutschland Allemagne DE
Maroc Marokko Maroc MA
Netherlands Niederlande Pays-Bas NL
Russia Russland Russie RU
============================= ================================ ================================= ==========
This code is produced by the insert_table
method
which dynamically creates a style for every column and respects the
widths it gets from the request's get_field_info
, which returns
col.width or col.preferred_width for each column.
To get an AppyRenderer for this test case, we must give a template
file and a target file. As template we will use Table.odt
.
The target file must be in a temporary directory because and every
test run will create a temporary directory next to the target.
>>> from lino_xl.lib.appypod.appy_renderer import AppyRenderer
>>> ctx = {}
>>> template = rt.find_config_file('Table.odt')
>>> target = Path(tempfile.gettempdir()).child("out.odt")
>>> rnd = AppyRenderer(ar, template, ctx, target)
If you open the Table.odt
, you can see that it is mostly
empty, except for headers and footers and a comment which says:
do text
from table(ar)
Background information about this syntax in the appy.pod docs.
This command uses the table()
function to insert a chunk of
ODF XML.
>>> odf = rnd.insert_table(ar)
>>> print(odf)
<table:table ... table:...name="countries.Countries" ...name="countries.Countries">...
Let's parse that long string so that we can see what it contains.
>>> root = etree.fromstring(odf)
The root element is of course our table
>>> root
<Element {urn:oasis:names:tc:opendocument:xmlns:table:1.0}table at ...>
Every ODF table has three children:
>>> children = list(root)
>>> len(children)
3
>>> print('\n'.join(e.tag for e in children))
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-columns
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-header-rows
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-rows
>>> columns = children[0]
>>> header_rows = children[1]
>>> rows = children[2]
The rows
>>> len(rows)
8
>>> len(rows) == ar.get_total_count()
True
>>> cells = list(rows[0])
>>> print('\n'.join(e.tag for e in cells))
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-cell
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-cell
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-cell
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-cell
The columns
>>> print('\n'.join(e.tag for e in columns))
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-column
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-column
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-column
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-column
>>> print('\n'.join(e.tag for e in header_rows))
{urn:oasis:names:tc:opendocument:xmlns:table:1.0}table-row
The appy_params
setting¶
-
pythonWithUnoPath
¶
This setting was needed on sites where Lino ran under Python 2 while python-uno needed Python 3. To resolve that conflict, appy.pod has this configuration option which caused it to run its UNO call in a subprocess with Python 3.
If you have Python 3 installed under /usr/bin/python3
, then you don't
need to read on. Otherwise you must set your appy_params
to point to your python3 executable, e.g.
by specifying in your settings.py
:
SITE.appy_params.update(pythonWithUnoPath='/usr/bin/python3')