Skip to content

Commit bdb66de

Browse files
committed
Add tests
1 parent adadf8a commit bdb66de

File tree

12 files changed

+453
-160
lines changed

12 files changed

+453
-160
lines changed

djangocms_rest/cms_config.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
from functools import cached_property
22

3+
from django.urls import NoReverseMatch, reverse
4+
35
from cms.app_base import CMSAppConfig
6+
from cms.models import Page
7+
from cms.utils.i18n import force_language, get_current_language
8+
9+
10+
try:
11+
from filer.models import File
12+
except (ImportError, ModuleNotFoundError):
13+
File = None
14+
15+
16+
def get_page_api_endpoint(page, language=None, fallback=True):
17+
"""Get the API endpoint for a given page in a specific language.
18+
If the page is a home page, return the root endpoint.
19+
"""
20+
if not language:
21+
language = get_current_language()
22+
23+
with force_language(language):
24+
try:
25+
if page.is_home:
26+
return reverse("page-root", kwargs={"language": language})
27+
path = page.get_path(language, fallback)
28+
return (
29+
reverse("page-detail", kwargs={"language": language, "path": path})
30+
if path
31+
else None
32+
)
33+
except NoReverseMatch:
34+
return None
35+
36+
37+
def get_file_api_endpoint(file):
38+
"""For a file reference, return the URL of the file if it is public."""
39+
if not file:
40+
return None
41+
if file.is_public:
42+
return file.url
43+
return None
444

545

646
class RESTToolbarMixin:
@@ -18,6 +58,9 @@ def content_renderer(self):
1858
return RESTRenderer(request=self.request)
1959

2060

21-
class VersioningCMSConfig(CMSAppConfig):
61+
class RESTCMSConfig(CMSAppConfig):
2262
cms_enabled = True
2363
cms_toolbar_mixin = RESTToolbarMixin
64+
65+
Page.add_to_class("get_api_endpoint", get_page_api_endpoint)
66+
File.add_to_class("get_api_endpoint", get_file_api_endpoint) if File else None

djangocms_rest/plugin_rendering.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
from cms.plugin_rendering import ContentRenderer
99
from cms.utils.plugins import get_plugins
1010

