diff --git a/task/models.py b/task/models.py index e305c4f..d3ce361 100644 --- a/task/models.py +++ b/task/models.py @@ -221,6 +221,22 @@ class Meta: def __str__(self) -> str: return '{}: {}'.format(self.task, self.rule) + @transaction.atomic + def clean_scheduled(self) -> List[int]: + """ + Clean all scheduled chunks which are no longer valid. + Returns the ids of all chunks that were deleted. + """ + chunks = self.chunks.filter(day__lt=self.start) + if self.end: + chunks |= self.chunks.filter(day__gt=self.end) + ids = [chunk.id for chunk in chunks] + chunks.delete() + + self.last_scheduled_day = self.chunks.aggregate(Max('day'))['day__max'] + self.save(update_fields=('last_scheduled_day',)) + return ids + @transaction.atomic def schedule( self, diff --git a/task/tests.py b/task/tests.py index e546360..ac8dd5f 100644 --- a/task/tests.py +++ b/task/tests.py @@ -3859,6 +3859,100 @@ def test_create_scheduled_task_duration(self): Decimal(scheduled['task']['scheduled_duration']), self.task.scheduled_duration) + @freeze_time('2010-05-03') + def test_update_cleaning(self): + """ + Test the cleaning of task chunks when modifying the end date. + """ + series = TaskChunkSeries.objects.create( + task=self.task, + start=date(2010, 5, 3), + end=date(2010, 5, 24), + rule='interval', + interval_days=7) + series.schedule() + + self.assertEqual( + TaskChunkSeries.objects.count(), + 1) + self.assertEqual( + TaskChunk.objects.count(), + 4) + + resp = self.client.put('/task/chunk/series/{}/'.format(series.pk), { + 'task_id': self.task.pk, + 'duration': '2', + 'start': '2010-05-03', + 'end': '2010-05-10', + 'rule': 'interval', + 'interval_days': 7, + }) + self.assertEqual( + resp.status_code, + status.HTTP_200_OK) + + self.assertSetEqual( + set(resp.data.keys()), + {'series', 'cleaned', 'scheduled'}) + + self.assertEqual( + len(resp.data['cleaned']), + 2) + self.assertEqual( + len(resp.data['scheduled']), + 0) + + self.assertEqual( + TaskChunk.objects.count(), + 2) + + @freeze_time('2010-05-03') + def test_update_scheduling(self): + """ + Test the scheduling of task chunks when modifying the end date. + """ + series = TaskChunkSeries.objects.create( + task=self.task, + start=date(2010, 5, 3), + end=date(2010, 5, 24), + rule='interval', + interval_days=7) + series.schedule() + + self.assertEqual( + TaskChunkSeries.objects.count(), + 1) + self.assertEqual( + TaskChunk.objects.count(), + 4) + + resp = self.client.put('/task/chunk/series/{}/'.format(series.pk), { + 'task_id': self.task.pk, + 'duration': '2', + 'start': '2010-05-03', + 'end': '2010-06-7', + 'rule': 'interval', + 'interval_days': 7, + }) + self.assertEqual( + resp.status_code, + status.HTTP_200_OK) + + self.assertSetEqual( + set(resp.data.keys()), + {'series', 'cleaned', 'scheduled'}) + + self.assertEqual( + len(resp.data['cleaned']), + 0) + self.assertEqual( + len(resp.data['scheduled']), + 2) + + self.assertEqual( + TaskChunk.objects.count(), + 6) + def test_partial_update(self): """ Test that it is not allowed to partially update a task chunk series. diff --git a/task/views.py b/task/views.py index ce1c475..8c80d2e 100644 --- a/task/views.py +++ b/task/views.py @@ -2,7 +2,7 @@ from django.db.models import F from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import action -from rest_framework.exceptions import MethodNotAllowed, ParseError, ValidationError +from rest_framework.exceptions import ParseError, ValidationError from rest_framework.generics import get_object_or_404 from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -38,7 +38,7 @@ def merge(self, request, pk: int, other_pk: int): class TaskChunkSeriesViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, - mixins.RetrieveModelMixin, mixins.UpdateModelMixin): + mixins.RetrieveModelMixin): permission_classes = IsAuthenticated, serializer_class = TaskChunkSeriesSerializer @@ -60,8 +60,23 @@ def create(self, request, *args, **kwargs): 'scheduled': scheduled_serializer.data, }, status=status.HTTP_201_CREATED) - def partial_update(self, request, *args, **kwargs): - raise MethodNotAllowed('PATCH') + @transaction.atomic + def update(self, request, *args, **kwargs): + instance = self.get_object() + serializer = self.get_serializer(instance, data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + + cleaned = instance.clean_scheduled() + + scheduled = instance.schedule() + scheduled_serializer = TaskChunkSerializer(scheduled, many=True) + + return Response({ + 'series': serializer.data, + 'scheduled': scheduled_serializer.data, + 'cleaned': cleaned, + }) class TaskChunkViewSet(viewsets.GenericViewSet, mixins.CreateModelMixin,