mirror of
https://github.com/TurTaskProject/TurTaskWeb.git
synced 2025-12-19 22:14:07 +01:00
Add RRule parser from 2 tasks and save Recurrence Tasks
This commit is contained in:
parent
b47e83e5cf
commit
d2f75c4fc1
@ -6,46 +6,22 @@ from rest_framework import viewsets
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from tasks.utils import get_service
|
from tasks.utils import get_service, generate_recurrence_rule
|
||||||
from tasks.models import Todo, RecurrenceTask
|
from tasks.models import Todo, RecurrenceTask
|
||||||
from tasks.serializers import TodoUpdateSerializer, RecurrenceTaskUpdateSerializer
|
from tasks.serializers import TodoUpdateSerializer, RecurrenceTaskUpdateSerializer
|
||||||
|
|
||||||
|
|
||||||
class GoogleCalendarEventViewset(viewsets.ViewSet):
|
class GoogleCalendarEventViewset(viewsets.ViewSet):
|
||||||
|
"""Viewset for list or save Google Calendar Events."""
|
||||||
permission_classes = (IsAuthenticated,)
|
permission_classes = (IsAuthenticated,)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.current_time = datetime.now(tz=timezone.utc).isoformat()
|
self.current_time = (datetime.now(tz=timezone.utc) + timedelta(days=-7)).isoformat()
|
||||||
self.event_fields = 'items(id,summary,description,created,recurringEventId,updated,start,end)'
|
self.max_time = (datetime.now(tz=timezone.utc) + timedelta(days=7)).isoformat()
|
||||||
|
self.event_fields = 'items(id,summary,description,created,recurringEventId,updated,start,end,originalStartTime)'
|
||||||
|
|
||||||
def _validate_serializer(self, serializer):
|
def _get_google_events(self, request):
|
||||||
if serializer.is_valid():
|
"""Get all events from Google Calendar. """
|
||||||
serializer.save()
|
|
||||||
return Response("Validate 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', []):
|
|
||||||
if event.get('recurringEventId'):
|
|
||||||
continue
|
|
||||||
event['start_datetime'] = event.get('start').get('dateTime')
|
|
||||||
event['end_datetime'] = event.get('end').get('dateTime')
|
|
||||||
event.pop('start')
|
|
||||||
event.pop('end')
|
|
||||||
try:
|
|
||||||
task = Todo.objects.get(google_calendar_id=event['id'])
|
|
||||||
serializer = TodoUpdateSerializer(instance=task, data=event)
|
|
||||||
return self._validate_serializer(serializer)
|
|
||||||
except Todo.DoesNotExist:
|
|
||||||
serializer = TodoUpdateSerializer(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=days)).isoformat()
|
|
||||||
|
|
||||||
service = get_service(request)
|
service = get_service(request)
|
||||||
events = []
|
events = []
|
||||||
next_page_token = None
|
next_page_token = None
|
||||||
@ -54,21 +30,77 @@ class GoogleCalendarEventViewset(viewsets.ViewSet):
|
|||||||
query = service.events().list(
|
query = service.events().list(
|
||||||
calendarId='primary',
|
calendarId='primary',
|
||||||
timeMin=self.current_time,
|
timeMin=self.current_time,
|
||||||
timeMax=max_time,
|
timeMax=self.max_time,
|
||||||
maxResults=200,
|
maxResults=200,
|
||||||
singleEvents=True,
|
singleEvents=True,
|
||||||
orderBy='startTime',
|
orderBy='startTime',
|
||||||
pageToken=next_page_token,
|
pageToken=next_page_token,
|
||||||
fields='items(id,summary,description,created,recurringEventId,updated,start,end)',
|
fields=self.event_fields,
|
||||||
)
|
)
|
||||||
|
|
||||||
page_results = query.execute()
|
page_results = query.execute()
|
||||||
page_events = page_results.get('items', [])
|
page_events = page_results.get('items', [])
|
||||||
|
|
||||||
events.extend(page_events)
|
events.extend(page_events)
|
||||||
next_page_token = page_results.get('nextPageToken')
|
next_page_token = page_results.get('nextPageToken')
|
||||||
|
|
||||||
if next_page_token is None:
|
if next_page_token is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
return Response(events, status=200)
|
return events
|
||||||
|
|
||||||
|
def _validate_serializer(self, serializer):
|
||||||
|
"""Validate serializer and return response."""
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response("Validate Successfully", status=200)
|
||||||
|
return Response(serializer.errors, status=400)
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
"""Create a new Google Calendar Event."""
|
||||||
|
events = self._get_google_events(request)
|
||||||
|
|
||||||
|
responses = []
|
||||||
|
recurrence_task_ids = []
|
||||||
|
for event in events:
|
||||||
|
start_datetime = event.get('start', {}).get('dateTime')
|
||||||
|
end_datetime = event.get('end', {}).get('dateTime')
|
||||||
|
|
||||||
|
event['start_datetime'] = start_datetime
|
||||||
|
event['end_datetime'] = end_datetime
|
||||||
|
event.pop('start')
|
||||||
|
event.pop('end')
|
||||||
|
|
||||||
|
if (event.get('recurringEventId') in recurrence_task_ids):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (event.get('recurringEventId') is not None):
|
||||||
|
originalStartTime = event.get('originalStartTime', {}).get('dateTime')
|
||||||
|
rrule_text = generate_recurrence_rule(event['start_datetime'], event['end_datetime'], originalStartTime)
|
||||||
|
event['recurrence'] = rrule_text
|
||||||
|
event.pop('originalStartTime')
|
||||||
|
recurrence_task_ids.append(event['recurringEventId'])
|
||||||
|
|
||||||
|
try:
|
||||||
|
task = RecurrenceTask.objects.get(google_calendar_id=event['id'])
|
||||||
|
serializer = RecurrenceTaskUpdateSerializer(instance=task, data=event)
|
||||||
|
except RecurrenceTask.DoesNotExist:
|
||||||
|
serializer = RecurrenceTaskUpdateSerializer(data=event, user=request.user)
|
||||||
|
|
||||||
|
responses.append(self._validate_serializer(serializer))
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
task = Todo.objects.get(google_calendar_id=event['id'])
|
||||||
|
serializer = TodoUpdateSerializer(instance=task, data=event)
|
||||||
|
except Todo.DoesNotExist:
|
||||||
|
serializer = TodoUpdateSerializer(data=event, user=request.user)
|
||||||
|
|
||||||
|
responses.append(self._validate_serializer(serializer))
|
||||||
|
|
||||||
|
return responses[0] if responses else Response("No events to process", status=200)
|
||||||
|
|
||||||
|
def list(self, request):
|
||||||
|
"""List all Google Calendar Events."""
|
||||||
|
return Response(self._get_google_events(request), status=200)
|
||||||
|
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.6 on 2023-11-14 15:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tasks', '0012_habit'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='recurrencetask',
|
||||||
|
name='recurrence_rule',
|
||||||
|
field=models.CharField(),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -68,7 +68,7 @@ class Todo(Task):
|
|||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
class RecurrenceTask(Task):
|
class RecurrenceTask(Task):
|
||||||
recurrence_rule = models.TextField()
|
recurrence_rule = models.CharField()
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.title} ({self.recurrence_rule})"
|
return f"{self.title} ({self.recurrence_rule})"
|
||||||
|
|||||||
@ -41,7 +41,7 @@ class RecurrenceTaskUpdateSerializer(serializers.ModelSerializer):
|
|||||||
description = serializers.CharField(source="notes", required=False)
|
description = serializers.CharField(source="notes", required=False)
|
||||||
created = serializers.DateTimeField(source="creation_date")
|
created = serializers.DateTimeField(source="creation_date")
|
||||||
updated = serializers.DateTimeField(source="last_update")
|
updated = serializers.DateTimeField(source="last_update")
|
||||||
recurrence = serializers.DateTimeField(source="recurrence_rule")
|
recurrence = serializers.CharField(source="recurrence_rule")
|
||||||
start_datetime = serializers.DateTimeField(source="start_event", required=False)
|
start_datetime = serializers.DateTimeField(source="start_event", required=False)
|
||||||
end_datetime = serializers.DateTimeField(source="end_event", required=False)
|
end_datetime = serializers.DateTimeField(source="end_event", required=False)
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,10 @@ class TodoViewSet(viewsets.ModelViewSet):
|
|||||||
serializer_class = TaskSerializer
|
serializer_class = TaskSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = Todo.objects.filter(user=self.request.user)
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
# Can't add ManytoMany at creation time (Tags)
|
# Can't add ManytoMany at creation time (Tags)
|
||||||
if self.action == 'create':
|
if self.action == 'create':
|
||||||
|
|||||||
@ -1,8 +1,55 @@
|
|||||||
|
from dateutil import rrule
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from googleapiclient.discovery import build
|
from googleapiclient.discovery import build
|
||||||
|
|
||||||
from authentications.access_token_cache import get_credential_from_cache_token
|
from authentications.access_token_cache import get_credential_from_cache_token
|
||||||
|
|
||||||
|
|
||||||
def get_service(request):
|
def get_service(request):
|
||||||
|
"""
|
||||||
|
Get a service that communicates to a Google API.
|
||||||
|
|
||||||
|
:param request: Http request object
|
||||||
|
:return: A Resource object with methods for interacting with the calendar service
|
||||||
|
"""
|
||||||
credentials = get_credential_from_cache_token(request.user.id)
|
credentials = get_credential_from_cache_token(request.user.id)
|
||||||
return build('calendar', 'v3', credentials=credentials)
|
return build('calendar', 'v3', credentials=credentials)
|
||||||
|
|
||||||
|
def _determine_frequency(time_difference):
|
||||||
|
if time_difference.days >= 365:
|
||||||
|
return rrule.YEARLY
|
||||||
|
elif time_difference.days >= 30:
|
||||||
|
return rrule.MONTHLY
|
||||||
|
elif time_difference.days >= 7:
|
||||||
|
return rrule.WEEKLY
|
||||||
|
elif time_difference.days >= 1:
|
||||||
|
return rrule.DAILY
|
||||||
|
elif time_difference.seconds >= 3600:
|
||||||
|
return rrule.HOURLY
|
||||||
|
elif time_difference.seconds >= 60:
|
||||||
|
return rrule.MINUTELY
|
||||||
|
else:
|
||||||
|
return rrule.SECONDLY
|
||||||
|
|
||||||
|
def generate_recurrence_rule(datetime1: str, datetime2: str, original_start_time: str) -> str:
|
||||||
|
"""
|
||||||
|
Generate recurrence rule from
|
||||||
|
difference between two datetime string.
|
||||||
|
|
||||||
|
:param task1: A task object
|
||||||
|
:param task2: A task object
|
||||||
|
:return: A recurrence rule string according to ICAL format
|
||||||
|
"""
|
||||||
|
start_time1 = datetime.fromisoformat(datetime1)
|
||||||
|
start_time2 = datetime.fromisoformat(datetime2)
|
||||||
|
|
||||||
|
time_difference = start_time2 - start_time1
|
||||||
|
|
||||||
|
recurrence_rule = rrule.rrule(
|
||||||
|
freq=_determine_frequency(time_difference),
|
||||||
|
dtstart=datetime.fromisoformat(original_start_time),
|
||||||
|
interval=time_difference.days if time_difference.days > 0 else 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return str(recurrence_rule)
|
||||||
@ -14,4 +14,5 @@ google_auth_oauthlib>=1.1
|
|||||||
google-auth-httplib2>=0.1
|
google-auth-httplib2>=0.1
|
||||||
django-storages[s3]>=1.14
|
django-storages[s3]>=1.14
|
||||||
Pillow>=10.1
|
Pillow>=10.1
|
||||||
drf-spectacular>=0.26
|
drf-spectacular>=0.26
|
||||||
|
python-dateutil>=2.8
|
||||||
Loading…
Reference in New Issue
Block a user