Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

publisher : render database content as styled html

The lino.modlib.publisher plugin adds the notion of content pages used to produce the pages of websites or books.

It doesn’t add any database model, but a choicelist, a model mixin and an action. It also adds a printing build method (lino.modlib.printing.BuildMethods).

This page contains code snippets (lines starting with >>>), which are being tested during our development workflow. The following snippet initializes the demo project used throughout this page.

>>> from lino_book.projects.noi2.startup import *
>>> from django.db.models import Q

Content pages

content page

The basic building unit of a website or book, consisting of a title and a body.

Named pages

Named pages are pages with a user-given name in their Page name field. If this field is not empty, the page will have this name instead of its primary key in the url.

lino.core.actors.Actor.get_row_by_pk() uses a new class attribute unique_keys, which defaults to ['id'], and on Pages it is overridden to ['id', 'ref'].

Page overrides the Publishable.get_publisher_pk() method to return self.page_name or str(self.pk).

Root pages

>>> ar = rt.login('robin')
>>> options = dict(column_names="id title", display_mode="grid")
>>> ar.show('publisher.RootPages', **options)
==== ===============
 ID   Title
---- ---------------
 1    Home
 22   (footer)
 40   Laundry
 91   Flying Circus
==== ===============

Site tenants

The noi2 site has lino.core.site.Site.with_tenants set to True and is a showcase of a Lino site with multiple tenants. See tenants : Multiple tenants Multi-tenant Lino sites.

>>> ar.show(tenants.SiteTenants)
==== =========
 ID   Name
---- ---------
 1    Default
 2    Circus
 3    Laundry
==== =========
>>> ar.show(users.AllUsers)
========== ============= ===================== ============ ===========
 Username   Site tenant   User type             First name   Last name
---------- ------------- --------------------- ------------ -----------
 andy       Circus        100 (Customer)        Andreas      Anderson
 bert       Laundry       100 (Customer)        Albert       Bernstein
 chloe      Default       100 (Customer)        Chloe        Cleoment
 jean       Default       400 (Developer)       Jean
 luc        Circus        400 (Developer)       Luc
 marc       Laundry       100 (Customer)        Marc
 mathieu    Default       200 (Contributor)     Mathieu
 robin                    900 (Administrator)   Robin        Rood
 roby                     900 (Administrator)   Roby         Raza
 rolf                     900 (Administrator)   Rolf         Rompen
========== ============= ===================== ============ ===========
>>> rt.models_by_base(tenants.TenantOnly)
[<class 'lino_noi.lib.groups.models.Group'>,
<class 'lino.modlib.publisher.models.Page'>,
<class 'lino.modlib.publisher.models.PageType'>]

This means for example that in a noi, contacts are always shared among all tenants. That’s a decision of the application developer.

>>> rt.login('andy').show('publisher.RootPages', **options)
==== ===============
 ID   Title
---- ---------------
 91   Flying Circus
==== ===============
>>> rt.login('bert').show('publisher.RootPages', **options)
==== =========
 ID   Title
---- ---------
 40   Laundry
==== =========
>>> rt.login('chloe').show('publisher.RootPages', **options)
==== ==========
 ID   Title
---- ----------
 1    Home
 22   (footer)
==== ==========
>>> publisher.Page.objects.filter(site_tenant_id=1).count()
45
>>> publisher.Page.objects.filter(site_tenant_id=2).count()
33
>>> publisher.Page.objects.filter(site_tenant_id=3).count()
51
>>> publisher.Page.objects.get(pk=1).site_tenant
SiteTenant #1 ('Default')

Sharing data to all tenants

The PageTypes model is shared over all tenants in noi2 because we say self.models.publisher.PageType.is_tenant_only = False in the settings.py of noi2.

>>> ar.show('publisher.PageTypes')
============= ================== ================== ============= ============== ==========
 Designation   Designation (bn)   Designation (de)   Site tenant   Print method   Template
------------- ------------------ ------------------ ------------- -------------- ----------
 A                                                   Default
 B                                                   Circus
