Create dedicated class to manage ics calendar files

This commit is contained in:
Antoine Bartuccio 2025-01-03 13:36:39 +01:00
parent 0a0f44607e
commit a60e1f1fdc
2 changed files with 52 additions and 25 deletions

View File

@ -1,15 +1,14 @@
from datetime import datetime, timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
import urllib3
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import Http404
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from ics import Calendar, Event from ics import Calendar, Event
from ninja_extra import ControllerBase, api_controller, route from ninja_extra import ControllerBase, api_controller, route
from com.models import NewsDate from com.models import IcsCalendar, NewsDate
from core.views.files import send_raw_file from core.views.files import send_raw_file
@ -19,28 +18,18 @@ class CalendarController(ControllerBase):
@route.get("/external.ics") @route.get("/external.ics")
def calendar_external(self): def calendar_external(self):
file = self.CACHE_FOLDER / "external.ics" """Return the ICS file of the AE Google Calendar
# Return cached file if updated less than an our ago
if (
file.exists()
and timezone.make_aware(datetime.fromtimestamp(file.stat().st_mtime))
+ timedelta(hours=1)
> timezone.now()
):
return send_raw_file(file)
calendar = urllib3.request( Because of Google's cors rules, we can't "just" do a request to google ics
"GET", from the frontend. Google is blocking CORS request in it's responses headers.
"https://calendar.google.com/calendar/ical/ae.utbm%40gmail.com/public/basic.ics", The only way to do it from the frontend is to use Google Calendar API with an API key
) This is not especially desirable as your API key is going to be provided to the frontend.
if calendar.status != 200:
return HttpResponse(status=calendar.status)
self.CACHE_FOLDER.mkdir(parents=True, exist_ok=True) This is why we have this backend based solution.
with open(file, "wb") as f: """
_ = f.write(calendar.data) if (calendar := IcsCalendar.get_external()) is not None:
return send_raw_file(calendar)
return send_raw_file(file) raise Http404
@route.get("/internal.ics") @route.get("/internal.ics")
def calendar_internal(self): def calendar_internal(self):

View File

@ -17,11 +17,16 @@
# details. # details.
# #
# You should have received a copy of the GNU General Public License along with # You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple # this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from datetime import datetime, timedelta
from pathlib import Path
from typing import final
import urllib3
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
@ -37,6 +42,39 @@ from club.models import Club
from core.models import Notification, Preferences, User from core.models import Notification, Preferences, User
@final
class IcsCalendar:
_CACHE_FOLDER: Path = settings.MEDIA_ROOT / "com" / "calendars"
_EXTERNAL_CALENDAR = _CACHE_FOLDER / "external.ics"
@classmethod
def get_external(cls, expiration: timedelta = timedelta(hours=1)) -> Path | None:
if (
cls._EXTERNAL_CALENDAR.exists()
and timezone.make_aware(
datetime.fromtimestamp(cls._EXTERNAL_CALENDAR.stat().st_mtime)
)
+ expiration
> timezone.now()
):
return cls._EXTERNAL_CALENDAR
return cls.make_external()
@classmethod
def make_external(cls) -> Path | None:
calendar = urllib3.request(
"GET",
"https://calendar.google.com/calendar/ical/ae.utbm%40gmail.com/public/basic.ics",
)
if calendar.status != 200:
return None
cls._CACHE_FOLDER.mkdir(parents=True, exist_ok=True)
with open(cls._EXTERNAL_CALENDAR, "wb") as f:
_ = f.write(calendar.data)
return cls._EXTERNAL_CALENDAR
class Sith(models.Model): class Sith(models.Model):
"""A one instance class storing all the modifiable infos.""" """A one instance class storing all the modifiable infos."""