From c926a9f332206aaa22e6c30970d34b5cccbd9cdd Mon Sep 17 00:00:00 2001 From: sosokker Date: Sat, 4 Nov 2023 03:30:21 +0700 Subject: [PATCH] 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