diff --git a/.gitignore b/.gitignore
index e70759a..f8ba705 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,11 @@ venv/
.idea
# Python packaging
-/build/
-/dist/
-wagtail_headless_preview.egg-info
+.Python
+build/
+dist/
+*.egg-info/
+*.egg
+
+
+.tox/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..ba644a1
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,43 @@
+language: python
+cache: pip
+
+# Use container-based infrastructure
+dist: xenial
+sudo: false
+
+matrix:
+ include:
+ - env: TOXENV=py37-django22-wagtail25
+ python: 3.7
+ - env: TOXENV=py36-django21-wagtail24
+ python: 3.6
+ - env: TOXENV=py36-django21-wagtail23
+ python: 3.6
+ - env: TOXENV=py35-django20-wagtail24
+ python: 3.5
+ - env: TOXENV=py35-django20-wagtail23
+ python: 3.5
+ - env: TOXENV=py35-django20-wagtail22
+ python: 3.5
+ - env: TOXENV=py35-django20-wagtail21
+ python: 3.5
+ - env: TOXENV=py35-django20-wagtail20
+ python: 3.5
+
+ allow_failures:
+ - env: TOXENV=py37-djangomaster-wagtail25
+
+install:
+ - pip install wheel flake8 isort
+ - pip install -e .[testing]
+
+before_script:
+ - TESTDIR=$(pwd)
+
+script:
+ - flake8 wagtail_headless_preview
+ - isort --check-only --diff --recursive wagtail_headless_preview
+ - cd wagtail_headless_preview/tests/client
+ - nohup python3 -m http.server 8020 > /dev/null 2>&1 &
+ - cd $TESTDIR
+ - tox
diff --git a/MANIFEST.in b/MANIFEST.in
index e6c68f7..1b4740b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,6 @@
include LICENSE *.rst *.txt *.md
-recursive-include wagtail_headless_preview/templates *
+recursive-include wagtail_headless_preview/templates wagtail_headless_preview/static *
global-exclude __pycache__
global-exclude *.py[co]
diff --git a/README.md b/README.md
index 0b5d023..029ef88 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,12 @@ INSTALLED_APPS = [
]
```
+Run migrations:
+
+```sh
+$ ./manage.py migrate
+```
+
then configure the preview client URL using the `HEADLESS_PREVIEW_CLIENT_URLS` setting.
For single site, the configuration should look like:
@@ -44,12 +50,14 @@ HEADLESS_PREVIEW_CLIENT_URLS = {
}
```
-Run migrations:
+Optionally, you can enable live preview functionality with the `HEADLESS_PREVIEW_LIVE` setting:
-```sh
-$ ./manage.py migrate
+```python
+# settings.py
+HEADLESS_PREVIEW_LIVE = True
```
+Note: Your front-end app must be set up for live preview, a feature that usually requires [Django Channels](https://github.com/django/channels/) or other WebSocket/async libraries.
## Usage
@@ -97,6 +105,7 @@ from rest_framework.response import Response
# Create the router. "wagtailapi" is the URL namespace
api_router = WagtailAPIRouter('wagtailapi')
+
class PagePreviewAPIEndpoint(PagesAPIEndpoint):
known_query_parameters = PagesAPIEndpoint.known_query_parameters.union(['content_type', 'token'])
diff --git a/runtests.py b/runtests.py
new file mode 100644
index 0000000..9127323
--- /dev/null
+++ b/runtests.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import warnings
+
+from django.core.management import execute_from_command_line
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'wagtail_headless_preview.tests.settings'
+
+
+def runtests():
+ # Don't ignore DeprecationWarnings
+ only_wagtail_headless_preview = r'^wagtail_headless_preview(\.|$)'
+ warnings.filterwarnings('default', category=DeprecationWarning, module=only_wagtail_headless_preview)
+ warnings.filterwarnings('default', category=PendingDeprecationWarning, module=only_wagtail_headless_preview)
+
+ args = sys.argv[1:]
+ argv = sys.argv[:1] + ['test'] + args
+ try:
+ execute_from_command_line(argv)
+ finally:
+ pass
+
+
+if __name__ == '__main__':
+ runtests()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..a315639
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,18 @@
+[bdist_wheel]
+universal = 1
+
+[metadata]
+description-file = README.md
+
+[flake8]
+max-line-length=120
+exclude=migrations
+
+[isort]
+known_first_party = wagtail_headless_preview
+known_django = django
+known_wagtail = wagtail
+skip = migrations
+sections = FUTURE, STDLIB, DJANGO, THIRDPARTY, FIRSTPARTY, LOCALFOLDER
+default_section = THIRDPARTY
+multi_line_output = 5
diff --git a/setup.py b/setup.py
index 4798ee2..7a4ca94 100644
--- a/setup.py
+++ b/setup.py
@@ -22,7 +22,17 @@
author="Matthew Westcott - POC, Karl Hobley",
author_email="matthew.westcott@torchbox.com",
license="BSD",
- install_requires=["wagtail>=2.0"],
+ install_requires=[
+ "wagtail>=2.0"
+ ],
+
+ extras_require={
+ 'testing': [
+ 'tox',
+ 'django-cors-headers'
+ ],
+ },
+
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..87e2962
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,35 @@
+[tox]
+skipsdist = True
+
+envlist =
+ py{35,36}-django{20,21}-wagtail{20,21,22,23,24}
+ py37-django{22,master}-wagtail25
+
+[testenv]
+install_command = pip install -e ".[testing]" -U {opts} {packages}
+commands =
+ python runtests.py
+
+basepython =
+ py35: python3.5
+ py36: python3.6
+ py37: python3.7
+
+deps =
+ django200: django>=2.0,<2.1
+ django21: Django>=2.1,<2.2
+ django22: Django>=2.2,<2.3
+ djangomaster: git+https://github.com/django/django.git@master#egg=Django
+ wagtail20: wagtail>=2.0,<2.1
+ wagtail21: wagtail>=2.1,<2.2
+ wagtail22: wagtail>=2.2,<2.3
+ wagtail23: wagtail>=2.3,<2.4
+ wagtail24: wagtail>=2.4,<2.5
+ wagtail25: wagtail>=2.5,<2.6
+
+[testenv:flake8]
+deps=flake8>3.7
+commands=flake8 wagtail_headless_preview
+
+[flake8]
+ignore = D100,D101,D102,D103,D105,D200,D202,D204,D205,D209,D400,D401,E303,E501,W503,N805,N806
diff --git a/wagtail_headless_preview/models.py b/wagtail_headless_preview/models.py
index a938a73..50185e6 100644
--- a/wagtail_headless_preview/models.py
+++ b/wagtail_headless_preview/models.py
@@ -6,8 +6,7 @@
from django.contrib.contenttypes.models import ContentType
from django.core.signing import TimestampSigner
from django.db import models
-from django.http import HttpResponseRedirect
-from django.shortcuts import get_object_or_404, render
+from django.shortcuts import render
class PagePreview(models.Model):
@@ -53,10 +52,19 @@ def create_page_preview(self):
content_json=self.to_json(),
)
+ def update_page_preview(self, token):
+ return PagePreview.objects.update_or_create(
+ token=token,
+ defaults={
+ "content_type": self.content_type,
+ "content_json": self.to_json(),
+ },
+ )
+
def get_client_root_url(self):
try:
return settings.HEADLESS_PREVIEW_CLIENT_URLS[self.get_site().hostname]
- except KeyError:
+ except (AttributeError, KeyError):
return settings.HEADLESS_PREVIEW_CLIENT_URLS["default"]
@classmethod
@@ -72,17 +80,40 @@ def get_preview_url(self, token):
)
)
+ def dummy_request(self, original_request=None, **meta):
+ request = super(HeadlessPreviewMixin, self).dummy_request(
+ original_request=original_request, **meta
+ )
+ request.GET = request.GET.copy()
+ request.GET["live_preview"] = original_request.GET.get("live_preview")
+ return request
+
def serve_preview(self, request, mode_name):
- page_preview = self.create_page_preview()
- page_preview.save()
- PagePreview.garbage_collect()
+ use_live_preview = request.GET.get("live_preview")
+ token = request.COOKIES.get("used-token")
+ if use_live_preview and token:
+ page_preview, existed = self.update_page_preview(token)
+ PagePreview.garbage_collect()
+
+ from wagtail_headless_preview.signals import preview_update # Imported locally as live preview is optional
+ preview_update.send(sender=HeadlessPreviewMixin, token=token)
+ else:
+ PagePreview.garbage_collect()
+ page_preview = self.create_page_preview()
+ page_preview.save()
- return render(
+ response = render(
request,
"wagtail_headless_preview/preview.html",
{"preview_url": self.get_preview_url(page_preview.token)},
)
+ if use_live_preview:
+ # Set cookie that auto-expires after 5mins
+ response.set_cookie(key="used-token", value=page_preview.token, max_age=300)
+
+ return response
+
@classmethod
def get_page_from_preview_token(cls, token):
content_type = ContentType.objects.get_for_model(cls)
diff --git a/wagtail_headless_preview/signals.py b/wagtail_headless_preview/signals.py
new file mode 100644
index 0000000..114de03
--- /dev/null
+++ b/wagtail_headless_preview/signals.py
@@ -0,0 +1,3 @@
+from django.dispatch import Signal
+
+preview_update = Signal(providing_args=["token"])
diff --git a/wagtail_headless_preview/static/js/live-preview.js b/wagtail_headless_preview/static/js/live-preview.js
new file mode 100644
index 0000000..471eaaa
--- /dev/null
+++ b/wagtail_headless_preview/static/js/live-preview.js
@@ -0,0 +1,43 @@
+$(document).ready(() => {
+ let $previewButton = $('.action-preview');
+ // Make existing Wagtail code send form data to backend on KeyUp
+ $previewButton.attr('data-auto-update', "true");
+
+ // Trigger preview save on key up
+ let $form = $('#page-edit-form');
+ let previewUrl = $previewButton.data('action');
+ let triggerPreviewDataTimeout = -1;
+ let autoUpdatePreviewDataTimeout = -1;
+
+ const triggerPreviewUpdate = () => {
+ return $.ajax({
+ url: `${previewUrl}?live_preview=true`,
+ method: 'GET',
+ data: new FormData($form[0]),
+ processData: false,
+ contentType: false
+ })
+ };
+
+ const setPreviewData = () => {
+ return $.ajax({
+ url: previewUrl,
+ method: 'POST',
+ data: new FormData($form[0]),
+ processData: false,
+ contentType: false
+ });
+ };
+
+ $previewButton.one('click', function () {
+ if ($previewButton.data('auto-update')) {
+ $form.on('click change keyup DOMSubtreeModified', function () {
+ clearTimeout(triggerPreviewDataTimeout);
+ triggerPreviewDataTimeout = setTimeout(triggerPreviewUpdate, 500);
+
+ clearTimeout(autoUpdatePreviewDataTimeout);
+ autoUpdatePreviewDataTimeout = setTimeout(setPreviewData, 300);
+ }).trigger('change');
+ }
+ })
+});
diff --git a/wagtail_headless_preview/tests/__init__.py b/wagtail_headless_preview/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/wagtail_headless_preview/tests/client/index.html b/wagtail_headless_preview/tests/client/index.html
new file mode 100644
index 0000000..28f4cc3
--- /dev/null
+++ b/wagtail_headless_preview/tests/client/index.html
@@ -0,0 +1,23 @@
+
+
+
+ Headless preview
+
+
+
+
diff --git a/wagtail_headless_preview/tests/settings.py b/wagtail_headless_preview/tests/settings.py
new file mode 100644
index 0000000..e3f4e52
--- /dev/null
+++ b/wagtail_headless_preview/tests/settings.py
@@ -0,0 +1,109 @@
+from __future__ import absolute_import, unicode_literals
+
+import os
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': os.environ.get('DATABASE_ENGINE', 'django.db.backends.sqlite3'),
+ 'NAME': os.environ.get('DATABASE_NAME', 'wagtail_review'),
+ 'USER': os.environ.get('DATABASE_USER', None),
+ 'PASSWORD': os.environ.get('DATABASE_PASS', None),
+ 'HOST': os.environ.get('DATABASE_HOST', None),
+
+ 'TEST': {
+ 'NAME': os.environ.get('DATABASE_NAME', None),
+ }
+ }
+}
+
+
+SECRET_KEY = 'not needed'
+
+ROOT_URLCONF = 'wagtail_headless_preview.tests.urls'
+
+STATIC_URL = '/static/'
+
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+)
+
+USE_TZ = True
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ 'django.template.context_processors.request',
+ ],
+ 'debug': True,
+ },
+ },
+]
+
+# Django 1.11
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+
+ 'wagtail.core.middleware.SiteMiddleware',
+)
+
+# Django 2.x
+MIDDLEWARE = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+
+ 'wagtail.core.middleware.SiteMiddleware',
+)
+
+INSTALLED_APPS = (
+ 'wagtail_headless_preview',
+ 'wagtail_headless_preview.tests.testapp',
+ 'wagtail.search',
+ 'wagtail.sites',
+ 'wagtail.users',
+ 'wagtail.images',
+ 'wagtail.documents',
+ 'wagtail.admin',
+ 'wagtail.core',
+ 'wagtail.api.v2',
+
+ 'taggit',
+
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+)
+
+PASSWORD_HASHERS = (
+ 'django.contrib.auth.hashers.MD5PasswordHasher', # don't use the intentionally slow default password hasher
+)
+
+WAGTAIL_SITE_NAME = 'wagtail-headless-preview test'
+BASE_URL = 'http://test.local'
+
+HEADLESS_PREVIEW_CLIENT_URLS = {
+ 'default': 'http://localhost:8020/',
+}
+
+CORS_ORIGIN_ALLOW_ALL = True
+CORS_URLS_REGEX = r'^/api/v2/'
diff --git a/wagtail_headless_preview/tests/test_frontend.py b/wagtail_headless_preview/tests/test_frontend.py
new file mode 100644
index 0000000..f35d72a
--- /dev/null
+++ b/wagtail_headless_preview/tests/test_frontend.py
@@ -0,0 +1,63 @@
+import urllib
+
+from django.contrib.auth.models import User
+from django.test import TestCase
+from django.urls import reverse
+
+from wagtail.core.models import Page
+
+from wagtail_headless_preview.models import PagePreview
+from wagtail_headless_preview.tests.testapp.models import SimplePage
+
+
+class TestFrontendViews(TestCase):
+ fixtures = ['test.json']
+
+ def setUp(self):
+ self.admin_user = User.objects.create_superuser(
+ username='admin', email='admin@example.com', password='password'
+ )
+
+ self.homepage = Page.objects.get(url_path='/home/').specific
+ self.page = SimplePage(title="Simple page original", slug="simple-page")
+ self.homepage.add_child(instance=self.page)
+
+ self.page.title = "Simple page submitted"
+ self.page.save_revision()
+
+ self.page.title = "Simple page with draft edit"
+ self.page.save_revision()
+
+ def test_view(self):
+ self.client.login(username=self.admin_user.username, password='password')
+
+ self.assertEqual(PagePreview.objects.count(), 0)
+ # Try getting page draft
+ view_draft_url = reverse('wagtailadmin_pages:view_draft', args=(self.page.id,))
+ response = self.client.get(view_draft_url)
+
+ # User can view
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(PagePreview.objects.count(), 1)
+
+ preview_token = PagePreview.objects.first().token
+ self.assertContains(response, urllib.parse.urlencode({
+ 'token': preview_token
+ }))
+ self.assertContains(response, urllib.parse.urlencode({
+ 'content_type': 'testapp.simplepage',
+ }))
+
+ params = {'content_type': 'testapp.simplepage', 'token': preview_token, 'format': 'json'}
+ preview_api_url = '{base_url}{page_id}/?{params}'.format(
+ base_url=reverse('wagtailapi_v2:page_preview:listing'),
+ page_id=self.page.id,
+ params=urllib.parse.urlencode(params)
+ )
+
+ response = self.client.get(preview_api_url)
+ self.assertContains(response, 'Simple page with draft edit')
+
+ # TODO fix this.
+ # response = self.client.get(self.page.get_preview_url(preview_token))
+ # self.assertContains(response, 'Headless preview')
diff --git a/wagtail_headless_preview/tests/testapp/__init__.py b/wagtail_headless_preview/tests/testapp/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/wagtail_headless_preview/tests/testapp/api.py b/wagtail_headless_preview/tests/testapp/api.py
new file mode 100644
index 0000000..3797dbd
--- /dev/null
+++ b/wagtail_headless_preview/tests/testapp/api.py
@@ -0,0 +1,39 @@
+from django.contrib.contenttypes.models import ContentType
+
+from rest_framework.response import Response
+from wagtail.api.v2.endpoints import PagesAPIEndpoint
+from wagtail.api.v2.router import WagtailAPIRouter
+
+from wagtail_headless_preview.models import PagePreview
+
+# Create the router. "wagtailapi" is the URL namespace
+api_router = WagtailAPIRouter('wagtailapi_v2')
+
+
+class PagePreviewAPIEndpoint(PagesAPIEndpoint):
+ known_query_parameters = PagesAPIEndpoint.known_query_parameters.union(['content_type', 'token'])
+
+ def listing_view(self, request):
+ page = self.get_object()
+ serializer = self.get_serializer(page)
+ return Response(serializer.data)
+
+ 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', PagePreviewAPIEndpoint)
diff --git a/wagtail_headless_preview/tests/testapp/fixtures/test.json b/wagtail_headless_preview/tests/testapp/fixtures/test.json
new file mode 100644
index 0000000..9222182
--- /dev/null
+++ b/wagtail_headless_preview/tests/testapp/fixtures/test.json
@@ -0,0 +1,65 @@
+[
+{
+ "pk": 1,
+ "model": "wagtailcore.page",
+ "fields": {
+ "title": "Root",
+ "numchild": 1,
+ "show_in_menus": false,
+ "live": true,
+ "depth": 1,
+ "content_type": [
+ "wagtailcore",
+ "page"
+ ],
+ "path": "0001",
+ "url_path": "/",
+ "slug": "root"
+ }
+},
+
+{
+ "pk": 2,
+ "model": "wagtailcore.page",
+ "fields": {
+ "title": "Home",
+ "numchild": 0,
+ "show_in_menus": false,
+ "live": true,
+ "depth": 2,
+ "content_type": ["wagtailcore", "page"],
+ "path": "00010001",
+ "url_path": "/home/",
+ "slug": "home"
+ }
+},
+
+{
+ "pk": 1,
+ "model": "wagtailcore.site",
+ "fields": {
+ "root_page": 2,
+ "hostname": "localhost",
+ "port": 80,
+ "is_default_site": true
+ }
+},
+
+{
+ "pk": 1,
+ "model": "auth.user",
+ "fields": {
+ "username": "horseman",
+ "first_name": "Headless",
+ "last_name": "Horseman",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": true,
+ "groups": [
+ ],
+ "user_permissions": [],
+ "password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
+ "email": "headless@horseman.dev"
+ }
+}
+]
diff --git a/wagtail_headless_preview/tests/testapp/migrations/0001_initial.py b/wagtail_headless_preview/tests/testapp/migrations/0001_initial.py
new file mode 100644
index 0000000..0174ea9
--- /dev/null
+++ b/wagtail_headless_preview/tests/testapp/migrations/0001_initial.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('wagtailcore', '0040_page_draft_title'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SimplePage',
+ fields=[
+ ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ bases=('wagtailcore.page',),
+ ),
+ ]
diff --git a/wagtail_headless_preview/tests/testapp/migrations/__init__.py b/wagtail_headless_preview/tests/testapp/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/wagtail_headless_preview/tests/testapp/models.py b/wagtail_headless_preview/tests/testapp/models.py
new file mode 100644
index 0000000..46739f1
--- /dev/null
+++ b/wagtail_headless_preview/tests/testapp/models.py
@@ -0,0 +1,7 @@
+from wagtail.core.models import Page
+
+from wagtail_headless_preview.models import HeadlessPreviewMixin
+
+
+class SimplePage(HeadlessPreviewMixin, Page):
+ pass
diff --git a/wagtail_headless_preview/tests/testapp/templates/tests/simple_page.html b/wagtail_headless_preview/tests/testapp/templates/tests/simple_page.html
new file mode 100644
index 0000000..799740e
--- /dev/null
+++ b/wagtail_headless_preview/tests/testapp/templates/tests/simple_page.html
@@ -0,0 +1,9 @@
+
+
+
+ {{ page.title }}
+
+
+ {{ page.title }}
+
+
diff --git a/wagtail_headless_preview/tests/urls.py b/wagtail_headless_preview/tests/urls.py
new file mode 100644
index 0000000..7edbfc1
--- /dev/null
+++ b/wagtail_headless_preview/tests/urls.py
@@ -0,0 +1,17 @@
+from __future__ import absolute_import, unicode_literals
+
+from django.conf.urls import include, url
+
+from wagtail.admin import urls as wagtailadmin_urls
+from wagtail.core import urls as wagtail_urls
+
+from wagtail_headless_preview.tests.testapp.api import api_router
+
+urlpatterns = [
+ url(r'^admin/', include(wagtailadmin_urls)),
+ url(r'^api/v2/', api_router.urls),
+
+ # For anything not caught by a more specific rule above, hand over to
+ # Wagtail's serving mechanism
+ url(r'', include(wagtail_urls)),
+]
diff --git a/wagtail_headless_preview/wagtail_hooks.py b/wagtail_headless_preview/wagtail_hooks.py
new file mode 100644
index 0000000..0731a29
--- /dev/null
+++ b/wagtail_headless_preview/wagtail_hooks.py
@@ -0,0 +1,18 @@
+from django.conf import settings
+from django.utils.html import format_html_join
+
+from wagtail.core import hooks
+
+
+@hooks.register("insert_editor_js")
+def editor_js():
+ if hasattr(settings, 'HEADLESS_PREVIEW_LIVE') and settings.HEADLESS_PREVIEW_LIVE:
+ js_files = ["js/realtime_preview.js"]
+
+ return format_html_join(
+ "\n",
+ '',
+ ((settings.STATIC_URL, filename) for filename in js_files),
+ )
+
+ return ''