From 6581b547a0d596864771e9c3581877c9df600e76 Mon Sep 17 00:00:00 2001 From: sosokker Date: Thu, 2 Nov 2023 11:22:13 +0700 Subject: [PATCH 1/4] Add Retreive data from calendar API --- backend/core/settings.py | 9 ++++++ backend/tasks/api.py | 24 ++++++++++++++ backend/tasks/serializers.py | 8 +++++ backend/tasks/urls.py | 2 ++ backend/users/access_token_cache.py | 50 +++++++++++++++++++++++++++++ backend/users/views.py | 3 ++ 6 files changed, 96 insertions(+) create mode 100644 backend/tasks/api.py create mode 100644 backend/tasks/serializers.py create mode 100644 backend/users/access_token_cache.py diff --git a/backend/core/settings.py b/backend/core/settings.py index c795b7c..0423a00 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -162,6 +162,15 @@ DATABASES = { } +# Cache + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.db.DatabaseCache", + "LOCATION": "dbtest", + } +} + # Password validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators diff --git a/backend/tasks/api.py b/backend/tasks/api.py new file mode 100644 index 0000000..94a2b8e --- /dev/null +++ b/backend/tasks/api.py @@ -0,0 +1,24 @@ +from datetime import datetime, timedelta +from django.utils import timezone + +from rest_framework import viewsets +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated, AllowAny + +from googleapiclient.discovery import build + +from .serializers import GoogleCalendarEventSerializer +from users.access_token_cache import get_credential_from_cache_token + + +class GoogleCalendarEventViewset(viewsets.ViewSet): + permission_classes = (IsAuthenticated,) + + def list(self, request, days=7): + current_time = datetime.now(tz=timezone.utc).isoformat() + max_time = (datetime.now(tz=timezone.utc) + timedelta(days=days)).isoformat() + credentials = get_credential_from_cache_token(request.user.id) + service = build('calendar', 'v3', credentials=credentials) + events = service.events().list(calendarId='primary', timeMin=current_time, timeMax=max_time).execute() + + return Response(events.get('items', []), status=200) diff --git a/backend/tasks/serializers.py b/backend/tasks/serializers.py new file mode 100644 index 0000000..a9f03e2 --- /dev/null +++ b/backend/tasks/serializers.py @@ -0,0 +1,8 @@ +from rest_framework import serializers + + +class GoogleCalendarEventSerializer(serializers.Serializer): + summary = serializers.CharField() + start = serializers.DateTimeField() + end = serializers.DateTimeField() + description = serializers.CharField(required=False) \ No newline at end of file diff --git a/backend/tasks/urls.py b/backend/tasks/urls.py index ef94e06..2308167 100644 --- a/backend/tasks/urls.py +++ b/backend/tasks/urls.py @@ -1,11 +1,13 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter +from .api import GoogleCalendarEventViewset from .tasks.views import TaskCreateView, TaskRetrieveView, TaskUpdateView, TaskDeleteView from .misc.views import TagViewSet, ReminderViewSet router = DefaultRouter() router.register(r'reminders', ReminderViewSet) router.register(r'tags', TagViewSet) +router.register(r'calendar-events', GoogleCalendarEventViewset, basename='calendar-events') urlpatterns = [ path('', include(router.urls)), diff --git a/backend/users/access_token_cache.py b/backend/users/access_token_cache.py new file mode 100644 index 0000000..37dada0 --- /dev/null +++ b/backend/users/access_token_cache.py @@ -0,0 +1,50 @@ +from django.core.cache import cache +from django.conf import settings + +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from googleapiclient.discovery import build + +from .models import CustomUser + + +def store_token(user_id, token, token_type): + cache_key = f"user_{token_type}_token:{user_id}" + cache.set(cache_key, token, timeout=3600) + + +def get_credential_from_cache_token(user_id): + access_token = cache.get(f"user_access_token:{user_id}") + id_token = cache.get(f"user_id_token:{user_id}") + refresh_token = CustomUser.objects.get(id=user_id).refresh_token + scopes = [ + 'https://www.googleapis.com/auth/userinfo.email', + 'https://www.googleapis.com/auth/userinfo.profile', + 'https://www.googleapis.com/auth/calendar.readonly', + ] + # credentials = Credentials.from_authorized_user_info( + # { + # 'access_token': access_token, + # 'token_uri': 'https://oauth2.googleapis.com/token', + # 'refresh_token': refresh_token, + # 'client_id': settings.GOOGLE_CLIENT_ID, + # 'client_secret': settings.GOOGLE_CLIENT_SECRET, + # 'id_token': id_token, + # } + + credentials = Credentials(token=access_token, + refresh_token=refresh_token, + token_uri='https://oauth2.googleapis.com/token', + client_id=settings.GOOGLE_CLIENT_ID, + client_secret=settings.GOOGLE_CLIENT_SECRET, + scopes=scopes, + id_token=id_token + ) + + # If can refresh, refresh + if credentials.expired and credentials.refresh_token: + credentials.refresh(Request()) + store_token(user_id, credentials.token, 'access') + store_token(user_id, credentials.id_token, 'id') + + return credentials \ No newline at end of file diff --git a/backend/users/views.py b/backend/users/views.py index 591c609..a448ed2 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -19,6 +19,7 @@ from dj_rest_auth.registration.views import SocialLoginView from google_auth_oauthlib.flow import InstalledAppFlow +from .access_token_cache import store_token from .serializers import MyTokenObtainPairSerializer, CustomUserSerializer from .managers import CustomAccountManager from .models import CustomUser @@ -168,6 +169,8 @@ class GoogleRetrieveUserInfo(APIView): user.email = user_info['email'] user.refresh_token = user_info['refresh_token'] user.save() + store_token(user.id, user_info['access_token'], 'access') + store_token(user.id, user_info['id_token'], 'id') return user def call_google_api(self, api_url, access_token): From 02236f98731b45a1daa24c513d45c67b0e62f093 Mon Sep 17 00:00:00 2001 From: sosokker Date: Fri, 3 Nov 2023 12:49:46 +0700 Subject: [PATCH 2/4] Remove Reminders from model --- .../0005_task_google_calendar_id.py | 18 +++++++++++ .../migrations/0006_alter_task_difficulty.py | 18 +++++++++++ ..._task_reminders_task_end_event_and_more.py | 30 +++++++++++++++++++ backend/tasks/misc/serializers.py | 6 +--- backend/tasks/misc/views.py | 7 ++--- backend/tasks/models.py | 16 +++------- backend/tasks/urls.py | 3 +- 7 files changed, 74 insertions(+), 24 deletions(-) create mode 100644 backend/tasks/migrations/0005_task_google_calendar_id.py create mode 100644 backend/tasks/migrations/0006_alter_task_difficulty.py create mode 100644 backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py diff --git a/backend/tasks/migrations/0005_task_google_calendar_id.py b/backend/tasks/migrations/0005_task_google_calendar_id.py new file mode 100644 index 0000000..bc59e7f --- /dev/null +++ b/backend/tasks/migrations/0005_task_google_calendar_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2023-11-02 07:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0004_rename_time_reminder_alerttime_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='google_calendar_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/backend/tasks/migrations/0006_alter_task_difficulty.py b/backend/tasks/migrations/0006_alter_task_difficulty.py new file mode 100644 index 0000000..2e58db5 --- /dev/null +++ b/backend/tasks/migrations/0006_alter_task_difficulty.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.6 on 2023-11-03 05:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0005_task_google_calendar_id'), + ] + + operations = [ + migrations.AlterField( + model_name='task', + name='difficulty', + field=models.PositiveSmallIntegerField(choices=[(1, 'Easy'), (2, 'Normal'), (3, 'Hard'), (4, 'Very Hard'), (5, 'Devil')], default=1), + ), + ] diff --git a/backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py b/backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py new file mode 100644 index 0000000..257bd25 --- /dev/null +++ b/backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.6 on 2023-11-03 05:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0006_alter_task_difficulty'), + ] + + operations = [ + migrations.RemoveField( + model_name='task', + name='reminders', + ), + migrations.AddField( + model_name='task', + name='end_event', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='task', + name='start_event', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.DeleteModel( + name='Reminder', + ), + ] diff --git a/backend/tasks/misc/serializers.py b/backend/tasks/misc/serializers.py index 99b312e..c38723b 100644 --- a/backend/tasks/misc/serializers.py +++ b/backend/tasks/misc/serializers.py @@ -1,10 +1,6 @@ from rest_framework import serializers -from ..models import Reminder, Tag +from ..models import Tag -class ReminderSerializer(serializers.ModelSerializer): - class Meta: - model = Reminder - fields = '__all__' class TagSerializer(serializers.ModelSerializer): class Meta: diff --git a/backend/tasks/misc/views.py b/backend/tasks/misc/views.py index e5feb01..3fbc837 100644 --- a/backend/tasks/misc/views.py +++ b/backend/tasks/misc/views.py @@ -1,10 +1,7 @@ from rest_framework import viewsets -from ..models import Reminder, Tag -from .serializers import ReminderSerializer, TagSerializer +from ..models import Tag +from .serializers import TagSerializer -class ReminderViewSet(viewsets.ModelViewSet): - queryset = Reminder.objects.all() - serializer_class = ReminderSerializer class TagViewSet(viewsets.ModelViewSet): queryset = Tag.objects.all() diff --git a/backend/tasks/models.py b/backend/tasks/models.py index 2f3f34a..719379d 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -3,16 +3,6 @@ from django.conf import settings from django.core import validators -class Reminder(models.Model): - """ - Represents a reminder associated with a task. - Fields: - - startDate: The optional date for which the reminder is set. - - time: The time at which the reminder is triggered. - """ - startDate = models.DateField(auto_now_add=True, null=True, blank=True) - alertTime = models.DateTimeField(null=False, blank=False) - class Tag(models.Model): """ Represents a tag that can be associated with tasks. @@ -66,7 +56,7 @@ class Task(models.Model): validators.MinValueValidator(0.1), validators.MaxValueValidator(2), ]) - difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES) + difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, default=1) attribute = models.CharField(max_length=15, choices=[ ('str', 'Strength'), ('int', 'Intelligence'), @@ -76,10 +66,12 @@ class Task(models.Model): ], default='str') user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) challenge = models.BooleanField(default=False) - reminders = models.ManyToManyField(Reminder, blank=True) fromSystem = models.BooleanField(default=False) creation_date = models.DateTimeField(auto_now_add=True) last_update = models.DateTimeField(auto_now=True) + google_calendar_id = models.CharField(blank=True, null=True, max_length=255) + start_event = models.DateTimeField(blank=True, null=True) + end_event = models.DateTimeField(blank=True, null=True) class Subtask(models.Model): diff --git a/backend/tasks/urls.py b/backend/tasks/urls.py index 2308167..c04bd68 100644 --- a/backend/tasks/urls.py +++ b/backend/tasks/urls.py @@ -2,10 +2,9 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter from .api import GoogleCalendarEventViewset from .tasks.views import TaskCreateView, TaskRetrieveView, TaskUpdateView, TaskDeleteView -from .misc.views import TagViewSet, ReminderViewSet +from .misc.views import TagViewSet router = DefaultRouter() -router.register(r'reminders', ReminderViewSet) router.register(r'tags', TagViewSet) router.register(r'calendar-events', GoogleCalendarEventViewset, basename='calendar-events') From fcf674f3f6cbb40be088cf0564b39899d00c743a Mon Sep 17 00:00:00 2001 From: sosokker Date: Fri, 3 Nov 2023 23:29:54 +0700 Subject: [PATCH 3/4] Clean up after remove reminders --- backend/tasks/tasks/serializers.py | 2 +- backend/tasks/tests/test_task_creation.py | 2 +- backend/tasks/tests/utils.py | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/tasks/tasks/serializers.py b/backend/tasks/tasks/serializers.py index 009c472..7876263 100644 --- a/backend/tasks/tasks/serializers.py +++ b/backend/tasks/tasks/serializers.py @@ -5,7 +5,7 @@ class TaskCreateSerializer(serializers.ModelSerializer): class Meta: model = Task # fields = '__all__' - exclude = ('tags', 'reminders') + exclude = ('tags',) def create(self, validated_data): # Create a new task with validated data diff --git a/backend/tasks/tests/test_task_creation.py b/backend/tasks/tests/test_task_creation.py index ff3c1ff..e7c1e1f 100644 --- a/backend/tasks/tests/test_task_creation.py +++ b/backend/tasks/tests/test_task_creation.py @@ -2,7 +2,7 @@ from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase -from .utils import create_test_user, login_user +from tasks.tests.utils import create_test_user, login_user from ..models import Task class TaskCreateViewTests(APITestCase): diff --git a/backend/tasks/tests/utils.py b/backend/tasks/tests/utils.py index 80767fc..f33b828 100644 --- a/backend/tasks/tests/utils.py +++ b/backend/tasks/tests/utils.py @@ -34,7 +34,6 @@ def create_task_json(user, **kwargs): "difficulty": 1, "attribute": "str", "challenge": False, - "reminders": False, "fromSystem": False, "creation_date": None, "last_update": None, @@ -57,7 +56,6 @@ def create_test_task(user, **kwargs): 'difficulty': 1, 'attribute': 'str', 'challenge': False, - 'reminders': False, 'fromSystem': False, } From c926a9f332206aaa22e6c30970d34b5cccbd9cdd Mon Sep 17 00:00:00 2001 From: sosokker Date: Sat, 4 Nov 2023 03:30:21 +0700 Subject: [PATCH 4/4] Improve Google API Sync --- backend/core/settings.py | 4 +- backend/tasks/api.py | 68 +++++++++++++++---- ..._task_reminders_task_end_event_and_more.py | 10 --- .../0008_task_end_event_task_start_event.py | 23 +++++++ backend/tasks/models.py | 6 +- backend/tasks/serializers.py | 30 +++++++- backend/tasks/tests/test_deserializer.py | 56 +++++++++++++++ backend/tasks/utils.py | 8 +++ 8 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 backend/tasks/migrations/0008_task_end_event_task_start_event.py create mode 100644 backend/tasks/tests/test_deserializer.py create mode 100644 backend/tasks/utils.py diff --git a/backend/core/settings.py b/backend/core/settings.py index 0423a00..504cd01 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -164,10 +164,12 @@ DATABASES = { # Cache +CACHES_LOCATION = f"{config('DB_NAME', default='db_test')}_cache" + CACHES = { "default": { "BACKEND": "django.core.cache.backends.db.DatabaseCache", - "LOCATION": "dbtest", + "LOCATION": CACHES_LOCATION, } } diff --git a/backend/tasks/api.py b/backend/tasks/api.py index 94a2b8e..4736582 100644 --- a/backend/tasks/api.py +++ b/backend/tasks/api.py @@ -1,24 +1,68 @@ from datetime import datetime, timedelta + from django.utils import timezone from rest_framework import viewsets from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated, AllowAny +from rest_framework.permissions import IsAuthenticated -from googleapiclient.discovery import build - -from .serializers import GoogleCalendarEventSerializer -from users.access_token_cache import get_credential_from_cache_token +from tasks.utils import get_service +from tasks.models import Task +from tasks.serializers import TaskUpdateSerializer class GoogleCalendarEventViewset(viewsets.ViewSet): permission_classes = (IsAuthenticated,) - def list(self, request, days=7): - current_time = datetime.now(tz=timezone.utc).isoformat() - max_time = (datetime.now(tz=timezone.utc) + timedelta(days=days)).isoformat() - credentials = get_credential_from_cache_token(request.user.id) - service = build('calendar', 'v3', credentials=credentials) - events = service.events().list(calendarId='primary', timeMin=current_time, timeMax=max_time).execute() + def __init__(self, *args, **kwargs): + super().__init__() + self.current_time = datetime.now(tz=timezone.utc).isoformat() + self.event_fields = 'items(id,summary,description,created,updated,start,end)' - return Response(events.get('items', []), status=200) + def _validate_serializer(self, serializer): + if serializer.is_valid(): + serializer.save() + return Response("Task Sync Successfully", status=200) + return Response(serializer.errors, status=400) + + def post(self, request): + service = get_service(request) + events = service.events().list(calendarId='primary', fields=self.event_fields).execute() + for event in events.get('items', []): + try: + task = Task.objects.get(google_calendar_id=event['id']) + serializer = TaskUpdateSerializer(instance=task, data=event) + return self._validate_serializer(serializer) + except Task.DoesNotExist: + serializer = TaskUpdateSerializer(data=event, user=request.user) + return self._validate_serializer(serializer) + + def list(self, request, days=7): + max_time = (datetime.now(tz=timezone.utc) + timedelta(days=3)).isoformat() + + service = get_service(request) + events = [] + next_page_token = None + + while True: + query = service.events().list( + calendarId='primary', + timeMin=self.current_time, + timeMax=max_time, + maxResults=20, + singleEvents=True, + orderBy='startTime', + pageToken=next_page_token, + fields='items(id,summary,description,created,updated,start,end)', + ) + + page_results = query.execute() + page_events = page_results.get('items', []) + + events.extend(page_events) + next_page_token = page_results.get('nextPageToken') + + if next_page_token is None: + break + + return Response(events, status=200) \ No newline at end of file diff --git a/backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py b/backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py index 257bd25..c0cfc9d 100644 --- a/backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py +++ b/backend/tasks/migrations/0007_remove_task_reminders_task_end_event_and_more.py @@ -14,16 +14,6 @@ class Migration(migrations.Migration): model_name='task', name='reminders', ), - migrations.AddField( - model_name='task', - name='end_event', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='task', - name='start_event', - field=models.DateTimeField(blank=True, null=True), - ), migrations.DeleteModel( name='Reminder', ), diff --git a/backend/tasks/migrations/0008_task_end_event_task_start_event.py b/backend/tasks/migrations/0008_task_end_event_task_start_event.py new file mode 100644 index 0000000..43570b1 --- /dev/null +++ b/backend/tasks/migrations/0008_task_end_event_task_start_event.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.6 on 2023-11-03 17:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0007_remove_task_reminders_task_end_event_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='task', + name='end_event', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='task', + name='start_event', + field=models.DateTimeField(null=True), + ), + ] diff --git a/backend/tasks/models.py b/backend/tasks/models.py index 719379d..686eab9 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -1,7 +1,7 @@ from django.db import models from django.conf import settings from django.core import validators - +from django.utils import timezone class Tag(models.Model): """ @@ -70,8 +70,8 @@ class Task(models.Model): creation_date = models.DateTimeField(auto_now_add=True) last_update = models.DateTimeField(auto_now=True) google_calendar_id = models.CharField(blank=True, null=True, max_length=255) - start_event = models.DateTimeField(blank=True, null=True) - end_event = models.DateTimeField(blank=True, null=True) + start_event = models.DateTimeField(null=True) + end_event = models.DateTimeField(null=True) class Subtask(models.Model): diff --git a/backend/tasks/serializers.py b/backend/tasks/serializers.py index a9f03e2..e2c6a85 100644 --- a/backend/tasks/serializers.py +++ b/backend/tasks/serializers.py @@ -1,8 +1,36 @@ from rest_framework import serializers +from django.utils.dateparse import parse_datetime +from .models import Task +from datetime import datetime class GoogleCalendarEventSerializer(serializers.Serializer): summary = serializers.CharField() start = serializers.DateTimeField() end = serializers.DateTimeField() - description = serializers.CharField(required=False) \ No newline at end of file + description = serializers.CharField(required=False) + + +class TaskUpdateSerializer(serializers.ModelSerializer): + id = serializers.CharField(source="google_calendar_id") + summary = serializers.CharField(source="title") + description = serializers.CharField(source="notes", required=False) + created = serializers.DateTimeField(source="creation_date") + updated = serializers.DateTimeField(source="last_update") + start_datetime = serializers.DateTimeField(source="start_event", required=False) + end_datetime = serializers.DateTimeField(source="end_event", required=False) + + + class Meta: + model = Task + fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime') + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop('user', None) + super(TaskUpdateSerializer, self).__init__(*args, **kwargs) + + def create(self, validated_data): + validated_data['user'] = self.user + task = Task.objects.create(**validated_data) + + return task \ No newline at end of file diff --git a/backend/tasks/tests/test_deserializer.py b/backend/tasks/tests/test_deserializer.py new file mode 100644 index 0000000..aa07480 --- /dev/null +++ b/backend/tasks/tests/test_deserializer.py @@ -0,0 +1,56 @@ +from datetime import datetime +from zoneinfo import ZoneInfo + +from django.test import TestCase +from django.utils import timezone + +from tasks.tests.utils import create_test_user, login_user +from tasks.serializers import TaskUpdateSerializer +from tasks.models import Task + +class TaskUpdateSerializerTest(TestCase): + def setUp(self): + self.user = create_test_user() + self.current_time = '2020-08-01T00:00:00Z' + self.end_time = '2020-08-01T00:00:00Z' + + def test_serializer_create(self): + data = { + 'id': '32141cwaNcapufh8jq2conw', + 'summary': 'Updated Task', + 'description': 'Updated description', + 'created': self.current_time, + 'updated': self.end_time, + 'start_datetime' : self.current_time, + 'end_datetie': self.end_time, + } + + serializer = TaskUpdateSerializer(data=data, user=self.user) + self.assertTrue(serializer.is_valid()) + serializer.is_valid() + task = serializer.save() + self.assertIsInstance(task, Task) + + def test_serializer_update(self): + task = Task.objects.create(title='Original Task', notes='Original description', user=self.user) + + data = { + 'id': '32141cwaNcapufh8jq2conw', + 'summary': 'Updated Task', + 'description': 'Updated description', + 'created': self.current_time, + 'updated': self.end_time, + 'start_datetime' : self.current_time, + 'end_datetie': self.end_time, + } + + serializer = TaskUpdateSerializer(instance=task, data=data) + self.assertTrue(serializer.is_valid()) + updated_task = serializer.save() + + self.assertEqual(updated_task.title, 'Updated Task') + self.assertEqual(updated_task.notes, 'Updated description') + self.assertEqual(updated_task.start_event, + datetime.strptime(self.current_time, + '%Y-%m-%dT%H:%M:%SZ') + .replace(tzinfo=ZoneInfo(key='UTC'))) \ No newline at end of file diff --git a/backend/tasks/utils.py b/backend/tasks/utils.py new file mode 100644 index 0000000..6440b8b --- /dev/null +++ b/backend/tasks/utils.py @@ -0,0 +1,8 @@ +from googleapiclient.discovery import build + +from users.access_token_cache import get_credential_from_cache_token + + +def get_service(request): + credentials = get_credential_from_cache_token(request.user.id) + return build('calendar', 'v3', credentials=credentials) \ No newline at end of file