from typing import Optional from django import forms from django.db import models from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.contrib.taggit import ClusterTaggableManager from taggit.models import TaggedItemBase from wagtail.api import APIField from wagtail.images.api.fields import ImageRenditionField from wagtail.models import Page, Orderable from wagtail.fields import RichTextField, StreamField from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel from wagtail.search import index from wagtail.snippets.models import register_snippet from blog.blocks import BlogPostBlock from wagtailseo.models import SeoMixin, SeoType class BlogIndexPage(SeoMixin, Page): intro = RichTextField(blank=True) def get_context(self, request): # Update context to include only published posts, ordered by reverse-chron context = super().get_context(request) blogpages = self.get_children().live().order_by('-first_published_at') context['blogpages'] = blogpages return context content_panels = Page.content_panels + [ FieldPanel('intro') ] promote_panels = SeoMixin.seo_panels class BlogTagIndexPage(Page): def get_context(self, request): tag = request.GET.get('tag') blogpages = BlogPage.objects.live().filter(tags__name=tag).order_by('-first_published_at') # Update template context context = super().get_context(request) context['blogpages'] = blogpages return context class BlogPageTag(TaggedItemBase): content_object = ParentalKey( 'BlogPage', related_name='tagged_items', on_delete=models.CASCADE ) class BlogPage(SeoMixin, Page): date = models.DateField("Post date") intro = models.TextField() body = StreamField( BlogPostBlock(), blank=True, use_json_field=True, help_text="Write anything", ) authors = ParentalManyToManyField('blog.Author', blank=True) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) def main_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None # Export fields over the API api_fields = [ APIField('gallery_images'), # Adds a URL to a rendered thumbnail of the image to the API APIField('body'), APIField('intro'), APIField('tags'), ] search_fields = Page.search_fields + [ index.SearchField('intro'), index.SearchField('body'), ] content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('date'), FieldPanel('authors', widget=forms.CheckboxSelectMultiple), FieldPanel('tags'), ], heading="Blog information"), FieldPanel('intro'), FieldPanel('body'), InlinePanel('gallery_images', label="Gallery images"), ] settings_panels = Page.settings_panels = [ FieldPanel('first_published_at') ] promote_panels = SeoMixin.seo_panels seo_content_type = SeoType.ARTICLE @property def seo_author(self) -> str: return ', '.join([author.name for author in self.authors.all()]) @property def seo_canonical_url(self) -> str: # replace http:// with https:// return super().seo_canonical_url.replace('http://', 'https://') @property def seo_struct_publisher_dict(self) -> Optional[dict]: return None class BlogPageGalleryImage(Orderable): page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='gallery_images') image = models.ForeignKey( 'wagtailimages.Image', on_delete=models.CASCADE, related_name='+' ) caption = models.CharField(blank=True, max_length=250) panels = [ FieldPanel('image'), FieldPanel('caption'), ] api_fields = [ APIField('image'), ] @register_snippet class Author(models.Model): name = models.CharField(max_length=255) author_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) panels = [ FieldPanel('name'), FieldPanel('author_image'), ] def __str__(self): return self.name class Meta: verbose_name_plural = 'Authors'