From 01791ccbc5e9fba8dc058eec6b7781f718c0b0f1 Mon Sep 17 00:00:00 2001
From: nickmoreton
Date: Sun, 6 Oct 2024 22:38:40 +0100
Subject: [PATCH 01/20] Implement pico styling
---
app/home/templatetags/__init__.py | 0
app/home/templatetags/navigation_tags.py | 10 ++++++++++
app/templates/base.html | 2 +-
3 files changed, 11 insertions(+), 1 deletion(-)
create mode 100644 app/home/templatetags/__init__.py
create mode 100644 app/home/templatetags/navigation_tags.py
diff --git a/app/home/templatetags/__init__.py b/app/home/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/home/templatetags/navigation_tags.py b/app/home/templatetags/navigation_tags.py
new file mode 100644
index 0000000..c6e6309
--- /dev/null
+++ b/app/home/templatetags/navigation_tags.py
@@ -0,0 +1,10 @@
+from django import template
+
+from app.blog.models import BlogIndexPage
+
+register = template.Library()
+
+
+@register.simple_tag
+def get_blog_index_url():
+ return BlogIndexPage.objects.first().url
diff --git a/app/templates/base.html b/app/templates/base.html
index 0d550de..d2a83db 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -1,4 +1,4 @@
-{% load static wagtailcore_tags wagtailuserbar blog_tags %}
+{% load static wagtailcore_tags wagtailuserbar navigation_tags blog_tags %}
From 2482b232cc34080653956e15d291d8cdfa4ec305 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 15 Dec 2024 12:27:18 +0000
Subject: [PATCH 02/20] Remove pico styles
---
static_src/scss/app.scss | 89 +++++++++++++++++++++++++++++++++++-----
1 file changed, 79 insertions(+), 10 deletions(-)
diff --git a/static_src/scss/app.scss b/static_src/scss/app.scss
index 63ebb17..f6666ce 100644
--- a/static_src/scss/app.scss
+++ b/static_src/scss/app.scss
@@ -1,13 +1,82 @@
-@use '../../node_modules/@picocss/pico/scss/pico.scss' with (
- $theme-color: "pumpkin",
- );
+// @use '../../node_modules/@picocss/pico/scss/pico.scss' with (
+// $theme-color: "pumpkin",
+// );
-// The below line are optional,
-// you can use it if you want to use the color utility classes
-// otherwise they can be removed to save some bytes
-@use '../../node_modules/@picocss/pico/scss/colors' as *;
-@use "../../node_modules/@picocss/pico/scss/colors/utilities";
+// // The below line are optional,
+// // you can use it if you want to use the color utility classes
+// // otherwise they can be removed to save some bytes
+// @use '../../node_modules/@picocss/pico/scss/colors' as *;
+// @use "../../node_modules/@picocss/pico/scss/colors/utilities";
-// Your custom styles goes here
-@use 'components/example';
+// // Your custom styles goes here
+// @use 'components/example';
+
+/**
+* Temproary styles for the tutorial
+*/
+*,
+::before,
+::after {
+ box-sizing: border-box;
+}
+
+html {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif, Apple Color Emoji, "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+body {
+ min-height: 100vh;
+ max-width: 800px;
+ margin: 0 auto;
+ padding: 10px;
+ display: grid;
+ gap: 3vw;
+ grid-template-rows: min-content 1fr min-content;
+}
+
+a {
+ color: currentColor;
+}
+
+footer {
+ border-top: 2px dotted;
+ text-align: center;
+}
+
+header {
+ border-bottom: 2px dotted;
+}
+
+.template-homepage main {
+ text-align: center;
+}
+
+// Skip link styles
+.skip-link {
+ position: absolute;
+ top: -30px;
+}
+
+.skip-link:focus-visible {
+ top: 5px;
+}
+
+// Page form styles
+.page-form label {
+ display: block;
+ margin-top: 10px;
+ margin-bottom: 5px;
+}
+
+.page-form :is(textarea, input, select) {
+ width: 100%;
+ max-width: 500px;
+ min-height: 40px;
+ margin-top: 5px;
+ margin-bottom: 10px;
+}
+
+.page-form .helptext {
+ font-style: italic;
+}
From a32eeff0b78b231eee9fc6739f4201b347db5e12 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sat, 14 Dec 2024 17:14:18 +0000
Subject: [PATCH 03/20] Customise the home page
---
.../migrations/0004_customise_home_page.py | 60 +++++++++++++++++++
app/home/models.py | 39 +++++++++++-
app/home/templates/home/home_page.html | 22 ++++---
3 files changed, 111 insertions(+), 10 deletions(-)
create mode 100644 app/home/migrations/0004_customise_home_page.py
diff --git a/app/home/migrations/0004_customise_home_page.py b/app/home/migrations/0004_customise_home_page.py
new file mode 100644
index 0000000..5b1ee9d
--- /dev/null
+++ b/app/home/migrations/0004_customise_home_page.py
@@ -0,0 +1,60 @@
+# Generated by Django 5.1.4 on 2024-12-14 17:13
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("home", "0003_homepage_body"),
+ ("wagtailcore", "0094_alter_page_locale"),
+ ("wagtailimages", "0027_image_description"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="homepage",
+ name="hero_cta",
+ field=models.CharField(
+ blank=True,
+ help_text="Text to display on Call to Action",
+ max_length=255,
+ verbose_name="Hero CTA",
+ ),
+ ),
+ migrations.AddField(
+ model_name="homepage",
+ name="hero_cta_link",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="Choose a page to link to for the Call to Action",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="wagtailcore.page",
+ verbose_name="Hero CTA link",
+ ),
+ ),
+ migrations.AddField(
+ model_name="homepage",
+ name="hero_text",
+ field=models.CharField(
+ blank=True,
+ help_text="Write an introduction for the site",
+ max_length=255,
+ ),
+ ),
+ migrations.AddField(
+ model_name="homepage",
+ name="image",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="Homepage image",
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="wagtailimages.image",
+ ),
+ ),
+ ]
diff --git a/app/home/models.py b/app/home/models.py
index fe0adbe..7527f15 100644
--- a/app/home/models.py
+++ b/app/home/models.py
@@ -1,11 +1,48 @@
-from wagtail.admin.panels import FieldPanel
+from django.db import models
+from wagtail.admin.panels import FieldPanel, MultiFieldPanel
from wagtail.fields import RichTextField
from wagtail.models import Page
class HomePage(Page):
+ image = models.ForeignKey(
+ "wagtailimages.Image",
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name="+",
+ help_text="Homepage image",
+ )
+ hero_text = models.CharField(
+ blank=True, max_length=255, help_text="Write an introduction for the site"
+ )
+ hero_cta = models.CharField(
+ blank=True,
+ verbose_name="Hero CTA",
+ max_length=255,
+ help_text="Text to display on Call to Action",
+ )
+ hero_cta_link = models.ForeignKey(
+ "wagtailcore.Page",
+ null=True,
+ blank=True,
+ on_delete=models.SET_NULL,
+ related_name="+",
+ verbose_name="Hero CTA link",
+ help_text="Choose a page to link to for the Call to Action",
+ )
+
body = RichTextField(blank=True)
content_panels = Page.content_panels + [
+ MultiFieldPanel(
+ [
+ FieldPanel("image"),
+ FieldPanel("hero_text"),
+ FieldPanel("hero_cta"),
+ FieldPanel("hero_cta_link"),
+ ],
+ heading="Hero section",
+ ),
FieldPanel("body"),
]
diff --git a/app/home/templates/home/home_page.html b/app/home/templates/home/home_page.html
index 86b072e..505c8d7 100644
--- a/app/home/templates/home/home_page.html
+++ b/app/home/templates/home/home_page.html
@@ -1,15 +1,19 @@
{% extends "base.html" %}
-
-{% load wagtailcore_tags %}
+{% load wagtailcore_tags wagtailimages_tags %}
{% block body_class %}template-homepage{% endblock %}
{% block content %}
-
- {% if page.body %}
- {{ page.body|richtext }}
- {% else %}
- No content has been added to this page body yet.
+
+
+ {{ page.body|richtext }}
+{% endblock content %}
From ffdddd42b510127e4149e07181ea1fc64c808249 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sat, 14 Dec 2024 17:34:10 +0000
Subject: [PATCH 04/20] Create footer for all pages
---
app/base/__init__.py | 0
app/base/apps.py | 6 +
app/base/migrations/0001_create_footer.py | 152 ++++++++++++++++++
app/base/migrations/__init__.py | 0
app/base/models.py | 58 +++++++
.../templates/base/includes/footer_text.html | 5 +
app/base/templatetags/__init__.py | 0
app/base/templatetags/navigation_tags.py | 18 +++
app/settings/base.py | 5 +-
app/templates/base.html | 4 +-
app/templates/includes/footer.html | 24 +++
11 files changed, 268 insertions(+), 4 deletions(-)
create mode 100644 app/base/__init__.py
create mode 100644 app/base/apps.py
create mode 100644 app/base/migrations/0001_create_footer.py
create mode 100644 app/base/migrations/__init__.py
create mode 100644 app/base/models.py
create mode 100644 app/base/templates/base/includes/footer_text.html
create mode 100644 app/base/templatetags/__init__.py
create mode 100644 app/base/templatetags/navigation_tags.py
create mode 100644 app/templates/includes/footer.html
diff --git a/app/base/__init__.py b/app/base/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/base/apps.py b/app/base/apps.py
new file mode 100644
index 0000000..141b278
--- /dev/null
+++ b/app/base/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class BaseConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "app.base"
diff --git a/app/base/migrations/0001_create_footer.py b/app/base/migrations/0001_create_footer.py
new file mode 100644
index 0000000..585c65c
--- /dev/null
+++ b/app/base/migrations/0001_create_footer.py
@@ -0,0 +1,152 @@
+# Generated by Django 5.1.4 on 2024-12-14 17:23
+
+import uuid
+
+import django.db.models.deletion
+import wagtail.fields
+import wagtail.models
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ("wagtailcore", "0094_alter_page_locale"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="NavigationSettings",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "linkedin_url",
+ models.URLField(blank=True, verbose_name="LinkedIn URL"),
+ ),
+ ("github_url", models.URLField(blank=True, verbose_name="GitHub URL")),
+ (
+ "mastodon_url",
+ models.URLField(blank=True, verbose_name="Mastodon URL"),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.CreateModel(
+ name="FooterText",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "translation_key",
+ models.UUIDField(default=uuid.uuid4, editable=False),
+ ),
+ (
+ "live",
+ models.BooleanField(
+ default=True, editable=False, verbose_name="live"
+ ),
+ ),
+ (
+ "has_unpublished_changes",
+ models.BooleanField(
+ default=False,
+ editable=False,
+ verbose_name="has unpublished changes",
+ ),
+ ),
+ (
+ "first_published_at",
+ models.DateTimeField(
+ blank=True,
+ db_index=True,
+ null=True,
+ verbose_name="first published at",
+ ),
+ ),
+ (
+ "last_published_at",
+ models.DateTimeField(
+ editable=False, null=True, verbose_name="last published at"
+ ),
+ ),
+ (
+ "go_live_at",
+ models.DateTimeField(
+ blank=True, null=True, verbose_name="go live date/time"
+ ),
+ ),
+ (
+ "expire_at",
+ models.DateTimeField(
+ blank=True, null=True, verbose_name="expiry date/time"
+ ),
+ ),
+ (
+ "expired",
+ models.BooleanField(
+ default=False, editable=False, verbose_name="expired"
+ ),
+ ),
+ ("body", wagtail.fields.RichTextField()),
+ (
+ "latest_revision",
+ models.ForeignKey(
+ blank=True,
+ editable=False,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="wagtailcore.revision",
+ verbose_name="latest revision",
+ ),
+ ),
+ (
+ "live_revision",
+ models.ForeignKey(
+ blank=True,
+ editable=False,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="+",
+ to="wagtailcore.revision",
+ verbose_name="live revision",
+ ),
+ ),
+ (
+ "locale",
+ models.ForeignKey(
+ editable=False,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="+",
+ to="wagtailcore.locale",
+ verbose_name="locale",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name_plural": "Footer Text",
+ "abstract": False,
+ "unique_together": {("translation_key", "locale")},
+ },
+ bases=(wagtail.models.PreviewableMixin, models.Model),
+ ),
+ ]
diff --git a/app/base/migrations/__init__.py b/app/base/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/base/models.py b/app/base/models.py
new file mode 100644
index 0000000..9bf0966
--- /dev/null
+++ b/app/base/models.py
@@ -0,0 +1,58 @@
+from django.db import models
+from wagtail.admin.panels import FieldPanel, MultiFieldPanel, PublishingPanel
+from wagtail.contrib.settings.models import BaseGenericSetting, register_setting
+from wagtail.fields import RichTextField
+from wagtail.models import (
+ DraftStateMixin,
+ PreviewableMixin,
+ RevisionMixin,
+ TranslatableMixin,
+)
+from wagtail.snippets.models import register_snippet
+
+
+@register_setting
+class NavigationSettings(BaseGenericSetting):
+ linkedin_url = models.URLField(verbose_name="LinkedIn URL", blank=True)
+ github_url = models.URLField(verbose_name="GitHub URL", blank=True)
+ mastodon_url = models.URLField(verbose_name="Mastodon URL", blank=True)
+
+ panels = [
+ MultiFieldPanel(
+ [
+ FieldPanel("linkedin_url"),
+ FieldPanel("github_url"),
+ FieldPanel("mastodon_url"),
+ ],
+ "Social settings",
+ )
+ ]
+
+
+@register_snippet
+class FooterText(
+ DraftStateMixin,
+ RevisionMixin,
+ PreviewableMixin,
+ TranslatableMixin,
+ models.Model,
+):
+
+ body = RichTextField()
+
+ panels = [
+ FieldPanel("body"),
+ PublishingPanel(),
+ ]
+
+ def __str__(self):
+ return "Footer text"
+
+ def get_preview_template(self, request, mode_name):
+ return "base.html"
+
+ def get_preview_context(self, request, mode_name):
+ return {"footer_text": self.body}
+
+ class Meta(TranslatableMixin.Meta):
+ verbose_name_plural = "Footer Text"
diff --git a/app/base/templates/base/includes/footer_text.html b/app/base/templates/base/includes/footer_text.html
new file mode 100644
index 0000000..a536e26
--- /dev/null
+++ b/app/base/templates/base/includes/footer_text.html
@@ -0,0 +1,5 @@
+{% load wagtailcore_tags %}
+
+
+ {{ footer_text|richtext }}
+
diff --git a/app/base/templatetags/__init__.py b/app/base/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/base/templatetags/navigation_tags.py b/app/base/templatetags/navigation_tags.py
new file mode 100644
index 0000000..ef3b3ac
--- /dev/null
+++ b/app/base/templatetags/navigation_tags.py
@@ -0,0 +1,18 @@
+from django import template
+
+from app.base.models import FooterText
+
+register = template.Library()
+
+
+@register.inclusion_tag("base/includes/footer_text.html", takes_context=True)
+def get_footer_text(context):
+ footer_text = context.get("footer_text", "")
+
+ if not footer_text:
+ instance = FooterText.objects.filter(live=True).first()
+ footer_text = instance.body if instance else ""
+
+ return {
+ "footer_text": footer_text,
+ }
diff --git a/app/settings/base.py b/app/settings/base.py
index c85d76a..1315723 100644
--- a/app/settings/base.py
+++ b/app/settings/base.py
@@ -25,11 +25,13 @@
# Application definition
INSTALLED_APPS = [
+ "app.base",
+ "app.blog",
"app.home",
"app.search",
- "app.blog",
"wagtail.contrib.forms",
"wagtail.contrib.redirects",
+ "wagtail.contrib.settings",
"wagtail.contrib.table_block",
"wagtail.embeds",
"wagtail.sites",
@@ -76,6 +78,7 @@
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
+ "wagtail.contrib.settings.context_processors.settings",
],
},
},
diff --git a/app/templates/base.html b/app/templates/base.html
index d2a83db..a3dbe23 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -54,9 +54,7 @@
{% block content %}{% endblock %}
-
+ {% include "includes/footer.html" %}
{# Global javascript #}
diff --git a/app/templates/includes/footer.html b/app/templates/includes/footer.html
new file mode 100644
index 0000000..24479f7
--- /dev/null
+++ b/app/templates/includes/footer.html
@@ -0,0 +1,24 @@
+{% load navigation_tags %}
+
+
From e988a2e623c302b412e873772c5e0e6e7599a2b1 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sat, 14 Dec 2024 17:37:27 +0000
Subject: [PATCH 05/20] Set up site menu
---
app/base/templatetags/navigation_tags.py | 6 ++++++
app/templates/base.html | 16 +---------------
app/templates/includes/header.html | 13 +++++++++++++
3 files changed, 20 insertions(+), 15 deletions(-)
create mode 100644 app/templates/includes/header.html
diff --git a/app/base/templatetags/navigation_tags.py b/app/base/templatetags/navigation_tags.py
index ef3b3ac..44d887c 100644
--- a/app/base/templatetags/navigation_tags.py
+++ b/app/base/templatetags/navigation_tags.py
@@ -1,4 +1,5 @@
from django import template
+from wagtail.models import Site
from app.base.models import FooterText
@@ -16,3 +17,8 @@ def get_footer_text(context):
return {
"footer_text": footer_text,
}
+
+
+@register.simple_tag(takes_context=True)
+def get_site_root(context):
+ return Site.find_for_request(context["request"]).root_page
diff --git a/app/templates/base.html b/app/templates/base.html
index a3dbe23..1f23cc3 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -34,21 +34,7 @@
{% wagtailuserbar %}
- {% blog_index_page as blog %}
-
+ {% include "includes/header.html" %}
{% block content %}{% endblock %}
diff --git a/app/templates/includes/header.html b/app/templates/includes/header.html
new file mode 100644
index 0000000..a5c6cab
--- /dev/null
+++ b/app/templates/includes/header.html
@@ -0,0 +1,13 @@
+{% load wagtailcore_tags navigation_tags %}
+
+
From 98b2853a846bb2e9920b262ba4b9ee3823cb6aae Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sat, 14 Dec 2024 17:44:50 +0000
Subject: [PATCH 06/20] Improve user experience
---
app/templates/base.html | 6 ++++--
app/templates/includes/header.html | 15 ++++++++++-----
2 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/app/templates/base.html b/app/templates/base.html
index 1f23cc3..e83bd29 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -23,6 +23,9 @@
{% endif %}
+
+
+
{# Global stylesheets #}
@@ -32,11 +35,10 @@
- {% wagtailuserbar %}
{% include "includes/header.html" %}
-
+
{% block content %}{% endblock %}
diff --git a/app/templates/includes/header.html b/app/templates/includes/header.html
index a5c6cab..c6b1829 100644
--- a/app/templates/includes/header.html
+++ b/app/templates/includes/header.html
@@ -1,13 +1,18 @@
-{% load wagtailcore_tags navigation_tags %}
+{% load wagtailcore_tags navigation_tags wagtailuserbar %}
From f3d55a410c3818a0c6c6dd12daa7d4d31b2cbcb4 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sat, 14 Dec 2024 17:54:55 +0000
Subject: [PATCH 07/20] Create a contact page
---
app/base/migrations/0002_add_form_page.py | 162 ++++++++++++++++++
app/base/models.py | 39 ++++-
app/base/templates/base/form_page.html | 15 ++
.../templates/base/form_page_landing.html | 9 +
4 files changed, 224 insertions(+), 1 deletion(-)
create mode 100644 app/base/migrations/0002_add_form_page.py
create mode 100644 app/base/templates/base/form_page.html
create mode 100644 app/base/templates/base/form_page_landing.html
diff --git a/app/base/migrations/0002_add_form_page.py b/app/base/migrations/0002_add_form_page.py
new file mode 100644
index 0000000..054ed08
--- /dev/null
+++ b/app/base/migrations/0002_add_form_page.py
@@ -0,0 +1,162 @@
+# Generated by Django 5.0.9 on 2024-12-14 17:50
+
+import django.db.models.deletion
+import modelcluster.fields
+import wagtail.contrib.forms.models
+import wagtail.fields
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("base", "0001_create_footer"),
+ ("wagtailcore", "0094_alter_page_locale"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="FormPage",
+ 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",
+ ),
+ ),
+ (
+ "to_address",
+ models.CharField(
+ blank=True,
+ help_text="Optional - form submissions will be emailed to these addresses. Separate multiple addresses by comma.", # noqa: E501
+ max_length=255,
+ validators=[wagtail.contrib.forms.models.validate_to_address],
+ verbose_name="to address",
+ ),
+ ),
+ (
+ "from_address",
+ models.EmailField(
+ blank=True, max_length=255, verbose_name="from address"
+ ),
+ ),
+ (
+ "subject",
+ models.CharField(
+ blank=True, max_length=255, verbose_name="subject"
+ ),
+ ),
+ ("intro", wagtail.fields.RichTextField(blank=True)),
+ ("thank_you_text", wagtail.fields.RichTextField(blank=True)),
+ ],
+ options={
+ "abstract": False,
+ },
+ bases=(
+ wagtail.contrib.forms.models.FormMixin,
+ "wagtailcore.page",
+ models.Model,
+ ),
+ ),
+ migrations.CreateModel(
+ name="FormField",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "sort_order",
+ models.IntegerField(blank=True, editable=False, null=True),
+ ),
+ (
+ "clean_name",
+ models.CharField(
+ blank=True,
+ default="",
+ help_text="Safe name of the form field, the label converted to ascii_snake_case",
+ max_length=255,
+ verbose_name="name",
+ ),
+ ),
+ (
+ "label",
+ models.CharField(
+ help_text="The label of the form field",
+ max_length=255,
+ verbose_name="label",
+ ),
+ ),
+ (
+ "field_type",
+ models.CharField(
+ choices=[
+ ("singleline", "Single line text"),
+ ("multiline", "Multi-line text"),
+ ("email", "Email"),
+ ("number", "Number"),
+ ("url", "URL"),
+ ("checkbox", "Checkbox"),
+ ("checkboxes", "Checkboxes"),
+ ("dropdown", "Drop down"),
+ ("multiselect", "Multiple select"),
+ ("radio", "Radio buttons"),
+ ("date", "Date"),
+ ("datetime", "Date/time"),
+ ("hidden", "Hidden field"),
+ ],
+ max_length=16,
+ verbose_name="field type",
+ ),
+ ),
+ (
+ "required",
+ models.BooleanField(default=True, verbose_name="required"),
+ ),
+ (
+ "choices",
+ models.TextField(
+ blank=True,
+ help_text="Comma or new line separated list of choices. Only applicable in checkboxes, radio and dropdown.", # noqa: E501
+ verbose_name="choices",
+ ),
+ ),
+ (
+ "default_value",
+ models.TextField(
+ blank=True,
+ help_text="Default value. Comma or new line separated values supported for checkboxes.",
+ verbose_name="default value",
+ ),
+ ),
+ (
+ "help_text",
+ models.CharField(
+ blank=True, max_length=255, verbose_name="help text"
+ ),
+ ),
+ (
+ "page",
+ modelcluster.fields.ParentalKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="form_fields",
+ to="base.formpage",
+ ),
+ ),
+ ],
+ options={
+ "ordering": ["sort_order"],
+ "abstract": False,
+ },
+ ),
+ ]
diff --git a/app/base/models.py b/app/base/models.py
index 9bf0966..e2b0a75 100644
--- a/app/base/models.py
+++ b/app/base/models.py
@@ -1,5 +1,14 @@
from django.db import models
-from wagtail.admin.panels import FieldPanel, MultiFieldPanel, PublishingPanel
+from modelcluster.fields import ParentalKey
+from wagtail.admin.panels import (
+ FieldPanel,
+ FieldRowPanel,
+ InlinePanel,
+ MultiFieldPanel,
+ PublishingPanel,
+)
+from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormField
+from wagtail.contrib.forms.panels import FormSubmissionsPanel
from wagtail.contrib.settings.models import BaseGenericSetting, register_setting
from wagtail.fields import RichTextField
from wagtail.models import (
@@ -56,3 +65,31 @@ def get_preview_context(self, request, mode_name):
class Meta(TranslatableMixin.Meta):
verbose_name_plural = "Footer Text"
+
+
+class FormField(AbstractFormField):
+ page = ParentalKey("FormPage", on_delete=models.CASCADE, related_name="form_fields")
+
+
+class FormPage(AbstractEmailForm):
+ intro = RichTextField(blank=True)
+ thank_you_text = RichTextField(blank=True)
+
+ content_panels = AbstractEmailForm.content_panels + [
+ FormSubmissionsPanel(),
+ FieldPanel("intro"),
+ InlinePanel("form_fields", label="Form fields"),
+ FieldPanel("thank_you_text"),
+ MultiFieldPanel(
+ [
+ FieldRowPanel(
+ [
+ FieldPanel("from_address"),
+ FieldPanel("to_address"),
+ ]
+ ),
+ FieldPanel("subject"),
+ ],
+ "Email",
+ ),
+ ]
diff --git a/app/base/templates/base/form_page.html b/app/base/templates/base/form_page.html
new file mode 100644
index 0000000..db1fff2
--- /dev/null
+++ b/app/base/templates/base/form_page.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+{% load wagtailcore_tags %}
+
+{% block body_class %}template-formpage{% endblock %}
+
+{% block content %}
+ {{ page.title }}
+ {{ page.intro|richtext }}
+
+
+{% endblock content %}
diff --git a/app/base/templates/base/form_page_landing.html b/app/base/templates/base/form_page_landing.html
new file mode 100644
index 0000000..f4a1c07
--- /dev/null
+++ b/app/base/templates/base/form_page_landing.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{% load wagtailcore_tags %}
+
+{% block body_class %}template-formpage{% endblock %}
+
+{% block content %}
+ {{ page.title }}
+ {{ page.thank_you_text|richtext }}
+{% endblock content %}
From d58631132c03be776f8dca9cb08a2956ee444c3c Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 15 Dec 2024 12:27:42 +0000
Subject: [PATCH 08/20] allow mailhog email backend
---
app/settings/dev.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/app/settings/dev.py b/app/settings/dev.py
index 1ca04f2..3ab230b 100644
--- a/app/settings/dev.py
+++ b/app/settings/dev.py
@@ -9,8 +9,16 @@
# SECURITY WARNING: define the correct hosts in production!
ALLOWED_HOSTS = ["*"]
+# CONSOLE email backend
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
+# MAILHOG email backend.
+# Testing email with mailhog, the mailhog container is running on port 1025
+# UNCOMENT THE FOLLOWING LINES TO USE MAILHOG
+# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
+# EMAIL_HOST = "mailhog"
+# EMAIL_PORT = 1025
+
# Remove if not required
INSTALLED_APPS += [ # noqa F405
"app.style_guide",
From c8bbce6fffb92d79faa68ea63ed7d2fe6988db1b Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sat, 14 Dec 2024 18:12:54 +0000
Subject: [PATCH 09/20] Add a portfolio page
---
app/base/blocks.py | 47 +++++++++
.../base/blocks/captioned_image_block.html | 6 ++
.../templates/base/blocks/embed_block.html | 1 +
.../templates/base/blocks/heading_block.html | 7 ++
app/portfolio/__init__.py | 0
app/portfolio/apps.py | 6 ++
app/portfolio/blocks.py | 36 +++++++
.../migrations/0001_create_portfolio.py | 96 ++++++++++++++++++
.../migrations/0002_add_more_custom_blocks.py | 99 +++++++++++++++++++
app/portfolio/migrations/__init__.py | 0
app/portfolio/models.py | 20 ++++
.../portfolio/blocks/card_block.html | 8 ++
.../blocks/featured_posts_block.html | 16 +++
.../templates/portfolio/portfolio_page.html | 11 +++
app/settings/base.py | 1 +
15 files changed, 354 insertions(+)
create mode 100644 app/base/blocks.py
create mode 100644 app/base/templates/base/blocks/captioned_image_block.html
create mode 100644 app/base/templates/base/blocks/embed_block.html
create mode 100644 app/base/templates/base/blocks/heading_block.html
create mode 100644 app/portfolio/__init__.py
create mode 100644 app/portfolio/apps.py
create mode 100644 app/portfolio/blocks.py
create mode 100644 app/portfolio/migrations/0001_create_portfolio.py
create mode 100644 app/portfolio/migrations/0002_add_more_custom_blocks.py
create mode 100644 app/portfolio/migrations/__init__.py
create mode 100644 app/portfolio/models.py
create mode 100644 app/portfolio/templates/portfolio/blocks/card_block.html
create mode 100644 app/portfolio/templates/portfolio/blocks/featured_posts_block.html
create mode 100644 app/portfolio/templates/portfolio/portfolio_page.html
diff --git a/app/base/blocks.py b/app/base/blocks.py
new file mode 100644
index 0000000..fa0c27f
--- /dev/null
+++ b/app/base/blocks.py
@@ -0,0 +1,47 @@
+from wagtail.blocks import (
+ CharBlock,
+ ChoiceBlock,
+ RichTextBlock,
+ StreamBlock,
+ StructBlock,
+)
+from wagtail.embeds.blocks import EmbedBlock
+from wagtail.images.blocks import ImageBlock
+
+
+class CaptionedImageBlock(StructBlock):
+ image = ImageBlock(required=True)
+ caption = CharBlock(required=False)
+ attribution = CharBlock(required=False)
+
+ class Meta:
+ icon = "image"
+ template = "base/blocks/captioned_image_block.html"
+
+
+class HeadingBlock(StructBlock):
+ heading_text = CharBlock(classname="title", required=True)
+ size = ChoiceBlock(
+ choices=[
+ ("", "Select a heading size"),
+ ("h2", "H2"),
+ ("h3", "H3"),
+ ("h4", "H4"),
+ ],
+ blank=True,
+ required=False,
+ )
+
+ class Meta:
+ icon = "title"
+ template = "base/blocks/heading_block.html"
+
+
+class BaseStreamBlock(StreamBlock):
+ heading_block = HeadingBlock()
+ paragraph_block = RichTextBlock(icon="pilcrow")
+ image_block = CaptionedImageBlock()
+ embed_block = EmbedBlock(
+ help_text="Insert a URL to embed. For example, https://www.youtube.com/watch?v=SGJFWirQ3ks",
+ icon="media",
+ )
diff --git a/app/base/templates/base/blocks/captioned_image_block.html b/app/base/templates/base/blocks/captioned_image_block.html
new file mode 100644
index 0000000..a4b1bad
--- /dev/null
+++ b/app/base/templates/base/blocks/captioned_image_block.html
@@ -0,0 +1,6 @@
+{% load wagtailimages_tags %}
+
+
+ {% image self.image fill-600x338 loading="lazy" %}
+ {{ self.caption }} - {{ self.attribution }}
+
diff --git a/app/base/templates/base/blocks/embed_block.html b/app/base/templates/base/blocks/embed_block.html
new file mode 100644
index 0000000..e3125de
--- /dev/null
+++ b/app/base/templates/base/blocks/embed_block.html
@@ -0,0 +1 @@
+{{ self }}
diff --git a/app/base/templates/base/blocks/heading_block.html b/app/base/templates/base/blocks/heading_block.html
new file mode 100644
index 0000000..8405ce5
--- /dev/null
+++ b/app/base/templates/base/blocks/heading_block.html
@@ -0,0 +1,7 @@
+{% if self.size == 'h2' %}
+ {{ self.heading_text }}
+{% elif self.size == 'h3' %}
+ {{ self.heading_text }}
+{% elif self.size == 'h4' %}
+ {{ self.heading_text }}
+{% endif %}
diff --git a/app/portfolio/__init__.py b/app/portfolio/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/portfolio/apps.py b/app/portfolio/apps.py
new file mode 100644
index 0000000..390782e
--- /dev/null
+++ b/app/portfolio/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class PortfolioConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "app.portfolio"
diff --git a/app/portfolio/blocks.py b/app/portfolio/blocks.py
new file mode 100644
index 0000000..11fd69f
--- /dev/null
+++ b/app/portfolio/blocks.py
@@ -0,0 +1,36 @@
+from wagtail.blocks import (
+ CharBlock,
+ ListBlock,
+ PageChooserBlock,
+ RichTextBlock,
+ StructBlock,
+)
+from wagtail.images.blocks import ImageBlock
+
+from app.base.blocks import BaseStreamBlock
+
+
+class CardBlock(StructBlock):
+ heading = CharBlock()
+ text = RichTextBlock(features=["bold", "italic", "link"])
+ image = ImageBlock(required=False)
+
+ class Meta:
+ icon = "form"
+ template = "portfolio/blocks/card_block.html"
+
+
+class FeaturedPostsBlock(StructBlock):
+ heading = CharBlock()
+ text = RichTextBlock(features=["bold", "italic", "link"], required=False)
+ posts = ListBlock(PageChooserBlock(page_type="blog.BlogPage"))
+
+ class Meta:
+ icon = "folder-open-inverse"
+ template = "portfolio/blocks/featured_posts_block.html"
+
+
+class PortfolioStreamBlock(BaseStreamBlock):
+
+ card = CardBlock(group="Sections")
+ featured_posts = FeaturedPostsBlock(group="Sections")
diff --git a/app/portfolio/migrations/0001_create_portfolio.py b/app/portfolio/migrations/0001_create_portfolio.py
new file mode 100644
index 0000000..c7de119
--- /dev/null
+++ b/app/portfolio/migrations/0001_create_portfolio.py
@@ -0,0 +1,96 @@
+# Generated by Django 5.1.4 on 2024-12-14 18:02
+
+import django.db.models.deletion
+import wagtail.fields
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ("wagtailcore", "0094_alter_page_locale"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="PortfolioPage",
+ 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",
+ ),
+ ),
+ (
+ "body",
+ wagtail.fields.StreamField(
+ [
+ ("heading_block", 2),
+ ("paragraph_block", 3),
+ ("image_block", 6),
+ ("embed_block", 7),
+ ],
+ blank=True,
+ block_lookup={
+ 0: (
+ "wagtail.blocks.CharBlock",
+ (),
+ {"form_classname": "title", "required": True},
+ ),
+ 1: (
+ "wagtail.blocks.ChoiceBlock",
+ [],
+ {
+ "blank": True,
+ "choices": [
+ ("", "Select a heading size"),
+ ("h2", "H2"),
+ ("h3", "H3"),
+ ("h4", "H4"),
+ ],
+ "required": False,
+ },
+ ),
+ 2: (
+ "wagtail.blocks.StructBlock",
+ [[("heading_text", 0), ("size", 1)]],
+ {},
+ ),
+ 3: (
+ "wagtail.blocks.RichTextBlock",
+ (),
+ {"icon": "pilcrow"},
+ ),
+ 4: ("wagtail.images.blocks.ImageBlock", [], {}),
+ 5: ("wagtail.blocks.CharBlock", (), {"required": False}),
+ 6: (
+ "wagtail.blocks.StructBlock",
+ [[("image", 4), ("caption", 5), ("attribution", 5)]],
+ {},
+ ),
+ 7: (
+ "wagtail.embeds.blocks.EmbedBlock",
+ (),
+ {
+ "help_text": "Insert a URL to embed. For example, https://www.youtube.com/watch?v=SGJFWirQ3ks", # noqa: E501
+ "icon": "media",
+ },
+ ),
+ },
+ help_text="Use this section to list your projects and skills.",
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ bases=("wagtailcore.page",),
+ ),
+ ]
diff --git a/app/portfolio/migrations/0002_add_more_custom_blocks.py b/app/portfolio/migrations/0002_add_more_custom_blocks.py
new file mode 100644
index 0000000..5b88121
--- /dev/null
+++ b/app/portfolio/migrations/0002_add_more_custom_blocks.py
@@ -0,0 +1,99 @@
+# Generated by Django 5.1.4 on 2024-12-14 18:11
+
+import wagtail.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("portfolio", "0001_create_portfolio"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="portfoliopage",
+ name="body",
+ field=wagtail.fields.StreamField(
+ [
+ ("heading_block", 2),
+ ("paragraph_block", 3),
+ ("image_block", 6),
+ ("embed_block", 7),
+ ("card", 10),
+ ("featured_posts", 14),
+ ],
+ blank=True,
+ block_lookup={
+ 0: (
+ "wagtail.blocks.CharBlock",
+ (),
+ {"form_classname": "title", "required": True},
+ ),
+ 1: (
+ "wagtail.blocks.ChoiceBlock",
+ [],
+ {
+ "blank": True,
+ "choices": [
+ ("", "Select a heading size"),
+ ("h2", "H2"),
+ ("h3", "H3"),
+ ("h4", "H4"),
+ ],
+ "required": False,
+ },
+ ),
+ 2: (
+ "wagtail.blocks.StructBlock",
+ [[("heading_text", 0), ("size", 1)]],
+ {},
+ ),
+ 3: ("wagtail.blocks.RichTextBlock", (), {"icon": "pilcrow"}),
+ 4: ("wagtail.images.blocks.ImageBlock", [], {}),
+ 5: ("wagtail.blocks.CharBlock", (), {"required": False}),
+ 6: (
+ "wagtail.blocks.StructBlock",
+ [[("image", 4), ("caption", 5), ("attribution", 5)]],
+ {},
+ ),
+ 7: (
+ "wagtail.embeds.blocks.EmbedBlock",
+ (),
+ {
+ "help_text": "Insert a URL to embed. For example, https://www.youtube.com/watch?v=SGJFWirQ3ks", # noqa: E501
+ "icon": "media",
+ },
+ ),
+ 8: ("wagtail.blocks.CharBlock", (), {}),
+ 9: (
+ "wagtail.blocks.RichTextBlock",
+ (),
+ {"features": ["bold", "italic", "link"]},
+ ),
+ 10: (
+ "wagtail.blocks.StructBlock",
+ [[("heading", 8), ("text", 9), ("image", 4)]],
+ {"group": "Sections"},
+ ),
+ 11: (
+ "wagtail.blocks.RichTextBlock",
+ (),
+ {"features": ["bold", "italic", "link"], "required": False},
+ ),
+ 12: (
+ "wagtail.blocks.PageChooserBlock",
+ (),
+ {"page_type": ["blog.BlogPage"]},
+ ),
+ 13: ("wagtail.blocks.ListBlock", (12,), {}),
+ 14: (
+ "wagtail.blocks.StructBlock",
+ [[("heading", 8), ("text", 11), ("posts", 13)]],
+ {"group": "Sections"},
+ ),
+ },
+ help_text="Use this section to list your projects and skills.",
+ ),
+ ),
+ ]
diff --git a/app/portfolio/migrations/__init__.py b/app/portfolio/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/portfolio/models.py b/app/portfolio/models.py
new file mode 100644
index 0000000..b846d48
--- /dev/null
+++ b/app/portfolio/models.py
@@ -0,0 +1,20 @@
+from wagtail.admin.panels import FieldPanel
+from wagtail.fields import StreamField
+from wagtail.models import Page
+
+from app.portfolio.blocks import PortfolioStreamBlock
+
+
+class PortfolioPage(Page):
+ parent_page_types = ["home.HomePage"]
+
+ body = StreamField(
+ PortfolioStreamBlock(),
+ blank=True,
+ use_json_field=True,
+ help_text="Use this section to list your projects and skills.",
+ )
+
+ content_panels = Page.content_panels + [
+ FieldPanel("body"),
+ ]
diff --git a/app/portfolio/templates/portfolio/blocks/card_block.html b/app/portfolio/templates/portfolio/blocks/card_block.html
new file mode 100644
index 0000000..3a2e020
--- /dev/null
+++ b/app/portfolio/templates/portfolio/blocks/card_block.html
@@ -0,0 +1,8 @@
+{% load wagtailcore_tags wagtailimages_tags %}
+
+
{{ self.heading }}
+
{{ self.text|richtext }}
+ {% if self.image %}
+ {% image self.image width-480 %}
+ {% endif %}
+
diff --git a/app/portfolio/templates/portfolio/blocks/featured_posts_block.html b/app/portfolio/templates/portfolio/blocks/featured_posts_block.html
new file mode 100644
index 0000000..8b63bb3
--- /dev/null
+++ b/app/portfolio/templates/portfolio/blocks/featured_posts_block.html
@@ -0,0 +1,16 @@
+{% load wagtailcore_tags %}
+
+
{{ self.heading }}
+ {% if self.text %}
+
{{ self.text|richtext }}
+ {% endif %}
+
+
+ {% for page in self.posts %}
+
+ {% endfor %}
+
+
diff --git a/app/portfolio/templates/portfolio/portfolio_page.html b/app/portfolio/templates/portfolio/portfolio_page.html
new file mode 100644
index 0000000..862412a
--- /dev/null
+++ b/app/portfolio/templates/portfolio/portfolio_page.html
@@ -0,0 +1,11 @@
+{% extends "base.html" %}
+
+{% load wagtailcore_tags wagtailimages_tags %}
+
+{% block body_class %}template-portfolio{% endblock %}
+
+{% block content %}
+ {{ page.title }}
+
+ {{ page.body }}
+{% endblock %}
diff --git a/app/settings/base.py b/app/settings/base.py
index 1315723..e428a8c 100644
--- a/app/settings/base.py
+++ b/app/settings/base.py
@@ -28,6 +28,7 @@
"app.base",
"app.blog",
"app.home",
+ "app.portfolio",
"app.search",
"wagtail.contrib.forms",
"wagtail.contrib.redirects",
From 37ce1f8ba4db51f1625b2bb0a9989d577bd0c895 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 15 Dec 2024 12:00:44 +0000
Subject: [PATCH 10/20] Add search
---
app/search/templates/search/search.html | 12 ++++++++++--
app/templates/includes/header.html | 1 +
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/app/search/templates/search/search.html b/app/search/templates/search/search.html
index 476427f..3291ae8 100644
--- a/app/search/templates/search/search.html
+++ b/app/search/templates/search/search.html
@@ -14,7 +14,10 @@ Search
{% if search_results %}
-
+
+You searched{% if search_query %} for “{{ search_query }}”{% endif %}, {{ search_results.paginator.count }} result{{ search_results.paginator.count|pluralize }} found.
+
+
{% for result in search_results %}
@@ -23,7 +26,11 @@
{% endif %}
{% endfor %}
-
+
+
+{% if search_results.paginator.num_pages > 1 %}
+ Page {{ search_results.number }} of {{ search_results.paginator.num_pages }}, showing {{ search_results|length }} result{{ search_results|pluralize }} out of {{ search_results.paginator.count }}
+{% endif %}
{% if search_results.has_previous %}
Previous
@@ -32,6 +39,7 @@
{% if search_results.has_next %}
Next
{% endif %}
+
{% elif search_query %}
No results found
{% endif %}
diff --git a/app/templates/includes/header.html b/app/templates/includes/header.html
index c6b1829..6b436cd 100644
--- a/app/templates/includes/header.html
+++ b/app/templates/includes/header.html
@@ -12,6 +12,7 @@
{{ menuitem.title }} {% if not forloop.last %} | {% endif %}
{% endfor %}
+ | Search
{% wagtailuserbar "top-right" %}
From 6108c57660f87540c1750309e51f283b7bdd6665 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 15 Dec 2024 12:41:38 +0000
Subject: [PATCH 11/20] Add pico styling back
With the exceptrion of the skip link styles
---
static_src/scss/app.scss | 112 +++++++++++++++++++--------------------
1 file changed, 56 insertions(+), 56 deletions(-)
diff --git a/static_src/scss/app.scss b/static_src/scss/app.scss
index f6666ce..6aa3506 100644
--- a/static_src/scss/app.scss
+++ b/static_src/scss/app.scss
@@ -1,56 +1,56 @@
-// @use '../../node_modules/@picocss/pico/scss/pico.scss' with (
-// $theme-color: "pumpkin",
-// );
+@use '../../node_modules/@picocss/pico/scss/pico.scss' with (
+ $theme-color: "pumpkin",
+ );
-// // The below line are optional,
-// // you can use it if you want to use the color utility classes
-// // otherwise they can be removed to save some bytes
-// @use '../../node_modules/@picocss/pico/scss/colors' as *;
-// @use "../../node_modules/@picocss/pico/scss/colors/utilities";
+// The below line are optional,
+// you can use it if you want to use the color utility classes
+// otherwise they can be removed to save some bytes
+@use '../../node_modules/@picocss/pico/scss/colors' as *;
+@use "../../node_modules/@picocss/pico/scss/colors/utilities";
-// // Your custom styles goes here
-// @use 'components/example';
+// Your custom styles goes here
+@use 'components/example';
/**
* Temproary styles for the tutorial
*/
-*,
-::before,
-::after {
- box-sizing: border-box;
-}
+// *,
+// ::before,
+// ::after {
+// box-sizing: border-box;
+// }
-html {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif, Apple Color Emoji, "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
-}
+// html {
+// font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif, Apple Color Emoji, "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+// }
-body {
- min-height: 100vh;
- max-width: 800px;
- margin: 0 auto;
- padding: 10px;
- display: grid;
- gap: 3vw;
- grid-template-rows: min-content 1fr min-content;
-}
+// body {
+// min-height: 100vh;
+// max-width: 800px;
+// margin: 0 auto;
+// padding: 10px;
+// display: grid;
+// gap: 3vw;
+// grid-template-rows: min-content 1fr min-content;
+// }
-a {
- color: currentColor;
-}
+// a {
+// color: currentColor;
+// }
-footer {
- border-top: 2px dotted;
- text-align: center;
-}
+// footer {
+// border-top: 2px dotted;
+// text-align: center;
+// }
-header {
- border-bottom: 2px dotted;
-}
+// header {
+// border-bottom: 2px dotted;
+// }
-.template-homepage main {
- text-align: center;
-}
+// .template-homepage main {
+// text-align: center;
+// }
// Skip link styles
.skip-link {
@@ -62,21 +62,21 @@ header {
top: 5px;
}
-// Page form styles
-.page-form label {
- display: block;
- margin-top: 10px;
- margin-bottom: 5px;
-}
+// // Page form styles
+// .page-form label {
+// display: block;
+// margin-top: 10px;
+// margin-bottom: 5px;
+// }
-.page-form :is(textarea, input, select) {
- width: 100%;
- max-width: 500px;
- min-height: 40px;
- margin-top: 5px;
- margin-bottom: 10px;
-}
+// .page-form :is(textarea, input, select) {
+// width: 100%;
+// max-width: 500px;
+// min-height: 40px;
+// margin-top: 5px;
+// margin-bottom: 10px;
+// }
-.page-form .helptext {
- font-style: italic;
-}
+// .page-form .helptext {
+// font-style: italic;
+// }
From d7d8da716fdd9be9cf6d3e0441a7d17965eeac41 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 15 Dec 2024 12:47:16 +0000
Subject: [PATCH 12/20] Apply the container-fluid class to the main, header,
and footer elements
---
app/templates/base.html | 4 +++-
app/templates/includes/footer.html | 38 ++++++++++++++++--------------
app/templates/includes/header.html | 26 ++++++++++----------
3 files changed, 37 insertions(+), 31 deletions(-)
diff --git a/app/templates/base.html b/app/templates/base.html
index e83bd29..14638ff 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -39,7 +39,9 @@
{% include "includes/header.html" %}
- {% block content %}{% endblock %}
+
+ {% block content %}{% endblock %}
+
{% include "includes/footer.html" %}
diff --git a/app/templates/includes/footer.html b/app/templates/includes/footer.html
index 24479f7..0843e08 100644
--- a/app/templates/includes/footer.html
+++ b/app/templates/includes/footer.html
@@ -1,24 +1,26 @@
{% load navigation_tags %}
- Built with Wagtail
+
+
Built with Wagtail
- {% with linkedin_url=settings.base.NavigationSettings.linkedin_url github_url=settings.base.NavigationSettings.github_url mastodon_url=settings.base.NavigationSettings.mastodon_url %}
- {% if linkedin_url or github_url or mastodon_url %}
-
- Follow me on:
- {% if github_url %}
- GitHub
- {% endif %}
- {% if linkedin_url %}
- LinkedIn
- {% endif %}
- {% if mastodon_url %}
- Mastodon
- {% endif %}
-
- {% endif %}
- {% endwith %}
+ {% with linkedin_url=settings.base.NavigationSettings.linkedin_url github_url=settings.base.NavigationSettings.github_url mastodon_url=settings.base.NavigationSettings.mastodon_url %}
+ {% if linkedin_url or github_url or mastodon_url %}
+
+ Follow me on:
+ {% if github_url %}
+ GitHub
+ {% endif %}
+ {% if linkedin_url %}
+ LinkedIn
+ {% endif %}
+ {% if mastodon_url %}
+ Mastodon
+ {% endif %}
+
+ {% endif %}
+ {% endwith %}
- {% get_footer_text %}
+ {% get_footer_text %}
+
diff --git a/app/templates/includes/header.html b/app/templates/includes/header.html
index 6b436cd..2e48303 100644
--- a/app/templates/includes/header.html
+++ b/app/templates/includes/header.html
@@ -1,19 +1,21 @@
{% load wagtailcore_tags navigation_tags wagtailuserbar %}
From 465c83182a8bb925f312a65c49783f79ec83a027 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 15 Dec 2024 12:50:36 +0000
Subject: [PATCH 13/20] Hide body content in the blog index page
---
app/blog/templates/blog/blog_index_page.html | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/blog/templates/blog/blog_index_page.html b/app/blog/templates/blog/blog_index_page.html
index f208bca..a8f0e76 100644
--- a/app/blog/templates/blog/blog_index_page.html
+++ b/app/blog/templates/blog/blog_index_page.html
@@ -32,7 +32,9 @@
{% endwith %}
{{ post.intro }}
+ {% comment %} Don't display the body text here
{{ post.body|richtext }}
+ {% endcomment %}
{% endwith %}
From f28b0bbc69b4660f741ac7d69df682ec6778c53e Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 15 Dec 2024 22:28:39 +0000
Subject: [PATCH 14/20] Add semantic HTML5 elements to the templates
These are styled directy by Pico css, so no need to add any additional css.
---
app/base/templates/base/form_page.html | 12 +-
app/blog/templates/blog/blog_index_page.html | 75 ++++-----
app/blog/templates/blog/blog_page.html | 150 ++++++++++--------
app/home/templates/home/home_page.html | 29 ++--
.../templates/portfolio/portfolio_page.html | 7 +-
app/search/templates/search/search.html | 84 +++++-----
app/templates/includes/header.html | 9 +-
7 files changed, 210 insertions(+), 156 deletions(-)
diff --git a/app/base/templates/base/form_page.html b/app/base/templates/base/form_page.html
index db1fff2..d233174 100644
--- a/app/base/templates/base/form_page.html
+++ b/app/base/templates/base/form_page.html
@@ -4,12 +4,20 @@
{% block body_class %}template-formpage{% endblock %}
{% block content %}
- {{ page.title }}
- {{ page.intro|richtext }}
+
+
+
+
+ {{ page.title }}
+ {{ page.intro|richtext }}
+
+
+
+
{% endblock content %}
diff --git a/app/blog/templates/blog/blog_index_page.html b/app/blog/templates/blog/blog_index_page.html
index a8f0e76..20227cb 100644
--- a/app/blog/templates/blog/blog_index_page.html
+++ b/app/blog/templates/blog/blog_index_page.html
@@ -5,46 +5,49 @@
{% block body_class %}template-blogindexpage{% endblock %}
{% block content %}
-
- {{ page.title }}
- {{ page.intro|richtext }}
-
- {% for post in blogpages %}
+
+ {{ page.title }}
+ {{ page.intro|richtext }}
+
+
+{% for post in blogpages %}
+
+{% if forloop.first or forloop.counter0|divisibleby:3 %}
+{% comment %} Start a new row {% endcomment %}
+
+ {% endif %}
- {% if forloop.first or forloop.counter0|divisibleby:2 %}
- {% comment %} Start a new row {% endcomment %}
+
+ {% with post=post.specific %}
+
- {% endif %}
-
-
- {% with post=post.specific %}
-
-
- {% with post.main_image as main_image %}
- {% if main_image %}
-
- {% image main_image fill-160x100 %}
-
- {% endif %}
- {% endwith %}
-
-
{{ post.intro }}
- {% comment %} Don't display the body text here
- {{ post.body|richtext }}
- {% endcomment %}
-
-
- {% endwith %}
-
-
- {% if forloop.last or forloop.counter|divisibleby:2 %}
- {% comment %} End the row {% endcomment %}
+ {% with post.main_image as main_image %}
+
+ {% if main_image %}
+
+ {% image main_image fill-160x100 %}
+
+ {% endif %}
+
+ {% endwith %}
+
+
{{ post.intro }}
+ {% comment %} Don't display the body text here
+ {{ post.body|richtext }}
+ {% endcomment %}
+
- {% endif %}
+ {% endwith %}
+
+
+ {% if forloop.last or forloop.counter|divisibleby:3 %}
+ {% comment %} End the row {% endcomment %}
+
+{% endif %}
- {% endfor %}
+{% endfor %}
{% endblock %}
diff --git a/app/blog/templates/blog/blog_page.html b/app/blog/templates/blog/blog_page.html
index 9313091..338b5c9 100644
--- a/app/blog/templates/blog/blog_page.html
+++ b/app/blog/templates/blog/blog_page.html
@@ -6,77 +6,103 @@
{% block content %}
-
-
-
- {{ page.title }}
- {{ page.date }}
-
+
+
+
+
+ {{ page.title }}
+
+
-
-
- {% with authors=page.authors.all %}
- {% if authors %}
- Posted by:
- {% for author in authors %}
-
- {% image author.author_image fill-40x40 as author_image %}
- {% if author_image %}
-
- {% else %}
- user
- {% endif %}
- {{ author.name }}
-
- {% endfor %}
- {% endif %}
- {% endwith %}
-
+
+
+
+
+ Posted by:
+ {% with authors=page.authors.all %}
+
+ {% if authors %}
+
+ {% for author in authors %}
+
+ {% image author.author_image fill-40x40 as author_image %}
+ {% if author_image %}
+
+ {% else %}
+
+ user
+
+
+
+
+
+ {% endif %}
+ {{ author.name }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% endwith %}
+
{% blog_tags_page as tags_page %}
+
{% with tags=page.tags.all %}
- {% if tags %}
- Tags:
- {% if tags_page %}
- {% for tag in tags %}
- {{ tag }}
- {% endfor %}
- {% else %}
- Create a blog tags page
- {% endif %}
- {% endif %}
+
+ {% if tags %}
+
+ Tags:
+
+ {% if tags_page %}
+ {% for tag in tags %}
+ {{ tag }}
+ {% endfor %}
+
+ {% else %}
+
+ Create a blog tags page
+ {% endif %}
+
+ {% endif %}
+
{% endwith %}
-
+
+
+
+
+
+
+
+ {{ page.body|richtext }}
-
-
-
- {{ page.intro }}
- {{ page.body|richtext }}
-
-
-
-
- {% for item in page.gallery_images.all %}
- {% if forloop.first or forloop.counter0|divisibleby:3 %}
- {% comment %} Start a new row {% endcomment %}
-
- {% endif %}
-
- {% image item.image fill-600x400 %}
-
- {{ item.caption }}
-
-
- {% if forloop.last or forloop.counter|divisibleby:3 %}
-
- {% endif %}
- {% endfor %}
+ {% for item in page.gallery_images.all %}
+
+ {% if forloop.first or forloop.counter0|divisibleby:4 %}
+ {% comment %} Start a new row {% endcomment %}
+
+ {% endif %}
+
+ {% image item.image fill-600x400 %}
+
+ {{ item.caption }}
+
+
-
+ {% if forloop.last or forloop.counter|divisibleby:4 %}
+ {% comment %} End row {% endcomment %}
+
+
+ {% endif %}
+
+ {% endfor %}
+
+
{% endblock %}
diff --git a/app/home/templates/home/home_page.html b/app/home/templates/home/home_page.html
index 505c8d7..c71facc 100644
--- a/app/home/templates/home/home_page.html
+++ b/app/home/templates/home/home_page.html
@@ -4,16 +4,23 @@
{% block body_class %}template-homepage{% endblock %}
{% block content %}
-
- {{ page.body|richtext }}
+{% image page.image fill-1920x1080 as hero_image %}
+
+
+
+ {{ page.hero_text }}
+
+ {% if page.hero_cta_link %}
+
+ {% firstof page.hero_cta page.hero_cta_link.title %}
+
+ {% endif %}
+
+
+
+ {{ page.title }}
+ {{ page.body|richtext }}
+
+
{% endblock content %}
diff --git a/app/portfolio/templates/portfolio/portfolio_page.html b/app/portfolio/templates/portfolio/portfolio_page.html
index 862412a..e2d2e86 100644
--- a/app/portfolio/templates/portfolio/portfolio_page.html
+++ b/app/portfolio/templates/portfolio/portfolio_page.html
@@ -5,7 +5,10 @@
{% block body_class %}template-portfolio{% endblock %}
{% block content %}
- {{ page.title }}
- {{ page.body }}
+
+ {{ page.title }}
+ {{ page.body }}
+
+
{% endblock %}
diff --git a/app/search/templates/search/search.html b/app/search/templates/search/search.html
index 3291ae8..f30ba6b 100644
--- a/app/search/templates/search/search.html
+++ b/app/search/templates/search/search.html
@@ -6,41 +6,49 @@
{% block title %}Search{% endblock %}
{% block content %}
-Search
-
-
-
-{% if search_results %}
-
-You searched{% if search_query %} for “{{ search_query }}”{% endif %}, {{ search_results.paginator.count }} result{{ search_results.paginator.count|pluralize }} found.
-
-
- {% for result in search_results %}
-
-
- {% if result.search_description %}
- {{ result.search_description }}
- {% endif %}
-
- {% endfor %}
-
-
-{% if search_results.paginator.num_pages > 1 %}
- Page {{ search_results.number }} of {{ search_results.paginator.num_pages }}, showing {{ search_results|length }} result{{ search_results|pluralize }} out of {{ search_results.paginator.count }}
-{% endif %}
-
-{% if search_results.has_previous %}
-Previous
-{% endif %}
-
-{% if search_results.has_next %}
-Next
-{% endif %}
-
-{% elif search_query %}
-No results found
-{% endif %}
-{% endblock %}
+
+
+ Search
+
+
+ {% if search_results %}
+
+ You searched{% if search_query %} for “{{ search_query }}”{% endif %}, {{ search_results.paginator.count }} result{{ search_results.paginator.count|pluralize }} found.
+
+
+ {% for result in search_results %}
+
+ {{ result }}
+ {% if result.search_description %}
+ {{ result.search_description }}
+ {% endif %}
+
+ {% endfor %}
+
+
+ {% if search_results.paginator.num_pages > 1 %}
+ Page {{ search_results.number }} of {{ search_results.paginator.num_pages }}, showing {{ search_results|length }} result{{ search_results|pluralize }} out of {{ search_results.paginator.count }}
+ {% endif %}
+
+ {% if search_results.has_previous %}
+ Previous
+ {% endif %}
+
+ {% if search_results.has_next %}
+ Next
+ {% endif %}
+
+ {% elif search_query %}
+
+ No results found
+
+ {% endif %}
+
+ {% endblock %}
+
+
diff --git a/app/templates/includes/header.html b/app/templates/includes/header.html
index 2e48303..b0f8842 100644
--- a/app/templates/includes/header.html
+++ b/app/templates/includes/header.html
@@ -6,14 +6,13 @@
{% get_site_root as site_root %}
- {{ site_root.title }} |
+ {{ site_root.title }}
{% for menuitem in site_root.get_children.live.in_menu %}
-
{# Add the child pages of your HomePage that have their `Show in menu` checked #}
- {{ menuitem.title }} {% if not forloop.last %} | {% endif %}
-
+ {{ menuitem.title }}
+ {% comment %} {% if not forloop.last %} | {% endif %} {% endcomment %}
{% endfor %}
- | Search
+ Search
From 7dbca732e0ad4ecfc7da541605a686d41164ac9d Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Mon, 16 Dec 2024 11:22:32 +0000
Subject: [PATCH 15/20] Custom navigation styling
---
app/home/templates/home/home_page.html | 6 +-
app/templates/includes/header.html | 7 ++-
static_src/js/app.js | 24 +++----
static_src/scss/app.scss | 73 +---------------------
static_src/scss/components/example.scss | 1 -
static_src/scss/components/hero.scss | 37 +++++++++++
static_src/scss/components/navigation.scss | 46 ++++++++++++++
static_src/scss/components/skip-link.scss | 9 +++
static_src/scss/vars.scss | 6 ++
9 files changed, 116 insertions(+), 93 deletions(-)
delete mode 100644 static_src/scss/components/example.scss
create mode 100644 static_src/scss/components/hero.scss
create mode 100644 static_src/scss/components/navigation.scss
create mode 100644 static_src/scss/components/skip-link.scss
create mode 100644 static_src/scss/vars.scss
diff --git a/app/home/templates/home/home_page.html b/app/home/templates/home/home_page.html
index c71facc..7513ec9 100644
--- a/app/home/templates/home/home_page.html
+++ b/app/home/templates/home/home_page.html
@@ -7,10 +7,8 @@
{% image page.image fill-1920x1080 as hero_image %}
-
-
- {{ page.hero_text }}
-
+
+ {{ page.hero_text }}
{% if page.hero_cta_link %}
{% firstof page.hero_cta page.hero_cta_link.title %}
diff --git a/app/templates/includes/header.html b/app/templates/includes/header.html
index b0f8842..b473c70 100644
--- a/app/templates/includes/header.html
+++ b/app/templates/includes/header.html
@@ -4,8 +4,9 @@
Skip to content
{% get_site_root as site_root %}
-
-
+
+ Menu
+
{{ site_root.title }}
{% for menuitem in site_root.get_children.live.in_menu %}
{# Add the child pages of your HomePage that have their `Show in menu` checked #}
@@ -13,7 +14,7 @@
{% comment %} {% if not forloop.last %} | {% endif %} {% endcomment %}
{% endfor %}
Search
-
+
{% wagtailuserbar "top-right" %}
diff --git a/static_src/js/app.js b/static_src/js/app.js
index a4ce6c7..02a556e 100644
--- a/static_src/js/app.js
+++ b/static_src/js/app.js
@@ -1,20 +1,14 @@
-/**
- * This is a simple example.
- * It will log a message to the console.
- * Remove this content and write your scripts as required.
- */
-class ShowMessage {
- constructor(times=3) {
- this.message = 'Hello from Wagtail starter kit!';
- this.times = times;
+class Navigation {
+ constructor() {
+ this.menu = document.querySelector('[role="navigation"]');
+ this.menuButton = this.menu.querySelector('button:first-of-type');
+ this.menuButton.addEventListener('click', this.toggleMenu.bind(this));
+ console.log('Navigation initialized');
}
- showMessage() {
- for (let i = 0; i < this.times; i++) {
- console.log(this.message);
- }
+ toggleMenu() {
+ this.menu.classList.toggle('is-open');
}
}
-const showMessage = new ShowMessage();
-showMessage.showMessage();
+new Navigation();
diff --git a/static_src/scss/app.scss b/static_src/scss/app.scss
index 6aa3506..83cfafe 100644
--- a/static_src/scss/app.scss
+++ b/static_src/scss/app.scss
@@ -10,73 +10,6 @@
// Your custom styles goes here
-@use 'components/example';
-
-/**
-* Temproary styles for the tutorial
-*/
-// *,
-// ::before,
-// ::after {
-// box-sizing: border-box;
-// }
-
-// html {
-// font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif, Apple Color Emoji, "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
-// }
-
-// body {
-// min-height: 100vh;
-// max-width: 800px;
-// margin: 0 auto;
-// padding: 10px;
-// display: grid;
-// gap: 3vw;
-// grid-template-rows: min-content 1fr min-content;
-// }
-
-// a {
-// color: currentColor;
-// }
-
-// footer {
-// border-top: 2px dotted;
-// text-align: center;
-// }
-
-// header {
-// border-bottom: 2px dotted;
-// }
-
-// .template-homepage main {
-// text-align: center;
-// }
-
-// Skip link styles
-.skip-link {
- position: absolute;
- top: -30px;
-}
-
-.skip-link:focus-visible {
- top: 5px;
-}
-
-// // Page form styles
-// .page-form label {
-// display: block;
-// margin-top: 10px;
-// margin-bottom: 5px;
-// }
-
-// .page-form :is(textarea, input, select) {
-// width: 100%;
-// max-width: 500px;
-// min-height: 40px;
-// margin-top: 5px;
-// margin-bottom: 10px;
-// }
-
-// .page-form .helptext {
-// font-style: italic;
-// }
+@use 'components/skip-link';
+@use 'components/hero';
+@use 'components/navigation';
diff --git a/static_src/scss/components/example.scss b/static_src/scss/components/example.scss
deleted file mode 100644
index eb0c546..0000000
--- a/static_src/scss/components/example.scss
+++ /dev/null
@@ -1 +0,0 @@
-// example imported/used in app.scss
diff --git a/static_src/scss/components/hero.scss b/static_src/scss/components/hero.scss
new file mode 100644
index 0000000..19a3966
--- /dev/null
+++ b/static_src/scss/components/hero.scss
@@ -0,0 +1,37 @@
+@use '../vars.scss' as *;
+
+// Hero styles
+[role=hero] {
+ display: relative;
+ min-height: 200px;
+ background-position: bottom;
+ background-size: cover;
+ position: relative;
+ mark {
+ display: block;
+ background: transparent;
+ text-align: center;
+ font-size: 2rem;
+ font-weight: bold;
+ color: white;
+ text-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
+ }
+ [role=button] {
+ position: absolute;
+ bottom: -10px;
+ left: 50%;
+ transform: translateX(-50%);
+ }
+ @media screen and (min-width: $sm) {
+ min-height: 300px;
+ }
+ @media screen and (min-width: $md) {
+ min-height: 400px;
+ }
+ @media screen and (min-width: $lg) {
+ min-height: 500px;
+ }
+ @media screen and (min-width: $xl) {
+ min-height: 600px;
+ }
+}
diff --git a/static_src/scss/components/navigation.scss b/static_src/scss/components/navigation.scss
new file mode 100644
index 0000000..1ef1935
--- /dev/null
+++ b/static_src/scss/components/navigation.scss
@@ -0,0 +1,46 @@
+@use '../vars.scss' as *;
+
+[role=navigation] {
+ button:first-child {
+ display: block;
+ @media screen and (min-width: $md) {
+ display: none;
+ }
+ }
+ > div {
+ position: absolute;
+ z-index: 1;
+ top: -3px;
+ left: 100px;
+ margin-left: -100%;
+ padding: 20px;
+ opacity: 0;
+ background-color: rgba(255, 255, 255, 0.9);
+ border-radius: 5px;
+ a[role=button] {
+ display: block;
+ margin-bottom: 3px;
+ }
+ @media screen and (min-width: $md) {
+ position: inherit;
+ top: auto;
+ left: auto;
+ margin-left: 0;
+ padding: 0;
+ opacity: 1;
+ background-color: transparent;
+ border-radius: 0;
+ a[role=button] {
+ display: inline-block;
+ }
+ }
+ }
+ &.is-open {
+ > div {
+ margin-left: 0;
+ opacity: 1;
+ transition: all 0.3s ease;
+ }
+ }
+
+}
diff --git a/static_src/scss/components/skip-link.scss b/static_src/scss/components/skip-link.scss
new file mode 100644
index 0000000..1e271ba
--- /dev/null
+++ b/static_src/scss/components/skip-link.scss
@@ -0,0 +1,9 @@
+// Skip link styles
+.skip-link {
+ position: absolute;
+ top: -30px;
+}
+
+.skip-link:focus-visible {
+ top: 5px;
+}
diff --git a/static_src/scss/vars.scss b/static_src/scss/vars.scss
new file mode 100644
index 0000000..0db429c
--- /dev/null
+++ b/static_src/scss/vars.scss
@@ -0,0 +1,6 @@
+// Breakpoints
+$sm: 576px;
+$md: 768px;
+$lg: 1024px;
+$xl: 1280px;
+$xxl: 1536px;
From 9028c85a3ca5a3c95ebcab9cadef6915294a6316 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Mon, 16 Dec 2024 16:28:10 +0000
Subject: [PATCH 16/20] Add styling for custom blocks
---
app/base/blocks.py | 1 +
.../base/blocks/captioned_image_block.html | 11 ++++++----
.../templates/base/blocks/embed_block.html | 6 ++++-
.../templates/base/form_page_landing.html | 8 +++++--
.../portfolio/blocks/card_block.html | 4 ++--
.../blocks/featured_posts_block.html | 4 ++--
static_src/scss/app.scss | 1 +
static_src/scss/components/embed.scss | 22 +++++++++++++++++++
8 files changed, 46 insertions(+), 11 deletions(-)
create mode 100644 static_src/scss/components/embed.scss
diff --git a/app/base/blocks.py b/app/base/blocks.py
index fa0c27f..a70ed80 100644
--- a/app/base/blocks.py
+++ b/app/base/blocks.py
@@ -44,4 +44,5 @@ class BaseStreamBlock(StreamBlock):
embed_block = EmbedBlock(
help_text="Insert a URL to embed. For example, https://www.youtube.com/watch?v=SGJFWirQ3ks",
icon="media",
+ template="base/blocks/embed_block.html",
)
diff --git a/app/base/templates/base/blocks/captioned_image_block.html b/app/base/templates/base/blocks/captioned_image_block.html
index a4b1bad..453ed66 100644
--- a/app/base/templates/base/blocks/captioned_image_block.html
+++ b/app/base/templates/base/blocks/captioned_image_block.html
@@ -1,6 +1,9 @@
{% load wagtailimages_tags %}
-
- {% image self.image fill-600x338 loading="lazy" %}
- {{ self.caption }} - {{ self.attribution }}
-
+
+
+ {% image self.image fill-600x338 loading="lazy" %}
+ {{ self.caption }} - {{ self.attribution }}
+
+
+
diff --git a/app/base/templates/base/blocks/embed_block.html b/app/base/templates/base/blocks/embed_block.html
index e3125de..7e927ba 100644
--- a/app/base/templates/base/blocks/embed_block.html
+++ b/app/base/templates/base/blocks/embed_block.html
@@ -1 +1,5 @@
-{{ self }}
+{% load wagtailembeds_tags %}
+
+
+ {{ self }}
+
diff --git a/app/base/templates/base/form_page_landing.html b/app/base/templates/base/form_page_landing.html
index f4a1c07..7d7b85e 100644
--- a/app/base/templates/base/form_page_landing.html
+++ b/app/base/templates/base/form_page_landing.html
@@ -4,6 +4,10 @@
{% block body_class %}template-formpage{% endblock %}
{% block content %}
- {{ page.title }}
- {{ page.thank_you_text|richtext }}
+
+
+ {{ page.title }}
+ {{ page.thank_you_text|richtext }}
+
+
{% endblock content %}
diff --git a/app/portfolio/templates/portfolio/blocks/card_block.html b/app/portfolio/templates/portfolio/blocks/card_block.html
index 3a2e020..3f86120 100644
--- a/app/portfolio/templates/portfolio/blocks/card_block.html
+++ b/app/portfolio/templates/portfolio/blocks/card_block.html
@@ -1,8 +1,8 @@
{% load wagtailcore_tags wagtailimages_tags %}
-
+
{{ self.heading }}
{{ self.text|richtext }}
{% if self.image %}
{% image self.image width-480 %}
{% endif %}
-
+
diff --git a/app/portfolio/templates/portfolio/blocks/featured_posts_block.html b/app/portfolio/templates/portfolio/blocks/featured_posts_block.html
index 8b63bb3..ae59369 100644
--- a/app/portfolio/templates/portfolio/blocks/featured_posts_block.html
+++ b/app/portfolio/templates/portfolio/blocks/featured_posts_block.html
@@ -1,5 +1,5 @@
{% load wagtailcore_tags %}
-
+
{{ self.heading }}
{% if self.text %}
{{ self.text|richtext }}
@@ -13,4 +13,4 @@ {{ self.heading }}
{% endfor %}
-
+
diff --git a/static_src/scss/app.scss b/static_src/scss/app.scss
index 83cfafe..bfd76e3 100644
--- a/static_src/scss/app.scss
+++ b/static_src/scss/app.scss
@@ -13,3 +13,4 @@
@use 'components/skip-link';
@use 'components/hero';
@use 'components/navigation';
+@use 'components/embed';
diff --git a/static_src/scss/components/embed.scss b/static_src/scss/components/embed.scss
new file mode 100644
index 0000000..da1fd3d
--- /dev/null
+++ b/static_src/scss/components/embed.scss
@@ -0,0 +1,22 @@
+@use '../vars.scss' as *;
+
+[role=video] {
+
+ > div {
+ position: relative;
+ padding-bottom: 420px;
+ height: 0;
+ overflow: hidden;
+ max-width: 720px;
+ max-height: 420px;
+ }
+
+ iframe {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+}
From 7d2d70d57ee728c8cbbde31afd27a5b22ac7449a Mon Sep 17 00:00:00 2001
From: nickmoreton
Date: Fri, 15 Aug 2025 15:40:23 +0100
Subject: [PATCH 17/20] Adjust the populate commands
---
app/blog/management/commands/populate_blog.py | 1 +
app/home/management/commands/populate_homepage.py | 13 +++++++++++++
2 files changed, 14 insertions(+)
diff --git a/app/blog/management/commands/populate_blog.py b/app/blog/management/commands/populate_blog.py
index bfaf936..e7309a0 100644
--- a/app/blog/management/commands/populate_blog.py
+++ b/app/blog/management/commands/populate_blog.py
@@ -128,6 +128,7 @@ def create_blog_index(self, home_page):
"Welcome to our blog! Here you'll find the latest news, "
"insights, and stories from our team.
"
),
+ show_in_menus=True,
)
home_page.add_child(instance=blog_index)
blog_index.save_revision().publish()
diff --git a/app/home/management/commands/populate_homepage.py b/app/home/management/commands/populate_homepage.py
index 29f39c7..e5be3ad 100644
--- a/app/home/management/commands/populate_homepage.py
+++ b/app/home/management/commands/populate_homepage.py
@@ -37,6 +37,9 @@ def handle(self, *args, **options):
available_images = Image.objects.all()
selected_image = None
image_embed = ""
+ hero_image = None
+ hero_text = None
+ hero_cta = None
if available_images.exists():
# Select a random image
@@ -47,6 +50,10 @@ def handle(self, *args, **options):
f'format="left" id="{selected_image.id}"/>'
)
self.stdout.write(f"Selected image: {selected_image.title}")
+ # Set hero section fields if they are not already set
+ hero_image = selected_image
+ hero_text = "Your Hero Section Title"
+ hero_cta = "Your Hero Section CTA, you need to set a link"
else:
self.stdout.write(
"No images found - content will be created without images"
@@ -100,6 +107,12 @@ def handle(self, *args, **options):
Happy building with Wagtail!
""".strip()
+ if hero_image:
+ # Update the hero section fields
+ home_page.image = hero_image
+ home_page.hero_text = hero_text
+ home_page.hero_cta = hero_cta
+
# Update the home page body content
home_page.body = sample_content
home_page.save()
From 158ccbf10870a7fc1e04b41ee42c59f68d7f3d53 Mon Sep 17 00:00:00 2001
From: nickmoreton
Date: Fri, 15 Aug 2025 16:07:56 +0100
Subject: [PATCH 18/20] Remove conflicting template tags
---
app/home/templatetags/__init__.py | 0
app/home/templatetags/navigation_tags.py | 10 ----------
2 files changed, 10 deletions(-)
delete mode 100644 app/home/templatetags/__init__.py
delete mode 100644 app/home/templatetags/navigation_tags.py
diff --git a/app/home/templatetags/__init__.py b/app/home/templatetags/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/app/home/templatetags/navigation_tags.py b/app/home/templatetags/navigation_tags.py
deleted file mode 100644
index c6e6309..0000000
--- a/app/home/templatetags/navigation_tags.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django import template
-
-from app.blog.models import BlogIndexPage
-
-register = template.Library()
-
-
-@register.simple_tag
-def get_blog_index_url():
- return BlogIndexPage.objects.first().url
From de7091f0cbbc01cf51fa642051ef2e05c3494308 Mon Sep 17 00:00:00 2001
From: nickmoreton
Date: Fri, 15 Aug 2025 16:24:54 +0100
Subject: [PATCH 19/20] Update failing test
---
app/home/tests.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/home/tests.py b/app/home/tests.py
index c681f59..bacc9bf 100644
--- a/app/home/tests.py
+++ b/app/home/tests.py
@@ -35,7 +35,6 @@ def test_home_frontend_returns_200(self):
"""Test that the home page frontend returns 200 OK."""
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
- self.assertContains(response, "Wagtail Blog Tutorial")
self.assertTemplateUsed(response, "home/home_page.html")
def test_home_admin_edit_returns_200(self):
From 1b719cce38acc8d67f214c76a15969d5162a1416 Mon Sep 17 00:00:00 2001
From: Nick Moreton
Date: Sun, 28 Dec 2025 21:36:02 +0000
Subject: [PATCH 20/20] Alter order of class Meta
---
app/base/models.py | 7 +++----
app/portfolio/blocks.py | 1 -
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/app/base/models.py b/app/base/models.py
index e2b0a75..1540386 100644
--- a/app/base/models.py
+++ b/app/base/models.py
@@ -46,7 +46,6 @@ class FooterText(
TranslatableMixin,
models.Model,
):
-
body = RichTextField()
panels = [
@@ -54,6 +53,9 @@ class FooterText(
PublishingPanel(),
]
+ class Meta(TranslatableMixin.Meta):
+ verbose_name_plural = "Footer Text"
+
def __str__(self):
return "Footer text"
@@ -63,9 +65,6 @@ def get_preview_template(self, request, mode_name):
def get_preview_context(self, request, mode_name):
return {"footer_text": self.body}
- class Meta(TranslatableMixin.Meta):
- verbose_name_plural = "Footer Text"
-
class FormField(AbstractFormField):
page = ParentalKey("FormPage", on_delete=models.CASCADE, related_name="form_fields")
diff --git a/app/portfolio/blocks.py b/app/portfolio/blocks.py
index 11fd69f..44a1ade 100644
--- a/app/portfolio/blocks.py
+++ b/app/portfolio/blocks.py
@@ -31,6 +31,5 @@ class Meta:
class PortfolioStreamBlock(BaseStreamBlock):
-
card = CardBlock(group="Sections")
featured_posts = FeaturedPostsBlock(group="Sections")