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:
parent
153b2c0c59
commit
690095ea52
9 changed files with 2068 additions and 84 deletions
|
@ -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
21
website/blog/migrations/0015_blogpostpage_tags.py
Normal file
21
website/blog/migrations/0015_blogpostpage_tags.py
Normal 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
13
website/blog/templates/blog/blog_post_tag_list_page.html
Normal file
13
website/blog/templates/blog/blog_post_tag_list_page.html
Normal 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 %}
|
13
website/blog/templates/blog/blog_post_tag_page.html
Normal file
13
website/blog/templates/blog/blog_post_tag_page.html
Normal 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 %}
|
|
@ -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",
|
||||||
|
)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue