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

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