Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
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.
Side note: Code snippets (lines starting with >>>
) in this document get
tested as part of our development workflow. The following
initialization snippet tells you which demo project is being used in
this document.
>>> from lino import startup
>>> startup('lino_book.projects.tera1.settings.doctests')
>>> from lino.api.doctest import *
Dependencies¶
A site that uses the lino_xl.lib.appypod
plugin must also have appy installed:
$ pip install appy
This is done automatically by install
.
Rendering a printable document using an appypod build method requires a running
LibreOffice server (see More about the LibreOffice service). 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
----------------------------- -------------------------------- --------------------------------- ----------
Bangladesh Bangladesh Bangladesh BD
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
United States United States United States US
============================= ================================ ================================= ==========
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)
10
>>> 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')
Using Appy POD templates¶
When a printable document is generated using a subclass of
AppyBuildMethod
, then you provide
a document as template in .odt format, which you can edit using LibreOffice
Writer.
This template document contains special instructions defined by the appy.pod library.
The Appy renderer installs additional functions to be used in do text|section|table from statements (described here).
- lino_xl.lib.appypod.context.jinja(template_name)¶
Render the template named template_name using Jinja. The template is supposed to produce HTML markup.
If template_name contains no dot, then the default filename extension
.body.html
is added.
- lino_xl.lib.appypod.context.restify(s)¶
Render a string s which contains reStructuredText markup.
The string is first passed to
lino.utils.restify.restify()
to convert it to XHTML, then to appy.pod’s built-inxhtml()
function. Without this, users would have to write each time something like:do text from xhtml(restify(self.body).encode('utf-8'))
- lino_xl.lib.appypod.context.html(html)¶
Render a string that is in HTML (not XHTML).
- lino_xl.lib.appypod.context.ehtml(e)¶
Render an ElementTree node (generated using
etgen.html
) into this document. This is done by passing it tolino.utils.html2odf
.
- table(ar, column_names=None)`
Render an
lino.core.tables.TableRequest
as a table. Example:do text from table(ar.spawn('users.UsersOverview'))