487 lines
		
	
	
		
			No EOL
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			487 lines
		
	
	
		
			No EOL
		
	
	
		
			19 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Demo resource for the OpenEventDatabase.
 | |
| This module imports and re-exports the demo resources from the demo package.
 | |
| """
 | |
| 
 | |
| import falcon
 | |
| import requests
 | |
| import json
 | |
| import os
 | |
| from collections import defaultdict
 | |
| from oedb.utils.logging import logger
 | |
| from oedb.utils.db import load_env_from_file
 | |
| from oedb.resources.demo import demo_main, demo_traffic, demo_view_events
 | |
| 
 | |
| class DemoResource:
 | |
|     """
 | |
|     Resource for the demo endpoint.
 | |
|     Handles the /demo endpoint and related demo pages.
 | |
|     """
 | |
|     
 | |
|     def __init__(self):
 | |
|         """
 | |
|         Initialize the resource with a Jinja2 environment.
 | |
|         """
 | |
|         # Set up Jinja2 environment
 | |
|         import jinja2
 | |
|         import os
 | |
|         template_dir = os.path.join(os.path.dirname(__file__), 'demo', 'templates')
 | |
|         self.jinja_env = jinja2.Environment(
 | |
|             loader=jinja2.FileSystemLoader(template_dir),
 | |
|             autoescape=jinja2.select_autoescape(['html', 'xml'])
 | |
|         )
 | |
|     
 | |
|     def on_get_edit(self, req, resp, id=None):
 | |
|         """
 | |
|         Handle GET requests to the /demo/edit endpoint.
 | |
|         Returns an HTML page with a form for editing an existing event.
 | |
|         
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|             id: The event ID to edit.
 | |