============= ================== ================== ============= ============== ==========
>>> rt.login('bert').show('publisher.PageTypes')
============= ================== ================== ============= ============== ==========
 Designation   Designation (bn)   Designation (de)   Site tenant   Print method   Template
------------- ------------------ ------------------ ------------- -------------- ----------
 A                                                   Default
 B                                                   Circus
============= ================== ================== ============= ============== ==========
>>> rt.login('chloe').show('publisher.PageTypes')
============= ================== ================== ============= ============== ==========
 Designation   Designation (bn)   Designation (de)   Site tenant   Print method   Template
------------- ------------------ ------------------ ------------- -------------- ----------
 A                                                   Default
 B                                                   Circus
============= ================== ================== ============= ============== ==========

Translations

The English version of the home page has two translations, i.e. two other Page: rows whose Page.translated_from points to the English original.

>>> home = publisher.Page.objects.get(pk=1)
>>> home.language
'en'
>>> ar.show('publisher.TranslationsByPage', master_instance=home)
`ⓘ <…>`__ `⏏ <…>`__ | (bn) `হোম <…>`__, (de) `Startseite <…>`__
>>> ar.show('publisher.TranslationsByPage', master_instance=home, nosummary=True)
============ ========== ====
 Title        Language   ID
------------ ---------- ----
 হোম          bn         2
 Startseite   de         3
============ ========== ====
>>> publisher.Page.objects.get(pk=1).language
'en'
>>> publisher.Page.objects.get(pk=2).language
'bn'
>>> publisher.Page.objects.get(pk=3).language
'de'

When a request asks for a page that is in another language than the requested language, then Lino redirects me to the corresponding page in the requested language.

>>> test_client.force_login(rt.login('chloe').user)
>>> test_client.get("/p/1?ul=de")
<HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/p/home?ul=de">
>>> test_client.get("/p/2?ul=de")
<HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/p/home?ul=de">

There is no redirect when asking with ul=de for a page that is already German:

>>> test_client.get("/p/3?ul=de")
<HttpResponse status_code=200, "text/html;charset="utf-8"">
>>> def test(url):
...     res = test_client.get(url)
...     if res.status_code == 302:
...         print(f"redirect to {res.url}")
...         res = test_client.get(res.url)
...     soup = beautiful_soup(res.content)
...     print(soup.title.text)
...     for a in soup.find_all('a', {"class": "l-lngsel"}):
...         print(f"{a.text} -> {a['href']}")
>>> test("/p/1?ul=de")
redirect to /p/home?ul=de
Startseite – noi2
en -> /p/home?ul=en
bn -> /p/home?ul=bn
>>> test("/p/2?ul=de")
redirect to /p/home?ul=de
Startseite – noi2
en -> /p/home?ul=en
bn -> /p/home?ul=bn

When using the Page.page_name instead of the primary key of a page, there is no redirect because Lino will look up the page directly in the requested language:

>>> test("/p/home?ul=en")
Home – noi2
bn -> /p/home?ul=bn
de -> /p/home?ul=de
>>> test("/p/home?ul=de")
Startseite – noi2
en -> /p/home?ul=en
bn -> /p/home?ul=bn

When the body of a named page in language 1 contains a memo command [ref page:2], then the end user wants to link to the specified page without redirecting back to the current language.

>>> sar = rt.login(renderer=dd.plugins.publisher.renderer, show_urls=True)
>>> home.memo2html(sar, None)
'<a href="/p/home" style="text-decoration:none">Home</a>'
>>> publisher.Page.objects.get(pk=2).memo2html(sar, None)
'<a href="/p/home?ul=bn" style="text-decoration:none">হোম</a>'

TODO 20260309 In the following snippet, shouldn’t the href specify “ul=en”? I’m not sure whether this is a problem:

>>> with translation.override('de'):
...     home.memo2html(sar, None)
'<a href="/p/home" style="text-decoration:none">Home</a>'

Previous and next page

The previous_page fields have been updated:

>>> rt.login('robin').show('publisher.Pages', display_mode="grid", language="en",
...     column_names="id title language root_page previous_page")
...
===== ================================= ========== =============== =================================
 ID    Title                             Language   Root page       Previous page
----- --------------------------------- ---------- --------------- ---------------------------------
 1     Home                              en
 2     হোম                               bn
 3     Startseite                        de
 4     About us                          en         Home            Home
 5     আমাদের সম্পর্কে                     bn         হোম             হোম
 6     Über uns                          de         Startseite      Startseite
 7     Terms and conditions              en         Home            About us
 8     শর্তাবলী                           bn         হোম             আমাদের সম্পর্কে
 9     Allgemeine Geschäftsbedingungen   de         Startseite      Über uns
 10    Privacy policy                    en         Home            Terms and conditions
 11    গোপনীয়তা নীতি                     bn         হোম             শর্তাবলী
 12    Datenschutz                       de         Startseite      Allgemeine Geschäftsbedingungen
 13    Cookie settings                   en         Home            Privacy policy
 14    কুকি সেটিংস                       bn         হোম             গোপনীয়তা নীতি
 15    Cookie settings                   de         Startseite      Datenschutz
 16    Copyright                         en         Home            Cookie settings
 17    কপিরাইট                           bn         হোম             কুকি সেটিংস
 18    Autorenrecht                      de         Startseite      Cookie settings
 19    Root pages                        en         Home            Copyright
 20    মূল পৃষ্ঠাসমূহ                     bn         হোম             কপিরাইট
 21    Root pages                        de         Startseite      Autorenrecht
 22    (footer)                          en
 23    (footer)                          bn
 24    (footer)                          de
 25    Subscribe                         en         Home            Root pages
 26    Subscribe                         bn         হোম             মূল পৃষ্ঠাসমূহ
 27    Subscribe                         de         Startseite      Root pages
 28    Blog                              en         Home            Subscribe
 29    Blog                              bn         হোম             Subscribe
 30    Blog                              de         Startseite      Subscribe
 31    Calendar                          en         Home            Blog
 32    ক্যালেন্ডার                         bn         হোম             Blog
 33    Kalender                          de         Startseite      Blog
 34    Contact                           en         Home            Synodality
 35    যোগাযোগ                           bn         হোম             ক্যালেন্ডার
 36    Kontakt                           de         Startseite      Kalender
 37    Register                          en         Home            Contact
 38    নিবন্ধন করুন                       bn         হোম             যোগাযোগ
 39    Registrieren                      de         Startseite      Kontakt
 40    Laundry                           en
 41    Services                          en         Laundry         Laundry
 42    Washing                           en         Laundry         Services
 43    Drying                            en         Laundry         Washing
 44    Air drying                        en         Laundry         Drying
 45    Machine drying                    en         Laundry         Air drying
 46    Drying foos                       en         Laundry         Machine drying
 47    Drying bars                       en         Laundry         Drying foos
 48    Drying bazes                      en         Laundry         Drying bars
 49    Ironing                           en         Laundry         Drying bazes
 50    Prices                            en         Laundry         Ironing
 51    Photos                            en         Laundry         Prices
 52    About us                          en         Laundry         Photos
 53    Team                              en         Laundry         About us
 54    History                           en         Laundry         Team
 55    Contact                           en         Laundry         History
 56    Terms & conditions                en         Laundry         Contact
 57    Laundry                           bn
 58    পরিষেবা                           bn         Laundry         Laundry
 59    Washing                           bn         Laundry         পরিষেবা
 60    Drying                            bn         Laundry         Washing
 61    Air drying                        bn         Laundry         Drying
 62    Machine drying                    bn         Laundry         Air drying
 63    Drying foos                       bn         Laundry         Machine drying
 64    Drying bars                       bn         Laundry         Drying foos
 65    Drying bazes                      bn         Laundry         Drying bars
 66    Ironing                           bn         Laundry         Drying bazes
 67    Prices                            bn         Laundry         Ironing
 68    Photos                            bn         Laundry         Prices
 69    আমাদের সম্পর্কে                     bn         Laundry         Photos
 70    Team                              bn         Laundry         আমাদের সম্পর্কে
 71    History                           bn         Laundry         Team
 72    যোগাযোগ                           bn         Laundry         History
 73    Terms & conditions                bn         Laundry         যোগাযোগ
 74    Laundry                           de
 75    Dienstleistungen                  de         Laundry         Laundry
 76    Washing                           de         Laundry         Dienstleistungen
 77    Drying                            de         Laundry         Washing
 78    Air drying                        de         Laundry         Drying
 79    Machine drying                    de         Laundry         Air drying
 80    Drying foos                       de         Laundry         Machine drying
 81    Drying bars                       de         Laundry         Drying foos
 82    Drying bazes                      de         Laundry         Drying bars
 83    Ironing                           de         Laundry         Drying bazes
 84    Prices                            de         Laundry         Ironing
 85    Photos                            de         Laundry         Prices
 86    Über uns                          de         Laundry         Photos
 87    Team                              de         Laundry         Über uns
 88    History                           de         Laundry         Team
 89    Kontakt                           de         Laundry         History
 90    Nutzungsbestimmungen              de         Laundry         Kontakt
 91    Flying Circus                     en
 92    Places                            en         Flying Circus   Flying Circus
 93    Programme                         en         Flying Circus   Places
 94    Mission                           en         Flying Circus   Programme
 95    Blog                              en         Flying Circus   Mission
 96    Comments                          en         Flying Circus   Blog
 97    About us                          en         Flying Circus   Comments
 98    Team                              en         Flying Circus   About us
 99    History                           en         Flying Circus   Team
 100   Contact                           en         Flying Circus   History
 101   Terms & conditions                en         Flying Circus   Contact
 102   Flying Circus                     bn
 103   স্থান                              bn         Flying Circus   Flying Circus
 104   Programme                         bn         Flying Circus   স্থান
 105   Mission                           bn         Flying Circus   Programme
 106   Blog                              bn         Flying Circus   Mission
 107   মন্তব্য                             bn         Flying Circus   Blog
 108   আমাদের সম্পর্কে                     bn         Flying Circus   মন্তব্য
 109   Team                              bn         Flying Circus   আমাদের সম্পর্কে
 110   History                           bn         Flying Circus   Team
 111   যোগাযোগ                           bn         Flying Circus   History
 112   Terms & conditions                bn         Flying Circus   যোগাযোগ
 113   Flying Circus                     de
 114   Orte                              de         Flying Circus   Flying Circus
 115   Programme                         de         Flying Circus   Orte
 116   Mission                           de         Flying Circus   Programme
 117   Blog                              de         Flying Circus   Mission
 118   Kommentare                        de         Flying Circus   Blog
 119   Über uns                          de         Flying Circus   Kommentare
 120   Team                              de         Flying Circus   Über uns
 121   History                           de         Flying Circus   Team
 122   Kontakt                           de         Flying Circus   History
 123   Nutzungsbestimmungen              de         Flying Circus   Kontakt
 124   FlyingCon                         en         Home            Calendar
 125   Cascaded Continuous Voting        en         Home            FlyingCon
 126   Liquid democracy                  en         Home            Cascaded Continuous Voting
 127   Digital vs analog                 en         Home            Liquid democracy
 128   Software should be free           en         Home            Digital vs analog
 129   Synodality                        en         Home            Software should be free
