Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 76 additions & 7 deletions core/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.shortcuts import render, get_object_or_404
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
import logging

from .utils import (
Expand Down Expand Up @@ -67,7 +68,8 @@ def blog_index(request):
Blog index view for PyOpenSci.

Static Django view that queries Wagtail BlogPage instances
to display all blog posts in a consistent index page.
to display all blog posts in a consistent index page with pagination
and optional year filtering.

Parameters
----------
Expand All @@ -77,15 +79,44 @@ def blog_index(request):
Returns
-------
HttpResponse
Rendered blog index page with blog posts.
Rendered blog index page with paginated blog posts.
"""
# Get year filter from query params
year_filter = request.GET.get('year')

# Base queryset
blog_posts = BlogPage.objects.live().select_related('author').order_by('-date')

# Apply year filter if provided
if year_filter and year_filter.isdigit():
blog_posts = blog_posts.filter(date__year=int(year_filter))

# Get available years for filter dropdown
available_years = (
BlogPage.objects.live()
.dates('date', 'year', order='DESC')
)

# Pagination: 12 posts per page
paginator = Paginator(blog_posts, 12)
page = request.GET.get('page')

try:
paginated_posts = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page
paginated_posts = paginator.page(1)
except EmptyPage:
# If page is out of range, deliver last page of results
paginated_posts = paginator.page(paginator.num_pages)

context = {
'page_title': 'pyOpenSci Blog',
'hero_title': 'pyOpenSci Blog',
'hero_subtitle': 'Here we will both post updates about pyOpenSci and also highlight contributors. We will also highlight new packages that have been reviewed and accepted into the pyOpenSci ecosystem.',
'blog_posts': blog_posts,
'blog_posts': paginated_posts,
'available_years': available_years,
'selected_year': year_filter,
}
return render(request, 'core/blog_index.html', context)

Expand All @@ -95,7 +126,8 @@ def events_index(request):
Events index view for PyOpenSci.

Static Django view that queries Wagtail EventPage instances
to display all events in a consistent index page.
to display all events in a consistent index page with pagination
and optional year filtering.

Parameters
----------
Expand All @@ -105,15 +137,52 @@ def events_index(request):
Returns
-------
HttpResponse
Rendered events index page with events.
Rendered events index page with paginated events.
"""
# Get year filter from query params
year_filter = request.GET.get('year')

# Base queryset
events = EventPage.objects.live().select_related('author').prefetch_related('tags').order_by('-start_date')

# Apply year filter if provided
if year_filter and year_filter.isdigit():
events = events.filter(start_date__year=int(year_filter))

# Get available years for filter dropdown
available_years = (
EventPage.objects.live()
.dates('start_date', 'year', order='DESC')
)

# Pagination: 15 events per page
paginator = Paginator(events, 15)
page = request.GET.get('page')

try:
paginated_events = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page
paginated_events = paginator.page(1)
except EmptyPage:
# If page is out of range, deliver last page of results
paginated_events = paginator.page(paginator.num_pages)

# Separate upcoming and past events
from django.utils import timezone
today = timezone.now().date()

upcoming_events = EventPage.objects.live().filter(start_date__gte=today).select_related('author').prefetch_related('tags').order_by('start_date')

context = {
'page_title': 'pyOpenSci Events',
'hero_title': 'pyOpenSci Events',
'hero_subtitle': 'Join us for workshops, webinars, and community events. Connect with the scientific Python community and learn about open source best practices.',
'events': events,
'hero_subtitle': 'pyOpenSci holds events that support scientists developing open science skills.',
'events': paginated_events,
'upcoming_events': upcoming_events,
'available_years': available_years,
'selected_year': year_filter,
'today': today,
}
return render(request, 'core/events_index.html', context)

Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"build-prod": "tailwindcss -i ./static/css/input.css -o ./static/css/styles.css --minify"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"tailwindcss": "^3.4.0"
}
}
}
Empty file.
Empty file.
135 changes: 135 additions & 0 deletions publications/management/commands/create_dummy_posts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from datetime import timedelta
from publications.models import BlogPage, EventPage, Author
from wagtail.models import Page


class Command(BaseCommand):
help = "Create dummy blog posts and events for testing pagination and filtering"