|         """
 | |
|         logger.info(f"Processing GET request to /demo/edit for event ID: {id}")
 | |
|         
 | |
|         if id is None:
 | |
|             resp.status = falcon.HTTP_400
 | |
|             resp.text = "Event ID is required"
 | |
|             return
 | |
|         
 | |
|         try:
 | |
|             # Set content type to HTML
 | |
|             resp.content_type = 'text/html'
 | |
|             
 | |
|             # Fetch the event data from the OEDB API
 | |
|             logger.info(f"Fetching event data from API for event ID: {id}")
 | |
|             response = requests.get(f'https://api.openeventdatabase.org/event/{id}')
 | |
| 
 | |
|             if response.status_code != 200:
 | |
|                 logger.error(f"API returned status {response.status_code} for event {id}")
 | |
|                 resp.status = falcon.HTTP_404
 | |
|                 resp.text = f"Event with ID {id} not found on API"
 | |
|                 return
 | |
| 
 | |
|             event_data = response.json()
 | |
|             logger.info(f"Successfully retrieved event data for {id}: {type(event_data)}")
 | |
| 
 | |
|             # Validate event data structure
 | |
|             if not isinstance(event_data, dict):
 | |
|                 logger.error(f"Invalid event data type: {type(event_data)}")
 | |
|                 resp.status = falcon.HTTP_500
 | |
|                 resp.text = "Invalid event data format received from API"
 | |
|                 return
 | |
| 
 | |
|             if 'properties' not in event_data:
 | |
|                 logger.error(f"Event data missing 'properties': {event_data}")
 | |
|                 resp.status = falcon.HTTP_500
 | |
|                 resp.text = "Invalid event data structure - missing properties"
 | |
|                 return
 | |
| 
 | |
|             # Render the template with the event data
 | |
|             template = self.jinja_env.get_template('edit.html')
 | |
|             html = template.render(
 | |
|                 id=id,
 | |
|                 event_data=event_data  # Pass Python object directly, let Jinja2 handle JSON conversion
 | |
|             )
 | |
| 
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success(f"Successfully processed GET request to /demo/edit for event ID: {id}")
 | |
|         except requests.RequestException as e:
 | |
|             logger.error(f"Error fetching event data from API: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error fetching event data: {str(e)}"
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/edit: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
|     
 | |
|     def on_get(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo endpoint.
 | |
|         Delegates to the demo_main resource.
 | |
|         
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         return demo_main.on_get(req, resp)
 | |
|     
 | |
|     def on_get_by_what(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo/by-what endpoint.
 | |
|         Returns an HTML page with links to events organized by their "what" type.
 | |
|         
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         logger.info("Processing GET request to /demo/by-what")
 | |
|         
 | |
|         try:
 | |
|             # Set content type to HTML
 | |
|             resp.content_type = 'text/html'
 | |
|             
 | |
|             # Fetch events from the API
 | |
|             try:
 | |
|                 response = requests.get('/event?limit=5000')
 | |
|                 if response.status_code == 200 and response.text:
 | |
|                     events_data = response.json()
 | |
|                 else:
 | |
|                     logger.error(f"Error fetching events: Status code {response.status_code}, Response: {response.text}")
 | |
|                     events_data = {"features": []}
 | |
|             except json.JSONDecodeError as e:
 | |
|                 logger.error(f"Error parsing JSON response: {e}")
 | |
|                 events_data = {"features": []}
 | |
|             except Exception as e:
 | |
|                 logger.error(f"Error fetching events: {e}")
 | |
|                 events_data = {"features": []}
 | |
|             
 | |
|             # Group events by "what" type
 | |
|             events_by_what = defaultdict(list)
 | |
|             
 | |
|             if events_data.get('features'):
 | |
|                 for feature in events_data['features']:
 | |
|                     properties = feature.get('properties', {})
 | |
|                     what = properties.get('what', 'Unknown')
 | |
|                     events_by_what[what].append({
 | |
|                         'id': properties.get('id'),
 | |
|                         'label': properties.get('label', 'Unnamed Event'),
 | |
|                         'coordinates': feature.get('geometry', {}).get('coordinates', [0, 0])
 | |
|                     })
 | |
|             
 | |
|             # Load and render the template with the appropriate variables
 | |
|             template = self.jinja_env.get_template('by_what.html')
 | |
|             
 | |
|             # Sort event types alphabetically if we have events
 | |
|             sorted_what_types = sorted(events_by_what.keys()) if events_by_what else []
 | |
|             
 | |
|             html = template.render(
 | |
|                 events_by_what=events_by_what,
 | |
|                 sorted_what_types=sorted_what_types
 | |
|             )
 | |
|             
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success("Successfully processed GET request to /demo/by-what")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/by-what: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
|         
 | |
|     def on_get_search(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo/search endpoint.
 | |
|         Returns an HTML page with a form for searching events and displaying results.
 | |
|         
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         logger.info("Processing GET request to /demo/search")
 | |
|         
 | |
|         try:
 | |
|             # Set content type to HTML
 | |
|             resp.content_type = 'text/html'
 | |
|             
 | |
|             # Render the template
 | |
|             template = self.jinja_env.get_template('search.html')
 | |
|             html = template.render()
 | |
|             
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success("Successfully processed GET request to /demo/search")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/search: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
|     
 | |
|     def on_get_map_by_what(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo/map-by-what endpoint.
 | |
|         Returns an HTML page with a MapLibre map showing events filtered by "what" type.
 | |
|         
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         logger.info("Processing GET request to /demo/map-by-what")
 | |
|         
 | |
|         try:
 | |
|             # Set content type to HTML
 | |
|             resp.content_type = 'text/html'
 | |
|             
 | |
|             # Render the template
 | |
|             template = self.jinja_env.get_template('map_by_what.html')
 | |
|             html = template.render()
 | |
|             
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success("Successfully processed GET request to /demo/map-by-what")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/map-by-what: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
| 
 | |
|     def on_get_map_by_what_type(self, req, resp, event_type):
 | |
|         """
 | |
|         Handle GET requests to the /demo/map-by-what/{type} endpoint.
 | |
|         Returns an HTML page with a MapLibre map showing events of a specific type,
 | |
|         colored by temporality (past/present/future) with a detailed table.
 | |
| 
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|             event_type: The event type to filter by.
 | |
