Skip to content

Commit f488bda

Browse files
authored
Merge pull request #97 from springload/feature/blog-page-bed
blog page and blog landing page
2 parents 4891783 + 95b3324 commit f488bda

18 files changed

+402
-154
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Generated by Django 5.0.5 on 2024-06-11 21:08
2+
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("blog", "0012_alter_blogpost_body"),
9+
]
10+
11+
operations = [
12+
migrations.RenameField(
13+
model_name="blogpost",
14+
old_name="featured_image",
15+
new_name="image",
16+
),
17+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by Django 5.0.5 on 2024-06-11 21:17
2+
3+
import wagtail.fields
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("blog", "0013_rename_featured_image_blogpost_image"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="blogpost",
15+
name="alt_text",
16+
field=models.CharField(
17+
blank=True,
18+
help_text="Describe the image for screen readers",
19+
max_length=80,
20+
),
21+
),
22+
migrations.AddField(
23+
model_name="blogpost",
24+
name="caption",
25+
field=wagtail.fields.RichTextField(
26+
blank=True, help_text="A short caption for the image.", max_length=180
27+
),
28+
),
29+
migrations.AddField(
30+
model_name="blogpost",
31+
name="credit",
32+
field=wagtail.fields.RichTextField(
33+
blank=True,
34+
help_text="A credit line or attribution for the image.",
35+
max_length=80,
36+
),
37+
),
38+
]
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 5.0.5 on 2024-06-11 21:21
2+
3+
import wagtail.fields
4+
from django.db import migrations
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("blog", "0014_blogpost_alt_text_blogpost_caption_blogpost_credit"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="blogpost",
15+
name="description",
16+
field=wagtail.fields.RichTextField(
17+
blank=True,
18+
help_text="Short introduction to the page, aim for max two clear sentences (max. 200 chars).",
19+
max_length=200,
20+
null=True,
21+
verbose_name="Blog description",
22+
),
23+
),
24+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Generated by Django 5.0.5 on 2024-06-11 21:50
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("blog", "0015_alter_blogpost_description"),
9+
]
10+
11+
operations = [
12+
migrations.AddField(
13+
model_name="blogpost",
14+
name="short_description",
15+
field=models.TextField(
16+
blank=True,
17+
help_text="A short description of the content for promotional or navigation purposes. Displayed on tiles, not on page itself.",
18+
max_length=130,
19+
verbose_name="Short description",
20+
),
21+
),
22+
migrations.AddField(
23+
model_name="blogpost",
24+
name="short_title",
25+
field=models.CharField(
26+
blank=True,
27+
default="",
28+
help_text="Displayed on tiles, breadcrumbs etc., not on page itself. ",
29+
max_length=80,
30+
verbose_name="Short title",
31+
),
32+
),
33+
]
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Generated by Django 5.0.5 on 2024-06-12 04:24
2+
3+
import django.db.models.deletion
4+
import wagtail.fields
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("blog", "0016_blogpost_short_description_blogpost_short_title"),
11+
("cdhpages", "0047_alter_contentpage_body_alter_homepage_body_and_more"),
12+
("wagtailcore", "0089_log_entry_data_json_null_to_object"),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="BlogLandingPage",
18+
fields=[
19+
(
20+
"page_ptr",
21+
models.OneToOneField(
22+
auto_created=True,
23+
on_delete=django.db.models.deletion.CASCADE,
24+
parent_link=True,
25+
primary_key=True,
26+
serialize=False,
27+
to="wagtailcore.page",
28+
),
29+
),
30+
(
31+
"description",
32+
wagtail.fields.RichTextField(
33+
blank=True,
34+
help_text="Short introduction to the page, aim for max two clear sentences (max. 200 chars). \n Used to orient the user and help them identify relevancy of the page to meet their needs. ",
35+
max_length=200,
36+
null=True,
37+
verbose_name="Page Summary",
38+
),
39+
),
40+
],
41+
options={
42+
"abstract": False,
43+
},
44+
bases=("wagtailcore.page", models.Model),
45+
),
46+
migrations.RenameModel(
47+
old_name="BlogLinkPage",
48+
new_name="BlogLinkPageArchived",
49+
),
50+
migrations.AddField(
51+
model_name="blogpost",
52+
name="category",
53+
field=models.CharField(
54+
blank=True,
55+
help_text="Category tag to display on tile",
56+
max_length=20,
57+
verbose_name="Category",
58+
),
59+
),
60+
]

Diff for: cdhweb/blog/models.py

+94-28
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1+
from datetime import date
2+
13
from django.db import models
24
from django.db.models.fields.related import RelatedField
35
from django.urls import reverse
46
from django.utils.dateformat import format
7+
from django.utils.functional import cached_property
58
from django.utils.text import Truncator
69
from django.utils.translation import gettext_lazy as _
710
from modelcluster.contrib.taggit import ClusterTaggableManager
811
from modelcluster.fields import ParentalKey
912
from modelcluster.models import ClusterableModel
1013
from taggit.models import TaggedItemBase
1114
from wagtail.admin.panels import FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel
15+
from wagtail.fields import RichTextField
1216
from wagtail.models import Orderable, Page, PageManager, PageQuerySet
1317
from wagtail.search import index
1418
from wagtailautocomplete.edit_handlers import AutocompletePanel
1519

20+
from cdhweb.pages.mixin import StandardHeroMixinNoImage
1621
from cdhweb.pages.models import BasePage, LinkPage, PagePreviewDescriptionMixin
1722
from 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

Comments
 (0)