def add_arguments(self, parser):
parser.add_argument(
'--blog-posts',
type=int,
default=25,
help='Number of blog posts to create (default: 25)'
)
parser.add_argument(
'--events',
type=int,
default=20,
help='Number of events to create (default: 20)'
)
parser.add_argument(
'--delete',
action='store_true',
help='Delete existing dummy posts before creating new ones'
)

def handle(self, *args, **options):
num_blog_posts = options['blog_posts']
num_events = options['events']
delete_existing = options['delete']

# Get or create a default author
author, created = Author.objects.get_or_create(
slug='test-author',
defaults={
'name': 'Test Author',
'bio': 'This is a test author for dummy content.',
}
)

if created:
self.stdout.write(self.style.SUCCESS(f'Created test author: {author.name}'))

# Get the home page to use as parent
home_page = Page.objects.get(depth=2).specific

# Delete existing dummy posts if requested
if delete_existing:
deleted_blogs = BlogPage.objects.filter(title__startswith='Test Blog Post').delete()
deleted_events = EventPage.objects.filter(title__startswith='Test Event').delete()
self.stdout.write(self.style.WARNING(
f'Deleted {deleted_blogs[0]} blog posts and {deleted_events[0]} events'
))

# Create blog posts
self.stdout.write(self.style.NOTICE(f'\nCreating {num_blog_posts} blog posts...'))
current_date = timezone.now()

for i in range(1, num_blog_posts + 1):
# Distribute posts across multiple years
year_offset = (i - 1) // 10 # 10 posts per year
month_offset = (i - 1) % 12
post_date = current_date - timedelta(days=365 * year_offset + 30 * month_offset)

blog_post = BlogPage(
title=f'Test Blog Post {i}',
slug=f'test-blog-post-{i}',
date=post_date,
author=author,
excerpt=f'This is a test excerpt for blog post {i}. '
'It provides a brief overview of the post content.',
body=f'<p>This is the body content for test blog post {i}.</p>'
'<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>',
enable_comments=True,
)
home_page.add_child(instance=blog_post)
blog_post.save_revision().publish()

if i % 5 == 0:
self.stdout.write(f' Created {i}/{num_blog_posts} blog posts...')

self.stdout.write(self.style.SUCCESS(f'✓ Created {num_blog_posts} blog posts'))

# Create events
self.stdout.write(self.style.NOTICE(f'\nCreating {num_events} events...'))

event_types = ['workshop', 'webinar', 'conference', 'meetup', 'other']

for i in range(1, num_events + 1):
# Mix of past and future events
if i % 2 == 0:
# Future event
event_date = current_date + timedelta(days=30 * i)
else:
# Past event
event_date = current_date - timedelta(days=30 * i)

event_type = event_types[i % len(event_types)]

event = EventPage(
title=f'Test Event {i}',
slug=f'test-event-{i}',
date=current_date,
author=author,
start_date=event_date.date(),
end_date=(event_date + timedelta(days=1)).date() if i % 3 == 0 else event_date.date(),
location='Online' if i % 2 == 0 else 'San Francisco, CA',
event_type=event_type,
excerpt=f'This is a test excerpt for event {i}. Join us for this exciting event!',
body=f'<p>This is the body content for test event {i}.</p>'
'<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>',
enable_comments=True,
)
home_page.add_child(instance=event)
event.save_revision().publish()

if i % 5 == 0:
self.stdout.write(f' Created {i}/{num_events} events...')

self.stdout.write(self.style.SUCCESS(f'✓ Created {num_events} events'))

# Summary
self.stdout.write(self.style.SUCCESS(
f'\n✅ Successfully created {num_blog_posts} blog posts and {num_events} events!'
))
self.stdout.write(self.style.NOTICE(
'\nYou can now test:\n'
' - Blog pagination at /blog/ (12 posts per page)\n'
' - Events pagination at /events/ (15 events per page)\n'
' - Year filtering on blog page\n'
))
Binary file added static/images/headers/pyopensci-learn-header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added static/images/headers/pyopensci-learn-header.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ module.exports = {
}
},
},
plugins: [],
plugins: [
require('@tailwindcss/typography'),
],
}
Loading