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"), ] 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