|         """
 | |
|         logger.info(f"Processing GET request to /demo/map-by-what/{event_type}")
 | |
| 
 | |
|         try:
 | |
|             # Set content type to HTML
 | |
|             resp.content_type = 'text/html'
 | |
| 
 | |
|             # Render the template with the event type
 | |
|             template = self.jinja_env.get_template('map_by_what_type.html')
 | |
|             html = template.render(
 | |
|                 event_type=event_type,
 | |
|                 event_type_label=event_type.replace('_', ' ').title()
 | |
|             )
 | |
| 
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success(f"Successfully processed GET request to /demo/map-by-what/{event_type}")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/map-by-what/{event_type}: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
|             events_by_what = defaultdict(list)
 | |
|             
 | |
|             if events_data.get('features'):
 | |
|                 for feature in events_data['features']:
 | |
|                     properties = feature.get('properties', {})
 | |
|                     what = properties.get('what', 'Unknown')
 | |
|                     events_by_what[what].append({
 | |
|                         'id': properties.get('id'),
 | |
|                         'label': properties.get('label', 'Unnamed Event'),
 | |
|                         'coordinates': feature.get('geometry', {}).get('coordinates', [0, 0])
 | |
|                     })
 | |
|             
 | |
|             # Create HTML response
 | |
|             html = """
 | |
|             <!DOCTYPE html>
 | |
|             <html lang="en">
 | |
|             <head>
 | |
|                 <meta charset="UTF-8">
 | |
|                 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | |
|                 <title>Events by Type - OpenEventDatabase</title>
 | |
|                 <style>
 | |
|                     body { 
 | |
|                         font-family: Arial, sans-serif; 
 | |
|                         line-height: 1.6;
 | |
|                         max-width: 1200px;
 | |
|                         margin: 0 auto;
 | |
|                         padding: 20px;
 | |
|                     }
 | |
