From 029d0a13a7266001d250cb39947b0b8cb1f14172 Mon Sep 17 00:00:00 2001 From: Haydn Greatnews Date: Tue, 3 Sep 2024 12:30:39 +1200 Subject: [PATCH 1/4] Refactor opengraph image generation and supporting URL builders --- cdhweb/context_processors.py | 21 ++----------- cdhweb/pages/mixin.py | 21 ++++++++++++- cdhweb/pages/models.py | 24 +++++++++------ cdhweb/pages/utils.py | 49 +++++++++++++++++++++---------- cdhweb/projects/models.py | 7 ++--- templates/snippets/head_meta.html | 7 ++--- 6 files changed, 78 insertions(+), 51 deletions(-) diff --git a/cdhweb/context_processors.py b/cdhweb/context_processors.py index 29bdc1c4f..0ed3b322a 100644 --- a/cdhweb/context_processors.py +++ b/cdhweb/context_processors.py @@ -2,7 +2,7 @@ from django.templatetags.static import static from wagtail.models import Site -from cdhweb.pages.utils import absolutize_url +from cdhweb.pages.utils import get_default_preview_img_url def template_settings(request): @@ -11,19 +11,10 @@ def template_settings(request): feature_flags = getattr(settings, "FEATURE_FLAGS", []) - # default social preview image, relative to static url - if "purple-mode" in feature_flags: - default_preview_img = "img/alt-modes/purple/cdhlogo_square.png" - else: - default_preview_img = "images/cdhlogo_square.jpg" - context_extras = { "SHOW_TEST_WARNING": getattr(settings, "SHOW_TEST_WARNING", False), "site": Site.find_for_request(request), - "default_preview_image": request.build_absolute_uri( - static(default_preview_img) - ), - # try using template tag import static tag and use here instead of join django utils static + "default_preview_image": get_default_preview_img_url(), # Include analytics based on settings.DEBUG or override in settings.py # Defaults to opposite of settings.DEBUG "INCLUDE_ANALYTICS": getattr(settings, "INCLUDE_ANALYTICS", not settings.DEBUG), @@ -41,18 +32,12 @@ def favicon_path(): """Determine which favicon to use based on any feature flags and test warning. Returns full local path, including static url. """ - feature_flags = getattr(settings, "FEATURE_FLAGS", []) - - base_path = "" - if "purple-mode" in feature_flags: - base_path = "img/alt-modes/purple/" - icon_version = "favicon.ico" # use test favicon when test warning is enabled as another visual indicator if getattr(settings, "SHOW_TEST_WARNING", False): icon_version = "favicon-test.ico" - return "".join([settings.STATIC_URL, base_path, icon_version]) + return static(icon_version) def show_test_warning(request): diff --git a/cdhweb/pages/mixin.py b/cdhweb/pages/mixin.py index 1f8cac590..4beacc6ae 100644 --- a/cdhweb/pages/mixin.py +++ b/cdhweb/pages/mixin.py @@ -10,7 +10,12 @@ from wagtail.models import Page from wagtail.search import index -from .utils import LengthOverrideWidget +from .utils import ( + LengthOverrideWidget, + absolutize_url, + get_default_preview_img_url, + get_first_of, +) class HomePageHeroMixin(models.Model): @@ -158,3 +163,17 @@ def sidebar_navigation(self): class Meta: abstract = True + + +class OpenGraphMixin(models.Model): + class Meta: + abstract = True + + @cached_property + def og_image_url(self): + image = get_first_of(self, "feed_image", "image") + if not image: + return get_default_preview_img_url() + + rendition = image.get_rendition("fill-1200x627") + return absolutize_url(rendition.url) diff --git a/cdhweb/pages/models.py b/cdhweb/pages/models.py index 0025bc45d..803581aa6 100644 --- a/cdhweb/pages/models.py +++ b/cdhweb/pages/models.py @@ -3,17 +3,15 @@ import bleach from django.apps import apps -from django.conf import settings from django.core.exceptions import ValidationError from django.db import models from django.template.defaultfilters import striptags, truncatechars_html -from django.utils.functional import cached_property from springkit.blocks import CTABlock, JumplinkableH2Block from springkit.models.mixins import JumplinksMixin from taggit.managers import TaggableManager from wagtail.admin.panels import FieldPanel, MultiFieldPanel from wagtail.blocks import RichTextBlock, StreamBlock, StructBlock, TextBlock -from wagtail.contrib.settings.models import BaseSiteSetting, register_setting +from wagtail.contrib.settings.models import BaseGenericSetting, register_setting from wagtail.documents.blocks import DocumentChooserBlock from wagtail.documents.models import AbstractDocument, DocumentQuerySet from wagtail.embeds.blocks import EmbedBlock @@ -25,7 +23,7 @@ from wagtail.snippets.models import register_snippet from wagtailcodeblock.blocks import CodeBlock -from cdhweb.pages import snippets # needed for import order +from cdhweb.pages import snippets # noqa needed for import order from .blocks.accordion_block import AccordionBlock from .blocks.article_index_block import ArticleTileBlock @@ -42,7 +40,12 @@ from .blocks.table_block import TableBlock from .blocks.tile_block import StandardTileBlock from .blocks.video_block import Video -from .mixin import HomePageHeroMixin, SidebarNavigationMixin, StandardHeroMixin +from .mixin import ( + HomePageHeroMixin, + OpenGraphMixin, + SidebarNavigationMixin, + StandardHeroMixin, +) #: common features for paragraph text PARAGRAPH_FEATURES = [ @@ -246,7 +249,7 @@ class LinkPage(Page): is_creatable = False -class BasePage(Page): +class BasePage(OpenGraphMixin, Page): """Abstract Page class from which all Wagtail page types are derived.""" body = StreamField( @@ -587,11 +590,14 @@ def clean_fields(self, exclude=None): @register_setting(icon="edit") -class PurpleMode(BaseSiteSetting): +class PurpleMode(BaseGenericSetting): purple_mode = models.BooleanField( default=False, - help_text="""This will turn the site purple - by transforming all shades of cyan on the site into shades of purple""", + help_text=""" + This will turn the site purple by transforming + all shades of cyan on the site into shades of + purple + """, ) panels = [FieldPanel("purple_mode", icon="pick")] diff --git a/cdhweb/pages/utils.py b/cdhweb/pages/utils.py index 22f94399d..e06e44e15 100644 --- a/cdhweb/pages/utils.py +++ b/cdhweb/pages/utils.py @@ -1,31 +1,28 @@ -from django.contrib.sites.models import Site +from urllib.parse import urljoin +from wagtail.models import Site from django.forms.widgets import TextInput +from django.templatetags.static import static -def absolutize_url(local_url): + +def absolutize_url(local_url, request=None): """Convert a local url to an absolute url, with scheme and server name, based on the current configured :class:`~django.contrib.sites.models.Site`. :param local_url: local url to be absolutized, e.g. something generated by :meth:`~django.core.urlresolvers.reverse` """ - if local_url.startswith("https"): + if local_url.startswith("https://") or local_url.startswith("http://"): return local_url # add scheme and server (i.e., the http://example.com) based - # on the django Sites infrastructure. - root = Site.objects.get_current().domain - # but also add the http:// if necessary, since most sites docs - # suggest using just the domain name - # NOTE: this is problematic for dev/test sites without https - if not root.startswith("https"): - root = "https://" + root - - # make sure there is no double slash between site url and local url - if local_url.startswith("/"): - root = root.rstrip("/") + # on the Wagtail request or default Site + site = Site.objects.order_by("is_default_site").first() + if request: + site = Site.find_for_request(request) + root = site.root_url - return root + local_url + return urljoin(root, local_url) class LengthOverrideWidget(TextInput): @@ -55,3 +52,25 @@ def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) context["widget"]["attrs"]["maxlength"] = self.max_length return context + + +def get_first_of(instance, *fields, default=None): + """ + Return the first non empty value from fields on `instance` - or `default` + """ + return next( + (value for field in fields if (value := getattr(instance, field, ""))), default + ) + + +def get_default_preview_img_url(): + from cdhweb.pages.models import PurpleMode + + # default social preview image, relative to static url + purple_mode_setting = PurpleMode.load() + if purple_mode_setting.purple_mode: + default_preview_img = "images/alt-modes/purple/cdhlogo_square.png" + else: + default_preview_img = "images/cdhlogo_square.jpg" + + return absolutize_url(static(default_preview_img)) diff --git a/cdhweb/projects/models.py b/cdhweb/projects/models.py index 971d93fda..8e14bd5f4 100644 --- a/cdhweb/projects/models.py +++ b/cdhweb/projects/models.py @@ -5,15 +5,14 @@ from django.utils import timezone from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.models import ClusterableModel -from wagtail import blocks from wagtail.admin.panels import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel -from wagtail.fields import RichTextField, StreamField +from wagtail.fields import StreamField from wagtail.models import Page, PageManager, PageQuerySet from wagtail.search import index from wagtailautocomplete.edit_handlers import AutocompletePanel from cdhweb.pages.blocks.accordion_block import ProjectAccordion -from cdhweb.pages.mixin import StandardHeroMixin +from cdhweb.pages.mixin import OpenGraphMixin, StandardHeroMixin from cdhweb.pages.models import BasePage, DateRange, LandingPage, LinkPage, RelatedLink from cdhweb.people.models import Person @@ -80,7 +79,7 @@ def recent(self): ProjectManager = PageManager.from_queryset(ProjectQuerySet) -class Project(BasePage, ClusterableModel, StandardHeroMixin): +class Project(BasePage, OpenGraphMixin, ClusterableModel, StandardHeroMixin): """Page type for a CDH sponsored project or working group.""" template = "projects/project_page.html" diff --git a/templates/snippets/head_meta.html b/templates/snippets/head_meta.html index 694268e2b..b3072ebae 100644 --- a/templates/snippets/head_meta.html +++ b/templates/snippets/head_meta.html @@ -1,5 +1,5 @@ {% load static cdh_tags %} -{% firstof preview_image page.get_url|url_to_icon_path default_preview_image as meta_preview_image %} +{% firstof preview_image page.og_image_url default_preview_image as meta_preview_image_url %} {% firstof page_title page.seo_title page.title as meta_title %} {% autoescape off %} {% firstof page.get_plaintext_description page_intro.paragraph as meta_description %} @@ -8,12 +8,11 @@ {# html metadata #} {% if page.tags.exists %}{% endif %} -{# determine preview image: specified by page, cdh icon based on url, or default image #} {# open graph metadata #} - + {% if meta_description %}{% endif %} {# twitter card #} @@ -21,5 +20,5 @@ {% if meta_description %}{% endif %} - + {% endautoescape %} From 45c5b6666e3e3b808fe247ce60ce095470392a22 Mon Sep 17 00:00:00 2001 From: Haydn Greatnews Date: Tue, 3 Sep 2024 12:36:00 +1200 Subject: [PATCH 2/4] Add migration for PurpleMode migration to GenericSetting Rather than SiteSetting --- .../0061_purplemode_to_genericsetting.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 cdhweb/pages/migrations/0061_purplemode_to_genericsetting.py diff --git a/cdhweb/pages/migrations/0061_purplemode_to_genericsetting.py b/cdhweb/pages/migrations/0061_purplemode_to_genericsetting.py new file mode 100644 index 000000000..821d63048 --- /dev/null +++ b/cdhweb/pages/migrations/0061_purplemode_to_genericsetting.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.8 on 2024-09-03 00:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cdhpages', '0060_alter_contentpage_body_alter_homepage_body_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='purplemode', + name='site', + ), + migrations.AlterField( + model_name='purplemode', + name='purple_mode', + field=models.BooleanField(default=False, help_text='\n This will turn the site purple by transforming\n all shades of cyan on the site into shades of\n purple\n '), + ), + ] From e9a4ca87bbe263400b7d9b8444d915d0be3eb754 Mon Sep 17 00:00:00 2001 From: Haydn Greatnews Date: Tue, 3 Sep 2024 12:36:51 +1200 Subject: [PATCH 3/4] Set hostname for docker DB restore so we don't leave it unpopulated Also, remove a print statement --- cdhweb/pages/views.py | 1 - docker-compose.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cdhweb/pages/views.py b/cdhweb/pages/views.py index e4a66bca9..2f45f89b4 100644 --- a/cdhweb/pages/views.py +++ b/cdhweb/pages/views.py @@ -74,7 +74,6 @@ def get_queryset(self): form = self.get_form() if not form.is_valid(): - print(form.errors) return queryset.none() # get keyword query; support filters & phrase matching with double quotes diff --git a/docker-compose.yml b/docker-compose.yml index 7868bab71..0f9fd1166 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -42,7 +42,7 @@ services: volumes: - "./docker/database:/docker-entrypoint-initdb.d/:ro" environment: - WAGTAIL_SITE_HOSTNAME: "${SITE_HOSTNAME}" + WAGTAIL_SITE_HOSTNAME: "cdh.dev.springload.nz" WAGTAIL_SITE_PORT: "443" POSTGRES_DB: "cdhweb" POSTGRES_USER: "cdhweb" From f442b574a8be22ee4f43305a83931684b6f93511 Mon Sep 17 00:00:00 2001 From: Haydn Greatnews Date: Tue, 3 Sep 2024 12:48:28 +1200 Subject: [PATCH 4/4] Include hero image in OG resolution order --- cdhweb/pages/mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdhweb/pages/mixin.py b/cdhweb/pages/mixin.py index 4beacc6ae..45aea1f7c 100644 --- a/cdhweb/pages/mixin.py +++ b/cdhweb/pages/mixin.py @@ -171,7 +171,7 @@ class Meta: @cached_property def og_image_url(self): - image = get_first_of(self, "feed_image", "image") + image = get_first_of(self, "feed_image", "image", "hero_image") if not image: return get_default_preview_img_url()