A simple event calendar application built with Flask and FullCalendar.
- Monthly, weekly, and daily calendar views
- Create, edit, and delete events
- Recurring events support using iCalendar RRULE format
- Venue management
- Custom event colors
- Virtual and hybrid events support
- Full-text search functionality
- WordPress integration via widgets
- Advanced caching system with management interface
- Create a virtual environment (recommended):
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate- Install dependencies:
pip install -r requirements.txt- Run the application:
python app.py- Open your browser and navigate to
http://localhost:5000
config.yaml file!
The application requires a config.yaml file in the root directory with the following settings:
# Flask Events Configuration
# Database Settings
database:
path: "events.db"
# Flask Settings
secret_key: "your-secret-key-change-this-in-production"
# Admin login
admin:
username: "admin"
password_hash: "" # see Admin Login below
# Timezone Settings
timezone:
local: "America/New_York"- Timezone: Set to your local timezone (e.g., "America/New_York", "America/Chicago")
- Database Path: Default is "events.db" in the application root
- Secret Key: Use a strong random value in production (required for session cookies)
- Admin Login: Required to access
/admin/, event create/edit/delete, and cache management
The application will exit with an error if the config.yaml file is missing or malformed.
Admin credentials are stored in config.yaml (no user-management UI). To set up:
- Set
admin.usernameto the desired login name. - Generate a password hash:
python hash_password.py 'your-password' - Paste the output into
admin.password_hashinconfig.yaml. - Log in at
/loginto access the admin panel, event forms, and cache management.
Public calendar views and the /events API (WordPress widgets) remain accessible without login.
CORS is not configured in config.yaml. Allowed origins are hardcoded in app.py for the WordPress host site:
https://thedetroitilove.comhttps://www.thedetroitilove.com
No extra setup is required for that site. To allow a different WordPress domain, edit the CORS_ORIGINS list in app.py.
- Click on any date to view the daily view
- Click the "New Event" button to create an event
- Click on an existing event to edit it
- Use the navigation buttons to move between months
- Switch between month, week, and day views using the view buttons
- Use the search functionality to find specific events
- Manage venues through the Venues section
The application supports recurring events using the iCalendar RRULE format.
Examples:
- Weekly on Monday, Wednesday, Friday:
FREQ=WEEKLY;BYDAY=MO,WE,FR - Daily:
FREQ=DAILY - Monthly on the 15th:
FREQ=MONTHLY;BYMONTHDAY=15 - Every other week:
FREQ=WEEKLY;INTERVAL=2 - First Monday of every month:
FREQ=MONTHLY;BYDAY=1MO
How it works:
- Storage: Each recurring event is stored as a single row with its RRULE in the
rrulecolumn, plusis_recurringandrecurring_untilfields for the series end. - Expansion: When a calendar or day view is requested, the backend expands recurring events into individual instances for the requested date range using the
dateutil.rrulelibrary. - Query logic: For each request, the backend fetches non-recurring events in the date range directly, fetches recurring events that could have instances in the range (via an index on
is_recurringandrecurring_until), and expands only those relevant to the requested range. - Editing: The event form lets users specify or edit the recurrence rule and series end date; the backend updates the relevant fields accordingly.
This Flask Events app can be integrated with WordPress using the included WordPress plugin, displaying events on any WordPress site while keeping event data on the Flask backend.
For a typical setup with Flask on a subdomain (e.g., flaskevents.thedetroitilove.com) and WordPress on the main site (thedetroitilove.com):
-
Copy WordPress Plugin Files
cp -r wp_flask_events/ /path/to/wordpress/wp-content/plugins/
-
Activate the Plugin
- Go to WordPress admin → Plugins → Installed Plugins
- Find "Flask Events" and click Activate
-
Configure Flask App URL
- Edit
wp_flask_events/wp_flask_events.php - Update the
FLASK_EVENTS_URLconstant:
define('FLASK_EVENTS_URL', 'https://flaskevents.example.com');
- Edit
-
Add widgets or shortcodes
Test page (recommended): create a page and add these shortcodes in a Shortcode block:
[flask_events] [flask_events_list]Sidebar widgets: go to Appearance → Widgets, add Flask Events and Flask Events List to a widget area your theme displays.
-
View the page — publish and open it on the site.
Key Points:
- SSL Required: Both sites need HTTPS for CORS to work properly
- CORS: Already allowed for
thedetroitilove.com(hardcoded inapp.py— no config needed) - Two embed options: Flask Events (monthly calendar) and Flask Events List (daily list)
- No Database Changes: WordPress and Flask remain completely separate
- Displays a compact monthly calendar for event navigation
- Click on any date to open that day on the Flask app
- Navigation between months; responsive design for sidebars
- Shows all events for the current day
- Navigation buttons to move between days
- Displays event time, title, description, and venue
Shortcodes (easiest for a test page):
- Pages → Add New (e.g. "Flask Events Test")
- Add a Shortcode block with
[flask_events] - Add another Shortcode block with
[flask_events_list] - Publish and view the page
Widgets:
- Go to Appearance → Widgets
- Add Flask Events and Flask Events List to a sidebar or footer area
- Save — widgets appear wherever your theme renders that area
The WordPress integration uses a client-side approach. WordPress handles content while Flask handles event data, keeping event queries off the WordPress database.
WordPress Site Flask Events
┌─────────────────┐ ┌─────────────────────┐
│ │ │ │
│ WordPress │ │ Flask App │
│ Frontend │ │ (Python) │
│ │ │ │
│ ┌─────────────┐│ │ ┌─────────────────┐│
│ │ Widget ││ │ │ Database ││
│ │ (HTML) ││ │ │ (SQLite) ││
│ └─────────────┘│ │ └─────────────────┘│
│ │ │ │
│ ┌─────────────┐│ │ ┌─────────────────┐│
│ │ JavaScript ││◄─────────────┤ │ API Endpoints ││
│ │ (Widget) ││ │ │ (/events, ││
│ └─────────────┘│ │ │ /search) ││
│ │ │ └─────────────────┘│
└─────────────────┘ └─────────────────────┘
- Flask App URL: Set in the WordPress plugin (
FLASK_EVENTS_URL) - CORS: Hardcoded in
app.pyforthedetroitilove.com(see Configuration) - Widget CSS: Customize via
wp_flask_events/css/flask-events.css
- CORS: Allowed origins are hardcoded in
app.py— see CORS (WordPress integration) - Rate limiting: Consider rate limiting for
/eventsand/searchendpoints in production - Input validation: The Flask app validates inputs; ensure WordPress-side validation for any user-submitted data
- Widgets Not Loading — Verify the Flask app URL, confirm the app is running, check browser console for JavaScript errors
- CORS Errors — Confirm the WordPress site URL matches an entry in
CORS_ORIGINSinapp.py(currentlythedetroitilove.comwith and withoutwww); both sites must use HTTPS - Events Not Displaying — Verify the
/eventsendpoint works and events exist in the database; check browser network tab - Styling Issues — WordPress theme CSS may conflict with widget styles; test with a default theme
Debug mode: Add define('FLASK_EVENTS_DEBUG', true); to wp_flask_events.php for additional troubleshooting output.
For custom shortcodes, REST API proxying, plugin hooks, and widget development, see the source in wp_flask_events/ — particularly wp_flask_events.php and the classes in includes/.
wp_flask_events/
├── wp_flask_events.php # Main plugin file
├── includes/
│ ├── class-flask-events-widget.php
│ └── class-flask-events-list-widget.php
├── css/
│ └── flask-events.css
└── js/
└── flask-events.js
Shortcodes: [flask_events] and [flask_events_list]
When integrated with WordPress, this solution provides several advantages:
- Decoupled Architecture: WordPress handles content management while Flask handles event data
- Scalability: Event data doesn't impact WordPress performance
- Caching: Flask's caching layer works independently of WordPress caching
- Database Efficiency: SQLite with clustered indexing vs WordPress's MySQL queries
- Resource Isolation: Event processing doesn't compete with WordPress resources
Flask app: Use a production WSGI server (Gunicorn, uWSGI), reverse proxy (Nginx, Apache), SSL, and process management (systemd, supervisor).
WordPress integration: Ensure HTTPS for both sites; CORS for thedetroitilove.com is built in. Set up monitoring as needed.
Database: SQLite works well for moderate loads; consider PostgreSQL or MySQL for high traffic. Implement backups.
Performance optimization:
-
Flask App
- Enable response compression
- Configure proper caching headers
- Use database connection pooling
- Implement request rate limiting
-
WordPress Plugin
- Minimize JavaScript and CSS
- Use WordPress transients for caching
- Implement lazy loading for widgets
- Optimize API request frequency
This calendar application solves a critical performance problem that plagues WordPress Events Calendar Pro:
- WordPress Events Calendar Pro starts to slow down around 1,000 events
- At 5,000 events, most queries take 2+ seconds per request
- This design provides nearly instantaneous speed even with a million events
- Most requests complete in under 0.02 seconds (for non-cached requests)
The performance difference is achieved through a clustered index database design that optimizes for calendar-specific queries, combined with a comprehensive caching layer that serves most requests nearly instantly.
This application uses a clustered index design for event storage, which has been proven to be more efficient than conventional indexing for calendar-based queries.
-
Composite Primary Key (start_date, id)
- Events are physically stored in order by date, then by ID
- Events for the same date are stored together on disk
- Reduces disk I/O when querying events for a specific date or date range
-
Performance Benefits
- Single day queries: ~32% faster than conventional indexing
- 3-day range queries: ~32% faster than conventional indexing
- 7-day range queries: ~12% faster than conventional indexing
- More consistent performance (lower standard deviation in query times)
-
Why This Matters
- Calendar applications typically query events by date ranges
- Most queries are for single days or small ranges (1-7 days)
- The clustered design optimizes for these common use cases
- Reduces disk seeks and improves cache efficiency
Conventional vs clustered index comparison:
| Conventional | Clustered |
|---|---|
| Simple auto-incrementing primary key | Composite primary key (start_date, id) |
| Separate index on start_date | Events physically ordered by date |
| Events stored in insertion order | No additional index lookups needed |
| Requires additional index lookups | Better cache utilization |
Performance test results (100,000 events, 200 queries each):
Single Day Queries:
- Clustered: 0.000645s mean, 0.000000s median
- Conventional: 0.000951s mean, 0.000000s median
- 32.12% faster with clustered index
3-Day Range Queries:
- Clustered: 0.001014s mean, 0.000000s median
- Conventional: 0.001497s mean, 0.001510s median
- 32.27% faster with clustered index
7-Day Range Queries:
- Clustered: 0.002587s mean, 0.002003s median
- Conventional: 0.002959s mean, 0.002488s median
- 12.56% faster with clustered index
Event model:
class Event(Base):
__tablename__ = 'event'
start_date = Column(Date, nullable=False)
id = Column(Integer, nullable=True) # Nullable to allow ID generation after object creation
__table_args__ = (
PrimaryKeyConstraint('start_date', 'id'),
)ID generation and nullability:
- The
idcolumn is nullable to support a two-step object creation process:- Create event object (initially with null ID)
- Generate and assign ID based on the date
- This is safe because:
start_dateis always present (NOT NULL)idis only null during object creation- Final database state never has null values in the composite key
- SQLite treats NULL values as distinct in composite keys
- Clustering still works effectively because:
- Primary clustering is by
start_date(always present) - Secondary clustering by
idwithin each date - Temporary nullability doesn't affect query performance
- Primary clustering is by
Query optimization:
- Queries use the clustered index naturally
- No need for additional index hints
- Efficient for both exact date and range queries
Why this design is better:
- Disk I/O Efficiency — Related events are stored together; reduces disk seeks; better cache utilization
- Query Performance — Faster for common calendar queries; more consistent response times; better scalability with large datasets
- Maintenance — No need for additional indexes; simpler query plans; less index maintenance overhead
- Real-world Benefits — Faster calendar loading; better user experience; more efficient resource usage
The application implements a multi-level caching system:
-
Day-Based Caching: Complete day events (both non-recurring and expanded recurring events) are cached for 1 hour. Key format:
"2025-01-15"→ complete list of events for that day. Subsequent requests for the same date return cached results instantly, avoiding the computational overhead of database queries and recurrence rule expansion. -
Calendar Range Caching: Calendar widget requests (week/month views) cache the entire date range results. Key format:
"calendar_2025-01-01_2025-01-31". Particularly effective since users often navigate between adjacent weeks/months. -
Cache Invalidation: Cache is automatically cleared when events are created, modified, or deleted, ensuring data consistency.
-
Memory Efficiency: Uses TTL (Time To Live) of 1 hour with maximum size limits (1,000 day entries, 100 calendar entries) to prevent memory bloat.
Real-world performance impact: In typical usage patterns, 80-90% of all event requests are served from cache, making them nearly instantaneous. The 0.02 seconds metric represents the worst-case scenario for non-cached requests, while cached requests typically complete in under 0.001 seconds. Database queries and recurrence expansion are only performed for the first request to a date; subsequent requests reduce computational load by an order of magnitude.
Manage the cache via the /cache-management admin page.
To ensure the application remains fast even with a large number of events and recurring series:
- Clustered Indexing: The database uses a composite primary key
(start_date, id)so that events for the same date are stored together on disk. This makes range and day queries extremely efficient for non-recurring events. - Targeted Recurring Queries: Instead of expanding all recurring events, the backend only considers those whose recurrence could affect the requested date range, filtering on
start_date,is_recurring, andrecurring_untilwith proper indexes. - On-the-fly Expansion: Recurring events are expanded in memory only for the relevant date range, avoiding the need to store every instance in the database and keeping storage requirements low.
- Efficient Algorithms: The
dateutil.rrulelibrary is a robust, well-tested implementation of the iCalendar RRULE standard. Instead of writing custom code to interpret recurrence rules — error-prone for edge cases like leap years, daylight saving time, or complex BYDAY/BYMONTH rules —dateutil.rrulehandles this efficiently. It is written in C and Python, optimized for performance, and used in many production systems. - Indexing: An additional index on
(is_recurring, recurring_until)ensures that queries for recurring events are fast, even as the number of events grows.
These strategies ensure that the calendar remains highly performant, even with thousands of events and complex recurrence patterns.
For information about planned features and enhancements, see ROADMAP.md. The roadmap covers upcoming features, implementation details, priorities, and development guidance — including enhanced venue management, custom event fields, advanced filtering, map view, event duplication, API improvements, and video conferencing integrations.
All templates extend base.html (Bootstrap 5, FullCalendar 6, navigation, flash messages).
base.html (foundation)
├── widget_test.html (main interface - home & day views)
├── month.html (monthly calendar view)
├── event_form.html (event creation/editing)
├── venue_form.html (venue creation/editing)
├── venues.html (venue management list)
└── cache_management.html (cache administration)
| Template | Purpose | Routes |
|---|---|---|
base.html |
Layout, nav, shared assets | Extended by all templates |
widget_test.html |
Home page and day view with search + calendar | /, /day/<date>, /widget-test |
month.html |
Compact monthly calendar | /month/<year>/<month> |
event_form.html |
Create/edit events | /event/new, /event/<id>/edit |
venue_form.html |
Create/edit venues | /venue/new, /venue/<id>/edit |
venues.html |
Venue list | /venues |
cache_management.html |
Cache stats and admin | /cache-management |
- widget_test.html is the most complex template, handling both home and day views
- month.html uses aggressive CSS optimization for compact display
- cache_management.html provides real-time monitoring of the caching system
- All templates use Bootstrap for consistent, responsive design
- JavaScript is loaded at the end of the page for optimal loading performance