Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
wagtail-headless-preview
Advanced tools
With Wagtail as the backend, and a separate app for the front-end (for example a single page React app), editors are no longer able to preview their changes. This is because the front-end is no longer within Wagtail's direct control. The preview data therefore needs to be exposed to the front-end app.
This package enables previews for Wagtail pages when used in a headless setup by routing the preview to the specified front-end URL.
Install using pip:
pip install wagtail-headless-preview
After installing the module, add wagtail_headless_preview
to installed apps in your settings file:
# settings.py
INSTALLED_APPS = [
# ...
"wagtail_headless_preview",
]
Run migrations:
$ python manage.py migrate
Then configure the preview client URL using the CLIENT_URLS
option in the WAGTAIL_HEADLESS_PREVIEW
setting.
wagtail_headless_preview
uses a single settings dictionary:
# settings.py
WAGTAIL_HEADLESS_PREVIEW = {
"CLIENT_URLS": {}, # defaults to an empty dict. You must at the very least define the default client URL.
"SERVE_BASE_URL": None, # can be used for HeadlessServeMixin
"REDIRECT_ON_PREVIEW": False, # set to True to redirect to the preview instead of using the Wagtail default mechanism
"ENFORCE_TRAILING_SLASH": True, # set to False in order to disable the trailing slash enforcement
}
For single sites, add the front-end URL as the default entry:
WAGTAIL_HEADLESS_PREVIEW = {
"CLIENT_URLS": {
"default": "http://localhost:8020",
}
}
If you have configured your Wagtail Site
entry to use the front-end URL, then you can update your configuration to:
WAGTAIL_HEADLESS_PREVIEW = {
"CLIENT_URLS": {
"default": "{SITE_ROOT_URL}",
}
}
The {SITE_ROOT_URL}
placeholder is replaced with the root_url
property of the Site
the preview page belongs to.
For a multi-site setup, add each site as a separate entry in the CLIENT_URLS
option in the WAGTAIL_HEADLESS_PREVIEW
setting:
WAGTAIL_HEADLESS_PREVIEW = {
"CLIENT_URLS": {
"default": "https://wagtail.org", # adjust to match your front-end URL. e.g. locally it may be something like http://localhost:8020
"cms.wagtail.org": "https://wagtail.org",
"cms.torchbox.com": "http://torchbox.com",
},
# ...
}
To make the editing experience seamles and to avoid server errors due to missing templates,
you can use the HeadlessMixin
which combines the HeadlessServeMixin
and HeadlessPreviewMixin
mixins.
HeadlessServeMixin
overrides the Wagtail Page.serve
method to redirect to the client URL. By default,
it uses the hosts defined in CLIENT_URLS
. However, you can provide a single URL to rule them all:
# settings.py
WAGTAIL_HEADLESS_PREVIEW = {
# ...
"SERVE_BASE_URL": "https://my.headless.site",
}
By default, wagtail_headless_preview
enforces a trailing slash on the client URL. You can disable this behaviour by
setting ENFORCE_TRAILING_SLASH
to False
:
# settings.py
WAGTAIL_HEADLESS_PREVIEW = {
# ...
"ENFORCE_TRAILING_SLASH": False
}
To enable preview as well as wire in the "View live" button in the Wagtail UI, add the HeadlessMixin
to your Page
class:
from wagtail.models import Page
from wagtail_headless_preview.models import HeadlessMixin
class MyWonderfulPage(HeadlessMixin, Page):
pass
If you require more granular control, or if you've modified you Page
model's serve
method, you can
add HeadlessPreviewMixin
to your Page
class to only handle previews:
from wagtail.models import Page
from wagtail_headless_preview.models import HeadlessPreviewMixin
class MyWonderfulPage(HeadlessPreviewMixin, Page):
pass
This depends on your project, as it will be dictated by the requirements of your front-end app.
The following example uses a Wagtail API endpoint to access previews - your app may opt to access page previews using GraphQL instead.
This example sets up an API endpoint which will return the preview for a page, and then displays that data on a simplified demo front-end app.
wagtail.api.v2
to the installed apps:# settings.py
INSTALLED_APPS = [
# ...
"wagtail.api.v2",
]
api.py
file in your project directory:from django.contrib.contenttypes.models import ContentType
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.api.v2.views import PagesAPIViewSet
from wagtail_headless_preview.models import PagePreview
from rest_framework.response import Response
# Create the router. "wagtailapi" is the URL namespace
api_router = WagtailAPIRouter("wagtailapi")
class PagePreviewAPIViewSet(PagesAPIViewSet):
known_query_parameters = PagesAPIViewSet.known_query_parameters.union(
["content_type", "token"]
)
def listing_view(self, request):
# Delegate to detail_view, specifically so there's no
# difference between serialization formats.
self.action = "detail_view"
return self.detail_view(request, 0)
def detail_view(self, request, pk):
page = self.get_object()
serializer = self.get_serializer(page)
return Response(serializer.data)
def get_object(self):
app_label, model = self.request.GET["content_type"].split(".")
content_type = ContentType.objects.get(app_label=app_label, model=model)
page_preview = PagePreview.objects.get(
content_type=content_type, token=self.request.GET["token"]
)
page = page_preview.as_page()
if not page.pk:
# fake primary key to stop API URL routing from complaining
page.pk = 0
return page
api_router.register_endpoint("page_preview", PagePreviewAPIViewSet)
# urls.py
from .api import api_router
urlpatterns = [
# ...
path("api/v2/", api_router.urls),
# ...
# Ensure that the api_router line appears above the default Wagtail page serving route
path("", include(wagtail_urls)),
]
For further information about configuring the wagtail API, refer to the Wagtail API v2 Configuration Guide
client/index.html
file in your project root. This will query the API to display our preview:<!DOCTYPE html>
<html>
<head>
<script>
function go() {
var querystring = window.location.search.replace(/^\?/, '');
var params = {};
querystring.replace(/([^=&]+)=([^&]*)/g, function(m, key, value) {
params[decodeURIComponent(key)] = decodeURIComponent(value);
});
var apiUrl = 'http://localhost:8000/api/v2/page_preview/1/?content_type=' + encodeURIComponent(params['content_type']) + '&token=' + encodeURIComponent(params['token']) + '&format=json';
fetch(apiUrl).then(function(response) {
response.text().then(function(text) {
document.body.innerText = text;
});
});
}
</script>
</head>
<body onload="go()"></body>
</html>
pip install django-cors-headers
# settings.py
CORS_ORIGIN_ALLOW_ALL = True
CORS_URLS_REGEX = r"^/api/v2/"
and follow the rest of the setup instructions for django-cors-headers.
python manage.py runserver 0:8000
client/index.html
at http://localhost:8020/
python3 -m http.server 8020
from inside the client directoryHeadlessPreviewMixin
The preview page should now show you the API response for the preview! 🎉
This is where a real front-end would take over and display the preview as it would be seen on the live site.
All contributions are welcome!
Note that this project uses pre-commit. To set up locally:
# if you don't have it yet
$ pip install pre-commit
# go to the project directory
$ cd wagtail-headless-preview
# initialize pre-commit
$ pre-commit install
# Optional, run all checks once for this, then the checks will run only on the changed files
$ pre-commit run --all-files
Now you can run tests as shown below:
tox -p
or, you can run them for a specific environment tox -e py311-django4.2-wagtail5.1
or specific test
tox -e py311-django4.2-wagtail5.0 -- wagtail_headless_preview.tests.test_frontend.TestFrontendViews.test_redirect_on_preview
FAQs
Enhance Wagtail previews in headless setups.
We found that wagtail-headless-preview demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.