Skip to content
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
266bce7
feat: Add RESTRenderer
fsbraun Jul 8, 2025
1a6a8fd
ruff issues
fsbraun Jul 8, 2025
870f30d
Add json formatting
fsbraun Jul 8, 2025
e03978a
Fix placeholder rendering
fsbraun Jul 8, 2025
4edcc0f
feat: collapse objects
fsbraun Jul 8, 2025
c7b57b6
better evt handling
fsbraun Jul 8, 2025
ae98857
update formatting
fsbraun Jul 8, 2025
3914853
Remove js
fsbraun Jul 9, 2025
0060988
css tweaks
fsbraun Jul 9, 2025
81c7f43
Robust plugin moving
fsbraun Jul 9, 2025
eeb624d
fix rendering
fsbraun Jul 9, 2025
61e6153
Unify rendering
fsbraun Jul 15, 2025
492ffff
Fix linting
fsbraun Jul 15, 2025
adadf8a
Sort imports
fsbraun Jul 15, 2025
bdb66de
Add tests
fsbraun Jul 15, 2025
baba357
Fix rendering bug
fsbraun Jul 15, 2025
6225467
Fix bool formatting
fsbraun Jul 15, 2025
51f9c85
Add link resolution
fsbraun Jul 16, 2025
36c07c6
Fix tests
fsbraun Jul 16, 2025
9f7f8f4
Improve tests
fsbraun Jul 16, 2025
6711164
Add bs4 test requirement
fsbraun Jul 16, 2025
98ba50a
Add tests for fk serialization
fsbraun Jul 16, 2025
1854a71
Update djangocms_rest/serializers/pages.py
fsbraun Jul 16, 2025
a8f45ef
Update djangocms_rest/apps.py
fsbraun Jul 16, 2025
4f91903
Update djangocms_rest/cms_config.py
fsbraun Jul 16, 2025
256b2cd
Update djangocms_rest/cms_config.py
fsbraun Jul 16, 2025
abee91d
Update djangocms_rest/serializers/plugins.py
fsbraun Jul 16, 2025
34b0fa9
Add test for preview endpoint
fsbraun Jul 16, 2025
721bf5b
Removed last trailing comma
fsbraun Jul 16, 2025
5026714
Add comment
fsbraun Jul 16, 2025
c4f8366
Add setting for JSON view and update readme
fsbraun Jul 16, 2025
3db89c4
Fix tests
fsbraun Jul 16, 2025
ebad887
Update plugin-list
fsbraun Jul 24, 2025
9f41aac
Remove unused method
fsbraun Jul 24, 2025
0ad048c
Test enum types
fsbraun Jul 24, 2025
4c05132
Add test for nested serializer
fsbraun Jul 24, 2025
ea97287
Remove debug code
fsbraun Jul 24, 2025
346bd63
Add autolinks
fsbraun Jul 24, 2025
e6a9442
feat: Add ellipses for folded json
fsbraun Aug 1, 2025
b2429eb
fix: reflect site when getting current language
fsbraun Aug 7, 2025
f8f733f
Merge branch 'main' into feat/json-edit
fsbraun Aug 19, 2025
94db37c
fix: Import get_current_site
fsbraun Aug 19, 2025
bbe4863
Merge branch 'feat/json-edit' of github.com:fsbraun/djangocms-rest in…
fsbraun Aug 19, 2025
6a027f1
fix: Import cycle
fsbraun Aug 19, 2025
20f4a0b
Feat/json edit review (#44)
metaforx Aug 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ projects, this is often not the case.

**Limitations and considerations in headless mode:**

- Inline editing and content preview are currently only available in a structured view. (Solutions
are currently being evaluated).
- Inline editing and content preview are available as JSON views on both edit and preview mode. Turn
JSON rendering on and of using the `REST_JSON_RENDERING` setting.
- Not all features of a standard Django CMS are available through the API (eg. templates and tags).
- The API focuses on fetching plugin content and page structure as JSON data.
- Website rendering is entirely decoupled and must be implemented in the frontend framework.
Expand Down Expand Up @@ -140,12 +140,15 @@ class CustomHeadingPluginModel(CMSPlugin):
Yes, djangocms-rest provides out of the box support for any and all django CMS plugins whose content
can be serialized.

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

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

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

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

Expand Down
12 changes: 12 additions & 0 deletions djangocms_rest/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.apps import AppConfig


class DjangocmsRestConfig(AppConfig):
"""
AppConfig for the djangocms_rest application.
This application provides RESTful APIs for Django CMS.
"""

default_auto_field = "django.db.models.BigAutoField"
name = "djangocms_rest"
verbose_name = "Django CMS REST API"
75 changes: 75 additions & 0 deletions djangocms_rest/cms_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from functools import cached_property

from django.conf import settings
from django.urls import NoReverseMatch, reverse

from cms.app_base import CMSAppConfig
from cms.models import Page
from cms.utils.i18n import force_language, get_current_language


try:
from filer.models import File
except ImportError:
File = None


def get_page_api_endpoint(page, language=None, fallback=True):
"""Get the API endpoint for a given page in a specific language.
If the page is a home page, return the root endpoint.
"""
if not language:
language = get_current_language()

with force_language(language):
try:
if page.is_home:
return reverse("page-root", kwargs={"language": language})
path = page.get_path(language, fallback)
return (
reverse("page-detail", kwargs={"language": language, "path": path})
if path
else None
)
except NoReverseMatch:
return None


def get_file_api_endpoint(file):
"""For a file reference, return the URL of the file if it is public."""
if not file:
return None
return file.url if file.is_public else None


class RESTToolbarMixin:
"""
Mixin to add REST rendering capabilities to the CMS toolbar.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

if getattr(
settings, "REST_JSON_RENDERING", not getattr(settings, "CMS_TEMPLATES", False)
):
try:
from djangocms_text import settings

settings.TEXT_INLINE_EDITING = False
except ImportError:

Check notice

Code scanning / CodeQL

Empty except Note

'except' clause does nothing but pass and there is no explanatory comment.

Copilot Autofix

AI 16 days ago

To fix the problem, we should ensure that the except ImportError: block on line 60 does not silently ignore the exception. The best way to do this without changing existing functionality is to add a comment explaining why the exception is being ignored, or to log the error for debugging purposes. Since the code appears to be designed to work even if the import fails, adding a comment is sufficient and least intrusive. The change should be made in the file djangocms_rest/cms_config.py, specifically in the except ImportError: block on line 60.

Suggested changeset 1
djangocms_rest/cms_config.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/djangocms_rest/cms_config.py b/djangocms_rest/cms_config.py
--- a/djangocms_rest/cms_config.py
+++ b/djangocms_rest/cms_config.py
@@ -58,6 +58,7 @@
 
             settings.TEXT_INLINE_EDITING = False
         except ImportError:
+            # djangocms_text is not installed; inline editing will not be disabled.
             pass
 
         @cached_property
EOF
@@ -58,6 +58,7 @@

settings.TEXT_INLINE_EDITING = False
except ImportError:
# djangocms_text is not installed; inline editing will not be disabled.
pass

@cached_property
Copilot is powered by AI and may make mistakes. Always verify output.
pass

@cached_property
def content_renderer(self):
from .plugin_rendering import RESTRenderer

return RESTRenderer(request=self.request)


class RESTCMSConfig(CMSAppConfig):
cms_enabled = True
cms_toolbar_mixin = RESTToolbarMixin

Page.add_to_class("get_api_endpoint", get_page_api_endpoint)
File.add_to_class("get_api_endpoint", get_file_api_endpoint) if File else None
8 changes: 8 additions & 0 deletions djangocms_rest/cms_toolbars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from cms.toolbar_base import CMSToolbar
from cms.toolbar_pool import toolbar_pool


@toolbar_pool.register
class RestToolbar(CMSToolbar):
class Media:
css = {"all": ("djangocms_rest/highlight.css",)}
4 changes: 3 additions & 1 deletion djangocms_rest/permissions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from cms.models import Page, PageContent
from cms.utils import get_current_site
from cms.utils.i18n import get_language_tuple, get_languages
from cms.utils.page_permissions import user_can_view_page

from rest_framework.exceptions import NotFound
from rest_framework.permissions import BasePermission
from rest_framework.request import Request
Expand Down Expand Up @@ -30,7 +32,7 @@ class IsAllowedPublicLanguage(IsAllowedLanguage):
def has_permission(self, request: Request, view: BaseAPIView) -> bool:
super().has_permission(request, view)
language = view.kwargs.get("language")
languages = get_languages()
languages = get_languages(get_current_site(request).pk)
public_languages = [
lang["code"] for lang in languages if lang.get("public", True)
]
Expand Down
Loading
Loading