-
-
Notifications
You must be signed in to change notification settings - Fork 271
Implement calendar events scheduling #2211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6b88721
fa4d306
813f956
81739f1
132daa8
65dd9ef
a2be0b9
eb65838
cfa7c70
b472521
92b36f4
7df13b9
1b4a4c4
419d915
7d9cee1
a68f08f
368b4bc
3ad20fc
3793188
98e938e
26cb33c
e56f225
728e434
b629dcc
a5c9f46
fc6b908
b1dc6c0
3f83f9f
44c101f
a54a559
711db4c
61f76dd
65d1d4c
870ee45
a35d73f
a8443b5
2f2f1e8
5f8d7da
8d5b0eb
e53d256
0d6b30e
f09d0af
8c8fdcd
7d9256c
7b85dd3
874667b
f030677
1764fa9
d90f611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| """Google Calendar API Client.""" | ||
|
|
||
| from django.utils import timezone | ||
| from googleapiclient.discovery import build | ||
|
|
||
| from apps.nest.models.google_account_authorization import GoogleAccountAuthorization | ||
|
|
||
ahmedxgouda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| class GoogleCalendarClient: | ||
| """Google Calendar API Client Class.""" | ||
|
|
||
| def __init__(self, google_account_authorization: GoogleAccountAuthorization): | ||
| """Initialize the Google Calendar API Client.""" | ||
| self.google_account_authorization = google_account_authorization | ||
| self.service = build( | ||
| "calendar", "v3", credentials=google_account_authorization.credentials | ||
| ) | ||
ahmedxgouda marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def get_events(self, min_time=None, max_time=None) -> list[dict]: | ||
| """Retrieve events from Google Calendar.""" | ||
| if not min_time: | ||
| min_time = timezone.now() | ||
| if not max_time: | ||
| max_time = min_time + timezone.timedelta(days=1) | ||
| events_result = ( | ||
| self.service.events() | ||
| .list( | ||
| calendarId="primary", | ||
| timeMin=min_time.isoformat(), | ||
| timeMax=max_time.isoformat(), | ||
| singleEvents=True, | ||
| orderBy="startTime", | ||
| ) | ||
| .execute() | ||
| ) | ||
| return events_result.get("items", []) | ||
|
|
||
| def get_event(self, google_calendar_id: str) -> dict: | ||
| """Retrieve a specific event from Google Calendar.""" | ||
| return ( | ||
| self.service.events().get(calendarId="primary", eventId=google_calendar_id).execute() | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| """Handlers for Calendar Events.""" | ||
|
|
||
| from django.core.cache import cache | ||
| from django.core.exceptions import ValidationError | ||
| from django.db import transaction | ||
| from django.utils import timezone | ||
|
|
||
| from apps.nest.clients.google_calendar import GoogleCalendarClient | ||
| from apps.nest.models.google_account_authorization import GoogleAccountAuthorization | ||
| from apps.nest.models.reminder import Reminder | ||
| from apps.nest.models.reminder_schedule import ReminderSchedule | ||
| from apps.owasp.models.event import Event | ||
| from apps.slack.models.member import Member | ||
|
|
||
|
|
||
| def schedule_reminder( | ||
| reminder: Reminder, | ||
| scheduled_time: timezone.datetime, | ||
| recurrence=ReminderSchedule.Recurrence.ONCE, | ||
| ) -> ReminderSchedule: | ||
| """Schedule a reminder.""" | ||
| if scheduled_time < timezone.now(): | ||
| message = "Scheduled time must be in the future." | ||
| raise ValidationError(message) | ||
| if recurrence not in ReminderSchedule.Recurrence.values: | ||
| message = "Invalid recurrence value." | ||
| raise ValidationError(message) | ||
| return ReminderSchedule.objects.create( | ||
| reminder=reminder, | ||
| scheduled_time=scheduled_time, | ||
| recurrence=recurrence, | ||
| ) | ||
|
Comment on lines
+16
to
+32
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Your single piece of text functions are still hard to read.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe adding comments will improve this? |
||
|
|
||
|
|
||
| def set_reminder( | ||
| channel: str, | ||
| event_number: str, | ||
| user_id: str, | ||
| minutes_before: int, | ||
| recurrence: str | None = None, | ||
| message: str = "", | ||
| ) -> ReminderSchedule: | ||
| """Set a reminder for a user.""" | ||
| if minutes_before <= 0: | ||
| message = "Minutes before must be a positive integer." | ||
| raise ValidationError(message) | ||
| auth = GoogleAccountAuthorization.authorize(user_id) | ||
| if not isinstance(auth, GoogleAccountAuthorization): | ||
| message = "User is not authorized with Google. Please sign in first." | ||
| raise ValidationError(message) | ||
| google_calendar_id = cache.get(f"{user_id}_{event_number}") | ||
| if not google_calendar_id: | ||
| message = ( | ||
| "Invalid or expired event number. Please get a new event number from the events list." | ||
| ) | ||
| raise ValidationError(message) | ||
|
Comment on lines
+44
to
+56
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one has some splitting but still could use better partitioning. |
||
|
|
||
| client = GoogleCalendarClient(auth) | ||
| event = Event.parse_google_calendar_event(client.get_event(google_calendar_id)) | ||
| if not event: | ||
| message = "Could not retrieve the event details. Please try again later." | ||
| raise ValidationError(message) | ||
|
|
||
| reminder_time = event.start_date - timezone.timedelta(minutes=minutes_before) | ||
| if reminder_time < timezone.now(): | ||
| message = "Reminder time must be in the future. Please adjust the minutes before." | ||
| raise ValidationError(message) | ||
|
|
||
| if recurrence and recurrence not in ReminderSchedule.Recurrence.values: | ||
| message = "Invalid recurrence value." | ||
| raise ValidationError(message) | ||
|
|
||
| with transaction.atomic(): | ||
| # Saving event to the database after validation | ||
| event.save() | ||
|
|
||
| member = Member.objects.get(slack_user_id=user_id) | ||
| reminder, _ = Reminder.objects.get_or_create( | ||
| channel_id=channel, | ||
| event=event, | ||
| member=member, | ||
| defaults={"message": f"{event.name} - {message}" if message else event.name}, | ||
| ) | ||
| return schedule_reminder( | ||
| reminder=reminder, | ||
| scheduled_time=reminder_time, | ||
| recurrence=recurrence or ReminderSchedule.Recurrence.ONCE, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Generated by Django 5.2.4 on 2025-09-03 14:26 | ||
|
|
||
| import django.db.models.deletion | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("nest", "0008_reminder_reminderschedule"), | ||
| ("owasp", "0052_remove_event_calendar_id_event_google_calendar_id"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="reminder", | ||
| name="event", | ||
| field=models.ForeignKey( | ||
| null=True, | ||
| on_delete=django.db.models.deletion.SET_NULL, | ||
| related_name="reminders", | ||
| to="owasp.event", | ||
| verbose_name="Event", | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Generated by Django 5.2.4 on 2025-09-07 21:43 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("nest", "0009_reminder_event"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddConstraint( | ||
| model_name="reminderschedule", | ||
| constraint=models.UniqueConstraint( | ||
| fields=("reminder", "scheduled_time"), name="unique_reminder_schedule" | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Generated by Django 5.2.4 on 2025-09-08 05:48 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("nest", "0005_alter_userbadge_user"), | ||
| ("nest", "0010_reminderschedule_unique_reminder_schedule"), | ||
| ] | ||
|
|
||
| operations = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Generated by Django 5.2.6 on 2025-09-12 04:39 | ||
|
|
||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("nest", "0011_merge_20250908_0548"), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name="reminderschedule", | ||
| name="job_id", | ||
| field=models.CharField( | ||
| blank=True, | ||
| help_text="ID of the scheduled job in the task queue.", | ||
| max_length=255, | ||
| verbose_name="Job ID", | ||
| ), | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| # Generated by Django 5.2.6 on 2025-09-15 04:11 | ||
|
|
||
| from django.db import migrations | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
| dependencies = [ | ||
| ("nest", "0006_delete_apikey"), | ||
| ("nest", "0012_reminderschedule_job_id"), | ||
| ] | ||
|
|
||
| operations = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one should be on its place in this file