diff --git a/.env.example b/.env.example index 4e1f6fe..8675427 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,10 @@ OV_DB_ENGINE=django.db.backends.postgresql -OV_DB_HOST=0.0.0.0 +OV_DB_HOST=localhost OV_DB_PORT=5432 OV_DB_NAME=postgres OV_DB_USER=postgres OV_DB_PASSWORD="" + +OV_BASE_URL=http://localhost:3000 +OV_ADMIN_BASE_URL=http://localhost:8000 +OV_PREVIEW_URL=http://localhost:3000/preview diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..545fc86 --- /dev/null +++ b/client/index.html @@ -0,0 +1,22 @@ + + + + + + + diff --git a/exhibits/models.py b/exhibits/models.py index b330722..0f019fb 100644 --- a/exhibits/models.py +++ b/exhibits/models.py @@ -9,6 +9,7 @@ from wagtail.images.api.fields import ImageRenditionField from wagtail.models import Orderable, Page from wagtail.search import index +from wagtail_headless_preview.models import HeadlessMixin from authors.serializers import AuthorSerializer from ov_wag.serializers import RichTextSerializer @@ -65,7 +66,7 @@ class ExhibitPageApiSchema(BaseModel): hero_thumb: ImageApiSchema -class ExhibitPage(Page): +class ExhibitPage(HeadlessMixin, Page): body = RichTextField(blank=True) cover_image = models.ForeignKey( diff --git a/home/models.py b/home/models.py index f9eb39a..7f9dec0 100644 --- a/home/models.py +++ b/home/models.py @@ -4,9 +4,10 @@ from wagtail.api import APIField from wagtail.fields import RichTextField from wagtail.models import Page +from wagtail_headless_preview.models import HeadlessMixin -class HomePage(Page): +class HomePage(HeadlessMixin, Page): body = RichTextField(blank=True) content_panels: ClassVar[list[FieldPanel]] = [ diff --git a/ov_collections/models.py b/ov_collections/models.py index 4c61bcf..43048de 100644 --- a/ov_collections/models.py +++ b/ov_collections/models.py @@ -9,11 +9,12 @@ from wagtail.images.blocks import ImageChooserBlock from wagtail.models import Page from wagtail.search import index +from wagtail_headless_preview.models import HeadlessMixin from .blocks import ContentBlock, ContentImageBlock -class Collection(Page): +class Collection(HeadlessMixin, Page): introduction = RichTextField(blank=True) content = StreamField( diff --git a/ov_wag/api.py b/ov_wag/api.py index ca72a92..dcd55c7 100644 --- a/ov_wag/api.py +++ b/ov_wag/api.py @@ -1,15 +1,52 @@ -from wagtail.api.v2.views import PagesAPIViewSet +from django.contrib.contenttypes.models import ContentType +from rest_framework.response import Response from wagtail.api.v2.router import WagtailAPIRouter -from wagtail.images.api.v2.views import ImagesAPIViewSet +from wagtail.api.v2.views import PagesAPIViewSet from wagtail.documents.api.v2.views import DocumentsAPIViewSet +from wagtail.images.api.v2.views import ImagesAPIViewSet +from wagtail_headless_preview.models import PagePreview + from authors.views import AuthorsAPIViewSet from exhibits.views import ExhibitsAPIViewSet from ov_collections.views import CollectionAPIViewSet -# Create the router. "wagtailapi" is the URL namespace +# Create the router. 'wagtailapi' is the URL namespace api_router = WagtailAPIRouter('wagtailapi') -# Add the three endpoints using the "register_endpoint" method. + +class PagePreviewAPIViewSet(PagesAPIViewSet): + known_query_parameters = PagesAPIViewSet.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', PagePreviewAPIViewSet) + +# Add the three endpoints using the 'register_endpoint' method. # The first parameter is the name of the endpoint (eg. pages, images). This # is used in the URL of the endpoint # The second parameter is the endpoint class that handles the requests diff --git a/ov_wag/settings/base.py b/ov_wag/settings/base.py index ae18704..f795b02 100644 --- a/ov_wag/settings/base.py +++ b/ov_wag/settings/base.py @@ -47,6 +47,7 @@ 'wagtail.admin', 'wagtail', 'wagtail.api.v2', + 'corsheaders', 'rest_framework', 'modelcluster', 'taggit', @@ -56,9 +57,11 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'wagtail_headless_preview', ] MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -125,6 +128,9 @@ }, ] +# CORS to allow cross-origin requests from the client +CORS_ALLOW_ALL_ORIGINS = True +CORS_URLS_REGEX = r'^/api/v2/' # Internationalization # https://docs.djangoproject.com/en/3.2/topics/i18n/ @@ -166,7 +172,7 @@ # Wagtail settings -WAGTAIL_SITE_NAME = "ov-wag" +WAGTAIL_SITE_NAME = 'ov-wag' # Search # https://docs.wagtail.io/en/stable/topics/search/backends.html @@ -182,3 +188,7 @@ WAGTAIL_BASE_URL = os.environ.get('OV_BASE_URL') WAGTAILADMIN_BASE_URL = os.environ.get('OV_ADMIN_BASE_URL', '') + +WAGTAIL_HEADLESS_PREVIEW = { + 'CLIENT_URLS': {'default': os.environ.get('OV_PREVIEW_URL')}, +} diff --git a/pdm.lock b/pdm.lock index 1d8e642..7a4549f 100644 --- a/pdm.lock +++ b/pdm.lock @@ -436,6 +436,20 @@ files = [ {file = "Django-4.2.7.tar.gz", hash = "sha256:8e0f1c2c2786b5c0e39fe1afce24c926040fad47c8ea8ad30aaf1188df29fc41"}, ] +[[package]] +name = "django-cors-headers" +version = "4.3.1" +requires_python = ">=3.8" +summary = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." +dependencies = [ + "Django>=3.2", + "asgiref>=3.6", +] +files = [ + {file = "django-cors-headers-4.3.1.tar.gz", hash = "sha256:0bf65ef45e606aff1994d35503e6b677c0b26cafff6506f8fd7187f3be840207"}, + {file = "django_cors_headers-4.3.1-py3-none-any.whl", hash = "sha256:0b1fd19297e37417fc9f835d39e45c8c642938ddba1acce0c1753d3edef04f36"}, +] + [[package]] name = "django-filter" version = "23.3" @@ -2023,6 +2037,19 @@ files = [ {file = "wagtail_factories-4.1.0.tar.gz", hash = "sha256:067e3e1a30721a90eaa1514c8214aae7a171490bbedf026eeaa904a2ed9abcd0"}, ] +[[package]] +name = "wagtail-headless-preview" +version = "0.7.0" +requires_python = ">=3.8" +summary = "Enhance Wagtail previews in headless setups." +dependencies = [ + "Wagtail>=4.1", +] +files = [ + {file = "wagtail_headless_preview-0.7.0-py3-none-any.whl", hash = "sha256:105a62e130b7cfe3f39b1ff2cf8aef5080f578e98553af02fb4099cdacf5dfa9"}, + {file = "wagtail_headless_preview-0.7.0.tar.gz", hash = "sha256:730e86ea2de91602b64cc2412b5ff3e0b3f3679e3767eb417c109c9110209c07"}, +] + [[package]] name = "wcwidth" version = "0.2.10" diff --git a/pyproject.toml b/pyproject.toml index 37d6066..88a3382 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ dependencies = [ "psycopg2~=2.9", "python-dotenv~=1.0", "gunicorn~=21.2", + "wagtail-headless-preview>=0.7.0", + "django-cors-headers>=4.3.1", ] requires-python = '>=3.9,<4.0'