From f9c47cc852b9852c74cbd9a3be07ffdf797b091b Mon Sep 17 00:00:00 2001 From: Dan Braghis Date: Tue, 20 Aug 2019 16:20:22 +0100 Subject: [PATCH] Add live preview capabilities (#6) - Add live preview capabilities - Better error handling - Garbage collect before creating new preview - Fix live preview logic - Update README --- .gitignore | 11 +- .travis.yml | 43 +++++++ MANIFEST.in | 2 +- README.md | 15 ++- runtests.py | 27 +++++ setup.cfg | 18 +++ setup.py | 12 +- tox.ini | 35 ++++++ wagtail_headless_preview/models.py | 45 ++++++-- wagtail_headless_preview/signals.py | 3 + .../static/js/live-preview.js | 43 +++++++ wagtail_headless_preview/tests/__init__.py | 0 .../tests/client/index.html | 23 ++++ wagtail_headless_preview/tests/settings.py | 109 ++++++++++++++++++ .../tests/test_frontend.py | 63 ++++++++++ .../tests/testapp/__init__.py | 0 wagtail_headless_preview/tests/testapp/api.py | 39 +++++++ .../tests/testapp/fixtures/test.json | 65 +++++++++++ .../tests/testapp/migrations/0001_initial.py | 27 +++++ .../tests/testapp/migrations/__init__.py | 0 .../tests/testapp/models.py | 7 ++ .../testapp/templates/tests/simple_page.html | 9 ++ wagtail_headless_preview/tests/urls.py | 17 +++ wagtail_headless_preview/wagtail_hooks.py | 18 +++ 24 files changed, 616 insertions(+), 15 deletions(-) create mode 100644 .travis.yml create mode 100644 runtests.py create mode 100644 setup.cfg create mode 100644 tox.ini create mode 100644 wagtail_headless_preview/signals.py create mode 100644 wagtail_headless_preview/static/js/live-preview.js create mode 100644 wagtail_headless_preview/tests/__init__.py create mode 100644 wagtail_headless_preview/tests/client/index.html create mode 100644 wagtail_headless_preview/tests/settings.py create mode 100644 wagtail_headless_preview/tests/test_frontend.py create mode 100644 wagtail_headless_preview/tests/testapp/__init__.py create mode 100644 wagtail_headless_preview/tests/testapp/api.py create mode 100644 wagtail_headless_preview/tests/testapp/fixtures/test.json create mode 100644 wagtail_headless_preview/tests/testapp/migrations/0001_initial.py create mode 100644 wagtail_headless_preview/tests/testapp/migrations/__init__.py create mode 100644 wagtail_headless_preview/tests/testapp/models.py create mode 100644 wagtail_headless_preview/tests/testapp/templates/tests/simple_page.html create mode 100644 wagtail_headless_preview/tests/urls.py create mode 100644 wagtail_headless_preview/wagtail_hooks.py 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 ''