Skip to content

Commit 1a5d292

Browse files
authored
fix: Add menu endpoint with root_id for the root page
fix: Add menu endpoint with root_id for the root page fix: #62
2 parents 2841b49 + f4482f0 commit 1a5d292

File tree

3 files changed

+43
-31
lines changed

3 files changed

+43
-31
lines changed

djangocms_rest/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
views.MenuView.as_view(),
4949
name="menu",
5050
),
51+
path(
52+
"<slug:language>/menu/<slug:root_id>/<int:from_level>/<int:to_level>/<int:extra_inactive>/<int:extra_active>/",
53+
views.MenuView.as_view(),
54+
name="menu",
55+
),
5156
path(
5257
"<slug:language>/menu/<slug:root_id>/<int:from_level>/<int:to_level>/<int:extra_inactive>/<int:extra_active>/<path:path>/",
5358
views.MenuView.as_view(),

djangocms_rest/views.py

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from cms.models import Page, PageContent, Placeholder
99
from cms.utils.conf import get_languages
1010
from cms.utils.page_permissions import user_can_view_page
11+
from menus.menu_pool import menu_pool
1112
from menus.templatetags.menu_tags import ShowBreadcrumb, ShowMenu, ShowSubMenu
1213

1314

@@ -90,9 +91,7 @@ def extend_placeholder_schema(func: Callable[P, T]) -> Callable[P, T]:
9091
# Attn: Dynamic changes to the plugin pool will not be reflected in the
9192
# plugin definitions.
9293
# If you need to update the plugin definitions, you need to reassign the variable.
93-
PLUGIN_DEFINITIONS = lazy(
94-
PluginDefinitionSerializer.generate_plugin_definitions, dict
95-
)()
94+
PLUGIN_DEFINITIONS = lazy(PluginDefinitionSerializer.generate_plugin_definitions, dict)()
9695

9796

9897
class LanguageListView(BaseAPIView):
@@ -161,9 +160,7 @@ def get(self, request, language):
161160
except PageContent.DoesNotExist:
162161
raise NotFound()
163162

164-
serializer = self.serializer_class(
165-
pages, many=True, read_only=True, context={"request": request}
166-
)
163+
serializer = self.serializer_class(pages, many=True, read_only=True, context={"request": request})
167164
return Response(serializer.data)
168165

169166

@@ -182,9 +179,7 @@ def get(self, request: Request, language: str, path: str = "") -> Response:
182179
page_content = getattr(page, self.content_getter)(language, fallback=True)
183180
if page_content is None:
184181
raise PageContent.DoesNotExist()
185-
serializer = self.serializer_class(
186-
page_content, read_only=True, context={"request": request}
187-
)
182+
serializer = self.serializer_class(page_content, read_only=True, context={"request": request})
188183
return Response(serializer.data)
189184
except PageContent.DoesNotExist:
190185
raise NotFound()
@@ -216,19 +211,13 @@ def get(
216211
- "html": The content rendered as html. Sekizai blocks such as "js" or "css" will be added
217212
as separate attributes"""
218213
try:
219-
placeholder = Placeholder.objects.get(
220-
content_type_id=content_type_id, object_id=object_id, slot=slot
221-
)
214+
placeholder = Placeholder.objects.get(content_type_id=content_type_id, object_id=object_id, slot=slot)
222215
except Placeholder.DoesNotExist:
223216
raise NotFound()
224217

225218
source_model = placeholder.content_type.model_class()
226219
content_manager = "admin_manager" if self._preview_requested() else "content"
227-
source = (
228-
getattr(source_model, content_manager, source_model.objects)
229-
.filter(pk=placeholder.object_id)
230-
.first()
231-
)
220+
source = getattr(source_model, content_manager, source_model.objects).filter(pk=placeholder.object_id).first()
232221

233222
if source is None:
234223
raise NotFound()
@@ -242,9 +231,7 @@ def get(
242231

243232
self.check_object_permissions(request, placeholder)
244233

245-
serializer = self.serializer_class(
246-
instance=placeholder, request=request, language=language, read_only=True
247-
)
234+
serializer = self.serializer_class(instance=placeholder, request=request, language=language, read_only=True)
248235
return Response(serializer.data)
249236

250237

@@ -278,9 +265,7 @@ def method_schema_decorator(method):
278265
Decorator for adding OpenAPI schema to a method.
279266
Needed to force the schema to use many=True for NavigationNodeSerializer.
280267
"""
281-
return extend_schema(
282-
responses=OpenApiResponse(response=NavigationNodeSerializer(many=True))
283-
)(method)
268+
return extend_schema(responses=OpenApiResponse(response=NavigationNodeSerializer(many=True)))(method)
284269

285270
except ImportError: # pragma: no cover
286271

@@ -306,9 +291,7 @@ def get(
306291
"""Get the menu structure for a specific language and path."""
307292
self.populate_defaults(kwargs)
308293
menu = self.get_menu_structure(request, language, path, **kwargs)
309-
serializer = self.serializer_class(
310-
menu, many=True, context={"request": request}
311-
)
294+
serializer = self.serializer_class(menu, many=True, context={"request": request})
312295
return Response(serializer.data)
313296

314297
def populate_defaults(self, kwargs: dict[str, Any]) -> None:
@@ -341,22 +324,31 @@ def get_menu_structure(
341324
if path == "":
342325
api_endpoint = reverse("page-root", kwargs={"language": language})
343326
else:
344-
api_endpoint = reverse(
345-
"page-detail", kwargs={"language": language, "path": path}
346-
)
327+
api_endpoint = reverse("page-detail", kwargs={"language": language, "path": path})
347328

348329
request.api_endpoint = api_endpoint
349330
request.LANGUAGE_CODE = language
350331
request.current_page = get_object(self.site, path)
351332
self.check_object_permissions(request, request.current_page)
352-
context = {"request": request}
333+
menu_renderer = menu_pool.get_renderer(request)
334+
menu_renderer.site = self.site
335+
context = {"request": request, "cms_menu_renderer": menu_renderer}
353336

354337
context = tag_instance.get_context(
355338
context=context,
356339
**kwargs,
357340
template=None,
358341
)
359-
return context.get(self.return_key, [])
342+
result = context.get(self.return_key, [])
343+
if not result and kwargs.get("root_id"):
344+
# Edge case: No menu nodes found but a root_id was specified.
345+
# This might be due to a non-existing root_id.
346+
nodes = menu_renderer.get_nodes(kwargs.get("namespace"), kwargs["root_id"])
347+
id_nodes = menu_pool.get_nodes_by_attribute(nodes, "reverse_id", kwargs["root_id"])
348+
if not id_nodes:
349+
raise NotFound()
350+
351+
return result
360352

361353

362354
class SubMenuView(MenuView):

tests/endpoints/test_menu.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,21 @@ def test_default_levels(self):
109109
self.assertNotEqual(url1, url2)
110110
self.assertEqual(results1, results2)
111111

112+
def test_non_existing_root_id(self):
113+
url = reverse(
114+
"menu",
115+
kwargs={
116+
"language": "en",
117+
"from_level": 0,
118+
"to_level": 100,
119+
"extra_inactive": 0,
120+
"extra_active": 100,
121+
"root_id": "I_DO_NOT_EXIST",
122+
},
123+
)
124+
response = self.client.get(url)
125+
self.assertEqual(response.status_code, 404)
126+
112127
def test_submenu(self):
113128
# GET
114129
url = reverse(

0 commit comments

Comments
 (0)