|                     h1 { color: #333; }
 | |
|                     h2 { 
 | |
|                         color: #0078ff; 
 | |
|                         margin-top: 30px;
 | |
|                         padding-bottom: 5px;
 | |
|                         border-bottom: 1px solid #eee;
 | |
|                     }
 | |
|                     ul { padding-left: 20px; }
 | |
|                     li { margin-bottom: 8px; }
 | |
|                     a { color: #0078ff; text-decoration: none; }
 | |
|                     a:hover { text-decoration: underline; }
 | |
|                     .nav { 
 | |
|                         background-color: #f5f5f5;
 | |
|                         padding: 10px;
 | |
|                         border-radius: 5px;
 | |
|                         margin-bottom: 20px;
 | |
|                     }
 | |
|                     .nav a {
 | |
|                         margin-right: 15px;
 | |
|                     }
 | |
|                     .event-count {
 | |
|                         color: #666;
 | |
|                         font-size: 0.9em;
 | |
|                     }
 | |
|                 </style>
 | |
|             </head>
 | |
|             <body>
 | |
|                 <div class="nav">
 | |
|                     <a href="/">Home</a>
 | |
|                     <a href="/demo">Demo Map</a>
 | |
|                     <a href="/demo/map-by-what">Map by Event Type</a>
 | |
|                 </div>
 | |
|                 
 | |
|                 <h1>Events by Type</h1>
 | |
|                 <p>This page lists all events from the OpenEventDatabase organized by their type.</p>
 | |
|             """
 | |
|             
 | |
|             # Add event types and their events
 | |
|             if events_by_what:
 | |
|                 # Sort event types alphabetically
 | |
|                 sorted_what_types = sorted(events_by_what.keys())
 | |
|                 
 | |
|                 # Add quick navigation
 | |
|                 html += "<h2>Quick Navigation</h2><ul>"
 | |
|                 for what_type in sorted_what_types:
 | |
|                     event_count = len(events_by_what[what_type])
 | |
|                     html += f'<li><a href="#what-{what_type.replace(" ", "-")}">{what_type}</a> <span class="event-count">({event_count} events)</span></li>'
 | |
|                 html += "</ul>"
 | |
|                 
 | |
|                 # Add sections for each event type
 | |
|                 for what_type in sorted_what_types:
 | |
|                     events = events_by_what[what_type]
 | |
|                     html += f'<h2 id="what-{what_type.replace(" ", "-")}">{what_type} <span class="event-count">({len(events)} events)</span></h2>'
 | |
|                     html += "<ul>"
 | |
|                     
 | |
|                     # Sort events by label
 | |
|                     sorted_events = sorted(events, key=lambda x: x.get('label', ''))
 | |
|                     
 | |
|                     for event in sorted_events:
 | |
|                         event_id = event.get('id')
 | |
|                         event_label = event.get('label', 'Unnamed Event')
 | |
|                         coordinates = event.get('coordinates', [0, 0])
 | |
|                         
 | |
|                         html += f'<li><a href="/event/{event_id}" >{event_label}</a> '
 | |
|                         html += f'<small>[<a href="https://www.openstreetmap.org/?mlat={coordinates[1]}&mlon={coordinates[0]}&zoom=15" >map</a>]</small></li>'
 | |
|                     
 | |
|                     html += "</ul>"
 | |
|             else:
 | |
|                 html += "<p>No events found in the database.</p>"
 | |
|             
 | |
|             html += """
 | |
|             </body>
 | |
|             </html>
 | |
|             """
 | |
|             
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success("Successfully processed GET request to /demo/by-what")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/by-what: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
| 
 | |
|     def on_get_traffic(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo/traffic endpoint.
 | |
|         Delegates to the demo_traffic resource.
 | |
|         
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         return demo_traffic.on_get(req, resp)
 | |
|     
 | |
|     def on_get_view_events(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo/view-events endpoint.
 | |
|         Delegates to the demo_view_events resource.
 | |
| 
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         return demo_view_events.on_get(req, resp)
 | |
| 
 | |
|     def on_get_by_id(self, req, resp, id):
 | |
|         """
 | |
|         Handle GET requests to /demo/by_id/{id}.
 | |
|         Show a map with the event location and a table of its properties.
 | |
|         """
 | |
|         import requests
 | |
|         import json
 | |
|         logger.info(f"Processing GET request to /demo/by_id/{id}")
 | |
|         try:
 | |
|             resp.content_type = 'text/html'
 | |
|             r = requests.get(f"https://api.openeventdatabase.org/event/{id}")
 | |
|             r.raise_for_status()
 | |
|             feature = r.json()
 | |
|             
 | |
|             # Load and render the template
 | |
|             template = self.jinja_env.get_template('event_details.html')
 | |
|             html = template.render(
 | |
|                 id=id,
 | |
|                 feature_json=json.dumps(feature),
 | |
|                 properties=feature.get('properties') or {}
 | |
|             )
 | |
|             
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success(f"Successfully processed GET request to /demo/by_id/{id}")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/by_id/{id}: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
| 
 | |
|     def on_get_property_stats(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo/property-stats endpoint.
 | |
|         Returns an HTML page with statistics about property occurrences in the last 200 events.
 | |
| 
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         logger.info("Processing GET request to /demo/property-stats")
 | |
| 
 | |
|         try:
 | |
|             # Set content type to HTML
 | |
|             resp.content_type = 'text/html'
 | |
| 
 | |
|             # Fetch the last 200 events from the API
 | |
|             try:
 | |
|                 response = requests.get('https://api.openeventdatabase.org/event?limit=200')
 | |
|                 if response.status_code == 200 and response.text:
 | |
|                     events_data = response.json()
 | |
|                 else:
 | |
|                     logger.error(f"Error fetching events: Status code {response.status_code}")
 | |
|                     events_data = {"features": []}
 | |
|             except Exception as e:
 | |
|                 logger.error(f"Error fetching events: {e}")
 | |
|                 events_data = {"features": []}
 | |
| 
 | |
|             # Count property occurrences
 | |
|             property_counts = {}
 | |
|             total_events = len(events_data.get('features', []))
 | |
| 
 | |
|             for feature in events_data.get('features', []):
 | |
|                 properties = feature.get('properties', {})
 | |
|                 for prop_name in properties.keys():
 | |
|                     if prop_name in property_counts:
 | |
|                         property_counts[prop_name] += 1
 | |
|                     else:
 | |
|                         property_counts[prop_name] = 1
 | |
| 
 | |
|             # Sort properties by occurrence count (descending order)
 | |
|             sorted_properties = sorted(property_counts.items(), key=lambda x: x[1], reverse=True)
 | |
| 
 | |
|             # Render the template
 | |
|             template = self.jinja_env.get_template('property_stats.html')
 | |
|             html = template.render(
 | |
|                 property_stats=sorted_properties,
 | |
|                 total_events=total_events,
 | |
|                 unique_properties=len(sorted_properties)
 | |
|             )
 | |
| 
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success("Successfully processed GET request to /demo/property-stats")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/property-stats: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
| 
 | |
| # Create a global instance of DemoResource
 | |
| demo = DemoResource() | 
