add support for previewing pages

This commit is contained in:
2026-05-19 17:48:33 +02:00
parent f91c67f526
commit a5ebb897f1
25 changed files with 471 additions and 67 deletions
+3 -2
View File
@@ -10,13 +10,14 @@ from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField
from wagtail.models import Page
from wagtail.search import index
from wagtail_headless_preview.models import HeadlessMixin
from dnscms.fields import CommonStreamField
from dnscms.wordpress.models import WPImportedPageMixin
@register_singular_query_field("associationIndex")
class AssociationIndex(Page):
class AssociationIndex(HeadlessMixin, Page):
max_count = 1
subpage_types = ["associations.AssociationPage"]
@@ -37,7 +38,7 @@ class AssociationIndex(Page):
search_fields = Page.search_fields
class AssociationPage(WPImportedPageMixin, Page):
class AssociationPage(HeadlessMixin, WPImportedPageMixin, Page):
subpage_types = []
parent_page_types = ["associations.AssociationIndex"]
show_in_menus = False
+2 -1
View File
@@ -11,6 +11,7 @@ from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from wagtail.search import index
from wagtail.snippets.models import register_snippet
from wagtail_headless_preview.models import HeadlessMixin
from contacts.blocks import ContactSectionBlock
from dnscms.blocks import BASE_BLOCKS
@@ -22,7 +23,7 @@ PHONE_REGEX_VALIDATOR = RegexValidator(
@register_singular_query_field("contactIndex")
class ContactIndex(Page):
class ContactIndex(HeadlessMixin, Page):
max_count = 1
subpage_types = []
+16 -4
View File
@@ -41,6 +41,7 @@ INSTALLED_APPS = [
# end cms apps
"grapple",
"graphene_django",
"wagtail_headless_preview",
"wagtail.contrib.forms",
"wagtail.contrib.redirects",
"wagtail.contrib.settings",
@@ -185,11 +186,22 @@ WAGTAILSEARCH_BACKENDS = {
}
# Base URL to use when referring to full URLs within the Wagtail admin backend -
# e.g. in notification emails. Don't include '/admin' or a trailing slash
WAGTAILADMIN_BASE_URL = "http://example.com"
# e.g. in notification emails. Don't include '/admin' or a trailing slash.
# Also used by wagtail-grapple to make image URLs absolute.
WAGTAIL_BASE_URL = os.environ.get("WAGTAIL_BASE_URL", "http://127.0.0.1:8000").rstrip("/")
WAGTAILADMIN_BASE_URL = WAGTAIL_BASE_URL
BASE_URL = WAGTAIL_BASE_URL
# Required by wagtail-grapple to make image URLs absolute
BASE_URL = "http://example.com"
# Public URL of the Next.js frontend. Used to direct preview iframes and to
# redirect "View Live" clicks on the CMS host over to the headless frontend.
FRONTEND_BASE_URL = os.environ.get("FRONTEND_BASE_URL", "http://localhost:3000").rstrip("/")
WAGTAIL_HEADLESS_PREVIEW = {
"CLIENT_URLS": {"default": f"{FRONTEND_BASE_URL}/api/preview"},
"SERVE_BASE_URL": FRONTEND_BASE_URL,
"ENFORCE_TRAILING_SLASH": False,
"REDIRECT_ON_PREVIEW": False,
}
# https://docs.wagtail.org/en/latest/releases/6.4.html#data-upload-max-number-fields-update
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
-7
View File
@@ -11,13 +11,6 @@ ALLOWED_HOSTS = ["*"]
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# Base URL to use when referring to full URLs within the Wagtail admin backend -
# e.g. in notification emails. Don't include '/admin' or a trailing slash
WAGTAILADMIN_BASE_URL = "http://127.0.0.1:8000"
# Required by wagtail-grapple to make image URLs absolute
BASE_URL = "http://127.0.0.1:8000"
try:
from .local import *
except ImportError:
+4 -3
View File
@@ -30,6 +30,7 @@ from wagtail.fields import RichTextField
from wagtail.models import Orderable, Page, PageManager, PageQuerySet
from wagtail.search import index
from wagtail.snippets.models import register_snippet
from wagtail_headless_preview.models import HeadlessMixin
from associations.widgets import AssociationChooserWidget
from dnscms.fields import CommonStreamField
@@ -39,7 +40,7 @@ from venues.models import VenuePage
@register_singular_query_field("eventIndex")
class EventIndex(Page):
class EventIndex(HeadlessMixin, Page):
max_count = 1
subpage_types = ["events.EventPage"]
@@ -220,7 +221,7 @@ class EventPageQuerySet(PageQuerySet):
EventPageManager = PageManager.from_queryset(EventPageQuerySet)
class EventPage(WPImportedPageMixin, Page):
class EventPage(HeadlessMixin, WPImportedPageMixin, Page):
subpage_types = []
parent_page_types = ["events.EventIndex"]
show_in_menus = False
@@ -358,7 +359,7 @@ class EventPage(WPImportedPageMixin, Page):
GraphQLImage("featured_image"),
GraphQLRichText("lead"),
GraphQLStreamfield("body"),
GraphQLString("pig"),
GraphQLString("pig", required=True),
GraphQLString("ticket_url"),
GraphQLString("facebook_url"),
GraphQLBoolean("free"),
+2 -1
View File
@@ -4,13 +4,14 @@ from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from wagtail.search import index
from wagtail_headless_preview.models import HeadlessMixin
from dnscms.blocks import PageSectionBlock
from dnscms.fields import BASE_BLOCKS
from dnscms.options import ALL_PIGS
class GenericPage(Page):
class GenericPage(HeadlessMixin, Page):
subpage_types = ["generic.GenericPage"]
show_in_menus = True
+2 -1
View File
@@ -10,9 +10,10 @@ from wagtail.admin.panels import (
PageChooserPanel,
)
from wagtail.models import Orderable, Page
from wagtail_headless_preview.models import HeadlessMixin
class HomePage(Page):
class HomePage(HeadlessMixin, Page):
max_count = 1
content_panels = Page.content_panels + [
+3 -2
View File
@@ -5,13 +5,14 @@ from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField
from wagtail.models import Page
from wagtail.search import index
from wagtail_headless_preview.models import HeadlessMixin
from dnscms.fields import CommonStreamField
from dnscms.wordpress.models import WPImportedPageMixin
@register_singular_query_field("newsIndex")
class NewsIndex(Page):
class NewsIndex(HeadlessMixin, Page):
max_count = 1
subpage_types = ["news.NewsPage"]
@@ -28,7 +29,7 @@ class NewsIndex(Page):
search_fields = []
class NewsPage(WPImportedPageMixin, Page):
class NewsPage(HeadlessMixin, WPImportedPageMixin, Page):
subpage_types = []
parent_page_types = ["news.NewsIndex"]
show_in_menus = False
+1
View File
@@ -7,6 +7,7 @@ requires-python = ">=3.14, <3.15"
dependencies = [
"wagtail>=7.4,<8",
"wagtail-grapple>=0.31.0,<0.32",
"wagtail-headless-preview>=0.8,<0.9",
"django>=6.0.5,<7",
"django-extensions>=4.1,<5",
"psycopg2-binary>=2.9.12,<3",
+2 -1
View File
@@ -8,6 +8,7 @@ from wagtail.fields import RichTextField, StreamField
from wagtail.images.blocks import ImageChooserBlock
from wagtail.models import Page
from wagtail.search import index
from wagtail_headless_preview.models import HeadlessMixin
from dnscms.blocks import BASE_BLOCKS
@@ -34,7 +35,7 @@ class SponsorBlock(blocks.StructBlock):
@register_singular_query_field("sponsorsPage")
class SponsorsPage(Page):
class SponsorsPage(HeadlessMixin, Page):
max_count = 1
subpage_types = []
+2 -1
View File
@@ -10,13 +10,14 @@ from wagtail.admin.panels import FieldPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from wagtail.search import index
from wagtail_headless_preview.models import HeadlessMixin
from dnscms.blocks import BASE_BLOCKS, PageSectionBlock
from dnscms.options import ALL_PIGS
@register_singular_query_field("studioPage")
class StudioPage(Page):
class StudioPage(HeadlessMixin, Page):
max_count = 1
subpage_types = []
show_in_menus = True
+37
View File
@@ -0,0 +1,37 @@
"""Round-trip tests for wagtail-headless-preview token resolution via grapple."""
from generic.models import GenericPage
from tests.conftest import GenericPageFactory
def test_generic_page_preview_token_resolves_draft(home_page, graphql_post):
"""A minted preview token returns the unsaved draft via grapple's page(token: …)."""
# Publish a baseline so there's a live revision to diverge from.
page = GenericPageFactory(parent=home_page, title="Original title", slug="generic-preview")
# Mutate in-memory to simulate unsaved editor state, then mint a token.
# create_page_preview() snapshots the current to_json() into a PagePreview row.
page.title = "Edited title (draft)"
preview = page.create_page_preview()
response, body = graphql_post(
"""
query previewPage($token: String!) {
page: page(token: $token) {
__typename
... on GenericPage {
title
}
}
}
""",
variables={"token": preview.token},
)
assert response.status_code == 200
assert "errors" not in body, body
assert body["data"]["page"]["__typename"] == "GenericPage"
assert body["data"]["page"]["title"] == "Edited title (draft)"
# Live revision is unchanged — token short-circuits the published query.
assert GenericPage.objects.get(pk=page.pk).title == "Original title"
+2
View File
@@ -255,6 +255,7 @@ dependencies = [
{ name = "psycopg2-binary" },
{ name = "wagtail" },
{ name = "wagtail-grapple" },
{ name = "wagtail-headless-preview" },
{ name = "whitenoise" },
]
@@ -275,6 +276,7 @@ requires-dist = [
{ name = "psycopg2-binary", specifier = ">=2.9.12,<3" },
{ name = "wagtail", specifier = ">=7.4,<8" },
{ name = "wagtail-grapple", specifier = ">=0.31.0,<0.32" },
{ name = "wagtail-headless-preview", specifier = ">=0.8,<0.9" },
{ name = "whitenoise", specifier = ">=6.12.0,<7" },
]
+4 -3
View File
@@ -11,6 +11,7 @@ from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel
from wagtail.fields import RichTextField, StreamField
from wagtail.models import Page
from wagtail.search import index
from wagtail_headless_preview.models import HeadlessMixin
from dnscms.blocks import ImageSliderBlock
from dnscms.fields import CommonStreamField
@@ -18,7 +19,7 @@ from dnscms.wordpress.models import WPImportedPageMixin
@register_singular_query_field("venueIndex")
class VenueIndex(Page):
class VenueIndex(HeadlessMixin, Page):
# there can only be one venue index page
max_count = 1
subpage_types = ["venues.VenuePage"]
@@ -35,7 +36,7 @@ class VenueIndex(Page):
@register_singular_query_field("venueRentalIndex")
class VenueRentalIndex(Page):
class VenueRentalIndex(HeadlessMixin, Page):
# there can only be one venue index page
max_count = 1
subpage_types = []
@@ -51,7 +52,7 @@ class VenueRentalIndex(Page):
graphql_fields = [GraphQLRichText("lead"), GraphQLStreamfield("body")]
class VenuePage(WPImportedPageMixin, Page):
class VenuePage(HeadlessMixin, WPImportedPageMixin, Page):
# no children
subpage_types = []
parent_page_types = ["venues.VenueIndex"]