From de2a7acbe48c11e54573ea01fa88b483272706a5 Mon Sep 17 00:00:00 2001 From: ravenwing Date: Sun, 14 Jun 2026 18:46:19 +0200 Subject: [PATCH] Revert "Revert "fix: filter not wanted plugins changes" (#362)" This reverts commit 99d48fb63df99d917d6761fabb3a7cc5c14a1b12. --- goodmap/__init__.py | 2 + goodmap/goodmap.py | 20 ++++++++- goodmap/plugin.py | 12 ++++++ poetry.lock | 10 ++--- pyproject.toml | 2 +- tests/unit_tests/test_goodmap.py | 70 ++++++++++++++++++++++++++++++++ 6 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 goodmap/plugin.py diff --git a/goodmap/__init__.py b/goodmap/__init__.py index 8b137891..3c2ccef4 100644 --- a/goodmap/__init__.py +++ b/goodmap/__init__.py @@ -1 +1,3 @@ +from goodmap.plugin import GoodmapPluginBase +__all__ = ["GoodmapPluginBase"] diff --git a/goodmap/goodmap.py b/goodmap/goodmap.py index 0da28a6d..39a91dbc 100644 --- a/goodmap/goodmap.py +++ b/goodmap/goodmap.py @@ -12,6 +12,8 @@ from platzky import platzky from platzky.config import AttachmentConfig, languages_dict from platzky.models import CmsModule +from platzky.plugin.content_transformer import ContentTransformerPluginBase +from platzky.shortcodes import Shortcode from pydantic import BaseModel from goodmap.admin_api import admin_pages @@ -26,7 +28,7 @@ logger = logging.getLogger(__name__) -_PLUGIN_ENTRY_POINT_GROUP = "platzky.plugins" +_PLUGIN_ENTRY_POINT_GROUP = "goodmap.plugins" def _register_plugin_static_resources( @@ -167,6 +169,20 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine: max_size=5 * 1024 * 1024, # 5MB - reasonable for location photos ) + shortcodes: dict[str, Shortcode] = {} + for plugin in app.loaded_plugins: + if isinstance(plugin, ContentTransformerPluginBase): + for name, sc in plugin.shortcodes.items(): + if name in shortcodes: + logger.warning( + "Shortcode '%s' from plugin '%s' conflicts with " + "an already-registered shortcode; skipping", + name, + type(plugin).__name__, + ) + else: + shortcodes[name] = sc + cp = core_pages( app.db, languages_dict(config.languages), @@ -175,7 +191,7 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine: location_model, photo_attachment_config=photo_attachment_config, feature_flags=config.feature_flags, - shortcodes=app.shortcodes, + shortcodes=shortcodes, ) app.register_blueprint(cp) diff --git a/goodmap/plugin.py b/goodmap/plugin.py new file mode 100644 index 00000000..91cd3aca --- /dev/null +++ b/goodmap/plugin.py @@ -0,0 +1,12 @@ +"""Base class for goodmap map plugins.""" + +from typing import Any + +from platzky.plugin.plugin import PluginBase + + +class GoodmapPluginBase(PluginBase): + """Base class for goodmap map plugins.""" + + def __init__(self, config: dict[str, Any]) -> None: + super().__init__(config) diff --git a/poetry.lock b/poetry.lock index 777cabb5..45ff1228 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1999,14 +1999,14 @@ type = ["mypy (>=1.14.1)"] [[package]] name = "platzky" -version = "2.0.0a2" +version = "2.0.0a3" description = "Not only blog engine" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "platzky-2.0.0a2-py3-none-any.whl", hash = "sha256:39da542c56466152fb202cacc564b45a4daf66de5d6b0c44f0b4060fb2484a96"}, - {file = "platzky-2.0.0a2.tar.gz", hash = "sha256:5a09a424cf74f7678f3de7382cc09ffeaf98ce7a0bdd4e949a78f025ba1f2da8"}, + {file = "platzky-2.0.0a3-py3-none-any.whl", hash = "sha256:e647b22d890da71d954ba1df1dd322daa910b694b0c462dc17639eee1e5eb0a4"}, + {file = "platzky-2.0.0a3.tar.gz", hash = "sha256:1f1347579955bbf670dbb549f865a43579d6de841c0e8722bc2e1400cc026bd7"}, ] [package.dependencies] @@ -2016,7 +2016,7 @@ Flask = ">=3.1.0,<4.0.0" Flask-Babel = ">=4.0.0,<5.0.0" Flask-Minify = ">=0.50,<0.51" Flask-WTF = ">=1.2.1,<2.0.0" -google-cloud-storage = ">=2.5.0,<3.0.0" +google-cloud-storage = ">=2.5,<4.0" gql = ">=3.4,<5.0" humanize = ">=4.9.0,<5.0.0" puremagic = ">=1.30,<2.0" @@ -4013,4 +4013,4 @@ docs = ["myst-parser", "sphinx", "sphinx-rtd-theme"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "18cd461f3f07235dac94fff3e8e282335289b4751f5ea4aa9766cce7859f6cfb" +content-hash = "b4e486cf60e2c18231821460df74949b027e98d91365e3896e23ceef4ade7712" diff --git a/pyproject.toml b/pyproject.toml index f687e074..ce2aa39a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ Flask-WTF = "^1.2.1" gql = "^3.4.0" aiohttp = "^3.8.4" pydantic = "^2.12.0" -platzky = "2.0.0a2" +platzky = "2.0.0a3" deprecation = "^2.1.0" numpy = "^2.2.0" # Using fork because official PyPI version (0.7.7) has outdated numpy setup hack diff --git a/tests/unit_tests/test_goodmap.py b/tests/unit_tests/test_goodmap.py index a022211d..fa7f93a9 100644 --- a/tests/unit_tests/test_goodmap.py +++ b/tests/unit_tests/test_goodmap.py @@ -263,6 +263,76 @@ def test_admin_route_logged_in(): assert "Test User" in response_text +def test_field_renderer_shortcodes_collected_from_content_transformer_plugins() -> None: + """Shortcodes from all ContentTransformerPluginBase plugins are passed to core_pages.""" + from typing import ClassVar + + from platzky.content_types import ALL_CONTENT_TYPES + from platzky.plugin.content_transformer import ContentTransformerPluginBase + from platzky.shortcodes import Shortcode, ShortcodeAttrs + + class _FieldSC(Shortcode): + name = "testfieldsc" + description = "Field-capable shortcode" + + def render(self, attrs: ShortcodeAttrs, content: str) -> str: + return content + + class _PostSC(Shortcode): + name = "testpostsc" + description = "Post-only shortcode" + + def render(self, attrs: ShortcodeAttrs, content: str) -> str: + return content + + class _PluginA(ContentTransformerPluginBase): + shortcodes: ClassVar[dict[str, Shortcode]] = {"testfieldsc": _FieldSC()} + + class _PluginB(ContentTransformerPluginBase): + shortcodes: ClassVar[dict[str, Shortcode]] = {"testpostsc": _PostSC()} + + config = _make_test_app_config( + extra_data={ + "plugins": [ + { + "name": "field_plugin", + "config": {}, + "allowed_content_types": list(ALL_CONTENT_TYPES), + "allowed_topics": ["general", "content", "security"], + }, + { + "name": "post_plugin", + "config": {}, + "allowed_content_types": list(ALL_CONTENT_TYPES), + "allowed_topics": ["general", "content", "security"], + }, + ] + } + ) + + captured: dict[str, Any] = {} + orig_core_pages = goodmap.core_pages + + def _spy_core_pages(*args: Any, **kwargs: Any) -> Any: + captured["shortcodes"] = kwargs.get("shortcodes", {}) + return orig_core_pages(*args, **kwargs) + + field_ep = mock.MagicMock() + field_ep.name = "field_plugin" + field_ep.load.return_value = _PluginA + + post_ep = mock.MagicMock() + post_ep.name = "post_plugin" + post_ep.load.return_value = _PluginB + + with mock.patch("goodmap.goodmap.core_pages", side_effect=_spy_core_pages): + with mock.patch("importlib.metadata.entry_points", return_value=[field_ep, post_ep]): + goodmap.create_app_from_config(config) + + assert "testfieldsc" in captured["shortcodes"] + assert "testpostsc" in captured["shortcodes"] + + def test_plugin_blueprint_sets_cors_header(): """Should set Access-Control-Allow-Origin on plugin blueprint responses.""" config = _make_test_app_config()