Implement footer navigation and social media

This commit is contained in:
Warren Chen 2025-10-16 12:02:57 +09:00
parent 8a11ff2c28
commit b97fed5340
19 changed files with 339 additions and 1 deletions

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class BaseConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'base'

View File

@ -0,0 +1,55 @@
# Generated by Django 5.2.7 on 2025-10-15 03:30
import django.db.models.deletion
import uuid
import wagtail.fields
import wagtail.models.preview
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('wagtailcore', '0095_groupsitepermission'),
]
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.preview.PreviewableMixin, models.Model),
),
]

View File

@ -0,0 +1,36 @@
# Generated by Django 5.2.7 on 2025-10-15 06:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='navigationsettings',
name='github_url',
),
migrations.RemoveField(
model_name='navigationsettings',
name='mastodon_url',
),
migrations.AddField(
model_name='navigationsettings',
name='facebook_url',
field=models.URLField(blank=True, verbose_name='Facebook URL'),
),
migrations.AddField(
model_name='navigationsettings',
name='instagram_url',
field=models.URLField(blank=True, verbose_name='Instagram URL'),
),
migrations.AddField(
model_name='navigationsettings',
name='thread_url',
field=models.URLField(blank=True, verbose_name='Thread URL'),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 5.2.7 on 2025-10-15 06:48
import wagtail.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0002_remove_navigationsettings_github_url_and_more'),
]
operations = [
migrations.CreateModel(
name='SocialMediaSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('links', wagtail.fields.StreamField([('link', 2)], block_lookup={0: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('twitter', 'Twitter'), ('instagram', 'Instagram'), ('linkedin', 'LinkedIn'), ('youtube', 'YouTube')]}), 1: ('wagtail.blocks.URLBlock', (), {}), 2: ('wagtail.blocks.StructBlock', [[('platform', 0), ('url', 1)]], {})})),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 5.2.7 on 2025-10-15 08:49
import wagtail.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('base', '0003_socialmediasettings'),
]
operations = [
migrations.AlterModelOptions(
name='navigationsettings',
options={'verbose_name': 'Footer Navigation'},
),
migrations.RemoveField(
model_name='navigationsettings',
name='facebook_url',
),
migrations.RemoveField(
model_name='navigationsettings',
name='instagram_url',
),
migrations.RemoveField(
model_name='navigationsettings',
name='linkedin_url',
),
migrations.RemoveField(
model_name='navigationsettings',
name='thread_url',
),
migrations.AddField(
model_name='navigationsettings',
name='footer_links',
field=wagtail.fields.StreamField([('section', 5)], blank=True, block_lookup={0: ('wagtail.blocks.CharBlock', (), {'required': False}), 1: ('wagtail.blocks.CharBlock', (), {}), 2: ('wagtail.blocks.URLBlock', (), {}), 3: ('wagtail.blocks.StructBlock', [[('label', 1), ('url', 2)]], {}), 4: ('wagtail.blocks.ListBlock', (3,), {}), 5: ('wagtail.blocks.StructBlock', [[('title', 0), ('links', 4)]], {})}, null=True),
),
migrations.AlterField(
model_name='socialmediasettings',
name='links',
field=wagtail.fields.StreamField([('link', 2)], block_lookup={0: ('wagtail.blocks.ChoiceBlock', [], {'choices': [('facebook', 'Facebook'), ('twitter', 'Twitter'), ('instagram', 'Instagram'), ('thread', 'Thread'), ('linkedin', 'LinkedIn'), ('youtube', 'YouTube')]}), 1: ('wagtail.blocks.URLBlock', (), {}), 2: ('wagtail.blocks.StructBlock', [[('platform', 0), ('url', 1)]], {})}),
),
]

View File

@ -0,0 +1,100 @@
from django.db import models
from wagtail.admin.panels import (
FieldPanel,
MultiFieldPanel,
# import PublishingPanel:
PublishingPanel,
)
# import RichTextField:
from wagtail.fields import RichTextField
# import DraftStateMixin, PreviewableMixin, RevisionMixin, TranslatableMixin:
from wagtail.models import (
DraftStateMixin,
PreviewableMixin,
RevisionMixin,
TranslatableMixin,
)
from wagtail.contrib.settings.models import (
BaseGenericSetting,
register_setting,
)
from wagtail.snippets.models import register_snippet
from wagtail.fields import StreamField
from wagtail import blocks
@register_setting
class NavigationSettings(BaseGenericSetting):
footer_links = StreamField([
("section", blocks.StructBlock([
("title", blocks.CharBlock(required=False)),
("links", blocks.ListBlock(blocks.StructBlock([
("label", blocks.CharBlock()),
("url", blocks.URLBlock())
]))),
]))
], use_json_field=True, blank=True, null=True)
panels = [
FieldPanel("footer_links"),
]
class Meta:
verbose_name = "Footer Navigation"
class SocialLinkBlock(blocks.StructBlock):
SOCIAL_MEDIA_CHOICES = [
("facebook", "Facebook"),
("twitter", "Twitter"),
("instagram", "Instagram"),
("thread", "Thread"),
("linkedin", "LinkedIn"),
("youtube", "YouTube"),
]
platform = blocks.ChoiceBlock(choices=SOCIAL_MEDIA_CHOICES)
url = blocks.URLBlock()
class Meta:
icon = "link"
label = "Social Link"
@register_setting
class SocialMediaSettings(BaseGenericSetting):
links = StreamField([
("link", SocialLinkBlock()),
], use_json_field=True)
panels = [FieldPanel("links")]
@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"

View File

@ -0,0 +1,5 @@
{% load wagtailcore_tags %}
<div>
{{ footer_text|richtext }}
</div>

View File

@ -0,0 +1,18 @@
from django import template
from 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,
}

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@ -3,6 +3,10 @@
import os import os
import sys import sys
# from dotenv import load_dotenv
# env_file = os.environ.get("ENV_FILE", "../.env")
# load_dotenv(env_file)
def main(): def main():
"""Run administrative tasks.""" """Run administrative tasks."""

View File

@ -28,6 +28,7 @@ INSTALLED_APPS = [
"search", "search",
"wagtail.contrib.forms", "wagtail.contrib.forms",
"wagtail.contrib.redirects", "wagtail.contrib.redirects",
"wagtail.contrib.settings",
"wagtail.embeds", "wagtail.embeds",
"wagtail.sites", "wagtail.sites",
"wagtail.users", "wagtail.users",
@ -46,6 +47,7 @@ INSTALLED_APPS = [
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.staticfiles", "django.contrib.staticfiles",
"base",
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -74,6 +76,7 @@ TEMPLATES = [
"django.template.context_processors.request", "django.template.context_processors.request",
"django.contrib.auth.context_processors.auth", "django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"wagtail.contrib.settings.context_processors.settings",
], ],
}, },
}, },

View File

@ -36,6 +36,8 @@
{% block content %}{% endblock %} {% block content %}{% endblock %}
{% include "includes/footer.html" %}
{# Global javascript #} {# Global javascript #}
<script type="text/javascript" src="{% static 'js/mysite.js' %}"></script> <script type="text/javascript" src="{% static 'js/mysite.js' %}"></script>

View File

@ -0,0 +1,32 @@
{% load navigation_tags %}
<footer>
{% if settings.base.NavigationSettings.footer_links %}
<div class="footer-sections">
{% for section in settings.base.NavigationSettings.footer_links %}
<div class="footer-section">
{% if section.value.title %}
<h3>{{ section.value.title }}</h3>
{% endif %}
<ul>
{% for link in section.value.links %}
<li><a href="{{ link.url }}" target="_blank">{{ link.label }}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% endif %}
{% with social_links=settings.base.SocialMediaSettings.links %}
{% if social_links %}
<p>Follow us
{% for item in social_links %}
<a href="{{ item.value.url }}" target="_blank" alt="{{ item.value.platform }}">{{ item.value.platform }}</a>
{% endfor %}
</p>
{% endif %}
{% endwith %}
{% get_footer_text %}
</footer>