From e6acfe4e75dc808c5a64c0bad6ceb2bae779f410 Mon Sep 17 00:00:00 2001 From: Christopher Charbonneau Wells Date: Sun, 5 Nov 2017 14:18:30 -0500 Subject: [PATCH] Add date intersection validation for models with start and end dates. --- core/models.py | 19 +++++++++++++++ core/tests/tests_forms.py | 51 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/core/models.py b/core/models.py index 8a4e00b4..3931a910 100644 --- a/core/models.py +++ b/core/models.py @@ -26,6 +26,22 @@ def validate_duration(model, max_duration=timedelta(hours=24)): raise ValidationError('Duration too long.', code='max_duration') +def validate_unique_period(queryset, model): + """ + Confirm that model's start and end date do not intersect with other + instances. + :param queryset: a queryset of instances to check against. + :param model: a model instance with 'start' and 'end' attributes + :return: + """ + if model.id: + queryset = queryset.exclude(id=model.id) + if queryset.filter(start__lte=model.end, end__gte=model.start): + raise ValidationError( + 'Another entry already exists within the specified time period.', + code='period_intersection') + + def validate_time(time, field_name): """ Confirm that a time is not in the future. @@ -149,6 +165,7 @@ class Feeding(models.Model): validate_time(self.start, 'start') validate_time(self.end, 'end') validate_duration(self) + validate_unique_period(Feeding.objects.filter(child=self.child), self) # "Formula" Type may only be associated with "Bottle" Method. if self.type == 'formula'and self.method != 'bottle': @@ -216,6 +233,7 @@ class Sleep(models.Model): validate_time(self.start, 'start') validate_time(self.end, 'end') validate_duration(self) + validate_unique_period(Sleep.objects.filter(child=self.child), self) class Timer(models.Model): @@ -304,3 +322,4 @@ class TummyTime(models.Model): validate_time(self.start, 'start') validate_time(self.end, 'end') validate_duration(self) + validate_unique_period(TummyTime.objects.filter(child=self.child), self) diff --git a/core/tests/tests_forms.py b/core/tests/tests_forms.py index 32cfede2..f60486f8 100644 --- a/core/tests/tests_forms.py +++ b/core/tests/tests_forms.py @@ -97,6 +97,9 @@ class FormsTestCase(TestCase): page = self.c.post('/feedings/add/?timer={}'.format(timer.id), params) self.assertEqual(page.status_code, 302) + # Change start and end to prevent intersection validation errors. + params['start'] = '2000-01-01 2:01' + params['end'] = '2000-01-01 2:31' page = self.c.post('/feedings/{}/'.format(entry.id), params) self.assertEqual(page.status_code, 302) @@ -107,7 +110,7 @@ class FormsTestCase(TestCase): page, 'form', 'method', 'Only "Bottle" method is allowed with "Formula" type.') - def test_sleeping_forms(self): + def test_sleep_forms(self): params = { 'child': 1, 'start': '2000-01-01 1:01', @@ -119,6 +122,9 @@ class FormsTestCase(TestCase): page = self.c.post('/sleep/add/?timer={}'.format(timer.id), params) self.assertEqual(page.status_code, 302) + # Change start and end to prevent intersection validation errors. + params['start'] = '2000-01-01 4:01' + params['end'] = '2000-01-01 6:01' entry = models.Sleep.objects.first() page = self.c.post('/sleep/{}/'.format(entry.id), params) self.assertEqual(page.status_code, 302) @@ -167,11 +173,14 @@ class FormsTestCase(TestCase): '/tummy-time/add/?timer={}'.format(timer.id), params) self.assertEqual(page.status_code, 302) + # Change start and end to prevent intersection validation errors. + params['start'] = '2000-01-01 2:01' + params['end'] = '2000-01-01 2:11' entry = models.TummyTime.objects.first() page = self.c.post('/tummy-time/{}/'.format(entry.id), params) self.assertEqual(page.status_code, 302) - def test_validators(self): + def test_validate_duration(self): params = { 'child': 1, 'start': '2001-01-01 1:01', @@ -190,9 +199,43 @@ class FormsTestCase(TestCase): self.assertEqual(page.status_code, 200) self.assertFormError(page, 'form', None, 'Duration too long.') - tomorrow = (timezone.localtime() + timezone.timedelta(days=1)) - params['end'] = tomorrow.strftime('%Y-%m-%d %H:%M:%S') + def test_validate_time(self): + future = (timezone.localtime() + timezone.timedelta(hours=1)) + params = { + 'child': 1, + 'start': timezone.localtime().strftime('%Y-%m-%d %H:%M:%S'), + 'end': future.strftime('%Y-%m-%d %H:%M:%S'), + 'milestone': '' + } + entry = models.TummyTime.objects.first() + page = self.c.post('/tummy-time/{}/'.format(entry.id), params) self.assertEqual(page.status_code, 200) self.assertFormError(page, 'form', 'end', 'Date/time can not be in the future.') + + def test_validate_unique_period(self): + entry = models.TummyTime.objects.first() + base_time = timezone.localtime() + new_entry = models.TummyTime.objects.create( + child=entry.child, + start=base_time - timezone.timedelta(minutes=45), + end=base_time - timezone.timedelta(minutes=15), + ) + new_entry.save() + + params = { + 'child': 1, + 'start': (base_time - timezone.timedelta(minutes=35)).strftime( + '%Y-%m-%d %H:%M'), + 'end': (base_time - timezone.timedelta(minutes=5)).strftime( + '%Y-%m-%d %H:%M'), + 'milestone': '' + } + page = self.c.post('/tummy-time/{}/'.format(entry.id), params) + self.assertEqual(page.status_code, 200) + self.assertFormError( + page, + 'form', + None, + 'Another entry already exists within the specified time period.')