How do you use collective.lineage (for folders as subsites)?

Six Feet Up is looking for examples and stories of how you are using Lineage! Please reply below if you are using (or have used) Lineage with a sentence or two about it.

I have a story about Lineage.... :smile:

When I was preparing to travel to Washington DC to the 2008 Plone Conference, I had heard rumours about an add-on that would let me create what looked like full Plone sites but were, in fact, just folders inside one real Plone site.

At University of Wisconsin Oshkosh, we were preparing to launch the main site for the campus, and we had started creating full sites for departments and colleges.

I asked around and learned that this big cheese (well, ok, Indiana has bad cheese, so I'm told) @calvinhp was responsible for this add-on but there was a holdup in its release. The other two Plonistas involved were Kurt Bendl and @optilude because Kurt put me onto it and Martin had to patch something or other related to search and maybe portlets on the site root interface.

As it turned out, at the conference and between talks I saw Calvin heading for the men's room, and so I charged in after him. I had recognized him from a talk he'd just given. He was standing there, doing his thing, and I decided to pepper him with questions about when the add-on would be released, etc. He had all these excuses, and, lo and behold, in walks Kurt, who proceeds to stand next to us. As he starts explaining himself, in walks Martin, who also joins us facing the wall.

And that is how I met the people involved in creating collective.lineage.

3 Likes

We (BlueDynamics Alliance) are using Linage heavily in two larger/long term projects:

  1. Austrian Architectural Forum (a dozen of sites in one Plone https://architekturstiftung.at/ and see mini map in lower right to get to the other sites). This sites are running on Plone 5.2/Python 3 with Mosaic (fixed site and content layouts, no editor). Sites are one level only (no sub-subsites)
  2. Region "Am Kumma": Four town/governmental sites of neighbor communities in Vorarlberg (Region at https://amkumma.at/ see top nav for communities). Still runs on Plone 4.3. No Archetypes. Has sub-subsites. Old Plone installation we inherited from another company about 10 years ago, migrated to Plone 4.3 since, further migration still to do :roll_eyes:.
  3. Some other smaller short-term projects were implemented.

Meanwhile @thet and my person are the top contributors on collective.lineage according to github insights :wink: Also a bunch of add-ons were also developed by us:

  • lineage.registry - local transparent registry layer per subsite
  • lineage.themeselection - setting Diazo themes per subsite
  • lineage.controlpanels - controlpanels (not all) on subsite level to leverage usage of lineage.registry to user level.

We also helped to maintain collective.rooter and lineage.index. Furthermore we ensured Plone core since early Plone 4, Mosaic and a bunch of add-ons (i.e. Easyform) are Lineage-friendly.

@thet did I miss anything?

And we're just two out of more active contributors over time.

I would like to thank Six Feet Up for starting Lineage and all others helping to keep it working. It's a perfect example of a FOSS project, started by one initiator then used and further developed together with and by a bunch of other contributors, as a whole getting better and more mature with benefit for all. It is also a great example for long term sustainability - OMG 13 years! Its then a great example for our model of the Collective repository, where contribution is simple and with low friction.

Ops, this was more than a sentence or two..., sorry @tkimnguyen :smiley:

1 Like

I (https://medialog.no) use lineage for http://www.marfag.no
The site uses 'quite a bit of its features'.

  1. Initially (when I made it), I wanted to make it possible to 'read some of the books offline', so separate sites made it possible to use a manifest file to 'download all the required files'. This is currently not in use (I think), but should still work. Se 'point 6

  2. I use my add-on medialog.markdown and with 'Local Registry', I can have different control panel settings / setup for the editor for each subsite. Also I have different settings for 'enable live preview of markdown text'.

  3. I have different themes (although many of them are quite similar)*

3b) I use mathjax for LATEX math for some of the sites. This also has different setups.

  1. I have different language settings, this also includes voice setup for ' collective.texttospeech

  2. I have separate settings for 'generate PDF & Ebook' (pp.client.plone)

  3. I also have one URL for each, so F05 Skipsmaskineri I is also http://f05.marfag.no

  4. Search will usually be limited to one book (subsite)

UPDATE; This site is 5.1.5 (I think). It has not been updated to 5.2 since I did not get pp.client to work. I later got pp.client to work on a 5.2.x site

2 Likes

Hi :wave:

Our (Kombinat) project www.vorderland.com are 11 communities also located in Vorarlberg (direct neighbourhood to amkumma.at). Its on Plone 4.3 with lineage (dexterity only), each community has its own lineage folder with its settings (individually programmed dexterity-behavior for lineage folders) and its own diazo theme (eg www.rankweil.at) . This will be our next migration project to Plone 6 (classic theme) with mosaic and site-layout this year.

2 Likes

For www.dipf.de we (starzel.de) are managing 10 lineage-subsites, some of them are multilingual (LinguaPlone). It is still Plone 4 with Archetypes.

We're currently migrating it to Plone 6 and we will use Lineage again, this time with plone.app.multilingual.

Because lineage & multilingual is not straightforward, to setup a subsite I created a simple type that allows you to decide if it is multilingual or not on creation. A subscriber will create the appropriate language-root-folders for you:

from Acquisition import aq_base
from collective.lineage.utils import enable_childsite
from logging import getLogger
from plone import api
from plone.app.dexterity.behaviors.exclfromnav import IExcludeFromNavigation
from plone.app.layout.navigation.interfaces import INavigationRoot
from plone.app.multilingual.interfaces import ITranslationManager
from plone.autoform import directives
from plone.dexterity.content import Container
from plone.supermodel import model
from Products.CMFPlone.interfaces import ILanguage
from Products.CMFPlone.utils import _createObjectByType
from z3c.form.interfaces import IEditForm
from zope import schema
from zope.event import notify
from zope.interface import alsoProvides
from zope.interface import implementer
from zope.lifecycleevent import modified

log = getLogger(__name__)


class ISubsite(model.Schema):
    """Dexterity Schema for Subsite
    """

    directives.omitted(IEditForm, 'multilingual')
    multilingual = schema.Bool(
        title='Create language root-foldes for this subsite?',
        required=False,
        default=False,
        )


@implementer(ISubsite)
class Subsite(Container):
    """Subsite instance"""


def enable_subsite(obj, event):
    enable_childsite(obj)

    if obj.multilingual:
        subsite_setup = SetupMultilingualSubsite()
        subsite_setup.setupSubsite(obj)


class SetupMultilingualSubsite(object):

    # portal_type that is added as root language folder
    folder_type = 'LRF'

    def __init__(self, context=None):
        self.context = context
        self.folders = {}
        self.languages = []
        self.defaultLanguage = None

    def setupSubsite(self, context, forceOneLanguage=False):
        self.context = context
        self.folders = {}

        language_tool = api.portal.get_tool('portal_languages')
        self.languages = languages = language_tool.getSupportedLanguages()
        self.defaultLanguage = language_tool.getDefaultLanguage()

        if len(languages) == 1 and not forceOneLanguage:
            return 'Only one supported language configured.'

        doneSomething = False
        available = language_tool.getAvailableLanguages()
        for language in languages:
            info = available[language]
            name = info.get('native', info.get('name'))
            doneSomething += self.setUpLanguage(language, name)

        doneSomething += self.linkTranslations()
        doneSomething += self.setupLanguageSwitcher()

        if not doneSomething:
            return 'Nothing done.'
        else:
            return f'Setup of language root folders on Plone site {self.context.getId()}'

    def linkTranslations(self):
        """Links the translations of the default language Folders
        """
        doneSomething = False

        try:
            canonical = ITranslationManager(self.folders[self.defaultLanguage])
        except TypeError as e:
            raise TypeError(str(e) + u' Are your folders ITranslatable?')

        for language in self.languages:
            if language == self.defaultLanguage:
                continue
            if not canonical.has_translation(language):
                language_folder = self.folders[language]
                canonical.register_translation(language, language_folder)
                doneSomething = True

        if doneSomething:
            log.info(u'Translations linked.')

        return doneSomething

    def setUpLanguage(self, code, name):
        """Create the language folders in the subsite
        """
        doneSomething = False
        folderId = str(code)

        folder = getattr(aq_base(self.context), folderId, None)

        if folder is None:
            _createObjectByType(self.folder_type, self.context, folderId)
            folder = self.context[folderId]

            ILanguage(folder).set_language(code)
            folder.setTitle(name)
            api.content.transition(folder, to_state='published')

            # Exclude folder from navigation (if applicable)
            adapter = IExcludeFromNavigation(folder, None)
            if adapter is not None:
                adapter.exclude_from_nav = True

            # We've modified the object; reindex.
            notify(modified(folder))

            doneSomething = True
            log.info(f'Added {code} folder: {folderId}')

        self.folders[code] = folder
        if not INavigationRoot.providedBy(folder):
            alsoProvides(folder, INavigationRoot)

            doneSomething = True
            log.info(f'INavigationRoot setup on folder {code}')

        return doneSomething

    def setupLanguageSwitcher(self):
        doneSomething = False
        if self.context.getLayout() != '@@language-switcher':
            self.context.setLayout('@@language-switcher')
            self.context.reindexObject()
            log.info('Language switcher set up for subsite.')
        doneSomething = True
        return doneSomething

For super-basic styling we allow adding css and a logo on the subsites with a behavior:

from plone.autoform.interfaces import IFormFieldProvider
from plone.namedfile.field import NamedBlobImage
from plone.supermodel import model
from zope import schema
from zope.interface import provider


@provider(IFormFieldProvider)
class ISubsiteTheme(model.Schema):

    logo = NamedBlobImage(
        title=u'Logo',
        required=False,
        )

    css = schema.Text(
        title=u'Custom css',
        required=False,
        missing_value=u'',
        )
2 Likes

The search related thing Martin Aspelli was working on was GitHub - collective/collective.rooter: Tools to force catalog queries to obey the current navigation root.

One of my first contributions to collective.lineage probably due to migration work for amKumma was the removal of p4a.subtyper. That was 2014, it even feels as if it was decades ago.

I was also contributing to lineage.registry, which is quite powerful. But early on we had some issues with write on read here which led to miserable performance for an event ticket shop when many parents were coming in a rush after opening the registration period for free child care program. I remember @jensens and me fixing this on a sunny sunday summer day. This free child care program was a yearly event and at some point the stress did release because we did not get any more nervous help calls from the customer when this program started. Lineage and the ticketshop were just working fine.

I also use it for digital online courses for the "vienna poetry school". Some examples:
https://sfd.at/programm/2020/advenire
https://sfd.at/programm/2015/vage-waagen
https://sfd.at/programm/2012/unfassbare-zahlenpoesie

1 Like

Forgot to mention: at amKumma the customer creates subsites himself and is very happy with this functionality. If the subsite grows in scope it's still possible to put it on a seperate domain and to customize the look and feel (in simple cases just with: GitHub - collective/collective.localstyles: Add local styles to subsections in your Plone site. ) while keeping the overall design. /cc @jensens

2 Likes

I have "always" had 'custom CSS' in the control panel for my themes, so I can put it there.

But: since this is also possible with standard Plone now ( from 5.2.something), maybe that is an option for others? /@@theming-controlpanel#autotoc-item-autotoc-2

Combination of Mosaic (fragments/tiles) and lineage gives some 'problems'.

Usually, one can use:

<field name="background_image" type="zope.schema.Choice">
    <title>Choose image</title>
    <source>collective.themefragments.tiles.CatalogSource</source>

But with lineage, I only got it working with:

<field name="bkground_image" type="zope.schema.Choice">
     <vocabulary>medialog.dutchestheme.ShowImagesVocabulary</vocabulary>

Where the vocabulary is something similar to this:

    def ShowImagesVocabulary(context):

        images = api.content.find(portal_type='Image', sort_on='sortable_title')

        if images:
            terms = [ SimpleTerm(value=img.UID, token=img.UID, title=img.Title) for img in images ]
        return SimpleVocabulary(terms)

    directlyProvides(ShowImagesVocabulary, IVocabularyFactory)

I am not sure 'how correct' this approach is, but I could not find another way.

In case it should be of interest:

To make your own control panel work with subsides is very easy.

PS: You also need to make an action if you want that. If not you can just manually construct the url

In my case, if the control panel link is https://mysite/@@medialog_controlpanel , the subsite control panel url would be https://mysite/mysubsite/@@medialog_controlpanel

2 Likes