Source code for feincms3.regions

"""
.. warning::
   This module is deprecated. Use :mod:`feincms3.renderer.RegionRenderer`
   instead.
"""

import warnings
from collections import deque
from functools import wraps

from content_editor.contents import contents_for_item
from django.core.cache import cache
from django.utils.functional import SimpleLazyObject
from django.utils.html import mark_safe


__all__ = ("Regions", "matches", "cached_render")


warnings.warn(
    "feincms3.regions is deprecated. Switch to feincms3.renderer.RegionRenderer now.",
    DeprecationWarning,
    stacklevel=2,
)


[docs] def cached_render(fn): """ Decorator for ``Regions.render`` methods implementing caching behavior """ @wraps(fn) def render(self, region, context=None): key = self.cache_key(region) if self.cache_key else None if key: result = cache.get(key) if result is not None: return result result = fn(self, region, context) if key: cache.set(key, result, timeout=self.timeout) return result return render
[docs] class Regions: """ ``Regions`` uses ``content_editor.contents.Contents`` and the ``feincms3.renderer.TemplatePluginRenderer`` to convert a list of plugins into a rendered representation, most often a HTML fragment. This class may also be instantiated directly but using the factory methods (starting with ``from_``) below is probably more comfortable. """
[docs] @classmethod def from_contents(cls, contents, *, renderer, **kwargs): """ Create and return a regions instance using the bare minimum of a contents instance and a renderer. Additional keyword arguments are forwarded to the regions constructor. """ return cls(contents=contents, renderer=renderer, **kwargs)
[docs] @classmethod def from_item(cls, item, *, renderer, inherit_from=None, timeout=None, **kwargs): """ Create and return a regions instance for an item (for example a page, an article or anything else managed by django-content-editor). The item's plugins are determined by what is registered with the renderer. The plugin instances themselves are loaded lazily, and loading every time can be avoided completely by specifying a ``timeout``. """ if timeout is not None and "cache_key" not in kwargs: key = f"{item._meta.label_lower}-{item.pk}" kwargs["cache_key"] = lambda region: f"{key}-{region}" return cls.from_contents( SimpleLazyObject( lambda: contents_for_item( item, renderer.plugins(), inherit_from=inherit_from ) ), renderer=renderer, timeout=timeout, **kwargs, )
def __init__(self, *, contents, renderer, cache_key=None, timeout=None): self.contents = contents self.renderer = renderer self.cache_key = cache_key self.timeout = timeout self.handlers = { key[7:]: getattr(self, key) for key in dir(self) if key.startswith("handle_") } @property def regions(self): return self.contents.regions
[docs] @cached_render def render(self, region, context=None): """ Main function for rendering. Starts the generator and assembles all fragments into a safe HTML string. """ return mark_safe("".join(self.generate(self.contents[region], context)))
[docs] def generate(self, items, context): """ Inspects all items in the region for a ``subregion`` attribute and passes control to the subregions' respective rendering handler, named ``handle_<subregion>``. If ``subregion`` is not set or is falsy ``handle_default`` is invoked instead. This method raises a ``KeyError`` exception if no matching handler exists. You probably want to call this method when overriding ``render``. """ items = deque(items) while items: subregion = getattr(items[0], "subregion", None) or "default" yield from self.handlers[subregion](items, context)
[docs] def handle_default(self, items, context): """ Renders items from the queue using the renderer instance as long as the items either have no ``subregion`` attribute or whose ``subregion`` attribute is an empty string. """ while True: yield self.renderer.render_plugin(items.popleft(), context) if not items or not matches(items[0], subregions={None, ""}): break
[docs] def matches(item, *, plugins=None, subregions=None): """ Checks whether the item matches zero or more constraints. ``plugins`` should be a tuple of plugin classes or ``None`` if the type shouldn't be checked. ``subregions`` should be set of allowed ``subregion`` attribute values or ``None`` if the ``subregion`` attribute shouldn't be checked at all. Include ``None`` in the set if you want ``matches`` to succeed also when encountering an item without a ``subregion`` attribute. """ if plugins is not None and not isinstance(item, plugins): return False if subregions is not None and getattr(item, "subregion", None) not in subregions: return False return True