Skip to content

feat: Add RESTRenderer #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
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:
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