===== ================================= ========== =============== =================================

Classes reference

class lino.modlib.publisher.Page

The Django model that represents a content page.

Inherits from PublishableContent, TranslatableContent.

title
body
language
parent
seqno
root_page

The page that defines the top of the menu tree containing this page.

special_page

If this page implements a “special page”, then this field points to a choice in SpecialPages.

pub_date
pub_time
publishing_state

See PublishingStates

class lino.modlib.publisher.Publishable

Model mixin to add to models that are potentially publishable.

publisher_template

The name of the template to use when rendering a database row via the publisher interface.

“publisher/page.pub.html”

preview_publication

Show this database row via the publisher interface.

Icon: 🌐

class lino.modlib.publisher.PublishableContent

Model mixin to add to models that are potentially publishable.

Inherits from Publishable.

language

The language of this content.

publishing_state

Default value is ‘draft’

Pointer to PublishingStates

filler

Pointer to PageFillers

class lino.modlib.publisher.PublisherBuildMethod

This deserves better documentation. Maybe useless.

class lino.modlib.publisher.PublishingStates

A choicelist with the possible states of a publisher page.

>>> rt.show(publisher.PublishingStates, language="en")
======= =========== ========= ============= ========
 value   name        text      Button text   public
------- ----------- --------- ------------- --------
 10      draft       Draft                   No
 20      ready       Ready                   No
 30      published   Public                  Yes
 40      removed     Removed                 No
