|
|
|
from django.db import models
|
|
|
|
|
|
|
|
from modelcluster.fields import ParentalKey
|
|
|
|
from modelcluster.contrib.taggit import ClusterTaggableManager
|
|
|
|
from taggit.models import TaggedItemBase
|
|
|
|
|
|
|
|
from wagtail.core.models import Page, Orderable
|
|
|
|
from wagtail.core.fields import RichTextField
|
|
|
|
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel
|
|
|
|
from wagtail.images.edit_handlers import ImageChooserPanel
|
|
|
|
from wagtail.search import index
|
|
|
|
|
|
|
|
|
|
|
|
class BlogIndexPage(Page):
|
|
|
|
"""Entry point of the blog. Displays its children `BlogPages`"""
|
|
|
|
|
|
|
|
intro = RichTextField(blank=True)
|
|
|
|
|
|
|
|
content_panels = Page.content_panels + [FieldPanel("intro", classname="full")]
|
|
|
|
|
|
|
|
def get_context(self, request, *args, **kwargs):
|
|
|
|
"""
|
|
|
|
Overrides the default context to include only published pages, ordered by reverse chronological order.
|
|
|
|
These child pages can be accessed in the template using `blogpages` instead of page.get_children.
|
|
|
|
"""
|
|
|
|
context = super().get_context(request)
|
|
|
|
blogpages = self.get_children().live().order_by("-first_published_at")
|
|
|
|
context["blogpages"] = blogpages
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
class BlogPageTag(TaggedItemBase):
|
|
|
|
"""Provides a simple tagging feature to Blog Pages"""
|
|
|
|
|
|
|
|
content_object = ParentalKey(
|
|
|
|
"BlogPage", related_name="tagged_items", on_delete=models.CASCADE
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class BlogPage(Page):
|
|
|
|
"""Models a blog page entry."""
|
|
|
|
|
|
|
|
date = models.DateField("Post date")
|
|
|
|
intro = models.CharField(max_length=250)
|
|
|
|
body = RichTextField(blank=True)
|
|
|
|
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
|
|
|
|
|
|
|
|
def main_image(self):
|
|
|
|
"""Returns the first image associated with a `BlogPage` from `BlogPageGalleryImage`"""
|
|
|
|
gallery_item = self.gallery_images.first()
|
|
|
|
if gallery_item:
|
|
|
|
return gallery_item.image
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
search_fields = Page.search_fields + [
|
|
|
|
index.SearchField("intro"),
|
|
|
|
index.SearchField("body"),
|
|
|
|
]
|
|
|
|
|
|
|
|
content_panels = Page.content_panels + [
|
|
|
|
# Grouping Date and tags together for readability in the Admin panel
|
|
|
|
MultiFieldPanel(
|
|
|
|
[FieldPanel("date"), FieldPanel("tags")], heading="Blog information"
|
|
|
|
),
|
|
|
|
FieldPanel("intro"),
|
|
|
|
FieldPanel("body", classname="full"),
|
|
|
|
InlinePanel("gallery_images", label="Gallery images"),
|
|
|
|
]
|
|
|
|
|
|
|
|
def get_context(self, request, *args, **kwargs):
|
|
|
|
context = super().get_context(request, *args, **kwargs)
|
|
|
|
context["post"] = self
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BlogPageGalleryImage(Orderable):
|
|
|
|
"""
|
|
|
|
An image gallery descendant to a `BlogPage`
|
|
|
|
|
|
|
|
Inherits a `sort_order` field from the `Orderable` object which keeps track of image ordering.
|
|
|
|
The `ParentalKey` works similarly to a `ForeignKey` but also defines this class as a child of `BlogPage` model.
|
|
|
|
This means that the image gallery associated with a particular `BlogPage` instance is treated as a part of the page
|
|
|
|
in operations like submitting for moderation, and tracking version history.
|
|
|
|
|
|
|
|
The `BlogPageGalleryImage.image` is a `ForeignKey to the Wagtail built-in `Image` model where images themselves are
|
|
|
|
stored. The `ImageChooserPanel` provides a popup interface for choosing an existing image or uploading a new one.
|
|
|
|
This allows for the same image to exist in multiple galleries - effectively creating a many-to-many relationship
|
|
|
|
between pages and images.
|
|
|
|
"""
|
|
|
|
|
|
|
|
page = ParentalKey(
|
|
|
|
BlogPage, on_delete=models.CASCADE, related_name="gallery_images"
|
|
|
|
)
|
|
|
|
image = models.ForeignKey(
|
|
|
|
# on_delete here means that is an image is deleted from the system, the gallery entry is deleted as well.
|
|
|
|
"wagtailimages.Image",
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
related_name="+",
|
|
|
|
)
|
|
|
|
caption = models.CharField(blank=True, max_length=250)
|
|
|
|
|
|
|
|
panels = [ImageChooserPanel("image"), FieldPanel("caption")]
|
|
|
|
|
|
|
|
|
|
|
|
class BlogTagIndexPage(Page):
|
|
|
|
"""
|
|
|
|
Models the Index page of blogs by tag.
|
|
|
|
|
|
|
|
Even though this model does not define any fields of its own, it is a subclass of Page and is added to the Wagtail
|
|
|
|
ecosystem. This means that it can be given a title and a URL in the admin
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
def get_context(self, request, *args, **kwargs):
|
|
|
|
"""Filters by tage passed in request query string"""
|
|
|
|
tag = request.GET.get('tag')
|
|
|
|
blogpages = BlogPage.objects.filter(tags__name=tag)
|
|
|
|
|
|
|
|
context = super().get_context(request)
|
|
|
|
context['blogpages'] = blogpages
|
|
|
|
return context
|