diff --git a/reservations/migrations/0002_externalreservation.py b/reservations/migrations/0002_externalreservation.py new file mode 100644 index 0000000..9c0f2d2 --- /dev/null +++ b/reservations/migrations/0002_externalreservation.py @@ -0,0 +1,31 @@ +# Generated by Django 5.0.7 on 2024-08-06 17:17 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("reservations", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="ExternalReservation", + fields=[ + ( + "reservation_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="reservations.reservation", + ), + ), + ("title", models.CharField(max_length=1024)), + ], + bases=("reservations.reservation",), + ), + ] diff --git a/reservations/models.py b/reservations/models.py index 06cb9a2..40d58d7 100644 --- a/reservations/models.py +++ b/reservations/models.py @@ -132,3 +132,18 @@ class UserReservation(Reservation): return super().make_google_calendar_event() | { "summary": str(self.user), } + + +class ExternalReservation(Reservation): + """Reservations created by something else in Google Calendar""" + + title = models.CharField(max_length=1024) + + def __str__(self) -> str: + return f'External "{self.title}" | {super().__str__()}' + + def make_google_calendar_event(self): + """This should never be called, as these are reservations from Google Calendar and shouldn't be synced back""" + raise AttributeError( + "External Reservations should not be pushed back to Google Calendar" + ) diff --git a/reservations/tasks/sync_google_calendar.py b/reservations/tasks/sync_google_calendar.py index 8622b3a..6af498d 100644 --- a/reservations/tasks/sync_google_calendar.py +++ b/reservations/tasks/sync_google_calendar.py @@ -10,7 +10,7 @@ from googleapiclient.discovery import build from googleapiclient.errors import HttpError from cmsmanage.django_q2_helper import q_task_group -from reservations.models import Reservation, Resource +from reservations.models import ExternalReservation, Reservation, Resource logger = logging.getLogger(__name__) @@ -110,12 +110,21 @@ def sync_resource_from_google_calendar( sendUpdates="none", ).execute() else: - # TODO: handle external events (either Bookly or manually created) - # NOTE: this will also need to check for deleted events logger.debug( - "Event in Google Calendar not originated by CMSManage: skipping for now | %s", + "Event in Google Calendar not originated by CMSManage: adding/updating as external reservation | %s", event["id"], ) + # TODO: this might cause issues if something external + # creates events with matching IDs in different calendars + reservation, created = ExternalReservation.objects.update_or_create( + google_calendar_event_id=event["id"], + defaults={ + "title": event["summary"], + "start": parse_google_calendar_datetime(event["start"]), + "end": parse_google_calendar_datetime(event["end"]), + }, + ) + reservation.resources.add(resource) return {event["id"] for event in events} @@ -135,32 +144,39 @@ def sync_resource_from_database( # reservation has an event id, so check if we already handled it earlier elif reservation.google_calendar_event_id not in existing_event_ids: - # this event was in Google Calendar at some point (possibly for a different - # resource/calendar), but did not appear in list(). Try to update it, then - # fall back to insert - logger.info( - "Reservation with event id not in Google Calendar: trying update | %s", - reservation.google_calendar_event_id, - ) - try: - event = ( - service.events() - .get( - calendarId=resource.google_calendar, - eventId=reservation.google_calendar_event_id, - ) - .execute() + if isinstance(reservation, ExternalReservation): + logger.info( + "External event in database did not exist in future of Google Calendar: deleting locally | %s", + reservation.google_calendar_event_id, ) - update_calendar_event(service, resource, event, reservation) - except HttpError as error: - if error.status_code == HTTPStatus.NOT_FOUND: - logger.info( - "Event in database not in Google Calendar: inserting | %s", - reservation.google_calendar_event_id, + reservation.delete() + else: + # this event was in Google Calendar at some point (possibly for a different + # resource/calendar), but did not appear in list(). Try to update it, then + # fall back to insert + logger.info( + "Reservation with event id not in Google Calendar: trying update | %s", + reservation.google_calendar_event_id, + ) + try: + event = ( + service.events() + .get( + calendarId=resource.google_calendar, + eventId=reservation.google_calendar_event_id, + ) + .execute() ) - insert_calendar_event(service, resource, reservation) - else: - raise + update_calendar_event(service, resource, event, reservation) + except HttpError as error: + if error.status_code == HTTPStatus.NOT_FOUND: + logger.info( + "Event in database not in Google Calendar: inserting | %s", + reservation.google_calendar_event_id, + ) + insert_calendar_event(service, resource, reservation) + else: + raise def sync_resource(service, resource: Resource, now: datetime):