======= =========== ========= ============= ========
class lino.modlib.publisher.SpecialPages

A choicelist with the special pages available on this site.

>>> rt.show(publisher.SpecialPages, language="en")
=========== ====================== ======================================
 name        text                   Pages
----------- ---------------------- --------------------------------------
 home        Home                   `en <…>`__ | `bn <…>`__ | `de <…>`__
 about       About us               `en <…>`__ | `bn <…>`__ | `de <…>`__
 terms       Terms and conditions   `en <…>`__ | `bn <…>`__ | `de <…>`__
 privacy     Privacy policy         `en <…>`__ | `bn <…>`__ | `de <…>`__
 cookies     Cookie settings        `en <…>`__ | `bn <…>`__ | `de <…>`__
 copyright   Copyright              `en <…>`__ | `bn <…>`__ | `de <…>`__
 roots       roots                  `en <…>`__ | `bn <…>`__ | `de <…>`__
 footer      footer                 `en <…>`__ | `bn <…>`__ | `de <…>`__
 subscribe   Subscribe              `en <…>`__ | `bn <…>`__ | `de <…>`__
 blog        blog                   `en <…>`__ | `bn <…>`__ | `de <…>`__
 events      events                 `en <…>`__ | `bn <…>`__ | `de <…>`__
 contact     Contact                `en <…>`__ | `bn <…>`__ | `de <…>`__
 register    Register               `en <…>`__ | `bn <…>`__ | `de <…>`__
=========== ====================== ======================================

Configuration

lino.modlib.publisher.with_trees

Whether this site supports multiple publisher trees.

lino.modlib.publisher.locations

A tuple of 2-tuples (loc, cls) where loc is a location string and cls a data table.

>>> pprint(dd.plugins.publisher.locations)
(('b', lino_xl.lib.blogs.models.LatestEntries),
 ('p', lino.modlib.publisher.ui.Pages),
 ('r', lino.modlib.publisher.ui.RootPages),
 ('f', lino.modlib.uploads.ui.Uploads),
 ('books', lino_xl.lib.books.ui.Books),
 ('src', lino_xl.lib.sources.models.Sources),
 ('sng', lino_xl.lib.songs.ui.LatestSongs),
 ('e', lino_xl.lib.cal.ui.UpcomingEvents),
 ('t', lino_xl.lib.tickets.ui.Tickets),
 ('tp', lino_xl.lib.topics.models.Topics))

When setting this setting (usually in a get_plugin_configs() method), the application developer should specify the data tables using their names. The above locations have been set in lino_cms.lib.cms.settings as follows:

yield ('publisher', 'locations', (
    ('b', 'blogs.LatestEntries'),
    ('p', 'publisher.Pages'),
    ('t', 'topics.Topics'),
    ('u', 'users.Users')))
lino.modlib.publisher.skin

Which skin to use. Default value is ‘boots’.

Currently the only alternative to ‘boots’ is ‘silly’, which is there just to show an alternative. You can see it by activating a line in the noi2 demo project settings.py file.

lino.modlib.publisher.use_markup

(Deprecated)

Whether to use markup (instead of wysiwyg) for editing content.

When this is False, the body of pages gets edited using a wysiwyg editor and stored as (bleached) html.