Skip to content

Commit

Permalink
Add template outlets hook
Browse files Browse the repository at this point in the history
  • Loading branch information
rafalp committed Dec 5, 2023
1 parent 4a73a74 commit 5597617
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 14 deletions.
40 changes: 29 additions & 11 deletions misago/plugins/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,55 @@


class ActionHook(Generic[Action]):
actions: List[Action]
__slots__ = ("actions_first", "actions_last", "cache")

actions_first: List[Action]
actions_last: List[Action]
cache: List[Action] | None

def __init__(self):
self.actions = []
self.actions_first = []
self.actions_last = []
self.cache = None

def append(self, action: Action):
self.actions.append(action)
self.actions_last.append(action)
self.invalidate_cache()

def prepend(self, action: Action):
self.actions.insert(0, action)
self.actions_first.insert(0, action)
self.invalidate_cache()

def invalidate_cache(self):
self.cache = None

def __call__(self, *args, **kwargs) -> List[Any]:
if not self.actions:
if self.cache is None:
self.cache = self.actions_first + self.actions_last
if not self.cache:
return []

return [action(*args, **kwargs) for action in self.actions]
return [action(*args, **kwargs) for action in self.cache]


class FilterHook(Generic[Action, Filter]):
__slots__ = ("filters_first", "filters_last", "cache")

filters_first: List[Filter]
filters_last: List[Filter]
cache: Action | None
filters: List[Filter]

def __init__(self):
self.filters_first = []
self.filters_last = []
self.cache = None
self.filters = []

def append(self, filter_: Filter):
self.filters.append(filter_)
self.filters_last.append(filter_)
self.invalidate_cache()

def prepend(self, filter_: Filter):
self.filters.insert(0, filter_)
self.filters_first.insert(0, filter_)
self.invalidate_cache()

def invalidate_cache(self):
Expand All @@ -50,7 +67,8 @@ def reduced_filter(*args, **kwargs):

return cast(Action, reduced_filter)

return reduce(reduce_filter, self.filters, action)
filters = self.filters_first + self.filters_last
return reduce(reduce_filter, filters, action)

def __call__(self, action: Action, *args, **kwargs):
if self.cache is None:
Expand Down
3 changes: 2 additions & 1 deletion misago/plugins/templatetags/misago_plugins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django import template
from django.utils.safestring import mark_safe

from ..outlets import template_outlets

Expand All @@ -14,4 +15,4 @@ def pluginoutlet(context, name: str):
for plugin_content in template_outlets[name](context):
if plugin_content is not None:
content += plugin_content
return content
return mark_safe(content)
16 changes: 16 additions & 0 deletions misago/plugins/tests/__snapshots__/test_template_outlets.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# serializer version: 1
# name: test_empty_outlet_renders_nothing
'<div></div>'
# ---
# name: test_outlet_renders_appended_plugin
'<div><strong>none</strong></div>'
# ---
# name: test_outlet_renders_multiple_plugins
'<div><strong>none</strong><em>none</em><strong>none</strong></div>'
# ---
# name: test_outlet_renders_plugins_with_context
'<div><strong>context</strong><em>context</em><strong>context</strong></div>'
# ---
# name: test_outlet_renders_prepended_plugin
'<div><strong>none</strong></div>'
# ---
9 changes: 7 additions & 2 deletions misago/plugins/tests/test_action_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def uppercase_action(base: str) -> str:
return base.upper()


def reverse_action(base: str) -> str:
return "".join(reversed(base))


@pytest.fixture
def hook():
return MockActionHook()
Expand All @@ -37,6 +41,7 @@ def test_action_hook_calls_multiple_actions_and_returns_their_results(hook):


def test_action_hook_action_can_be_prepended_before_other_actions(hook):
hook.append(lowercase_action)
hook.prepend(uppercase_action)
assert hook("TeSt") == ["TEST", "test"]
hook.append(lowercase_action)
hook.prepend(reverse_action)
assert hook("TeSt") == ["tSeT", "TEST", "test"]
87 changes: 87 additions & 0 deletions misago/plugins/tests/test_template_outlets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from contextlib import contextmanager

from django.template import Context, Template

from ..outlets import (
PluginOutletName,
PluginOutletHook,
append_template_plugin,
prepend_template_plugin,
template_outlets,
)


def strong_action(context):
body = context.get("value") or "none"
return f"<strong>{body}</strong>"


def em_action(context):
body = context.get("value") or "none"
return f"<em>{body}</em>"


def render_template(context: dict | None = None):
template = Template(
"""
{% load misago_plugins %}
<div>{% pluginoutlet "TEST" %}</div>
"""
)

return template.render(Context(context or {})).strip()


@contextmanager
def patch_outlets():
try:
org_outlets = template_outlets.copy()
for key in template_outlets:
template_outlets[key] = PluginOutletHook()
yield template_outlets
finally:
for key, hook in org_outlets.items():
template_outlets[key] = hook


def test_empty_outlet_renders_nothing(snapshot):
with patch_outlets():
html = render_template()

assert snapshot == html


def test_outlet_renders_appended_plugin(snapshot):
with patch_outlets():
append_template_plugin(PluginOutletName.TEST, strong_action)
html = render_template()

assert snapshot == html


def test_outlet_renders_prepended_plugin(snapshot):
with patch_outlets():
prepend_template_plugin(PluginOutletName.TEST, strong_action)
html = render_template()

assert snapshot == html


def test_outlet_renders_multiple_plugins(snapshot):
with patch_outlets():
append_template_plugin(PluginOutletName.TEST, strong_action)
prepend_template_plugin(PluginOutletName.TEST, em_action)
prepend_template_plugin(PluginOutletName.TEST, strong_action)
html = render_template()

assert snapshot == html


def test_outlet_renders_plugins_with_context(snapshot):
with patch_outlets():
append_template_plugin(PluginOutletName.TEST, strong_action)
prepend_template_plugin(PluginOutletName.TEST, em_action)
prepend_template_plugin(PluginOutletName.TEST, strong_action)
html = render_template({"value": "context"})

assert snapshot == html

0 comments on commit 5597617

Please sign in to comment.