1+ from datetime import date
2+
13from django .db import models
24from django .db .models .fields .related import RelatedField
35from django .urls import reverse
46from django .utils .dateformat import format
7+ from django .utils .functional import cached_property
58from django .utils .text import Truncator
69from django .utils .translation import gettext_lazy as _
710from modelcluster .contrib .taggit import ClusterTaggableManager
811from modelcluster .fields import ParentalKey
912from modelcluster .models import ClusterableModel
1013from taggit .models import TaggedItemBase
1114from wagtail .admin .panels import FieldPanel , FieldRowPanel , InlinePanel , MultiFieldPanel
15+ from wagtail .fields import RichTextField
1216from wagtail .models import Orderable , Page , PageManager , PageQuerySet
1317from wagtail .search import index
1418from wagtailautocomplete .edit_handlers import AutocompletePanel
1519
20+ from cdhweb .pages .mixin import StandardHeroMixinNoImage
1621from cdhweb .pages .models import BasePage , LinkPage , PagePreviewDescriptionMixin
1722from cdhweb .people .models import Person
1823
@@ -54,40 +59,107 @@ class BlogPostTag(TaggedItemBase):
5459 )
5560
5661
57- class BlogPost (BasePage , ClusterableModel , PagePreviewDescriptionMixin ):
62+ class BlogPost (BasePage , ClusterableModel ):
5863 """A Blog post, implemented as a Wagtail page."""
5964
60- featured_image = models .ForeignKey (
65+ template = "blog/blog_post.html"
66+
67+ description = RichTextField (
68+ max_length = 200 ,
69+ blank = True ,
70+ null = True ,
71+ features = ["bold" , "italic" ],
72+ verbose_name = "Blog description" ,
73+ help_text = "Short introduction to the page, aim for max two clear sentences (max. 200 chars)." ,
74+ )
75+
76+ image = models .ForeignKey (
6177 "wagtailimages.image" ,
6278 null = True ,
6379 blank = True ,
6480 on_delete = models .SET_NULL ,
6581 related_name = "+" ,
6682 help_text = "Appears on the homepage carousel when post is featured." ,
6783 )
84+ caption = RichTextField (
85+ features = [
86+ "italic" ,
87+ "bold" ,
88+ "link" ,
89+ ],
90+ help_text = "A short caption for the image." ,
91+ blank = True ,
92+ max_length = 180 ,
93+ )
94+
95+ credit = RichTextField (
96+ features = [
97+ "italic" ,
98+ "bold" ,
99+ "link" ,
100+ ],
101+ help_text = "A credit line or attribution for the image." ,
102+ blank = True ,
103+ max_length = 80 ,
104+ )
105+
106+ alt_text = models .CharField (
107+ help_text = "Describe the image for screen readers" ,
108+ blank = True ,
109+ max_length = 80 ,
110+ )
68111 tags = ClusterTaggableManager (through = BlogPostTag , blank = True )
69112 featured = models .BooleanField (
70113 default = False , help_text = "Show the post in the carousel on the homepage."
71114 )
72115 people = models .ManyToManyField (Person , through = "blog.Author" , related_name = "posts" )
73116
117+ category = models .CharField (
118+ verbose_name = "Category" ,
119+ help_text = "Category tag to display on tile" ,
120+ blank = True ,
121+ max_length = 20 ,
122+ )
123+
74124 # can only be created underneath special link page
75- parent_page_types = ["blog.BlogLinkPage " ]
125+ parent_page_types = ["blog.BlogLinkPageArchived" , "blog.BlogLandingPage " ]
76126 # no allowed subpages
77127 subpage_types = []
78128
79129 # admin edit configuration
80130 content_panels = Page .content_panels + [
81- FieldRowPanel ((FieldPanel ("featured_image" ), FieldPanel ("featured" ))),
82131 FieldPanel ("description" ),
132+ MultiFieldPanel (
133+ [
134+ FieldPanel ("image" ),
135+ FieldPanel ("caption" ),
136+ FieldPanel ("credit" ),
137+ FieldPanel ("alt_text" ),
138+ ],
139+ heading = "Hero Image" ,
140+ ),
83141 MultiFieldPanel (
84142 [InlinePanel ("authors" , [AutocompletePanel ("person" )], label = "Author" )],
85143 heading = "Authors" ,
86144 ),
145+ FieldPanel ("category" ),
87146 FieldPanel ("body" ),
88147 FieldPanel ("attachments" ),
89148 ]
90- promote_panels = Page .promote_panels + [FieldPanel ("tags" )]
149+ promote_panels = (
150+ [
151+ MultiFieldPanel (
152+ [
153+ FieldPanel ("short_title" ),
154+ FieldPanel ("short_description" ),
155+ FieldPanel ("feed_image" ),
156+ ],
157+ "Share Page" ,
158+ ),
159+ ]
160+ + BasePage .promote_panels
161+ + [FieldPanel ("tags" )]
162+ )
91163
92164 # index description in addition to body content
93165 search_fields = BasePage .search_fields + [
@@ -104,34 +176,16 @@ class BlogPost(BasePage, ClusterableModel, PagePreviewDescriptionMixin):
104176 # custom manager/queryset logic
105177 objects = BlogPostManager ()
106178
107- # configure template path for wagtail preview
108- template = "blog/blogpost_detail.html"
109-
110- @property
111- def short_title (self ):
112- """Shorter title with ellipsis."""
113- return Truncator (self .title ).chars (65 )
114-
115- @property
116- def short_description (self ):
117- """Shorter description with ellipsis."""
118- return Truncator (self .get_description ()).chars (250 )
179+ @cached_property
180+ def breadcrumbs (self ):
181+ ancestors = self .get_ancestors ().live ().public ().specific ()
182+ return ancestors [1 :] # removing root
119183
120184 @property
121185 def author_list (self ):
122186 """Comma-separated list of author names."""
123187 return ", " .join (str (author .person ) for author in self .authors .all ())
124188
125- def __str__ (self ):
126- # string is used for logging actions on drafts,
127- # needs to handle cases where first published date is not set
128- if self .first_published_at :
129- pubdate = format (self .first_published_at , "F j, Y" )
130- else :
131- pubdate = "draft"
132-
133- return '"%s" (%s)' % (self .short_title , pubdate )
134-
135189 def get_url_parts (self , * args , ** kwargs ):
136190 """Custom blog post URLs of the form /updates/2014/03/01/my-post."""
137191 url_parts = super ().get_url_parts (* args , ** kwargs )
@@ -162,7 +216,7 @@ def get_sitemap_urls(self, request):
162216 return urls
163217
164218
165- class BlogLinkPage (LinkPage ):
219+ class BlogLinkPageArchived (LinkPage ):
166220 """Container page that defines where blog posts can be created."""
167221
168222 # NOTE this page can't be created in the page editor; it is only ever made
@@ -171,3 +225,15 @@ class BlogLinkPage(LinkPage):
171225 # NOTE the only allowed child page type is a BlogPost; this is so that
172226 # Events made in the admin automatically are created here.
173227 subpage_types = [BlogPost ]
228+
229+
230+ class BlogLandingPage (StandardHeroMixinNoImage , Page ):
231+ """Container page that defines where Event pages can be created."""
232+
233+ content_panels = StandardHeroMixinNoImage .content_panels
234+
235+ search_fields = StandardHeroMixinNoImage .search_fields
236+
237+ settings_panels = Page .settings_panels
238+
239+ subpage_types = [BlogPost ]
0 commit comments