Commit Message: feat: Implement web calendar application with Flask, Docker, and calendar integration Description: 1. Server Refactor: • Moved application logic from main.py to a structured directory server/app/. • Added server/app/__init__.py for Flask app initialization. • Introduced server/app/views.py to handle routes (dashboard and dashboard_image). • Created server/app/models.py for event modeling, supporting CalDAV and iCalendar events. • Added server/app/weather.py to fetch weather data using OpenWeatherMap API. 2. New Features: • Added an image generation route (/image) to render calendar views as BMP images. • Integrated OpenWeatherMap API for weather data on the dashboard. 3. Environment and Configurations: • Added a Dockerfile to build and deploy the app using uwsgi-nginx-flask. • Introduced compose.yml for running the app with Docker Compose. • Moved uwsgi.ini configuration to server/uwsgi.ini for modular organization. 4. Dependencies: • Updated requirements.txt to include new dependencies: imgkit, pillow, and Werkzeug==2.2.2. 5. Static Assets: • Added placeholder images out.png and test.png. 6. Code Cleanup: • Removed old files (main.py and root-level uwsgi.ini). • Updated .gitignore to include .idea/ folder. Additional Notes: • Enhanced event parsing to handle all-day and time-specific events using server/app/models.py. • Utilized Flask’s render_template for dynamic HTML rendering and imgkit for HTML-to-image conversion. • Integrated multiple calendar sources (CalDAV and public iCal feeds). Let me know if you need further adjustments!
99 lines
3.5 KiB
Python
99 lines
3.5 KiB
Python
from datetime import datetime, date, timedelta, timezone
|
|
from caldav.objects import Event as CaldavEvent
|
|
import dataclasses
|
|
|
|
from icalendar import Event as IcEvent
|
|
from icalendar import Calendar as Icalendar
|
|
|
|
@dataclasses.dataclass
|
|
class Event():
|
|
summary: str
|
|
dtstart: datetime
|
|
dtend: datetime
|
|
is_all_day: bool
|
|
|
|
@classmethod
|
|
def fromCalDavEvents(cls, caldav_events: [CaldavEvent]) -> list['Event']:
|
|
|
|
events = []
|
|
|
|
for event in caldav_events:
|
|
date_value = event.vobject_instance.vevent.dtstart.value
|
|
if isinstance(date_value, datetime):
|
|
dt_start = date_value
|
|
is_all_day = False
|
|
elif isinstance(date_value, date):
|
|
d_start = date_value
|
|
dt_start = datetime(d_start.year, d_start.month, d_start.day, tzinfo=datetime.now(timezone.utc).astimezone().tzinfo)
|
|
is_all_day = True
|
|
else:
|
|
raise Exception
|
|
|
|
date_value = event.vobject_instance.vevent.dtend.value
|
|
if isinstance(date_value, datetime):
|
|
dt_end = date_value
|
|
elif isinstance(date_value, date):
|
|
d_end = date_value - timedelta(days=1)
|
|
dt_end = datetime(d_end.year, d_end.month, d_end.day,tzinfo=datetime.now(timezone.utc).astimezone().tzinfo)
|
|
else:
|
|
raise Exception
|
|
|
|
events.append(Event(
|
|
summary=event.vobject_instance.vevent.summary.value,
|
|
dtstart=dt_start,
|
|
dtend=dt_end,
|
|
is_all_day = is_all_day
|
|
))
|
|
return events
|
|
|
|
@classmethod
|
|
def fromIcalendar(cls, icalendar: Icalendar) -> list['Event']:
|
|
events = []
|
|
for event in [e for e in icalendar.subcomponents if isinstance(e, IcEvent)]:
|
|
date_value = event['DTSTART'].dt
|
|
if isinstance(date_value, datetime):
|
|
dt_start = date_value
|
|
is_all_day = False
|
|
elif isinstance(date_value, date):
|
|
d_start = date_value
|
|
dt_start = datetime(d_start.year, d_start.month, d_start.day, tzinfo=datetime.now(timezone.utc).astimezone().tzinfo)
|
|
is_all_day = True
|
|
else:
|
|
raise Exception
|
|
|
|
date_value = event['DTEND'].dt if event.get('DTEND') else event['DTSTART'].dt
|
|
if isinstance(date_value, datetime):
|
|
dt_end = date_value
|
|
elif isinstance(date_value, date):
|
|
d_end = date_value
|
|
dt_end = datetime(d_end.year, d_end.month, d_end.day,tzinfo=datetime.now(timezone.utc).astimezone().tzinfo)
|
|
else:
|
|
raise Exception
|
|
|
|
events.append(Event(
|
|
summary=event['summary'],
|
|
dtstart=dt_start,
|
|
dtend=dt_end,
|
|
is_all_day = is_all_day
|
|
))
|
|
|
|
return events
|
|
|
|
@property
|
|
def range_str(self) -> str:
|
|
start_time_str = self.dtstart.strftime('%-I')
|
|
end_time_str = self.dtend.strftime('%-I')
|
|
|
|
if not(self.dtstart.hour and self.dtend.hour):
|
|
return ""
|
|
|
|
if self.dtstart.minute: start_time_str += self.dtstart.strftime(':%M')
|
|
if self.dtend.minute: end_time_str += self.dtend.strftime(':%M')
|
|
|
|
if not ((self.dtstart.hour < 12 and self.dtend.hour < 12) or (self.dtstart.hour > 12 and self.dtend.hour > 12)):
|
|
start_time_str += self.dtstart.strftime("%p")
|
|
|
|
end_time_str += self.dtend.strftime("%p")
|
|
|
|
return f"{start_time_str}-{end_time_str}"
|