Skip to content

Commit 973dea2

Browse files
fsbraunsourcery-ai[bot]metaforx
authored
feat: Add RESTRenderer (#42)
* feat: Add RESTRenderer * ruff issues * Add json formatting * Fix placeholder rendering * feat: collapse objects * better evt handling * update formatting * Remove js * css tweaks * Robust plugin moving * fix rendering * Unify rendering * Fix linting * Sort imports * Add tests * Fix rendering bug * Fix bool formatting * Add link resolution * Fix tests * Improve tests * Add bs4 test requirement * Add tests for fk serialization * Update djangocms_rest/serializers/pages.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update djangocms_rest/apps.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update djangocms_rest/cms_config.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update djangocms_rest/cms_config.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update djangocms_rest/serializers/plugins.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Add test for preview endpoint * Removed last trailing comma * Add comment * Add setting for JSON view and update readme * Fix tests * Update plugin-list * Remove unused method * Test enum types * Add test for nested serializer * Remove debug code * Add autolinks * feat: Add ellipses for folded json * fix: reflect site when getting current language * fix: Import get_current_site * fix: Import cycle * Feat/json edit review (#44) * fix: add render support for nested-only plugins * fix: TypeError: cannot pickle 'generator' object with caching * docs: fix typo and structure * Update djangocms_rest/serializers/plugins.py * Update djangocms_rest/serializers/plugins.py --------- Co-authored-by: Fabian Braun <[email protected]> --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Marc Widmer <[email protected]>
1 parent 05ad083 commit 973dea2

37 files changed

+1561
-483
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ projects, this is often not the case.
6666

6767
**Limitations and considerations in headless mode:**
6868

69-
- Inline editing and content preview are currently only available in a structured view. (Solutions
70-
are currently being evaluated).
71-
- Not all features of a standard Django CMS are available through the API (eg. templates and tags).
69+
- Inline editing and content preview are available as JSON views on both edit and preview mode. Turn
70+
JSON rendering on and off using the `REST_JSON_RENDERING` setting.
7271
- The API focuses on fetching plugin content and page structure as JSON data.
7372
- Website rendering is entirely decoupled and must be implemented in the frontend framework.
73+
- Not (yet) all features of a standard Django CMS are available through the API (eg. Menu).
7474

7575
## Are there js packages for drop-in support of frontend editing in the javascript framework of my choice?
7676

@@ -140,12 +140,15 @@ class CustomHeadingPluginModel(CMSPlugin):
140140
Yes, djangocms-rest provides out of the box support for any and all django CMS plugins whose content
141141
can be serialized.
142142

143+
Custom DRF serializers can be declared for custom plugins by setting its `serializer_class` property.
143144

144145
## Does the TextPlugin (Rich Text Editor, RTE) provide a json representation of the rich text?
145146

146147
Yes, djangocms-text has both HTML blob and structured JSON support for rich text.
147148

148-
URLs to other CMS objects are dynamic, in the form of `<app-name>.<object-name>:<uid>`, for example
149+
URLs to other Django model objects are dynamic and resolved to API endpoints if possible. If the referenced model
150+
provides a `get_api_endpoint()` method, it is used for resolution. If not, djangocms-rest tries to reverse `<model-name>-detail`.
151+
If resolution fails dynamic objects are returned in the form of `<app-name>.<object-name>:<uid>`, for example
149152
`cms.page:2`. The frontend can then use this to resolve the object and create the appropriate URLs
150153
to the object's frontend representation.
151154

djangocms_rest/apps.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from django.apps import AppConfig
2+
3+
4+
class DjangocmsRestConfig(AppConfig):
5+
"""
6+
AppConfig for the djangocms_rest application.
7+
This application provides RESTful APIs for Django CMS.
8+
"""
9+
10+
default_auto_field = "django.db.models.BigAutoField"
11+
name = "djangocms_rest"
12+
verbose_name = "Django CMS REST API"

djangocms_rest/cms_config.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from functools import cached_property
2+
3+
from django.conf import settings
4+
from django.urls import NoReverseMatch, reverse
5+
6+
from cms.app_base import CMSAppConfig
7+
from cms.models import Page
8+
from cms.utils.i18n import force_language, get_current_language
9+
10+
11+
try:
12+
from filer.models import File
13+
except ImportError:
14+
File = None
15+
16+
17+
def get_page_api_endpoint(page, language=None, fallback=True):
18+
"""Get the API endpoint for a given page in a specific language.
19+
If the page is a home page, return the root endpoint.
20+
"""
21+
if not language:
22+
language = get_current_language()
23+
24+
with force_language(language):
25+
try:
26+
if page.is_home:
27+
return reverse("page-root", kwargs={"language": language})
28+
path = page.get_path(language, fallback)
29+
return (
30+
reverse("page-detail", kwargs={"language": language, "path": path})
31+
if path
32+
else None
33+
)
34+
except NoReverseMatch:
35+
return None
36+
37+
38+
def get_file_api_endpoint(file):
39+
"""For a file reference, return the URL of the file if it is public."""
40+
if not file:
41+
return None
42+
return file.url if file.is_public else None
43+
44+
45+
class RESTToolbarMixin:
46+
"""
47+
Mixin to add REST rendering capabilities to the CMS toolbar.
48+
"""
49+
50+
def __init__(self, *args, **kwargs):
51+
super().__init__(*args, **kwargs)
52+
53+
if getattr(
54+
settings, "REST_JSON_RENDERING", not getattr(settings, "CMS_TEMPLATES", False)
55+
):
56+
try:
57+
from djangocms_text import settings
58+
59+
settings.TEXT_INLINE_EDITING = False
60+
except ImportError:
61+
pass
62+
63+
@cached_property
64+
def content_renderer(self):
65+
from .plugin_rendering import RESTRenderer
66+
67+
return RESTRenderer(request=self.request)
68+
69+
70+
class RESTCMSConfig(CMSAppConfig):
71+
cms_enabled = True
72+
cms_toolbar_mixin = RESTToolbarMixin
73+
74+
Page.add_to_class("get_api_endpoint", get_page_api_endpoint)
75+
File.add_to_class("get_api_endpoint", get_file_api_endpoint) if File else None

djangocms_rest/cms_toolbars.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from cms.toolbar_base import CMSToolbar
2+
from cms.toolbar_pool import toolbar_pool
3+
4+
5+
@toolbar_pool.register
6+
class RestToolbar(CMSToolbar):
7+
class Media:
8+
css = {"all": ("djangocms_rest/highlight.css",)}

djangocms_rest/permissions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from django.contrib.sites.shortcuts import get_current_site
2+
13
from cms.models import Page, PageContent
24
from cms.utils.i18n import get_language_tuple, get_languages
35
from cms.utils.page_permissions import user_can_view_page
6+
47
from rest_framework.exceptions import NotFound
58
from rest_framework.permissions import BasePermission
69
from rest_framework.request import Request
@@ -30,7 +33,7 @@ class IsAllowedPublicLanguage(IsAllowedLanguage):
3033
def has_permission(self, request: Request, view: BaseAPIView) -> bool:
3134
super().has_permission(request, view)
3235
language = view.kwargs.get("language")
33-
languages = get_languages()
36+
languages = get_languages(get_current_site(request).pk)
3437
public_languages = [
3538
lang["code"] for lang in languages if lang.get("public", True)
3639
]

0 commit comments

Comments
 (0)