Source code for org.homepage_widgets

from onegov.directory import DirectoryCollection
from onegov.event import OccurrenceCollection
from onegov.org import _, OrgApp
from onegov.org.elements import Link, LinkGroup
from onegov.org.models import ImageSet, ImageFile, News
from onegov.file.models.fileset import file_to_set_associations
from sqlalchemy import func

from onegov.org.models.directory import ExtendedDirectoryEntryCollection


from typing import NamedTuple, TYPE_CHECKING
if TYPE_CHECKING:
    from collections.abc import Iterable, Iterator, Sequence
    from onegov.core.types import RenderData
    from onegov.org.layout import DefaultLayout
    from onegov.org.models import ExtendedDirectory


[docs] def get_lead( text: str, max_chars: int = 180, consider_sentences: bool = True ) -> str: if len(text) > max_chars: first_point_ix = text.find('.') if first_point_ix < 1 or not consider_sentences: return text[0:max_chars] + '...' elif first_point_ix >= max_chars: # still return the entire first sentence, but nothing more return text[0:first_point_ix + 1] else: # return up to the n'th sentence that is still below the limit end = text.rindex('.', 0, max_chars) + 1 return text[0:end] return text
@OrgApp.homepage_widget(tag='row')
[docs] class RowWidget:
[docs] template = """ <xsl:template match="row"> <div class="row"> <xsl:apply-templates select="node()"/> </div> </xsl:template> """
@OrgApp.homepage_widget(tag='column')
[docs] class ColumnWidget:
[docs] template = """ <xsl:template match="column"> <div class="small-12 medium-{@span} columns"> <xsl:apply-templates select="node()"/> </div> </xsl:template> """
@OrgApp.homepage_widget(tag='text')
[docs] class TextWidget:
[docs] template = """ <xsl:template match="text"> <p class="homepage-text"> <xsl:apply-templates select="node()"/> </p> </xsl:template> """
@OrgApp.homepage_widget(tag='panel')
[docs] class PanelWidget: # panels with less than one link (not counting the more-link) are # hidden unless the user is logged-in
[docs] template = """ <xsl:template match="panel"> <div class="side-panel requires-children"> <xsl:attribute name="data-required-children"> <xsl:value-of select="'a:not(.more-link)'"/> </xsl:attribute> <xsl:attribute name="data-required-count"> <xsl:value-of select="'1'"/> </xsl:attribute> <xsl:attribute name="data-required-unless"> <xsl:value-of select="'.is-logged-in'"/> </xsl:attribute> <xsl:apply-templates select="node()"/> </div> </xsl:template> """
@OrgApp.homepage_widget(tag='links')
[docs] class LinksWidget:
[docs] template = """ <xsl:template match="links"> <xsl:if test="@title"> <h2> <xsl:value-of select="@title" /> </h2> </xsl:if> <ul class="panel-links"> <xsl:for-each select="link"> <li> <a> <xsl:attribute name="href"> <xsl:value-of select="@url" /> </xsl:attribute> <xsl:value-of select="node()" /> </a> <xsl:if test="@description"> <small> <xsl:value-of select="@description" /> </small> </xsl:if> </li> </xsl:for-each> </ul> </xsl:template> """
@OrgApp.homepage_widget(tag='directories')
[docs] class DirectoriesWidget:
[docs] template = """ <xsl:template match="directories"> <metal:block use-macro="layout.macros['directories-panel']" /> </xsl:template> """
[docs] def get_variables(self, layout: 'DefaultLayout') -> 'RenderData': directories: DirectoryCollection[ExtendedDirectory] directories = DirectoryCollection( layout.app.session(), type='extended') links = [ Link( text=d.title, url=layout.request.class_link( ExtendedDirectoryEntryCollection, {'directory_name': d.name} ), subtitle=d.lead ) for d in layout.request.exclude_invisible(directories.query()) ] links.append( Link( text=_('All directories'), url=layout.request.class_link(DirectoryCollection), classes=('more-link', ) ) ) return { 'directory_panel': LinkGroup( title=_('Directories'), links=links, ) }
@OrgApp.homepage_widget(tag='news')
[docs] class NewsWidget:
[docs] template = """ <xsl:template match="news"> <div metal:use-macro="layout.macros.newslist" tal:define="heading 'h3'; show_all_news_link True; hide_date True" /> </xsl:template> """
[docs] def get_variables(self, layout: 'DefaultLayout') -> 'RenderData': root_pages = layout.root_pages if not root_pages: return {'news': ()} news_index: int | None = None for index, page in enumerate(root_pages): if page.type == 'news': if page.children: # only bother doing the query if there are children news_index = index break if news_index is None: return {'news': ()} # FIXME: We probably don't need full fat News objects for this # and could instead just use children on the root news page # if we need additional attributes, we can just add them # to PageMeta, then we can also get rid of `news_query_for` # and refactor it back into a pure instance method # request more than the required amount of news to account for hidden # items which might be in front of the queue news_limit = layout.org.news_limit_homepage news = layout.request.exclude_invisible( News.news_query_for( root_pages[news_index], session=layout.request.session, limit=news_limit + 2, published_only=not layout.request.is_manager ).all() ) # limits the news, but doesn't count sticky news towards that limit def limited(news: 'Iterable[News]', limit: int) -> 'Iterator[News]': count = 0 for item in news: if count < limit or item.is_visible_on_homepage: yield item if not item.is_visible_on_homepage: count += 1 return { 'news': limited(news, limit=news_limit), 'get_lead': get_lead }
@OrgApp.homepage_widget(tag='homepage-cover')
[docs] class CoverWidget:
[docs] template = """ <xsl:template match="homepage-cover"> <div class="homepage-content page-text"> <tal:block content="structure layout.org.meta.get('homepage_cover')" /> </div> </xsl:template> """
@OrgApp.homepage_widget(tag='events')
[docs] class EventsWidget:
[docs] template = """ <xsl:template match="events"> <metal:block use-macro="layout.macros['events-panel']" /> </xsl:template> """
[docs] def get_variables(self, layout: 'DefaultLayout') -> 'RenderData': occurrences = OccurrenceCollection(layout.app.session()).query() occurrences = occurrences.limit(layout.org.event_limit_homepage) event_links = [ Link( text=o.title, url=layout.request.link(o), subtitle=layout.format_date(o.localized_start, 'event') .title() ) for o in occurrences ] event_links.append( Link( text=_('All events'), url=layout.events_url, classes=('more-link', ) ) ) latest_events = LinkGroup( title=_('Events'), links=event_links, ) return { 'event_panel': latest_events }
@OrgApp.homepage_widget(tag='homepage-tiles')
[docs] class TilesWidget:
[docs] template = """ <xsl:template match="homepage-tiles"> <xsl:choose> <xsl:when test="@show-title"> <metal:block use-macro="layout.macros['homepage-tiles']" tal:define="show_title True" /> </xsl:when> <xsl:otherwise> <metal:block use-macro="layout.macros['homepage-tiles']" tal:define="show_title False" /> </xsl:otherwise> </xsl:choose> </xsl:template> """
[docs] def get_variables(self, layout: 'DefaultLayout') -> 'RenderData': return {'tiles': tuple(self.get_tiles(layout))}
[docs] class Tile(NamedTuple):
[docs] page: Link
[docs] number: int
[docs] def get_tiles(self, layout: 'DefaultLayout') -> 'Iterator[Tile]': request = layout.request homepage_pages = request.homepage_pages classes = ('tile-sub-link', ) for ix, page in enumerate(layout.root_pages): if page.type == 'topic': yield self.Tile( page=Link(page.title, page.link(request)), number=ix + 1, links=tuple( Link( child.title, child.link(request), classes=classes, # this only accesses the `access` attribute # which we have, so this is safe, even though # it's not the actual page model model=child, ) for child in homepage_pages.get(page.id, ()) ) ) elif page.type == 'news': pass else: raise NotImplementedError
@OrgApp.homepage_widget(tag='line')
[docs] class HrWidget:
[docs] template = """ <xsl:template match="line"> <hr /> </xsl:template> """
@OrgApp.homepage_widget(tag='slider')
[docs] class SliderWidget:
[docs] template = """ <xsl:template match="slider"> <div metal:use-macro="layout.macros['slider']" tal:define="height_m '{@height-m}'; height_d '{@height-d}'" /> </xsl:template> """
[docs] def get_images_from_sets( self, layout: 'DefaultLayout' ) -> 'Iterator[RenderData]': session = layout.app.session() sets = tuple( set_id for set_id, meta in session.query(ImageSet) .with_entities(ImageSet.id, ImageSet.meta) if meta.get('show_images_on_homepage') and meta.get('access', 'public') == 'public' ) if not sets: return files = session.query(file_to_set_associations) files = files.with_entities(file_to_set_associations.c.file_id) files = files.filter(file_to_set_associations.c.fileset_id.in_( sets )) images = session.query(ImageFile) images = images.filter(ImageFile.id.in_(files.subquery())) images = images.order_by(func.random()) images = images.limit(6) for image in images: yield { 'note': image.note, 'src': layout.request.link(image) }
[docs] def get_variables(self, layout: 'DefaultLayout') -> 'RenderData': # if we don't have an album used for images, we use the images # shown on the homepage anyway to avoid having to show nothing images = tuple(self.get_images_from_sets(layout)) return { 'images': images, }