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));
|
||||
}
|
||||
}
|
||||
|
||||
.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.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from modelcluster.contrib.taggit import ClusterTaggableManager
|
||||
from modelcluster.fields import ParentalKey
|
||||
from taggit.models import ItemBase, TagBase
|
||||
from modelcluster.fields import ParentalManyToManyField
|
||||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.query import PageQuerySet
|
||||
|
||||
|
@ -17,7 +15,7 @@ from website.common.utils import TocEntry
|
|||
|
||||
class BlogListPage(BaseContentMixin, BasePage): # type: ignore[misc]
|
||||
max_count = 1
|
||||
subpage_types = ["blog.BlogPostPage"]
|
||||
subpage_types = ["blog.BlogPostPage", "blog.BlogPostTagListPage"]
|
||||
content_panels = BasePage.content_panels + BaseContentMixin.content_panels
|
||||
|
||||
@cached_property
|
||||
|
@ -31,8 +29,7 @@ class BlogListPage(BaseContentMixin, BasePage): # type: ignore[misc]
|
|||
def table_of_contents(self) -> list[TocEntry]:
|
||||
post_months = [
|
||||
dt.strftime("%Y-%m")
|
||||
for dt in self.get_children()
|
||||
.live()
|
||||
for dt in self.get_blog_posts()
|
||||
.annotate(post_month=TruncMonth("date", output_field=models.DateField()))
|
||||
.order_by("-post_month")
|
||||
.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]
|
||||
|
||||
def get_children(self) -> PageQuerySet:
|
||||
"""
|
||||
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_blog_posts(self) -> PageQuerySet:
|
||||
return BlogPostPage.objects.child_of(self).live() # type:ignore[attr-defined]
|
||||
|
||||
def get_context(self, request: HttpRequest) -> dict:
|
||||
context = super().get_context(request)
|
||||
context["child_pages"] = (
|
||||
self.get_children()
|
||||
.live()
|
||||
self.get_blog_posts()
|
||||
.select_related("hero_image")
|
||||
.select_related("hero_unsplash_photo")
|
||||
.prefetch_related("tags")
|
||||
.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
|
||||
|
||||
|
||||
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]
|
||||
subpage_types: list[Any] = []
|
||||
parent_page_types = [BlogListPage]
|
||||
|
||||
tags = ClusterTaggableManager(through=TaggedBlog, blank=True)
|
||||
tags = ParentalManyToManyField("blog.BlogPostTagPage", blank=True)
|
||||
date = models.DateField(default=timezone.now)
|
||||
|
||||
content_panels = (
|
||||
|
@ -104,3 +65,46 @@ class BlogPostPage(BaseContentMixin, BasePage): # type: ignore[misc]
|
|||
+ BaseContentMixin.content_panels
|
||||
+ [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" %}
|
||||
|
||||
{% load wagtailcore_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% 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">
|
||||
{% for page in child_pages %}
|
||||
{% 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):
|
||||
model = BlogPostTag
|
||||
menu_label = "Blog Post Tags"
|
||||
menu_icon = "tag"
|
||||
list_display = ["name", "slug"]
|
||||
search_fields = ["name", "slug"]
|
||||
|
||||
|
||||
modeladmin_register(BlogPostTagModelAdmin)
|
||||
@hooks.register("register_admin_menu_item")
|
||||
def register_blog_post_tags_menu_item() -> MenuItem:
|
||||
blog_post_tag_list_id = (
|
||||
BlogPostTagListPage.objects.live() # type:ignore[attr-defined]
|
||||
.values_list("id", flat=True)
|
||||
.get()
|
||||
)
|
||||
return MenuItem(
|
||||
"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}}">
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
<div class="tags has-addons">
|
||||
<span class="tag is-dark"><i class="fas fa-tags"></i></span>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue