2022-09-23 08:45:17 +01:00
|
|
|
from typing import Any, Optional, Type
|
2022-06-19 19:24:29 +01:00
|
|
|
|
2023-06-02 15:33:15 +01:00
|
|
|
from django.contrib.postgres.search import TrigramSimilarity
|
2022-06-19 20:55:37 +01:00
|
|
|
from django.db import models
|
2022-06-19 21:03:05 +01:00
|
|
|
from django.utils import timezone
|
2022-06-26 17:00:44 +01:00
|
|
|
from django.utils.functional import cached_property
|
2022-07-16 10:29:01 +01:00
|
|
|
from modelcluster.fields import ParentalManyToManyField
|
2022-06-19 20:55:37 +01:00
|
|
|
from wagtail.admin.panels import FieldPanel
|
2022-10-31 20:58:02 +00:00
|
|
|
from wagtail.search import index
|
2022-08-28 16:18:31 +01:00
|
|
|
from wagtailautocomplete.edit_handlers import AutocompletePanel
|
2022-06-19 19:24:29 +01:00
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
from website.common.models import BaseContentPage, BaseListingPage
|
2022-10-04 20:49:29 +01:00
|
|
|
from website.common.utils import TocEntry
|
2022-08-27 12:21:13 +01:00
|
|
|
from website.common.views import ContentPageFeed
|
2022-09-23 08:45:17 +01:00
|
|
|
from website.contrib.singleton_page.utils import SingletonPageCache
|
2022-06-19 19:24:29 +01:00
|
|
|
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
class BlogPostListPage(BaseListingPage):
|
2022-06-19 19:24:29 +01:00
|
|
|
max_count = 1
|
2022-07-16 15:45:57 +01:00
|
|
|
subpage_types = [
|
|
|
|
"blog.BlogPostPage",
|
|
|
|
"blog.BlogPostTagListPage",
|
2022-08-20 14:19:07 +01:00
|
|
|
"blog.BlogPostCollectionListPage",
|
|
|
|
"blog.BlogPostCollectionPage",
|
2022-07-16 15:45:57 +01:00
|
|
|
]
|
2022-06-19 19:24:29 +01:00
|
|
|
|
2022-06-26 17:00:44 +01:00
|
|
|
@cached_property
|
2022-10-29 14:53:34 +01:00
|
|
|
def show_table_of_contents(self) -> bool:
|
|
|
|
return False
|
2022-07-10 18:42:16 +01:00
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
def get_listing_pages(self) -> models.QuerySet:
|
2022-10-04 20:49:29 +01:00
|
|
|
return (
|
2022-08-28 16:18:31 +01:00
|
|
|
BlogPostPage.objects.descendant_of(self)
|
|
|
|
.live()
|
2022-08-28 16:51:27 +01:00
|
|
|
.public()
|
2022-08-28 16:18:31 +01:00
|
|
|
.order_by("-date", "title")
|
2022-06-19 19:24:29 +01:00
|
|
|
)
|
2022-08-27 12:21:13 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def feed_class(self) -> Type[ContentPageFeed]:
|
2022-07-25 21:58:06 +01:00
|
|
|
from .views import BlogPostPageFeed
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
return BlogPostPageFeed
|
2022-07-25 21:58:06 +01:00
|
|
|
|
2022-10-29 13:22:21 +01:00
|
|
|
@cached_property
|
|
|
|
def tag_list_page_url(self) -> Optional[str]:
|
|
|
|
return SingletonPageCache.get_url(BlogPostTagListPage)
|
|
|
|
|
2022-06-19 19:24:29 +01:00
|
|
|
|
2022-08-16 21:32:46 +01:00
|
|
|
class BlogPostPage(BaseContentPage):
|
2022-06-19 19:24:29 +01:00
|
|
|
subpage_types: list[Any] = []
|
2022-08-20 14:19:07 +01:00
|
|
|
parent_page_types = [BlogPostListPage, "blog.BlogPostCollectionPage"]
|
2022-06-19 20:55:37 +01:00
|
|
|
|
2022-07-16 10:29:01 +01:00
|
|
|
tags = ParentalManyToManyField("blog.BlogPostTagPage", blank=True)
|
2022-06-19 21:03:05 +01:00
|
|
|
date = models.DateField(default=timezone.now)
|
2022-06-19 20:55:37 +01:00
|
|
|
|
2022-09-21 13:30:06 +01:00
|
|
|
promote_panels = BaseContentPage.promote_panels + [
|
2022-08-16 21:32:46 +01:00
|
|
|
FieldPanel("date"),
|
2022-08-28 16:18:31 +01:00
|
|
|
AutocompletePanel("tags"),
|
2022-08-16 21:32:46 +01:00
|
|
|
]
|
2022-10-31 20:58:02 +00:00
|
|
|
|
|
|
|
search_fields = BaseContentPage.search_fields + [
|
|
|
|
index.RelatedFields("tags", [index.SearchField("title", boost=1)])
|
|
|
|
]
|
2022-07-16 10:29:01 +01:00
|
|
|
|
2022-09-23 08:45:17 +01:00
|
|
|
@cached_property
|
|
|
|
def tag_list_page_url(self) -> Optional[str]:
|
|
|
|
return SingletonPageCache.get_url(BlogPostTagListPage)
|
|
|
|
|
2023-06-02 15:33:15 +01:00
|
|
|
def get_similar_posts(self) -> models.QuerySet:
|
|
|
|
try:
|
|
|
|
listing_pages = BlogPostListPage.objects.get().get_listing_pages()
|
|
|
|
except BlogPostListPage.DoesNotExist:
|
|
|
|
return BlogPostPage.objects.none()
|
|
|
|
|
|
|
|
similar_posts = listing_pages.exclude(id=self.id).annotate(
|
|
|
|
title_similarity=TrigramSimilarity("title", self.title),
|
|
|
|
# If this page has no subtitle, ignore it as part of similarity
|
|
|
|
subtitle_similarity=TrigramSimilarity("subtitle", self.subtitle)
|
|
|
|
if self.subtitle
|
|
|
|
else models.Value(1),
|
|
|
|
)
|
|
|
|
|
|
|
|
page_tags = list(self.tags.values_list("id", flat=True))
|
|
|
|
similar_posts = similar_posts.annotate(
|
|
|
|
# If this page has no tags, ignore it as part of similarity
|
|
|
|
tag_similarity=models.Count("tags", filter=models.Q(tags__in=page_tags))
|
|
|
|
/ len(page_tags)
|
|
|
|
if page_tags
|
|
|
|
else models.Value(1)
|
|
|
|
)
|
|
|
|
|
|
|
|
similar_posts = similar_posts.annotate(
|
|
|
|
similarity=(models.F("tag_similarity") * 2)
|
|
|
|
* (models.F("title_similarity") * 10)
|
|
|
|
* (models.F("subtitle_similarity"))
|
|
|
|
).order_by("-similarity")[:3]
|
|
|
|
|
|
|
|
return similar_posts
|
|
|
|
|
2022-07-16 10:29:01 +01:00
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
class BlogPostTagListPage(BaseListingPage):
|
2022-07-16 10:29:01 +01:00
|
|
|
max_count = 1
|
2022-08-20 14:19:07 +01:00
|
|
|
parent_page_types = [BlogPostListPage]
|
2022-07-16 10:29:01 +01:00
|
|
|
subpage_types = ["blog.BlogPostTagPage"]
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def table_of_contents(self) -> list[TocEntry]:
|
2022-08-27 12:21:13 +01:00
|
|
|
return [
|
|
|
|
TocEntry(page.title, page.slug, 0, []) for page in self.get_listing_pages()
|
|
|
|
]
|
2022-07-16 10:29:01 +01:00
|
|
|
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
class BlogPostTagPage(BaseListingPage):
|
2022-07-16 10:29:01 +01:00
|
|
|
subpage_types: list[Any] = []
|
|
|
|
parent_page_types = [BlogPostTagListPage]
|
|
|
|
|
2022-08-27 13:12:45 +01:00
|
|
|
@cached_property
|
|
|
|
def html_title(self) -> str:
|
|
|
|
return f"Pages tagged with '{super().html_title}'"
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
def get_listing_pages(self) -> models.QuerySet:
|
2022-08-28 16:51:27 +01:00
|
|
|
blog_list_page = BlogPostListPage.objects.get()
|
2022-08-27 12:21:13 +01:00
|
|
|
return blog_list_page.get_listing_pages().filter(tags=self)
|
2022-07-16 10:29:01 +01:00
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
@property
|
|
|
|
def feed_class(self) -> Type[ContentPageFeed]:
|
2022-07-25 21:58:06 +01:00
|
|
|
from .views import BlogPostPageFeed
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
return BlogPostPageFeed
|
2022-07-25 21:58:06 +01:00
|
|
|
|
2022-07-16 15:45:57 +01:00
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
class BlogPostCollectionListPage(BaseListingPage):
|
2022-07-16 15:45:57 +01:00
|
|
|
subpage_types: list[Any] = []
|
2022-08-20 14:19:07 +01:00
|
|
|
parent_page_types = [BlogPostListPage]
|
2022-07-16 15:45:57 +01:00
|
|
|
max_count = 1
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def table_of_contents(self) -> list[TocEntry]:
|
|
|
|
return [
|
2022-08-27 12:21:13 +01:00
|
|
|
TocEntry(page.title, page.slug, 0, []) for page in self.get_listing_pages()
|
2022-07-16 15:45:57 +01:00
|
|
|
]
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
def get_listing_pages(self) -> models.QuerySet:
|
2022-08-28 20:22:55 +01:00
|
|
|
blog_list_page = BlogPostListPage.objects.get()
|
2022-08-28 16:51:27 +01:00
|
|
|
return BlogPostCollectionPage.objects.child_of(blog_list_page).live().public()
|
2022-07-16 15:45:57 +01:00
|
|
|
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
class BlogPostCollectionPage(BaseListingPage):
|
2022-08-20 14:19:07 +01:00
|
|
|
parent_page_types = [BlogPostListPage]
|
2022-07-16 15:45:57 +01:00
|
|
|
subpage_types = [BlogPostPage]
|
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
def get_listing_pages(self) -> models.QuerySet:
|
2022-10-04 20:49:29 +01:00
|
|
|
return (
|
2022-08-28 16:18:31 +01:00
|
|
|
BlogPostPage.objects.child_of(self)
|
|
|
|
.live()
|
2022-08-28 16:51:27 +01:00
|
|
|
.public()
|
2022-08-27 13:13:14 +01:00
|
|
|
.order_by("-date", "title")
|
|
|
|
)
|
2022-07-16 15:45:57 +01:00
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
@property
|
|
|
|
def feed_class(self) -> Type[ContentPageFeed]:
|
|
|
|
from .views import BlogPostPageFeed
|
2022-07-16 15:45:57 +01:00
|
|
|
|
2022-08-27 12:21:13 +01:00
|
|
|
return BlogPostPageFeed
|