Replace tag snippets with full pages

Makes the page tree a bit more messy, but is much more versatile and means there are fewer hacks in the code to make snippets act like pages in the tree.
This commit is contained in:
Jake Howard 2022-07-16 10:29:01 +01:00
parent 153b2c0c59
commit 690095ea52
Signed by: jake
GPG key ID: 57AFB45680EDD477
9 changed files with 2068 additions and 84 deletions

View file

@ -41,14 +41,3 @@ section.content {
max-width: calc(99% - ($column-gap * 2)); max-width: calc(99% - ($column-gap * 2));
} }
} }
.blog-filter {
&,
.tag {
font-size: 1.25rem;
}
.tag {
font-family: $family-code;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
# Generated by Django 4.0.5 on 2022-07-16 08:37
import modelcluster.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("blog", "0014_blogposttaglistpage_blogposttagpage_and_more"),
]
operations = [
migrations.AddField(
model_name="blogpostpage",
name="tags",
field=modelcluster.fields.ParentalManyToManyField(
blank=True, to="blog.blogposttagpage"
),
),
]

View file

@ -5,9 +5,7 @@ from django.db.models.functions import TruncMonth
from django.http.request import HttpRequest from django.http.request import HttpRequest
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from modelcluster.contrib.taggit import ClusterTaggableManager from modelcluster.fields import ParentalManyToManyField
from modelcluster.fields import ParentalKey
from taggit.models import ItemBase, TagBase
from wagtail.admin.panels import FieldPanel from wagtail.admin.panels import FieldPanel
from wagtail.query import PageQuerySet from wagtail.query import PageQuerySet
@ -17,7 +15,7 @@ from website.common.utils import TocEntry
class BlogListPage(BaseContentMixin, BasePage): # type: ignore[misc] class BlogListPage(BaseContentMixin, BasePage): # type: ignore[misc]
max_count = 1 max_count = 1
subpage_types = ["blog.BlogPostPage"] subpage_types = ["blog.BlogPostPage", "blog.BlogPostTagListPage"]
content_panels = BasePage.content_panels + BaseContentMixin.content_panels content_panels = BasePage.content_panels + BaseContentMixin.content_panels
@cached_property @cached_property
@ -31,8 +29,7 @@ class BlogListPage(BaseContentMixin, BasePage): # type: ignore[misc]
def table_of_contents(self) -> list[TocEntry]: def table_of_contents(self) -> list[TocEntry]:
post_months = [ post_months = [
dt.strftime("%Y-%m") dt.strftime("%Y-%m")
for dt in self.get_children() for dt in self.get_blog_posts()
.live()
.annotate(post_month=TruncMonth("date", output_field=models.DateField())) .annotate(post_month=TruncMonth("date", output_field=models.DateField()))
.order_by("-post_month") .order_by("-post_month")
.values_list("post_month", flat=True) .values_list("post_month", flat=True)
@ -41,62 +38,26 @@ class BlogListPage(BaseContentMixin, BasePage): # type: ignore[misc]
return [TocEntry(post_month, post_month, 0, []) for post_month in post_months] return [TocEntry(post_month, post_month, 0, []) for post_month in post_months]
def get_children(self) -> PageQuerySet: def get_blog_posts(self) -> PageQuerySet:
""" return BlogPostPage.objects.child_of(self).live() # type:ignore[attr-defined]
Since the children are always `BlogPostPage`, so juts use the specific queryset to save the `JOIN`.
"""
return BlogPostPage.objects.child_of(self) # type: ignore[attr-defined]
def get_context(self, request: HttpRequest) -> dict: def get_context(self, request: HttpRequest) -> dict:
context = super().get_context(request) context = super().get_context(request)
context["child_pages"] = ( context["child_pages"] = (
self.get_children() self.get_blog_posts()
.live()
.select_related("hero_image") .select_related("hero_image")
.select_related("hero_unsplash_photo") .select_related("hero_unsplash_photo")
.prefetch_related("tags") .prefetch_related("tags")
.order_by("-date") .order_by("-date")
) )
if tag := request.GET.get("tag"):
tag = BlogPostTag.objects.filter(slug=tag).first()
if tag:
context["filtering_by_tag"] = tag
context["no_table_of_contents"] = True
context["child_pages"] = context["child_pages"].filter(tags=tag)
return context return context
class BlogPostTag(TagBase):
free_tagging = False
panels = [FieldPanel("name")]
class Meta:
verbose_name = "blog tag"
verbose_name_plural = "blog tags"
def get_absolute_url(self) -> str:
return (
BlogListPage.objects.live().defer_streamfields().first().get_url() # type: ignore[attr-defined]
+ "?tag="
+ self.slug
)
class TaggedBlog(ItemBase):
tag = models.ForeignKey(
BlogPostTag, related_name="tagged_blogs", on_delete=models.CASCADE
)
content_object = ParentalKey(
"blog.BlogPostPage", on_delete=models.CASCADE, related_name="tagged_items"
)
class BlogPostPage(BaseContentMixin, BasePage): # type: ignore[misc] class BlogPostPage(BaseContentMixin, BasePage): # type: ignore[misc]
subpage_types: list[Any] = [] subpage_types: list[Any] = []
parent_page_types = [BlogListPage] parent_page_types = [BlogListPage]
tags = ClusterTaggableManager(through=TaggedBlog, blank=True) tags = ParentalManyToManyField("blog.BlogPostTagPage", blank=True)
date = models.DateField(default=timezone.now) date = models.DateField(default=timezone.now)
content_panels = ( content_panels = (
@ -104,3 +65,46 @@ class BlogPostPage(BaseContentMixin, BasePage): # type: ignore[misc]
+ BaseContentMixin.content_panels + BaseContentMixin.content_panels
+ [FieldPanel("date"), FieldPanel("tags")] + [FieldPanel("date"), FieldPanel("tags")]
) )
class BlogPostTagListPage(BaseContentMixin, BasePage): # type: ignore[misc]
max_count = 1
parent_page_types = [BlogListPage]
subpage_types = ["blog.BlogPostTagPage"]
content_panels = BasePage.content_panels + BaseContentMixin.content_panels
@cached_property
def table_of_contents(self) -> list[TocEntry]:
return [TocEntry(page.title, page.slug, 0, []) for page in self.get_tags()]
def get_tags(self) -> PageQuerySet:
return self.get_children().specific().live().order_by("title")
def get_context(self, request: HttpRequest) -> dict:
context = super().get_context(request)
context["tags"] = self.get_children().specific().live().order_by("title")
return context
class BlogPostTagPage(BaseContentMixin, BasePage): # type: ignore[misc]
subpage_types: list[Any] = []
parent_page_types = [BlogPostTagListPage]
content_panels = BasePage.content_panels + BaseContentMixin.content_panels
@cached_property
def table_of_contents(self) -> list[TocEntry]:
return [
TocEntry(page.title, page.slug, 0, []) for page in self.get_blog_posts()
]
def get_blog_posts(self) -> PageQuerySet:
blog_list_page = self.get_parent_pages().specific().reverse()[1]
assert isinstance(blog_list_page, BlogListPage)
return blog_list_page.get_blog_posts().filter(tags=self).order_by("-date")
def get_context(self, request: HttpRequest) -> dict:
context = super().get_context(request)
context["pages"] = self.get_blog_posts()
return context

View file

@ -1,23 +1,9 @@
{% extends "wagtail_base.html" %} {% extends "wagtail_base.html" %}
{% load wagtailcore_tags %}
{% block content %} {% block content %}
{% include "common/hero.html" %} {% include "common/hero.html" %}
{% if filtering_by_tag %}
<section class="container blog-filter">
<p>
Showing only:
<span class="tag is-light is-medium">
#{{ filtering_by_tag.name }}
<a class="delete is-small" href="{% pageurl page %}"></a>
</span>
</p>
</section>
{% endif %}
<section class="container"> <section class="container">
{% for page in child_pages %} {% for page in child_pages %}
{% if not filtering_by_tag %} {% if not filtering_by_tag %}

View file

@ -0,0 +1,13 @@
{% extends "wagtail_base.html" %}
{% block content %}
{% include "common/hero.html" %}
<div class="container">
{% for tag in tags %}
{% include "common/listing-item.html" with page=tag %}
{% endfor %}
</div>
{% endblock content %}

View file

@ -0,0 +1,13 @@
{% extends "wagtail_base.html" %}
{% block content %}
{% include "common/hero.html" %}
<div class="container">
{% for page in pages %}
{% include "common/listing-item.html" %}
{% endfor %}
</div>
{% endblock content %}

View file

@ -1,14 +1,19 @@
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register from django.urls import reverse
from wagtail import hooks
from wagtail.admin.menu import MenuItem
from .models import BlogPostTag from .models import BlogPostTagListPage
class BlogPostTagModelAdmin(ModelAdmin): @hooks.register("register_admin_menu_item")
model = BlogPostTag def register_blog_post_tags_menu_item() -> MenuItem:
menu_label = "Blog Post Tags" blog_post_tag_list_id = (
menu_icon = "tag" BlogPostTagListPage.objects.live() # type:ignore[attr-defined]
list_display = ["name", "slug"] .values_list("id", flat=True)
search_fields = ["name", "slug"] .get()
)
return MenuItem(
modeladmin_register(BlogPostTagModelAdmin) "Blog post tags",
reverse("wagtailadmin_explore", args=[blog_post_tag_list_id]),
icon_name="tag",
)

View file

@ -1,4 +1,4 @@
{% load wagtailcore_tags %}
<div class="content-details field is-grouped is-grouped-multiline {{extra_classes}}"> <div class="content-details field is-grouped is-grouped-multiline {{extra_classes}}">
@ -25,7 +25,7 @@
<div class="tags has-addons"> <div class="tags has-addons">
<span class="tag is-dark"><i class="fas fa-tags"></i></span> <span class="tag is-dark"><i class="fas fa-tags"></i></span>
{% for tag in page.tags.all %} {% for tag in page.tags.all %}
<span class="tag is-white"><a title="{{ tag.name }}" href="{{ tag.get_absolute_url }}">#{{ tag.slug }}</a></span> <span class="tag is-white"><a title="{{ tag.name }}" href="{% pageurl tag %}">#{{ tag.slug }}</a></span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>