Skip to content

Commit 74da419

Browse files
Phinart98lwasser
authored andcommitted
Add homepage blog integration, related posts, year filter dropdown, and updated documentation
- Display real blog posts on homepage instead of placeholders - Add related posts/events section to detail pages - Replace year filter buttons with dropdown for better scalability - Update README with test data generation instructions Changes: - Homepage now shows 3 most recent blog posts with images, dates, authors, and excerpts - Blog and event detail pages display related content based on tag overlap (fallback to recent posts) - Year filter dropdown styled with brand colors, replaces button row - README includes dummy data command and updated page links
1 parent 5b0e848 commit 74da419

File tree

7 files changed

+244
-35
lines changed

7 files changed

+244
-35
lines changed

README.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,40 @@ create a superuser (admin) account which can be used for Django Admin and Wagtai
5151
uv run python manage.py createsuperuser
5252
```
5353

54-
### 4. Run Development Server
54+
### 4. Generate Test Data (optional but recommended)
55+
56+
To see how blog and events pages look with content,
57+
generate dummy blog posts and events:
58+
59+
```bash
60+
# Generate default test data (25 blog posts, 20 events)
61+
uv run python manage.py create_dummy_posts
62+
63+
# Or specify custom amounts
64+
uv run python manage.py create_dummy_posts --blog-posts=30 --events=25
65+
66+
# Delete any existing dummy data
67+
uv run python manage.py create_dummy_posts --delete
68+
```
69+
70+
This command creates:
71+
- Blog posts distributed across multiple years (for testing year filters)
72+
- Events with both past and upcoming dates
73+
- Random tags, authors, and excerpts
74+
- All posts are automatically published and visible
75+
76+
### 5. Run Development Server
5577

5678
```bash
5779
# Start Django development server
5880
uv run python manage.py runserver
5981
```
6082

61-
## Testing the Homepage Migration
83+
## Available Pages
6284

6385
- **Homepage (Django)**: [http://127.0.0.1:8000](http://127.0.0.1:8000)
64-
- **Blog (Wagtail)**: [http://127.0.0.1:8000/blog/](http://127.0.0.1:8000/blog/)
86+
- **Blog Index**: [http://127.0.0.1:8000/blog/](http://127.0.0.1:8000/blog/)
87+
- **Events Index**: [http://127.0.0.1:8000/events/](http://127.0.0.1:8000/events/)
6588
- **Wagtail Admin**: [http://127.0.0.1:8000/cms/](http://127.0.0.1:8000/cms/)
6689
- **Django Admin**: [http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/)
6790

core/views.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,14 @@ def home(request):
5050

5151
# Fetch recent packages from YAML
5252
recent_packages = get_recent_packages(count=3)
53-
53+
54+
# Fetch recent blog posts from Wagtail
55+
recent_blog_posts = (
56+
BlogPage.objects.live()
57+
.select_related('author')
58+
.order_by('-date')[:3]
59+
)
60+
5461
context = {
5562
'page_title': 'Welcome to pyOpenSci',
5663
'hero_title': 'We make it easier for scientists to create, find, maintain, and contribute to reusable code and software.',
@@ -59,6 +66,8 @@ def home(request):
5966
'recent_contributors': recent_contributors,
6067
# Used for the "Recently Accepted Python Packages" section on the home page
6168
'recent_packages': recent_packages,
69+
# Used for the "Recent blog posts & updates" section on the home page
70+
'recent_blog_posts': recent_blog_posts,
6271
}
6372
return render(request, 'core/home.html', context)
6473

@@ -203,7 +212,37 @@ def serve_blog_page(request, slug):
203212
HttpResponse
204213
Rendered blog page using Wagtail's serve mechanism
205214
"""
206-
page = get_object_or_404(BlogPage.objects.live().select_related('author'), slug=slug)
215+
page = get_object_or_404(BlogPage.objects.live().select_related('author').prefetch_related('tags'), slug=slug)
216+
217+
# Get related posts based on tag overlap
218+
page_tags = page.tags.all()
219+
related_posts = []
220+
221+
if page_tags:
222+
# Find posts with overlapping tags
223+
from django.db.models import Count
224+
related_posts = (
225+
BlogPage.objects.live()
226+
.select_related('author')
227+
.prefetch_related('tags')
228+
.filter(tags__in=page_tags)
229+
.exclude(pk=page.pk)
230+
.annotate(same_tags=Count('pk'))
231+
.order_by('-same_tags', '-date')[:3]
232+
)
233+
234+
# Fallback to recent posts if no tag matches
235+
if not related_posts:
236+
related_posts = (
237+
BlogPage.objects.live()
238+
.select_related('author')
239+
.exclude(pk=page.pk)
240+
.order_by('-date')[:3]
241+
)
242+
243+
# Add related_posts to the page context
244+
page.related_posts = related_posts
245+
207246
return page.serve(request)
208247

209248

@@ -224,4 +263,34 @@ def serve_event_page(request, slug):
224263
Rendered event page using Wagtail's serve mechanism
225264
"""
226265
page = get_object_or_404(EventPage.objects.live().select_related('author').prefetch_related('tags'), slug=slug)
266+
267+
# Get related events based on tag overlap
268+
page_tags = page.tags.all()
269+
related_events = []
270+
271+
if page_tags:
272+
# Find events with overlapping tags
273+
from django.db.models import Count
274+
related_events = (
275+
EventPage.objects.live()
276+
.select_related('author')
277+
.prefetch_related('tags')
278+
.filter(tags__in=page_tags)
279+
.exclude(pk=page.pk)
280+
.annotate(same_tags=Count('pk'))
281+
.order_by('-same_tags', '-start_date')[:3]
282+
)
283+
284+
# Fallback to recent events if no tag matches
285+
if not related_events:
286+
related_events = (
287+
EventPage.objects.live()
288+
.select_related('author')
289+
.exclude(pk=page.pk)
290+
.order_by('-start_date')[:3]
291+
)
292+
293+
# Add related_events to the page context
294+
page.related_events = related_events
295+
227296
return page.serve(request)

templates/core/blog_index.html

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,28 @@ <h1 class="text-4xl font-bold mb-6">{{ hero_title }}</h1>
2121
<!-- Section Header -->
2222
<div class="text-center mb-12">
2323
<h2 class="text-3xl font-bold text-gray-900 mb-4">Recent pyOpenSci blog posts</h2>
24-
<!-- Year filter buttons -->
24+
<!-- Year filter dropdown -->
2525
{% if available_years %}
26-
<div class="flex flex-wrap justify-center gap-2 mb-8">
27-
<a href="{% url 'core:blog_index' %}"
28-
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors {% if not selected_year %}bg-pyos-deep-purple text-white hover:bg-pyos-dark-purple{% else %}bg-gray-300 text-gray-800 hover:bg-gray-400{% endif %}">
29-
All Years
30-
</a>
31-
{% for year in available_years %}
32-
<a href="{% url 'core:blog_index' %}?year={{ year.year }}"
33-
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors {% if selected_year == year.year|stringformat:'s' %}bg-pyos-deep-purple text-white hover:bg-pyos-dark-purple{% else %}bg-gray-300 text-gray-800 hover:bg-gray-400{% endif %}">
34-
{{ year.year }}
35-
</a>
36-
{% endfor %}
26+
<div class="flex justify-center items-center gap-4 mb-8">
27+
<label for="year-filter" class="text-sm font-medium text-gray-700">Filter by year:</label>
28+
<div class="relative inline-block">
29+
<select id="year-filter"
30+
onchange="window.location.href=this.value"
31+
class="appearance-none bg-white border-2 border-pyos-deep-purple rounded-lg px-4 py-2 pr-12 font-medium text-pyos-deep-purple hover:bg-pyos-light-purple focus:outline-none focus:ring-2 focus:ring-pyos-deep-purple focus:border-pyos-deep-purple transition-all cursor-pointer shadow-sm">
32+
<option value="{% url 'core:blog_index' %}" {% if not selected_year %}selected{% endif %}>All Years</option>
33+
{% for year in available_years %}
34+
<option value="{% url 'core:blog_index' %}?year={{ year.year }}"
35+
{% if selected_year == year.year|stringformat:'s' %}selected{% endif %}>
36+
{{ year.year }}
37+
</option>
38+
{% endfor %}
39+
</select>
40+
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-pyos-deep-purple">
41+
<svg class="w-4 h-4 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
42+
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/>
43+
</svg>
44+
</div>
45+
</div>
3746
</div>
3847
{% endif %}
3948
</div>

templates/core/events_index.html

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,28 @@ <h2 class="text-3xl font-bold text-gray-900 mb-4">Upcoming Events</h2>
8787
<h2 class="text-3xl font-bold text-gray-900 mb-4">Past Events</h2>
8888
<p class="text-gray-600 mb-4">Browse our complete event archive</p>
8989

90-
<!-- Year filter buttons -->
90+
<!-- Year filter dropdown -->
9191
{% if available_years %}
92-
<div class="flex flex-wrap justify-center gap-2 mb-8">
93-
<a href="{% url 'core:events_index' %}#past-events"
94-
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors {% if not selected_year %}bg-pyos-deep-purple text-white hover:bg-pyos-dark-purple{% else %}bg-gray-300 text-gray-800 hover:bg-gray-400{% endif %}">
95-
All Years
96-
</a>
97-
{% for year in available_years %}
98-
<a href="{% url 'core:events_index' %}?year={{ year.year }}#past-events"
99-
class="px-4 py-2 rounded-lg text-sm font-medium transition-colors {% if selected_year == year.year|stringformat:'s' %}bg-pyos-deep-purple text-white hover:bg-pyos-dark-purple{% else %}bg-gray-300 text-gray-800 hover:bg-gray-400{% endif %}">
100-
{{ year.year }}
101-
</a>
102-
{% endfor %}
92+
<div class="flex justify-center items-center gap-4 mb-8">
93+
<label for="year-filter-events" class="text-sm font-medium text-gray-700">Filter by year:</label>
94+
<div class="relative inline-block">
95+
<select id="year-filter-events"
96+
onchange="window.location.href=this.value"
97+
class="appearance-none bg-white border-2 border-pyos-deep-purple rounded-lg px-4 py-2 pr-12 font-medium text-pyos-deep-purple hover:bg-pyos-light-purple focus:outline-none focus:ring-2 focus:ring-pyos-deep-purple focus:border-pyos-deep-purple transition-all cursor-pointer shadow-sm">
98+
<option value="{% url 'core:events_index' %}#past-events" {% if not selected_year %}selected{% endif %}>All Years</option>
99+
{% for year in available_years %}
100+
<option value="{% url 'core:events_index' %}?year={{ year.year }}#past-events"
101+
{% if selected_year == year.year|stringformat:'s' %}selected{% endif %}>
102+
{{ year.year }}
103+
</option>
104+
{% endfor %}
105+
</select>
106+
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-3 text-pyos-deep-purple">
107+
<svg class="w-4 h-4 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
108+
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"/>
109+
</svg>
110+
</div>
111+
</div>
103112
</div>
104113
{% endif %}
105114
</div>

templates/core/home.html

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,15 +209,40 @@ <h2 class="text-3xl font-bold text-center mb-12 text-pyos-deep-purple font-poppi
209209
<h2 class="text-3xl font-bold text-center mb-8 text-pyos-deep-purple font-poppins">Recent blog posts & updates</h2>
210210

211211
<div class="grid md:grid-cols-3 gap-6 mb-8">
212-
<!-- Placeholder blog post cards -->
213-
{% for i in "123" %}
214-
<div class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
212+
{% for post in recent_blog_posts %}
213+
<a href="/blog/{{ post.slug }}/" class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300 block">
214+
<!-- Header Image -->
215+
{% if post.header_image %}
216+
<div class="h-40 overflow-hidden">
217+
<img src="{{ post.header_image.url }}"
218+
alt="{{ post.header_image_alt|default:post.title }}"
219+
class="w-full h-full object-cover">
220+
</div>
221+
{% else %}
215222
<div class="h-40 bg-gradient-to-br from-pyos-medium-purple to-pyos-dark-purple"></div>
223+
{% endif %}
224+
216225
<div class="p-6">
217-
<h3 class="font-bold mb-2 text-pyos-deep-purple">Blog Post Title {{ forloop.counter }}</h3>
218-
<p class="text-sm text-gray-600 mb-2">March {{ forloop.counter }}, 2025</p>
219-
<p class="text-gray-700 text-sm">Brief description of the blog post content...</p>
226+
<h3 class="font-bold mb-2 text-pyos-deep-purple hover:text-pyos-dark-purple transition-colors">{{ post.title }}</h3>
227+
228+
<!-- Meta: Date and Author -->
229+
<p class="text-sm text-gray-600 mb-3">
230+
{{ post.date|date:"F j, Y" }}
231+
{% if post.author %}
232+
· {{ post.author.name }}
233+
{% endif %}
234+
</p>
235+
236+
<!-- Excerpt -->
237+
{% if post.excerpt %}
238+
<p class="text-gray-700 text-sm">{{ post.excerpt|truncatewords:20 }}</p>
239+
{% endif %}
220240
</div>
241+
</a>
242+
{% empty %}
243+
<!-- Fallback if no blog posts exist -->
244+
<div class="col-span-3 text-center text-gray-500 py-8">
245+
<p>No blog posts available yet. Check back soon!</p>
221246
</div>
222247
{% endfor %}
223248
</div>

templates/publications/blog_page.html

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,42 @@ <h3 class="text-lg font-semibold mb-4">Comments</h3>
9494
<p class="text-gray-600">Comments system integration placeholder</p>
9595
</div>
9696
{% endif %}
97+
98+
<!-- Related Posts -->
99+
{% if page.related_posts %}
100+
<div class="mt-12">
101+
<h2 class="text-2xl font-bold text-gray-900 mb-6">You May Also Enjoy</h2>
102+
<div class="grid md:grid-cols-3 gap-6">
103+
{% for post in page.related_posts %}
104+
<a href="/blog/{{ post.slug }}/" class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300 block flex flex-col">
105+
<!-- Header Image -->
106+
{% if post.header_image %}
107+
<div class="h-40 overflow-hidden flex-shrink-0">
108+
<img src="{{ post.header_image.url }}"
109+
alt="{{ post.header_image_alt|default:post.title }}"
110+
class="w-full h-full object-cover">
111+
</div>
112+
{% else %}
113+
<div class="h-40 bg-gradient-to-br from-pyos-medium-purple to-pyos-dark-purple flex-shrink-0"></div>
114+
{% endif %}
115+
116+
<div class="p-6 flex-grow flex flex-col">
117+
<h3 class="font-bold text-base mb-3 text-pyos-deep-purple hover:text-pyos-dark-purple transition-colors line-clamp-2">{{ post.title }}</h3>
118+
119+
<!-- Meta: Date -->
120+
<p class="text-sm text-gray-600 mb-3">
121+
{{ post.date|date:"F j, Y" }}
122+
</p>
123+
124+
<!-- Excerpt -->
125+
{% if post.excerpt %}
126+
<p class="text-gray-700 text-sm line-clamp-3">{{ post.excerpt|truncatewords:15 }}</p>
127+
{% endif %}
128+
</div>
129+
</a>
130+
{% endfor %}
131+
</div>
132+
</div>
133+
{% endif %}
97134
</div>
98135
{% endblock %}

templates/publications/event_page.html

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,5 +139,42 @@ <h3 class="text-lg font-semibold mb-4">Comments</h3>
139139
<p class="text-gray-600">Comments system integration placeholder</p>
140140
</div>
141141
{% endif %}
142+
143+
<!-- Related Events -->
144+
{% if page.related_events %}
145+
<div class="mt-12">
146+
<h2 class="text-2xl font-bold text-gray-900 mb-6">You May Also Enjoy</h2>
147+
<div class="grid md:grid-cols-3 gap-6">
148+
{% for event in page.related_events %}
149+
<a href="/events/{{ event.slug }}/" class="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300 block flex flex-col">
150+
<!-- Header Image -->
151+
{% if event.header_image %}
152+
<div class="h-40 overflow-hidden flex-shrink-0">
153+
<img src="{{ event.header_image.url }}"
154+
alt="{{ event.header_image_alt|default:event.title }}"
155+
class="w-full h-full object-cover">
156+
</div>
157+
{% else %}
158+
<div class="h-40 bg-gradient-to-br from-pyos-medium-purple to-pyos-dark-purple flex-shrink-0"></div>
159+
{% endif %}
160+
161+
<div class="p-6 flex-grow flex flex-col">
162+
<h3 class="font-bold text-base mb-3 text-pyos-deep-purple hover:text-pyos-dark-purple transition-colors line-clamp-2">{{ event.title }}</h3>
163+
164+
<!-- Meta: Date -->
165+
<p class="text-sm text-gray-600 mb-3">
166+
{{ event.start_date|date:"F j, Y" }}
167+
</p>
168+
169+
<!-- Excerpt -->
170+
{% if event.excerpt %}
171+
<p class="text-gray-700 text-sm line-clamp-3">{{ event.excerpt|truncatewords:15 }}</p>
172+
{% endif %}
173+
</div>
174+
</a>
175+
{% endfor %}
176+
</div>
177+
</div>
178+
{% endif %}
142179
</div>
143180
{% endblock %}

0 commit comments

Comments
 (0)