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!
129 lines
4.7 KiB
Python
129 lines
4.7 KiB
Python
from app import app
|
|
from datetime import datetime, timedelta, timezone
|
|
import os
|
|
import caldav
|
|
import datetime
|
|
from icalendar import cal, Event
|
|
from flask import render_template, url_for, send_file, send_from_directory
|
|
from .models import Event
|
|
import requests
|
|
import imgkit
|
|
from PIL import Image, ImageOps
|
|
import io
|
|
caldav_url = os.getenv('caldav_url')
|
|
username = os.getenv('username')
|
|
password = os.getenv('password')
|
|
cal_id = os.getenv('cal_id')
|
|
from .weather import weather
|
|
|
|
def remove_emoji(string):
|
|
import re
|
|
emoji_pattern = re.compile("["
|
|
u"\U0001F600-\U0001F64F" # emoticons
|
|
u"\U0001F300-\U0001F5FF" # symbols & pictographs
|
|
u"\U0001F680-\U0001F6FF" # transport & map symbols
|
|
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
|
|
u"\U00002500-\U00002BEF" # chinese char
|
|
u"\U00002702-\U000027B0"
|
|
u"\U00002702-\U000027B0"
|
|
u"\U000024C2-\U0001F251"
|
|
u"\U0001f926-\U0001f937"
|
|
u"\U00010000-\U0010ffff"
|
|
u"\u2640-\u2642"
|
|
u"\u2600-\u2B55"
|
|
u"\u200d"
|
|
u"\u23cf"
|
|
u"\u23e9"
|
|
u"\u231a"
|
|
u"\ufe0f" # dingbats
|
|
u"\u3030"
|
|
"]+", flags=re.UNICODE)
|
|
return emoji_pattern.sub(r'', string)
|
|
|
|
def daterange(start_date, end_date):
|
|
for n in range(int((end_date - start_date).days)):
|
|
yield datetime.datetime.date(start_date + timedelta(n))
|
|
|
|
@app.route('/')
|
|
def dashboard():
|
|
today = datetime.datetime.now(tz=datetime.datetime.now(timezone.utc).astimezone().tzinfo)
|
|
# today = datetime.datetime(2022,6,5, tzinfo=datetime.datetime.now(timezone.utc).astimezone().tzinfo)
|
|
start_of_week = today - timedelta(days=(today.weekday()+1)) # Monday
|
|
end_of_week = start_of_week + timedelta(days=8) # Sunday
|
|
|
|
events = []
|
|
|
|
for url in [
|
|
'https://www.calendarlabs.com/ical-calendar/ics/76/US_Holidays.ics',
|
|
]:
|
|
r = requests.get(url)
|
|
c = cal.Calendar.from_ical(r.content)
|
|
|
|
events += Event.fromIcalendar(c)
|
|
|
|
with caldav.DAVClient(url=caldav_url, username=username, password=password) as client:
|
|
my_principal = client.principal()
|
|
|
|
calendars = my_principal.calendars()
|
|
for id in [cal_id]:
|
|
calendar = my_principal.calendar(cal_id=id)
|
|
events += Event.fromCalDavEvents(calendar.date_search(
|
|
start=start_of_week, end=end_of_week, expand=False))
|
|
|
|
for url in [
|
|
'http://ical-cdn.teamsnap.com/team_schedule/5f1ddc9e-15b0-4912-84a2-11cc70e9e375.ics'
|
|
]:
|
|
r = requests.get(url)
|
|
c = cal.Calendar.from_ical(r.content)
|
|
|
|
events += Event.fromIcalendar(c)
|
|
|
|
days = []
|
|
for single_date in daterange(start_of_week, end_of_week):
|
|
days_events = []
|
|
for e in events:
|
|
if (e.dtstart.date() <= single_date <= e.dtend.date()):
|
|
e.summary = remove_emoji(e.summary)
|
|
days_events.append(e)
|
|
|
|
days.append((single_date, days_events))
|
|
|
|
# breakpoint()
|
|
pass
|
|
# r = "<br>".join([event.vobject_instance.vevent.summary.value for event in events_fetched if event.vobject_instance.vevent.dtstart.value < datetime.now()])
|
|
return render_template("dashboard_static.html",
|
|
days=days,
|
|
today=today,
|
|
weather=weather()
|
|
)
|
|
|
|
@app.route('/image')
|
|
def dashboard_image():
|
|
page = dashboard()
|
|
out_file = os.path.join(os.path.dirname(__file__),
|
|
'static', "out.bmp"
|
|
)
|
|
|
|
s = imgkit.from_string(
|
|
page,
|
|
out_file,
|
|
options={
|
|
|
|
'width':600,
|
|
'height':800,
|
|
"disable-smart-width": "",
|
|
'enable-local-file-access': "",
|
|
'allow': os.path.join(os.path.dirname(__file__),
|
|
'static',
|
|
)
|
|
},
|
|
css=os.path.join(os.path.dirname(__file__),
|
|
'static',
|
|
'style.css'
|
|
)
|
|
)
|
|
image_file = Image.open(out_file) # open colour image
|
|
image_file = ImageOps.grayscale(image_file)
|
|
image_file.save(out_file, 'BMP')
|
|
return send_from_directory(directory = 'static', path='out.bmp', as_attachment=False, attachment_filename='out.bmp')
|