-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathregions.py
More file actions
159 lines (132 loc) · 5.29 KB
/
Copy pathregions.py
File metadata and controls
159 lines (132 loc) · 5.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
.. 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", "cached_render", "matches")
warnings.warn(
"feincms3.regions is deprecated. Switch to feincms3.renderer.RegionRenderer now.",
DeprecationWarning,
stacklevel=2,
)
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
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.
"""
@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)
@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
@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)))
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)
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
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
return not (
subregions is not None and getattr(item, "subregion", None) not in subregions
)