From 8230fe37601e41df1db5d6e589b916f4be216630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Tue, 9 Jun 2026 18:42:31 +0200 Subject: [PATCH 1/6] fix: filter not wanted plugins changes --- goodmap/__init__.py | 2 + goodmap/goodmap.py | 13 ++++-- goodmap/plugin.py | 12 ++++++ tests/unit_tests/test_goodmap.py | 73 ++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 3 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..181b68ee 100644 --- a/goodmap/goodmap.py +++ b/goodmap/goodmap.py @@ -11,6 +11,7 @@ from flask_wtf.csrf import CSRFProtect, generate_csrf from platzky import platzky from platzky.config import AttachmentConfig, languages_dict +from platzky.plugin.content_transformer import ContentTransformerPluginBase from platzky.models import CmsModule from pydantic import BaseModel @@ -26,7 +27,7 @@ logger = logging.getLogger(__name__) -_PLUGIN_ENTRY_POINT_GROUP = "platzky.plugins" +_PLUGIN_ENTRY_POINT_GROUP = "goodmap.plugins" def _register_plugin_static_resources( @@ -66,7 +67,7 @@ def _add_cors(response): manifest_entry = { "scope": ep.name, "url": f"/plugins/{ep.name}/static/remoteEntry.js", - "module": "./Button", + "module": "./Plugin", } return bp, manifest_entry except Exception: @@ -175,7 +176,13 @@ 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={ + name: sc + for plugin in app.loaded_plugins + if isinstance(plugin, ContentTransformerPluginBase) + and "field" in plugin.accepted_content_types + for name, sc in plugin.shortcodes.items() + }, ) 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/tests/unit_tests/test_goodmap.py b/tests/unit_tests/test_goodmap.py index a022211d..b4711faa 100644 --- a/tests/unit_tests/test_goodmap.py +++ b/tests/unit_tests/test_goodmap.py @@ -263,6 +263,79 @@ def test_admin_route_logged_in(): assert "Test User" in response_text +def test_field_renderer_shortcodes_filtered_by_accepted_content_types() -> None: + """Shortcodes from plugins with 'field' in accepted_content_types are passed to core_pages; + plugins without 'field' are excluded.""" + from typing import ClassVar + + from platzky.content_types import ALL_CONTENT_TYPES, ContentType + 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 _FieldPlugin(ContentTransformerPluginBase): + accepted_content_types: ClassVar[frozenset[ContentType]] = frozenset({"post", "field"}) + shortcodes: ClassVar[dict[str, Shortcode]] = {"testfieldsc": _FieldSC()} + + class _PostOnlyPlugin(ContentTransformerPluginBase): + accepted_content_types: ClassVar[frozenset[ContentType]] = frozenset({"post", "page"}) + 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 = _FieldPlugin + + post_ep = mock.MagicMock() + post_ep.name = "post_plugin" + post_ep.load.return_value = _PostOnlyPlugin + + 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" not 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() From 951e52a757acf6203e99de7e99c5c5f75e83b3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Sat, 13 Jun 2026 22:24:30 +0200 Subject: [PATCH 2/6] lint fixes --- goodmap/goodmap.py | 3 +-- poetry.lock | 10 +++++----- pyproject.toml | 2 +- tests/unit_tests/test_goodmap.py | 19 ++++++++----------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/goodmap/goodmap.py b/goodmap/goodmap.py index 181b68ee..9e966777 100644 --- a/goodmap/goodmap.py +++ b/goodmap/goodmap.py @@ -11,8 +11,8 @@ from flask_wtf.csrf import CSRFProtect, generate_csrf from platzky import platzky from platzky.config import AttachmentConfig, languages_dict -from platzky.plugin.content_transformer import ContentTransformerPluginBase from platzky.models import CmsModule +from platzky.plugin.content_transformer import ContentTransformerPluginBase from pydantic import BaseModel from goodmap.admin_api import admin_pages @@ -180,7 +180,6 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine: name: sc for plugin in app.loaded_plugins if isinstance(plugin, ContentTransformerPluginBase) - and "field" in plugin.accepted_content_types for name, sc in plugin.shortcodes.items() }, ) 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 a48a6c91..02138896 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 b4711faa..fa7f93a9 100644 --- a/tests/unit_tests/test_goodmap.py +++ b/tests/unit_tests/test_goodmap.py @@ -263,12 +263,11 @@ def test_admin_route_logged_in(): assert "Test User" in response_text -def test_field_renderer_shortcodes_filtered_by_accepted_content_types() -> None: - """Shortcodes from plugins with 'field' in accepted_content_types are passed to core_pages; - plugins without 'field' are excluded.""" +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, ContentType + from platzky.content_types import ALL_CONTENT_TYPES from platzky.plugin.content_transformer import ContentTransformerPluginBase from platzky.shortcodes import Shortcode, ShortcodeAttrs @@ -286,12 +285,10 @@ class _PostSC(Shortcode): def render(self, attrs: ShortcodeAttrs, content: str) -> str: return content - class _FieldPlugin(ContentTransformerPluginBase): - accepted_content_types: ClassVar[frozenset[ContentType]] = frozenset({"post", "field"}) + class _PluginA(ContentTransformerPluginBase): shortcodes: ClassVar[dict[str, Shortcode]] = {"testfieldsc": _FieldSC()} - class _PostOnlyPlugin(ContentTransformerPluginBase): - accepted_content_types: ClassVar[frozenset[ContentType]] = frozenset({"post", "page"}) + class _PluginB(ContentTransformerPluginBase): shortcodes: ClassVar[dict[str, Shortcode]] = {"testpostsc": _PostSC()} config = _make_test_app_config( @@ -322,18 +319,18 @@ def _spy_core_pages(*args: Any, **kwargs: Any) -> Any: field_ep = mock.MagicMock() field_ep.name = "field_plugin" - field_ep.load.return_value = _FieldPlugin + field_ep.load.return_value = _PluginA post_ep = mock.MagicMock() post_ep.name = "post_plugin" - post_ep.load.return_value = _PostOnlyPlugin + 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" not in captured["shortcodes"] + assert "testpostsc" in captured["shortcodes"] def test_plugin_blueprint_sets_cors_header(): From 69824fdca2965c771f7de27637f198deea0f81cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Sat, 13 Jun 2026 22:37:33 +0200 Subject: [PATCH 3/6] changed plugin odule --- goodmap/goodmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/goodmap/goodmap.py b/goodmap/goodmap.py index 9e966777..f7bf9f93 100644 --- a/goodmap/goodmap.py +++ b/goodmap/goodmap.py @@ -67,7 +67,7 @@ def _add_cors(response): manifest_entry = { "scope": ep.name, "url": f"/plugins/{ep.name}/static/remoteEntry.js", - "module": "./Plugin", + "module": "./Button", } return bp, manifest_entry except Exception: From 3f7f49bc0941c39184f8d7f9b0b6c2f5f71c3341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Sun, 14 Jun 2026 09:19:15 +0200 Subject: [PATCH 4/6] refactor --- goodmap/goodmap.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/goodmap/goodmap.py b/goodmap/goodmap.py index f7bf9f93..78dfd111 100644 --- a/goodmap/goodmap.py +++ b/goodmap/goodmap.py @@ -168,6 +168,11 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine: max_size=5 * 1024 * 1024, # 5MB - reasonable for location photos ) + shortcodes: dict[str, Any] = {} + for plugin in app.loaded_plugins: + if isinstance(plugin, ContentTransformerPluginBase): + shortcodes.update(plugin.shortcodes) + cp = core_pages( app.db, languages_dict(config.languages), @@ -176,12 +181,7 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine: location_model, photo_attachment_config=photo_attachment_config, feature_flags=config.feature_flags, - shortcodes={ - name: sc - for plugin in app.loaded_plugins - if isinstance(plugin, ContentTransformerPluginBase) - for name, sc in plugin.shortcodes.items() - }, + shortcodes=shortcodes, ) app.register_blueprint(cp) From 07fe043e034a046b21a0eb77f9ccb80ef80755db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Sun, 14 Jun 2026 14:18:51 +0200 Subject: [PATCH 5/6] fix after comments --- goodmap/goodmap.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/goodmap/goodmap.py b/goodmap/goodmap.py index 78dfd111..919e3a5e 100644 --- a/goodmap/goodmap.py +++ b/goodmap/goodmap.py @@ -13,6 +13,7 @@ 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 @@ -168,10 +169,18 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine: max_size=5 * 1024 * 1024, # 5MB - reasonable for location photos ) - shortcodes: dict[str, Any] = {} + shortcodes: dict[str, Shortcode] = {} for plugin in app.loaded_plugins: if isinstance(plugin, ContentTransformerPluginBase): - shortcodes.update(plugin.shortcodes) + 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, From 340716d9cbedb75abfeec56544022aa9c07f389e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ko=C5=82odzi=C5=84ski?= Date: Sun, 14 Jun 2026 14:47:10 +0200 Subject: [PATCH 6/6] fix lint --- goodmap/goodmap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/goodmap/goodmap.py b/goodmap/goodmap.py index 919e3a5e..39a91dbc 100644 --- a/goodmap/goodmap.py +++ b/goodmap/goodmap.py @@ -175,7 +175,8 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine: 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", + "Shortcode '%s' from plugin '%s' conflicts with " + "an already-registered shortcode; skipping", name, type(plugin).__name__, )