From c6342aa12ca911fc93302d98e51060a8b2846315 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Fri, 24 Oct 2025 16:10:25 +0200 Subject: [PATCH 1/5] Check for toolbar attribute before setting preview mode --- djangocms_rest/views_base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/djangocms_rest/views_base.py b/djangocms_rest/views_base.py index b29cb19..42ff366 100644 --- a/djangocms_rest/views_base.py +++ b/djangocms_rest/views_base.py @@ -69,7 +69,8 @@ def _preview_requested(self): "0", "false", ) - self.request.toolbar.preview_mode_active = preview_mode + if hasattr(self.request, "toolbar"): + self.request.toolbar.preview_mode_active = preview_mode return preview_mode @property From 701ede2fbaa6c8913f6a7134937d961cbe3f6590 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Fri, 24 Oct 2025 21:53:43 +0200 Subject: [PATCH 2/5] fix: Add toolbar object to request for preview mode (if not existent) --- djangocms_rest/views_base.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/djangocms_rest/views_base.py b/djangocms_rest/views_base.py index 42ff366..3787579 100644 --- a/djangocms_rest/views_base.py +++ b/djangocms_rest/views_base.py @@ -2,6 +2,9 @@ from django.contrib.sites.shortcuts import get_current_site from django.utils.functional import cached_property + +from cms.toolbar.toolbar import CMSToolbar + from rest_framework.generics import ListAPIView from rest_framework.permissions import IsAdminUser from rest_framework.views import APIView @@ -69,8 +72,10 @@ def _preview_requested(self): "0", "false", ) - if hasattr(self.request, "toolbar"): - self.request.toolbar.preview_mode_active = preview_mode + if preview_mode: + if not hasattr(self.request, "toolbar"): # Create toolbar if not present to mark preview mode + self.request.toolbar = CMSToolbar(self.request) + self.request.toolbar.preview_mode_active = True return preview_mode @property From 80d7e4e386a055ea9ddd932e8d87dbf0cb514a69 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Fri, 24 Oct 2025 22:21:26 +0200 Subject: [PATCH 3/5] feat: Add preview mode to "details" fields --- djangocms_rest/serializers/menus.py | 11 +++++++++-- djangocms_rest/serializers/pages.py | 5 +++++ djangocms_rest/serializers/placeholders.py | 12 ++++++++---- djangocms_rest/views_base.py | 22 +++++++++++++--------- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/djangocms_rest/serializers/menus.py b/djangocms_rest/serializers/menus.py index 14a809e..e4c7bf8 100644 --- a/djangocms_rest/serializers/menus.py +++ b/djangocms_rest/serializers/menus.py @@ -28,12 +28,19 @@ def get_children(self, obj: NavigationNode) -> list[dict]: def to_representation(self, obj: NavigationNode) -> dict: """Customize the base representation of the NavigationNode.""" + path = getattr(obj, "api_endpoint", "") + api_endpoint = get_absolute_frontend_url(self.request, path) if path else "" + if self.request._preview_mode: + if "?" in api_endpoint: + api_endpoint += "&preview=1" + else: + api_endpoint += "?preview=1" return { "namespace": getattr(obj, "namespace", None), "title": obj.title, "url": get_absolute_frontend_url(self.request, obj.url) or "", - "api_endpoint": get_absolute_frontend_url(self.request, getattr(obj, "api_endpoint", None)) or "", - "path": getattr(obj, "api_endpoint", ""), + "api_endpoint": api_endpoint, + "path": path, "visible": obj.visible, "selected": obj.selected or obj.attr.get("is_home", False) and getattr(self.request, "is_home", False), "attr": obj.attr, diff --git a/djangocms_rest/serializers/pages.py b/djangocms_rest/serializers/pages.py index 2b12e48..292e29d 100644 --- a/djangocms_rest/serializers/pages.py +++ b/djangocms_rest/serializers/pages.py @@ -42,6 +42,11 @@ def get_base_representation(self, page_content: PageContent) -> dict: path = page_content.page.get_path(page_content.language) absolute_url = get_absolute_frontend_url(request, path) api_endpoint = get_absolute_frontend_url(request, page_content.page.get_api_endpoint(page_content.language)) + if self.is_preview: + if "?" in api_endpoint: + api_endpoint += "&preview=1" + else: + api_endpoint += "?preview=1" redirect = str(page_content.redirect or "") xframe_options = str(page_content.xframe_options or "") application_namespace = str(page_content.page.application_namespace or "") diff --git a/djangocms_rest/serializers/placeholders.py b/djangocms_rest/serializers/placeholders.py index a031973..dde3f3c 100644 --- a/djangocms_rest/serializers/placeholders.py +++ b/djangocms_rest/serializers/placeholders.py @@ -11,9 +11,7 @@ class PlaceholderSerializer(serializers.Serializer): slot = serializers.CharField() label = serializers.CharField() language = serializers.CharField() - content = serializers.ListSerializer( - child=serializers.JSONField(), allow_empty=True, required=False - ) + content = serializers.ListSerializer(child=serializers.JSONField(), allow_empty=True, required=False) html = serializers.CharField(default="", required=False) def __init__(self, *args, **kwargs): @@ -64,7 +62,7 @@ def to_representation(self, instance): return super().to_representation(instance) def get_details(self, instance): - return get_absolute_frontend_url( + api_endpoint = get_absolute_frontend_url( self.request, reverse( "placeholder-detail", @@ -76,3 +74,9 @@ def get_details(self, instance): ], ), ) + if self.request._preview_mode: + if "?" in api_endpoint: + api_endpoint += "&preview=1" + else: + api_endpoint += "?preview=1" + return api_endpoint diff --git a/djangocms_rest/views_base.py b/djangocms_rest/views_base.py index 3787579..f9723a1 100644 --- a/djangocms_rest/views_base.py +++ b/djangocms_rest/views_base.py @@ -68,15 +68,19 @@ def site(self): return site if site is not None else get_current_site(self.request) def _preview_requested(self): - preview_mode = "preview" in self.request.GET and self.request.GET.get("preview", "").lower() not in ( - "0", - "false", - ) - if preview_mode: - if not hasattr(self.request, "toolbar"): # Create toolbar if not present to mark preview mode - self.request.toolbar = CMSToolbar(self.request) - self.request.toolbar.preview_mode_active = True - return preview_mode + if not hasattr(self.request, "_preview_mode"): + # Cache to not re-generate toolbar object for preview requests + self.request._preview_mode = "preview" in self.request.GET and self.request.GET.get( + "preview", "" + ).lower() not in ( + "0", + "false", + ) + if self.request._preview_mode: + if not hasattr(self.request, "toolbar"): # Create toolbar if not present to mark preview mode + self.request.toolbar = CMSToolbar(self.request) + self.request.toolbar.preview_mode_active = True + return self.request._preview_mode @property def content_getter(self): From 3203b76993b7bc9df70c1a95386019f8a6189976 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Fri, 24 Oct 2025 22:33:18 +0200 Subject: [PATCH 4/5] fix: EmptyPageContent did raise an exception --- djangocms_rest/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangocms_rest/views.py b/djangocms_rest/views.py index 0a9a550..3e6c291 100644 --- a/djangocms_rest/views.py +++ b/djangocms_rest/views.py @@ -177,7 +177,7 @@ def get(self, request: Request, language: str, path: str = "") -> Response: try: page_content = getattr(page, self.content_getter)(language, fallback=True) - if page_content is None: + if not page_content: raise PageContent.DoesNotExist() serializer = self.serializer_class(page_content, read_only=True, context={"request": request}) return Response(serializer.data) From 52969c22ba8b354e0f3b8a9b5f2b0d4ff35d7ec3 Mon Sep 17 00:00:00 2001 From: Fabian Braun Date: Fri, 24 Oct 2025 23:12:33 +0200 Subject: [PATCH 5/5] fix: See preview placeholders at page endpoint --- djangocms_rest/serializers/pages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/djangocms_rest/serializers/pages.py b/djangocms_rest/serializers/pages.py index 292e29d..c7cb10d 100644 --- a/djangocms_rest/serializers/pages.py +++ b/djangocms_rest/serializers/pages.py @@ -1,6 +1,7 @@ from django.db import models from cms.models import PageContent +from cms.utils.placeholder import get_declared_placeholders_for_obj from rest_framework import serializers @@ -137,7 +138,7 @@ def __init__(self, *args, **kwargs): self.request = self.context.get("request") def to_representation(self, page_content: PageContent) -> dict: - declared_slots = [placeholder.slot for placeholder in page_content.page.get_declared_placeholders()] + declared_slots = [placeholder.slot for placeholder in get_declared_placeholders_for_obj(page_content)] placeholders = [ placeholder for placeholder in page_content.placeholders.all() if placeholder.slot in declared_slots ]