11-
from rest_framework import serializers
12-
1311
from djangocms_rest.serializers.placeholders import PlaceholderSerializer
12+
from djangocms_rest.serializers.plugins import GenericPluginSerializer
1413
from djangocms_rest.serializers.utils.cache import (
1514
get_placeholder_rest_cache,
1615
set_placeholder_rest_cache,
@@ -50,7 +49,7 @@ def get_auto_model_serializer(model_class: type[ModelType]) -> type:
5049
)
5150
return type(
5251
f"{model_class.__name__}AutoSerializer",
53-
(serializers.ModelSerializer,),
52+
(GenericPluginSerializer,),
5453
{
5554
"Meta": meta_class,
5655
},

djangocms_rest/serializers/plugins.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import Any, Optional
22

33
from django.core.exceptions import FieldDoesNotExist
4-
from django.db.models import Field
4+
from django.db.models import Field, ForeignKey
5+
from django.urls import NoReverseMatch, reverse
56

67
from cms.models import CMSPlugin
78
from cms.plugin_pool import plugin_pool
@@ -14,13 +15,28 @@ def to_representation(self, instance: CMSPlugin):
1415
ret = super().to_representation(instance)
1516
for field in self.Meta.model._meta.get_fields():
1617
if field.is_relation and not field.many_to_many and not field.one_to_many:
17-
field_name = field.name
18-
if field_name in ret and getattr(instance, field_name, None):
19-
ret[field_name] = self.serialize_fk(field)
18+
if field.name in ret and getattr(instance, field.name, None):
19+
ret[field.name] = self.serialize_fk(instance, field)
2020
return ret
2121

22-
def serialize_fk(self, related_obj):
23-
pass
22+
def serialize_fk(self, instance: CMSPlugin, field: ForeignKey) -> dict[str, Any]:
23+
# First choice: Check for get_api_endpoint method
24+
related_model = field.related_model
25+
if hasattr(related_model, "get_api_endpoint"):
26+
return getattr(getattr(instance, field.name), "get_api_endpoint")()
27+
28+
# Second choice: Use DRF naming conventions to build the default API URL for the related model
29+
model_name = related_model._meta.model_name
30+
try:
31+
return reverse(
32+
f"{model_name}_details", args=(getattr(instance, field.name + "_id"),)
33+
)
34+
except NoReverseMatch:
35+
pass
36+
37+
# Fallback:
38+
app_name = related_model._meta.app_label
39+
return f"{app_name}.{model_name}:{getattr(instance, field.name + '_id')}"
2440

2541

2642
class PluginDefinitionSerializer(serializers.Serializer):

djangocms_rest/serializers/utils/render.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,9 @@
1-
from typing import Any, Optional
2-
3-
from cms.models import CMSPlugin
41
from cms.plugin_rendering import ContentRenderer
52

6-
from rest_framework import serializers
73
from sekizai.context import SekizaiContext
84
from sekizai.helpers import get_varname
95

106

11-
def render_plugin(
12-
instance: CMSPlugin, context: dict[str, Any]
13-
) -> Optional[dict[str, Any]]:
14-
instance, plugin = instance.get_plugin_instance()
15-
if not instance:
16-
return None
17-
18-
class PluginSerializer(serializers.ModelSerializer):
19-
class Meta:
20-
model = instance.__class__
21-
exclude = (
22-
"id",
23-
"placeholder",
24-
"language",
25-
"position",
26-
"creation_date",
27-
"changed_date",
28-
"parent",
29-
)
30-
31-
json = PluginSerializer(instance, context=context).data
32-
return json
33-
34-
357
def render_html(request, placeholder, language):
368
content_renderer = ContentRenderer(request)
379
context = SekizaiContext({"request": request, "LANGUAGE_CODE": language})

tests/endpoints/test_placeholders.py

Lines changed: 93 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def setUpClass(cls):
1515
Add placeholder and plugin to a test page.
1616
"""
1717
super().setUpClass()
18+
1819
cls.page = create_page(
1920
title="Test Page",
2021
template="INHERIT",
@@ -34,21 +35,13 @@ def setUpClass(cls):
3435
"content": [
3536
{
3637
"type": "paragraph",
37-
"attrs": {
38-
"textAlign": "left"
39-
},
40-
"content": [
41-
{
42-
"text": "Test content",
43-
"type": "text"
44-
}
45-
]
38+
"attrs": {"textAlign": "left"},
39+
"content": [{"text": "Test content", "type": "text"}],
4640
}
47-
]
48-
}
41+
],
42+
},
4943
)
5044

51-
5245
def test_get(self):
5346
"""
5447
Tests the placeholder detail endpoint API functionality.
@@ -73,12 +66,15 @@ def test_get(self):
7366

7467
# GET request
7568
response = self.client.get(
76-
reverse("placeholder-detail", kwargs={
77-
"language": "en",
78-
"content_type_id": self.page_content_type.id,
79-
"object_id": self.page_content.id,
80-
"slot": "content"
81-
})
69+
reverse(
70+
"placeholder-detail",
71+
kwargs={
72+
"language": "en",
73+
"content_type_id": self.page_content_type.id,
74+
"object_id": self.page_content.id,
75+
"slot": "content",
76+
},
77+
)
8278
)
8379
self.assertEqual(response.status_code, 200)
8480
placeholder = response.json()
@@ -118,70 +114,111 @@ def test_get(self):
118114

119115
# Error case - Invalid language
120116
response = self.client.get(
121-
reverse("placeholder-detail", kwargs={
122-
"language": "xx",
123-
"content_type_id": self.page_content_type.id,
124-
"object_id": self.page_content.id,
125-
"slot": "content"
126-
})
117+
reverse(
118+
"placeholder-detail",
119+
kwargs={
120+
"language": "xx",
121+
"content_type_id": self.page_content_type.id,
122+
"object_id": self.page_content.id,
123+
"slot": "content",
124+
},
125+
)
127126
)
128127
self.assertEqual(response.status_code, 404)
129128

130129
# Error case - Invalid content type
131130
response = self.client.get(
132-
reverse("placeholder-detail", kwargs={
133-
"language": "en",
134-
"content_type_id": 99999,
135-
"object_id": self.page_content.id,
136-
"slot": "content"
137-
})
131+
reverse(
132+
"placeholder-detail",
133+
kwargs={
134+
"language": "en",
135+
"content_type_id": 99999,
136+
"object_id": self.page_content.id,
137+
"slot": "content",
138+
},
139+
)
138140
)
139141
self.assertEqual(response.status_code, 404)
140142

141143
# Error case - Invalid object ID
142144
response = self.client.get(
143-
reverse("placeholder-detail", kwargs={
144-
"language": "en",
145-
"content_type_id": self.page_content_type.id,
146-
"object_id": 99999,
147-
"slot": "content"
148-
})
145+
reverse(
146+
"placeholder-detail",
147+
kwargs={
148+
"language": "en",
149+
"content_type_id": self.page_content_type.id,
150+
"object_id": 99999,
151+
"slot": "content",
152+
},
153+
)
149154
)
150155
self.assertEqual(response.status_code, 404)
151156

152157
# Error case - Invalid slot
153158
response = self.client.get(
154-
reverse("placeholder-detail", kwargs={
155-
"language": "en",
156-
"content_type_id": self.page_content_type.id,
157-
"object_id": self.page_content.id,
158-
"slot": "nonexistent"
159-
})
159+
reverse(
160+
"placeholder-detail",
161+
kwargs={
162+
"language": "en",
163+
"content_type_id": self.page_content_type.id,
164+
"object_id": self.page_content.id,
165+
"slot": "nonexistent",
166+
},
167+
)
160168
)
161169
self.assertEqual(response.status_code, 404)
162170

163-
164171
# GET PREVIEW
165172
response = self.client.get(
166-
reverse("preview-placeholder-detail", kwargs={
167-
"language": "en",
168-
"content_type_id": self.page_content_type.id,
169-
"object_id": self.page_content.id,
170-
"slot": "content"
171-
})
173+
reverse(
174+
"preview-placeholder-detail",
175+
kwargs={
176+
"language": "en",
177+
"content_type_id": self.page_content_type.id,
178+
"object_id": self.page_content.id,
179+
"slot": "content",
180+
},
181+
)
172182
)
173183
self.assertEqual(response.status_code, 403)
174184

175-
176185
# GET PREVIEW - Protected
177186
def test_get_protected(self):
178187
self.client.force_login(self.user)
179188
response = self.client.get(
180-
reverse("preview-placeholder-detail", kwargs={
181-
"language": "en",
182-
"content_type_id": self.page_content_type.id,
183-
"object_id": self.page_content.id,
184-
"slot": "content"
185-
})
189+
reverse(
190+
"preview-placeholder-detail",
191+
kwargs={
192+
"language": "en",
193+
"content_type_id": self.page_content_type.id,
194+
"object_id": self.page_content.id,
195+
"slot": "content",
196+
},
197+
)
186198
)
187199
self.assertEqual(response.status_code, 200)
200+
201+
def test_serialize_page_fk(self):
202+
add_plugin(
203+
placeholder=self.placeholder,
204+
plugin_type="DummyLinkPlugin",
205+
language="en",
206+
page=self.page,
207+
label="Test Link",
208+
)
209+
210+
response = self.client.get(
211+
reverse(
212+
"placeholder-detail",
213+
kwargs={
214+
"language": "en",
215+
"content_type_id": self.page_content_type.id,
216+
"object_id": self.page_content.id,
217+
"slot": "content",
218+
},
219+
)
220+
)
221+
rendered_plugin = response.json()["content"][-1]
222+
self.assertIn("page", rendered_plugin)
223+
self.assertIsInstance(rendered_plugin["page"], str)
224+
self.assertEqual(rendered_plugin["page"], self.page.get_api_endpoint("en"))

tests/requirements/base.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# requirements from setup.py
22
djangorestframework
33
djangocms-text
4+
django-filer
45
setuptools
56

67
# other requirements

0 commit comments

Comments
 (0)