From 4a3ef63d4e48f83732013f076701d873afb6c2c2 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Mon, 20 Feb 2023 15:43:52 -0500 Subject: [PATCH 01/19] allow irdering on retreat name for reservation --- retirement/tests/tests_viewset_Reservation.py | 56 +++++++++++++++++++ retirement/views.py | 1 + 2 files changed, 57 insertions(+) diff --git a/retirement/tests/tests_viewset_Reservation.py b/retirement/tests/tests_viewset_Reservation.py index fa9d5841..639b0cc8 100644 --- a/retirement/tests/tests_viewset_Reservation.py +++ b/retirement/tests/tests_viewset_Reservation.py @@ -625,6 +625,62 @@ def test_list(self): self.check_attributes(content['results'][0]) + def test_list_ordering_by_retreat_name(self): + """ + Test that we can order the reservation list by retreat name + """ + self.client.force_authenticate(user=self.admin) + response = self.client.get( + reverse('retreat:reservation-list'), + { + 'ordering': 'retreat__name' + }, + format='json', + ) + + content = json.loads(response.content) + + self.assertEqual( + response.status_code, + status.HTTP_200_OK + ) + + self.assertEqual(content['count'], 3) + self.assertEqual( + content['results'][0]['retreat_details']['name'], + self.retreat.name + ) + self.assertEqual( + content['results'][2]['retreat_details']['name'], + self.retreat2.name + ) + + self.client.force_authenticate(user=self.admin) + response = self.client.get( + reverse('retreat:reservation-list'), + { + 'ordering': '-retreat__name' + }, + format='json', + ) + + content = json.loads(response.content) + + self.assertEqual( + response.status_code, + status.HTTP_200_OK + ) + + self.assertEqual(content['count'], 3) + self.assertEqual( + content['results'][0]['retreat_details']['name'], + self.retreat2.name + ) + self.assertEqual( + content['results'][2]['retreat_details']['name'], + self.retreat.name + ) + def test_list_as_non_admin(self): """ Ensure that a user can list its reservations. diff --git a/retirement/views.py b/retirement/views.py index 11404de2..eb8f648f 100644 --- a/retirement/views.py +++ b/retirement/views.py @@ -581,6 +581,7 @@ class ReservationViewSet(ExportMixin, viewsets.ModelViewSet): 'cancelation_date', 'cancelation_reason', 'cancelation_action', + 'retreat__name', ) export_resource = ReservationResource() From 84962e4fdd62d28beaee047271ad1bbf4c7aa5ff Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Mon, 20 Feb 2023 17:43:08 -0500 Subject: [PATCH 02/19] factorise function for tomato view --- tomato/views.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tomato/views.py b/tomato/views.py index 39ca1e8f..55bf0a67 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -261,6 +261,16 @@ def get_permissions(self): ] return [permission() for permission in permission_classes] + @staticmethod + def get_queryset_number_of_tomatoes(queryset): + """ + Returns the number of tomatoes for a given queryset + """ + nb_tomatoes = queryset.aggregate( + Sum('number_of_tomato'))['number_of_tomato__sum'] + nb_tomatoes = nb_tomatoes if nb_tomatoes else 0 + return nb_tomatoes + @action(detail=False, methods=["get"]) def community_tomatoes(self, request): """ @@ -271,10 +281,7 @@ def community_tomatoes(self, request): start = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0) t = Tomato.objects.filter( acquisition_date__gte=start, acquisition_date__lte=today) - nb_tomatoes = t.aggregate( - Sum('number_of_tomato'))['number_of_tomato__sum'] - nb_tomatoes = nb_tomatoes if nb_tomatoes else 0 response_data = { - 'community_tomato': nb_tomatoes, + 'community_tomato': self.get_queryset_number_of_tomatoes(t), } return Response(response_data, status=status.HTTP_200_OK) From 68e1b0cefee51a9684831d190854f1bafc47d65c Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Mon, 20 Feb 2023 17:43:25 -0500 Subject: [PATCH 03/19] create custom action for tomato statistics --- tomato/views.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tomato/views.py b/tomato/views.py index 55bf0a67..273951e0 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -247,7 +247,7 @@ def get_queryset(self): return queryset def get_permissions(self): - if self.action in ['create', 'list', 'retrieve']: + if self.action in ['create', 'list', 'retrieve', 'statistics']: permission_classes = [ IsAuthenticated, ] @@ -285,3 +285,40 @@ def community_tomatoes(self, request): 'community_tomato': self.get_queryset_number_of_tomatoes(t), } return Response(response_data, status=status.HTTP_200_OK) + + @action(detail=False, methods=["get"]) + def statistics(self, request): + period = request.query_params.get('period', None) + if period and period in ['week', 'month', 'year']: + today = timezone.now() + first_day_init = today.replace( + hour=0, minute=0, second=0, microsecond=0) + first_day = { + 'year': first_day_init.replace(day=1, month=1), + 'month': first_day_init.replace(day=1), + 'week': first_day_init - timezone.timedelta( + first_day_init.weekday()), + } + all_queryset = Tomato.objects.filter( + acquisition_date__lte=today, + acquisition_date__gte=first_day[period], + ) + user_queryset = all_queryset.filter(user=request.user) + totals = { + "global": self.get_queryset_number_of_tomatoes(all_queryset), + "user": self.get_queryset_number_of_tomatoes(user_queryset) + } + return Response( + { + "totals": totals, + }, + status=status.HTTP_200_OK, + ) + + else: + return Response( + { + 'period': _('Please select a valid period'), + }, + status=status.HTTP_400_BAD_REQUEST, + ) From 2944f53562806ce84862afba9a42d4d4653c4555 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Mon, 20 Feb 2023 17:45:27 -0500 Subject: [PATCH 04/19] add tests for tomato statistics --- tomato/tests/tests_viewset_Tomato.py | 186 +++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) diff --git a/tomato/tests/tests_viewset_Tomato.py b/tomato/tests/tests_viewset_Tomato.py index 963dc517..80d4dc98 100644 --- a/tomato/tests/tests_viewset_Tomato.py +++ b/tomato/tests/tests_viewset_Tomato.py @@ -484,3 +484,189 @@ def test_community_tomatoes(self): result['community_tomato'], sum(current_entries) ) + + def test_statistics_tomatoes_invalid_period(self): + """ + Test we cant get tomatoes statistics with invalid period + """ + self.client.force_authenticate(user=self.user) + response = self.client.get( + reverse('tomato-statistics'), + data={'period': 'invalid period'}, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_400_BAD_REQUEST, + response.content + ) + + def test_statistics_tomatoes_no_period(self): + """ + Test we cant get tomatoes statistics with invalid period + """ + self.client.force_authenticate(user=self.user) + response = self.client.get( + reverse('tomato-statistics'), + format='json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_400_BAD_REQUEST, + response.content + ) + + def test_statistics_tomatoes_year(self): + """ + Test we can get tomatoes statistics of current year + """ + self.client.force_authenticate(user=self.user) + response = self.client.get( + reverse('tomato-statistics'), + data={'period': 'year'}, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], 0) + self.assertEqual(result['totals']['user'], 0) + + today = timezone.now() + # Out of year tomatoes + last_year = today - timedelta(days=365) + Tomato.objects.create(user=self.user, number_of_tomato=5) + Tomato.objects.create(user=self.admin, number_of_tomato=4) + Tomato.objects.create(user=self.user, number_of_tomato=7) + Tomato.objects.all().update(acquisition_date=last_year) + + t1 = Tomato.objects.create(user=self.user, number_of_tomato=15) + t2 = Tomato.objects.create(user=self.admin, number_of_tomato=23) + t3 = Tomato.objects.create(user=self.user, number_of_tomato=45) + current_entries = [ + t1.number_of_tomato, t2.number_of_tomato, t3.number_of_tomato] + user_entries = [ + t1.number_of_tomato, t3.number_of_tomato] + + response = self.client.get( + reverse('tomato-statistics'), + data={'period': 'year'}, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], sum(current_entries)) + self.assertEqual(result['totals']['user'], sum(user_entries)) + + def test_statistics_tomatoes_month(self): + """ + Test we can get tomatoes statistics of current month + """ + self.client.force_authenticate(user=self.user) + response = self.client.get( + reverse('tomato-statistics'), + data={'period': 'year'}, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], 0) + self.assertEqual(result['totals']['user'], 0) + + today = timezone.now() + # Out of month tomatoes + last_month = today - timedelta(days=32) + Tomato.objects.create(user=self.user, number_of_tomato=5) + Tomato.objects.create(user=self.admin, number_of_tomato=4) + Tomato.objects.create(user=self.user, number_of_tomato=7) + Tomato.objects.all().update(acquisition_date=last_month) + + t1 = Tomato.objects.create(user=self.user, number_of_tomato=15) + t2 = Tomato.objects.create(user=self.admin, number_of_tomato=23) + t3 = Tomato.objects.create(user=self.user, number_of_tomato=45) + current_entries = [ + t1.number_of_tomato, t2.number_of_tomato, t3.number_of_tomato] + user_entries = [ + t1.number_of_tomato, t3.number_of_tomato] + + response = self.client.get( + reverse('tomato-statistics'), + data={'period': 'month'}, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], sum(current_entries)) + self.assertEqual(result['totals']['user'], sum(user_entries)) + + def test_statistics_tomatoes_week(self): + """ + Test we can get tomatoes statistics of current week + """ + self.client.force_authenticate(user=self.user) + response = self.client.get( + reverse('tomato-statistics'), + data={'period': 'year'}, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], 0) + self.assertEqual(result['totals']['user'], 0) + + today = timezone.now() + # Out of week tomatoes + last_week = today - timedelta(days=8) + Tomato.objects.create(user=self.user, number_of_tomato=5) + Tomato.objects.create(user=self.admin, number_of_tomato=4) + Tomato.objects.create(user=self.user, number_of_tomato=7) + Tomato.objects.all().update(acquisition_date=last_week) + + t1 = Tomato.objects.create(user=self.user, number_of_tomato=15) + t2 = Tomato.objects.create(user=self.admin, number_of_tomato=23) + t3 = Tomato.objects.create(user=self.user, number_of_tomato=45) + current_entries = [ + t1.number_of_tomato, t2.number_of_tomato, t3.number_of_tomato] + user_entries = [ + t1.number_of_tomato, t3.number_of_tomato] + + response = self.client.get( + reverse('tomato-statistics'), + data={'period': 'week'}, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], sum(current_entries)) + self.assertEqual(result['totals']['user'], sum(user_entries)) From 4d514a60b249b6d553195688df32f9cfe01e5bc4 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Thu, 23 Feb 2023 13:42:04 -0500 Subject: [PATCH 05/19] retrieve email before cancelation --- retirement/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/retirement/models.py b/retirement/models.py index 1a66dd35..858ad723 100644 --- a/retirement/models.py +++ b/retirement/models.py @@ -997,11 +997,13 @@ def process_impacted_users(self, reason, reason_message, force_refund): they can book again the retreat if they want """ if self.total_reservations > 0: + # retrieve email before we cancel the reservation + emails = self.get_participants_emails() from .services import send_updated_retreat_email self.cancel_participants_reservation(force_refund) send_updated_retreat_email( self, - self.get_participants_emails(), + emails, reason, reason_message, ) From 44c5eb20cc6e7e26ccedf310ebceb7bcc449e816 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Thu, 23 Feb 2023 16:00:17 -0500 Subject: [PATCH 06/19] update to just takes 2 dates --- tomato/views.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/tomato/views.py b/tomato/views.py index 273951e0..a5f10088 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -1,4 +1,5 @@ import asyncio +from django.utils.dateparse import parse_datetime from tomato.models import ( Message, Attendance, @@ -288,20 +289,13 @@ def community_tomatoes(self, request): @action(detail=False, methods=["get"]) def statistics(self, request): - period = request.query_params.get('period', None) - if period and period in ['week', 'month', 'year']: - today = timezone.now() - first_day_init = today.replace( - hour=0, minute=0, second=0, microsecond=0) - first_day = { - 'year': first_day_init.replace(day=1, month=1), - 'month': first_day_init.replace(day=1), - 'week': first_day_init - timezone.timedelta( - first_day_init.weekday()), - } + start = parse_datetime(request.query_params.get('start_date', None)) + end = parse_datetime(request.query_params.get('end_date', None)) + + if start and end and end > start: all_queryset = Tomato.objects.filter( - acquisition_date__lte=today, - acquisition_date__gte=first_day[period], + acquisition_date__lte=end, + acquisition_date__gte=start, ) user_queryset = all_queryset.filter(user=request.user) totals = { @@ -318,7 +312,7 @@ def statistics(self, request): else: return Response( { - 'period': _('Please select a valid period'), + 'dates': _('Please select a valid start and end dates'), }, status=status.HTTP_400_BAD_REQUEST, ) From d6351a7a85b32a338e8a73fc139b8578939825de Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Thu, 23 Feb 2023 16:00:44 -0500 Subject: [PATCH 07/19] update tests --- tomato/tests/tests_viewset_Tomato.py | 140 ++++++++------------------- 1 file changed, 38 insertions(+), 102 deletions(-) diff --git a/tomato/tests/tests_viewset_Tomato.py b/tomato/tests/tests_viewset_Tomato.py index 80d4dc98..42d58617 100644 --- a/tomato/tests/tests_viewset_Tomato.py +++ b/tomato/tests/tests_viewset_Tomato.py @@ -485,14 +485,17 @@ def test_community_tomatoes(self): sum(current_entries) ) - def test_statistics_tomatoes_invalid_period(self): + def test_statistics_tomatoes_invalid_start(self): """ - Test we cant get tomatoes statistics with invalid period + Test we cant get tomatoes statistics with invalid start """ self.client.force_authenticate(user=self.user) response = self.client.get( reverse('tomato-statistics'), - data={'period': 'invalid period'}, + data={ + 'start_date': 'invalid start', + 'end_date': '2010-01-01T00:00:00Z' + }, content_type='application/json', ) result = response.json() @@ -502,13 +505,17 @@ def test_statistics_tomatoes_invalid_period(self): response.content ) - def test_statistics_tomatoes_no_period(self): + def test_statistics_tomatoes_invalid_end(self): """ - Test we cant get tomatoes statistics with invalid period + Test we cant get tomatoes statistics with invalid end """ self.client.force_authenticate(user=self.user) response = self.client.get( reverse('tomato-statistics'), + data={ + 'end_date': 'invalid start', + 'start_date': '2010-01-01T00:00:00Z' + }, format='json', ) result = response.json() @@ -518,116 +525,43 @@ def test_statistics_tomatoes_no_period(self): response.content ) - def test_statistics_tomatoes_year(self): + def test_statistics_tomatoes_invalid_dates(self): """ - Test we can get tomatoes statistics of current year + Test we cant get tomatoes statistics with invalid dates """ self.client.force_authenticate(user=self.user) response = self.client.get( reverse('tomato-statistics'), - data={'period': 'year'}, - content_type='application/json', - ) - result = response.json() - self.assertEqual( - response.status_code, - status.HTTP_200_OK, - response.content - ) - - self.assertEqual(result['totals']['global'], 0) - self.assertEqual(result['totals']['user'], 0) - - today = timezone.now() - # Out of year tomatoes - last_year = today - timedelta(days=365) - Tomato.objects.create(user=self.user, number_of_tomato=5) - Tomato.objects.create(user=self.admin, number_of_tomato=4) - Tomato.objects.create(user=self.user, number_of_tomato=7) - Tomato.objects.all().update(acquisition_date=last_year) - - t1 = Tomato.objects.create(user=self.user, number_of_tomato=15) - t2 = Tomato.objects.create(user=self.admin, number_of_tomato=23) - t3 = Tomato.objects.create(user=self.user, number_of_tomato=45) - current_entries = [ - t1.number_of_tomato, t2.number_of_tomato, t3.number_of_tomato] - user_entries = [ - t1.number_of_tomato, t3.number_of_tomato] - - response = self.client.get( - reverse('tomato-statistics'), - data={'period': 'year'}, - content_type='application/json', + data={ + 'start_date': '2012-01-01T00:00:00Z', + 'end_date': '2010-01-01T00:00:00Z' + }, + format='json', ) result = response.json() self.assertEqual( response.status_code, - status.HTTP_200_OK, + status.HTTP_400_BAD_REQUEST, response.content ) - self.assertEqual(result['totals']['global'], sum(current_entries)) - self.assertEqual(result['totals']['user'], sum(user_entries)) - - def test_statistics_tomatoes_month(self): + def test_statistics_tomatoes_year(self): """ - Test we can get tomatoes statistics of current month + Test we can get tomatoes statistics of current year """ - self.client.force_authenticate(user=self.user) - response = self.client.get( - reverse('tomato-statistics'), - data={'period': 'year'}, - content_type='application/json', - ) - result = response.json() - self.assertEqual( - response.status_code, - status.HTTP_200_OK, - response.content - ) - - self.assertEqual(result['totals']['global'], 0) - self.assertEqual(result['totals']['user'], 0) - today = timezone.now() - # Out of month tomatoes - last_month = today - timedelta(days=32) - Tomato.objects.create(user=self.user, number_of_tomato=5) - Tomato.objects.create(user=self.admin, number_of_tomato=4) - Tomato.objects.create(user=self.user, number_of_tomato=7) - Tomato.objects.all().update(acquisition_date=last_month) - - t1 = Tomato.objects.create(user=self.user, number_of_tomato=15) - t2 = Tomato.objects.create(user=self.admin, number_of_tomato=23) - t3 = Tomato.objects.create(user=self.user, number_of_tomato=45) - current_entries = [ - t1.number_of_tomato, t2.number_of_tomato, t3.number_of_tomato] - user_entries = [ - t1.number_of_tomato, t3.number_of_tomato] - - response = self.client.get( - reverse('tomato-statistics'), - data={'period': 'month'}, - content_type='application/json', - ) - result = response.json() - self.assertEqual( - response.status_code, - status.HTTP_200_OK, - response.content - ) + last_year = today - timedelta(days=366) + limit_last_year = last_year + timedelta(days=1) - self.assertEqual(result['totals']['global'], sum(current_entries)) - self.assertEqual(result['totals']['user'], sum(user_entries)) - - def test_statistics_tomatoes_week(self): - """ - Test we can get tomatoes statistics of current week - """ + end = today.strftime('%Y-%m-%dT%H:%M:%SZ') + start = limit_last_year.strftime('%Y-%m-%dT%H:%M:%SZ') self.client.force_authenticate(user=self.user) response = self.client.get( reverse('tomato-statistics'), - data={'period': 'year'}, + data={ + 'start_date': start, + 'end_date': end + }, content_type='application/json', ) result = response.json() @@ -639,14 +573,11 @@ def test_statistics_tomatoes_week(self): self.assertEqual(result['totals']['global'], 0) self.assertEqual(result['totals']['user'], 0) - - today = timezone.now() - # Out of week tomatoes - last_week = today - timedelta(days=8) + # Out of year tomatoes Tomato.objects.create(user=self.user, number_of_tomato=5) Tomato.objects.create(user=self.admin, number_of_tomato=4) Tomato.objects.create(user=self.user, number_of_tomato=7) - Tomato.objects.all().update(acquisition_date=last_week) + Tomato.objects.all().update(acquisition_date=last_year) t1 = Tomato.objects.create(user=self.user, number_of_tomato=15) t2 = Tomato.objects.create(user=self.admin, number_of_tomato=23) @@ -656,9 +587,14 @@ def test_statistics_tomatoes_week(self): user_entries = [ t1.number_of_tomato, t3.number_of_tomato] + today = timezone.now() + timedelta(seconds=1) + end = today.strftime('%Y-%m-%dT%H:%M:%SZ') response = self.client.get( reverse('tomato-statistics'), - data={'period': 'week'}, + data={ + 'start_date': start, + 'end_date': end + }, content_type='application/json', ) result = response.json() From 5024d940a2fc524b63a7047f4b7b5cdd5d27d225 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Fri, 24 Feb 2023 09:43:23 -0500 Subject: [PATCH 08/19] new function to get retreat active participants --- retirement/models.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/retirement/models.py b/retirement/models.py index 858ad723..28f25f8b 100644 --- a/retirement/models.py +++ b/retirement/models.py @@ -989,6 +989,16 @@ def get_participants_emails(self): participant_emails.add(reservation.user.email) return list(participant_emails) + def get_participants(self): + """ + Return a list of active participants + """ + participants = set() + active_reservations = self.reservations.filter(is_active=True) + for reservation in active_reservations: + participants.add(reservation.user) + return list(participants) + def process_impacted_users(self, reason, reason_message, force_refund): """ Notify and potentially refund user for a reason happening on retreat: @@ -997,13 +1007,13 @@ def process_impacted_users(self, reason, reason_message, force_refund): they can book again the retreat if they want """ if self.total_reservations > 0: - # retrieve email before we cancel the reservation - emails = self.get_participants_emails() + # retrieve active users before we cancel the reservation + users = self.get_participants() from .services import send_updated_retreat_email self.cancel_participants_reservation(force_refund) send_updated_retreat_email( self, - emails, + users, reason, reason_message, ) From e5cac0de48c724a6e36a377cb4af579d279e284b Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Fri, 24 Feb 2023 09:43:38 -0500 Subject: [PATCH 09/19] update email to also send list of users --- retirement/services.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/retirement/services.py b/retirement/services.py index 4597f149..e0106e2f 100644 --- a/retirement/services.py +++ b/retirement/services.py @@ -376,7 +376,7 @@ def send_automatic_email(user, retreat, email): return response_send_mail -def send_updated_retreat_email(retreat, users_emails, reason, reason_message): +def send_updated_retreat_email(retreat, users, reason, reason_message): """ This function sends an automatic email to notify all registered users of a retreat that it has been updated. For example dates have changed or @@ -393,7 +393,7 @@ def send_updated_retreat_email(retreat, users_emails, reason, reason_message): } response_send_mail = send_templated_email( - users_emails, + users, context, reason_template[reason] ) From 938662e841a902fec7cd0c0f7a2e2087a885067a Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Fri, 24 Feb 2023 09:44:12 -0500 Subject: [PATCH 10/19] update tests with new function --- retirement/tests/tests_model_Retreat.py | 25 +++++++++++++++++++ retirement/tests/tests_viewset_Retreat.py | 2 +- retirement/tests/tests_viewset_RetreatDate.py | 12 ++++----- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/retirement/tests/tests_model_Retreat.py b/retirement/tests/tests_model_Retreat.py index 79428107..9059f2f9 100644 --- a/retirement/tests/tests_model_Retreat.py +++ b/retirement/tests/tests_model_Retreat.py @@ -752,3 +752,28 @@ def test_get_participants_emails(self): self.assertEqual(2, len(emails)) self.assertTrue(user.email in emails) self.assertTrue(user2.email in emails) + + def test_get_participants_participants(self): + user = UserFactory() + user2 = UserFactory() + user3 = UserFactory() + + Reservation.objects.create( + user=user, + retreat=self.retreat, + is_active=True, + ) + Reservation.objects.create( + user=user2, + retreat=self.retreat, + is_active=True, + ) + Reservation.objects.create( + user=user3, + retreat=self.retreat, + is_active=False, + ) + users = self.retreat.get_participants() + self.assertEqual(2, len(users)) + self.assertTrue(user in users) + self.assertTrue(user2 in users) diff --git a/retirement/tests/tests_viewset_Retreat.py b/retirement/tests/tests_viewset_Retreat.py index 9e9e1865..e2eb5bf8 100644 --- a/retirement/tests/tests_viewset_Retreat.py +++ b/retirement/tests/tests_viewset_Retreat.py @@ -1111,7 +1111,7 @@ def test_delete_with_participants(self, mock_email, mock_cancel): mock_email.assert_called_once_with( self.retreat, - self.retreat.get_participants_emails(), + self.retreat.get_participants(), 'deletion', deletion_message ) diff --git a/retirement/tests/tests_viewset_RetreatDate.py b/retirement/tests/tests_viewset_RetreatDate.py index 4a14874e..27383674 100644 --- a/retirement/tests/tests_viewset_RetreatDate.py +++ b/retirement/tests/tests_viewset_RetreatDate.py @@ -204,7 +204,7 @@ def test_update_no_change_as_admin(self, mock_email, mock_cancel): mock_email.assert_called_once_with( self.retreat, - self.retreat.get_participants_emails(), + self.retreat.get_participants(), 'update', reason_message, ) @@ -254,7 +254,7 @@ def test_update_change_after_as_admin(self, mock_email, mock_cancel): mock_email.assert_called_once_with( self.retreat, - self.retreat.get_participants_emails(), + self.retreat.get_participants(), 'update', reason_message, ) @@ -304,7 +304,7 @@ def test_update_change_before_as_admin(self, mock_email, mock_cancel): mock_email.assert_called_once_with( self.retreat, - self.retreat.get_participants_emails(), + self.retreat.get_participants(), 'update', reason_message, ) @@ -374,7 +374,7 @@ def test_delete_no_change_as_admin(self, mock_email, mock_cancel): mock_email.assert_called_once_with( self.retreat, - self.retreat.get_participants_emails(), + self.retreat.get_participants(), 'update', reason_message, ) @@ -422,7 +422,7 @@ def test_delete_change_after_as_admin(self, mock_email, mock_cancel): mock_email.assert_called_once_with( self.retreat, - self.retreat.get_participants_emails(), + self.retreat.get_participants(), 'update', reason_message, ) @@ -470,7 +470,7 @@ def test_delete_change_before_as_admin(self, mock_email, mock_cancel): mock_email.assert_called_once_with( self.retreat, - self.retreat.get_participants_emails(), + self.retreat.get_participants(), 'update', reason_message, ) From adb45e6889fbf97f95702e510378b23096af8c76 Mon Sep 17 00:00:00 2001 From: RignonNoel Date: Sun, 26 Feb 2023 14:25:56 -0500 Subject: [PATCH 11/19] add graph on tomato statistics --- tomato/tests/tests_viewset_Tomato.py | 258 +++++++++++++++++++++++++-- tomato/views.py | 147 +++++++++++++-- 2 files changed, 376 insertions(+), 29 deletions(-) diff --git a/tomato/tests/tests_viewset_Tomato.py b/tomato/tests/tests_viewset_Tomato.py index 42d58617..f7d6c186 100644 --- a/tomato/tests/tests_viewset_Tomato.py +++ b/tomato/tests/tests_viewset_Tomato.py @@ -21,7 +21,7 @@ User = get_user_model() -class ReportTests(CustomAPITestCase): +class TomatoTests(CustomAPITestCase): ATTRIBUTES = [ 'id', @@ -36,7 +36,7 @@ class ReportTests(CustomAPITestCase): @classmethod def setUpClass(cls): - super(ReportTests, cls).setUpClass() + super(TomatoTests, cls).setUpClass() cls.client = APIClient() cls.user = UserFactory() @@ -545,9 +545,10 @@ def test_statistics_tomatoes_invalid_dates(self): response.content ) - def test_statistics_tomatoes_year(self): + def test_statistics_tomatoes_empty_data(self): """ - Test we can get tomatoes statistics of current year + Test we can get tomatoes statistics + even if there is no data at all """ today = timezone.now() last_year = today - timedelta(days=366) @@ -573,22 +574,227 @@ def test_statistics_tomatoes_year(self): self.assertEqual(result['totals']['global'], 0) self.assertEqual(result['totals']['user'], 0) - # Out of year tomatoes + self.assertEqual(result['graph']['labels'], []) + self.assertEqual( + result['graph']['datasets'], + [ + { + 'data': [], + 'label': 'number_of_tomato' + } + ], + ) + + def test_statistics_tomatoes_year(self): + """ + Test we can get tomatoes statistics of current year + """ + today = timezone.now() + last_year = today - timedelta(days=366) + limit_last_year = last_year + timedelta(days=1) + + end = today.strftime('%Y-%m-%dT%H:%M:%SZ') + start = limit_last_year.strftime('%Y-%m-%dT%H:%M:%SZ') + self.client.force_authenticate(user=self.user) + Tomato.objects.create(user=self.user, number_of_tomato=5) Tomato.objects.create(user=self.admin, number_of_tomato=4) Tomato.objects.create(user=self.user, number_of_tomato=7) Tomato.objects.all().update(acquisition_date=last_year) - t1 = Tomato.objects.create(user=self.user, number_of_tomato=15) - t2 = Tomato.objects.create(user=self.admin, number_of_tomato=23) - t3 = Tomato.objects.create(user=self.user, number_of_tomato=45) + t1 = Tomato.objects.create( + user=self.user, + number_of_tomato=15, + acquisition_date=today - timedelta(days=1), + ) + t2 = Tomato.objects.create( + user=self.admin, + number_of_tomato=23, + acquisition_date=today - timedelta(days=2), + ) + t3 = Tomato.objects.create( + user=self.user, + number_of_tomato=45, + acquisition_date=today - timedelta(days=3), + ) current_entries = [ - t1.number_of_tomato, t2.number_of_tomato, t3.number_of_tomato] + t1.number_of_tomato, + t2.number_of_tomato, + t3.number_of_tomato, + ] + user_entries = [ + t1.number_of_tomato, + t3.number_of_tomato, + ] + + response = self.client.get( + reverse('tomato-statistics'), + data={ + 'start_date': start, + 'end_date': end + }, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], sum(current_entries)) + self.assertEqual(result['totals']['user'], sum(user_entries)) + self.assertEqual( + result['graph']['labels'], + ['2023-02-01T00:00:00-0500'], + ) + self.assertEqual( + result['graph']['datasets'], + [ + { + 'label': 'number_of_tomato', + 'data': [ + { + 'x': '2023-02-01T00:00:00-0500', + 'y': 83.0 + } + ] + } + ], + ) + + def test_statistics_tomatoes_month(self): + """ + Test we can get tomatoes statistics of current month + """ + today = timezone.datetime(2023, 2, 26, 16, 0, 0) + last_month = today - timedelta(days=32) + limit_last_month = last_month + timedelta(days=1) + + end = today.strftime('%Y-%m-%dT%H:%M:%SZ') + start = limit_last_month.strftime('%Y-%m-%dT%H:%M:%SZ') + self.client.force_authenticate(user=self.user) + + Tomato.objects.create(user=self.user, number_of_tomato=5) + Tomato.objects.create(user=self.admin, number_of_tomato=4) + Tomato.objects.create(user=self.user, number_of_tomato=7) + Tomato.objects.all().update(acquisition_date=last_month) + + t1 = Tomato.objects.create( + user=self.user, + number_of_tomato=15, + acquisition_date=today - timedelta(days=1), + ) + t2 = Tomato.objects.create( + user=self.admin, + number_of_tomato=23, + acquisition_date=today - timedelta(days=2), + ) + t3 = Tomato.objects.create( + user=self.user, + number_of_tomato=45, + acquisition_date=today - timedelta(days=3), + ) + current_entries = [ + t1.number_of_tomato, + t2.number_of_tomato, + t3.number_of_tomato, + ] user_entries = [ - t1.number_of_tomato, t3.number_of_tomato] + t1.number_of_tomato, + t3.number_of_tomato, + ] + + response = self.client.get( + reverse('tomato-statistics'), + data={ + 'start_date': start, + 'end_date': end + }, + content_type='application/json', + ) + result = response.json() + self.assertEqual( + response.status_code, + status.HTTP_200_OK, + response.content + ) + + self.assertEqual(result['totals']['global'], sum(current_entries)) + self.assertEqual(result['totals']['user'], sum(user_entries)) + self.assertEqual( + result['graph']['labels'], + [ + '2023-02-23T00:00:00-0500', + '2023-02-24T00:00:00-0500', + '2023-02-25T00:00:00-0500' + ], + ) + self.assertEqual( + result['graph']['datasets'], + [ + { + 'label': 'number_of_tomato', + 'data': [ + { + 'x': '2023-02-23T00:00:00-0500', + 'y': 45.0 + }, + { + 'x': '2023-02-24T00:00:00-0500', + 'y': 23.0 + }, + { + 'x': '2023-02-25T00:00:00-0500', + 'y': 15.0 + } + ] + } + ], + ) + + def test_statistics_tomatoes_week(self): + """ + Test we can get tomatoes statistics of current week + """ + today = timezone.datetime(2023, 2, 26, 16, 0, 0) + last_week = today - timedelta(days=7) + limit_last_week = last_week + timedelta(days=1) - today = timezone.now() + timedelta(seconds=1) end = today.strftime('%Y-%m-%dT%H:%M:%SZ') + start = limit_last_week.strftime('%Y-%m-%dT%H:%M:%SZ') + self.client.force_authenticate(user=self.user) + + Tomato.objects.create(user=self.user, number_of_tomato=5) + Tomato.objects.create(user=self.admin, number_of_tomato=4) + Tomato.objects.create(user=self.user, number_of_tomato=7) + Tomato.objects.all().update(acquisition_date=last_week) + + t1 = Tomato.objects.create( + user=self.user, + number_of_tomato=15, + acquisition_date=today - timedelta(days=1), + ) + t2 = Tomato.objects.create( + user=self.admin, + number_of_tomato=23, + acquisition_date=today - timedelta(days=2), + ) + t3 = Tomato.objects.create( + user=self.user, + number_of_tomato=45, + acquisition_date=today - timedelta(days=3), + ) + current_entries = [ + t1.number_of_tomato, + t2.number_of_tomato, + t3.number_of_tomato, + ] + user_entries = [ + t1.number_of_tomato, + t3.number_of_tomato, + ] + response = self.client.get( reverse('tomato-statistics'), data={ @@ -606,3 +812,33 @@ def test_statistics_tomatoes_year(self): self.assertEqual(result['totals']['global'], sum(current_entries)) self.assertEqual(result['totals']['user'], sum(user_entries)) + self.assertEqual( + result['graph']['labels'], + [ + '2023-02-23T00:00:00-0500', + '2023-02-24T00:00:00-0500', + '2023-02-25T00:00:00-0500' + ], + ) + self.assertEqual( + result['graph']['datasets'], + [ + { + 'label': 'number_of_tomato', + 'data': [ + { + 'x': '2023-02-23T00:00:00-0500', + 'y': 45.0 + }, + { + 'x': '2023-02-24T00:00:00-0500', + 'y': 23.0 + }, + { + 'x': '2023-02-25T00:00:00-0500', + 'y': 15.0 + } + ] + } + ], + ) diff --git a/tomato/views.py b/tomato/views.py index a5f10088..c0ec7528 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -1,3 +1,4 @@ +import pytz import asyncio from django.utils.dateparse import parse_datetime from tomato.models import ( @@ -5,6 +6,7 @@ Attendance, Report, Tomato, ) +from django.db.models.functions import TruncMonth, TruncDay from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from rest_framework.response import Response @@ -292,27 +294,136 @@ def statistics(self, request): start = parse_datetime(request.query_params.get('start_date', None)) end = parse_datetime(request.query_params.get('end_date', None)) - if start and end and end > start: - all_queryset = Tomato.objects.filter( - acquisition_date__lte=end, - acquisition_date__gte=start, - ) - user_queryset = all_queryset.filter(user=request.user) - totals = { - "global": self.get_queryset_number_of_tomatoes(all_queryset), - "user": self.get_queryset_number_of_tomatoes(user_queryset) - } - return Response( - { - "totals": totals, - }, - status=status.HTTP_200_OK, - ) - - else: + if not start or not end or end < start: return Response( { 'dates': _('Please select a valid start and end dates'), }, status=status.HTTP_400_BAD_REQUEST, ) + + return Response( + { + "totals": self._get_total_data( + start_date=start, + end_date=end, + ), + "graph": self._get_graph_data( + start_date=start, + end_date=end, + ) + }, + status=status.HTTP_200_OK, + ) + + def _get_total_data(self, start_date, end_date): + all_queryset = Tomato.objects.filter( + acquisition_date__lte=end_date, + acquisition_date__gte=start_date, + ) + user_queryset = all_queryset.filter(user=self.request.user) + totals = { + "global": self.get_queryset_number_of_tomatoes(all_queryset), + "user": self.get_queryset_number_of_tomatoes(user_queryset) + } + + return totals + + def _get_graph_data(self, start_date, end_date): + interval_param = self._define_interval(start_date, end_date) + + self.timezone = self.request.META.get( + 'HTTP_REQUEST_TIMEZONE', + 'America/Montreal', + ) + queryset = Tomato.objects.all() + + if end_date: + queryset = queryset.filter(acquisition_date__lte=end_date) + if start_date: + queryset = queryset.filter(acquisition_date__gte=start_date) + + queryset = queryset.annotate( + interval=self._trunc_interval(interval_param), + ) + + response_data = { + 'labels': self._get_intervals(queryset), + 'datasets': self._get_datasets(queryset) + } + + return response_data + + @staticmethod + def _define_interval(start_date, end_date): + """ + Return interval based on date span. diff_date being given in days and + seconds, we translate it in full seconds to handle partial day. + """ + diff_date = end_date - start_date + seconds_in_day = 3600 * 24 + seconds = diff_date.seconds + (diff_date.days * seconds_in_day) + + if seconds <= 31 * seconds_in_day: + return 'day' + else: + return 'month' + + def _trunc_interval(self, interval_param): + if interval_param == 'hour': + # To handle Daylight date changes AmbiguousTimeError we can't + # Truncate by hour. But Dataset is made by hour, so we just use + # the hour_stamp + return F('acquisition_date') + + trunc_function = TruncMonth + + if interval_param == 'day': + trunc_function = TruncDay + + if interval_param == 'month': + trunc_function = TruncMonth + + return trunc_function( + 'acquisition_date', tzinfo=pytz.timezone(self.timezone) + ) + + @staticmethod + def _get_intervals(queryset): + labels = set( + [ + data.interval.strftime('%Y-%m-%dT%H:%M:%S%z') + for data in queryset + ] + ) + labels = list(labels) + labels.sort() + return labels + + def _get_datasets(self, queryset): + queryset_by_interval = queryset.values('interval').annotate( + number_of_tomato=(Sum('number_of_tomato')), + ) + data_set_types = ['number_of_tomato'] + + data_sets = [] + for data_set_type in data_set_types: + data_set = { + 'label': data_set_type, + 'data': self._get_data( + queryset_by_interval, + data_set_type, + ) + } + data_sets.append(data_set) + + return data_sets + + @staticmethod + def _get_data(queryset, data_set_type): + + return [dict( + {'x': data['interval'].strftime('%Y-%m-%dT%H:%M:%S%z'), + 'y': data.get(data_set_type) + }) + for data in queryset] \ No newline at end of file From 36e0e4057bc3ff7cdbf0d345385ca9e1b37e5d90 Mon Sep 17 00:00:00 2001 From: RignonNoel Date: Sun, 26 Feb 2023 14:27:20 -0500 Subject: [PATCH 12/19] remove unused variable in tests --- tomato/tests/tests_viewset_Tomato.py | 3 --- tomato/views.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tomato/tests/tests_viewset_Tomato.py b/tomato/tests/tests_viewset_Tomato.py index f7d6c186..c6bc3e6a 100644 --- a/tomato/tests/tests_viewset_Tomato.py +++ b/tomato/tests/tests_viewset_Tomato.py @@ -498,7 +498,6 @@ def test_statistics_tomatoes_invalid_start(self): }, content_type='application/json', ) - result = response.json() self.assertEqual( response.status_code, status.HTTP_400_BAD_REQUEST, @@ -518,7 +517,6 @@ def test_statistics_tomatoes_invalid_end(self): }, format='json', ) - result = response.json() self.assertEqual( response.status_code, status.HTTP_400_BAD_REQUEST, @@ -538,7 +536,6 @@ def test_statistics_tomatoes_invalid_dates(self): }, format='json', ) - result = response.json() self.assertEqual( response.status_code, status.HTTP_400_BAD_REQUEST, diff --git a/tomato/views.py b/tomato/views.py index c0ec7528..ccc2c42a 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -426,4 +426,4 @@ def _get_data(queryset, data_set_type): {'x': data['interval'].strftime('%Y-%m-%dT%H:%M:%S%z'), 'y': data.get(data_set_type) }) - for data in queryset] \ No newline at end of file + for data in queryset] From aafe733e72f6fd39ceff0f90091aec960fa4b080 Mon Sep 17 00:00:00 2001 From: RignonNoel Date: Thu, 2 Mar 2023 14:23:00 -0500 Subject: [PATCH 13/19] filter tomato stat graph by user --- tomato/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tomato/views.py b/tomato/views.py index ccc2c42a..0b209bd6 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -336,7 +336,7 @@ def _get_graph_data(self, start_date, end_date): 'HTTP_REQUEST_TIMEZONE', 'America/Montreal', ) - queryset = Tomato.objects.all() + queryset = Tomato.objects.filter(user=self.request.user) if end_date: queryset = queryset.filter(acquisition_date__lte=end_date) From dbe2aa452f59f997abecc8330c321816c1c52ced Mon Sep 17 00:00:00 2001 From: RignonNoel Date: Thu, 2 Mar 2023 16:26:04 -0500 Subject: [PATCH 14/19] adding empty tomato dataset with default 0 --- tomato/tests/tests_viewset_Tomato.py | 168 ++++++++++++++++++++++++--- tomato/views.py | 83 +++++++++---- 2 files changed, 208 insertions(+), 43 deletions(-) diff --git a/tomato/tests/tests_viewset_Tomato.py b/tomato/tests/tests_viewset_Tomato.py index c6bc3e6a..da4b4f13 100644 --- a/tomato/tests/tests_viewset_Tomato.py +++ b/tomato/tests/tests_viewset_Tomato.py @@ -547,7 +547,7 @@ def test_statistics_tomatoes_empty_data(self): Test we can get tomatoes statistics even if there is no data at all """ - today = timezone.now() + today = timezone.datetime(2023, 2, 26, 0, 0, 0) last_year = today - timedelta(days=366) limit_last_year = last_year + timedelta(days=1) @@ -571,13 +571,44 @@ def test_statistics_tomatoes_empty_data(self): self.assertEqual(result['totals']['global'], 0) self.assertEqual(result['totals']['user'], 0) - self.assertEqual(result['graph']['labels'], []) + self.assertEqual( + result['graph']['labels'], + [ + '2022-02-01T00:00:00-0500', + '2022-03-01T00:00:00-0500', + '2022-04-01T00:00:00-0500', + '2022-05-01T00:00:00-0500', + '2022-06-01T00:00:00-0500', + '2022-07-01T00:00:00-0500', + '2022-08-01T00:00:00-0500', + '2022-09-01T00:00:00-0500', + '2022-10-01T00:00:00-0500', + '2022-11-01T00:00:00-0500', + '2022-12-01T00:00:00-0500', + '2023-01-01T00:00:00-0500', + '2023-02-01T00:00:00-0500', + ], + ) self.assertEqual( result['graph']['datasets'], [ { - 'data': [], - 'label': 'number_of_tomato' + 'label': 'number_of_tomato', + 'data': [ + {'x': '2022-02-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-03-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-04-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-05-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-06-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-07-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-08-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-09-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-10-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-11-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-12-01T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-01T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-01T00:00:00-0500', 'y': 0.0}, + ] } ], ) @@ -586,7 +617,7 @@ def test_statistics_tomatoes_year(self): """ Test we can get tomatoes statistics of current year """ - today = timezone.now() + today = timezone.datetime(2023, 2, 26, 0, 0, 0) last_year = today - timedelta(days=366) limit_last_year = last_year + timedelta(days=1) @@ -643,7 +674,21 @@ def test_statistics_tomatoes_year(self): self.assertEqual(result['totals']['user'], sum(user_entries)) self.assertEqual( result['graph']['labels'], - ['2023-02-01T00:00:00-0500'], + [ + '2022-02-01T00:00:00-0500', + '2022-03-01T00:00:00-0500', + '2022-04-01T00:00:00-0500', + '2022-05-01T00:00:00-0500', + '2022-06-01T00:00:00-0500', + '2022-07-01T00:00:00-0500', + '2022-08-01T00:00:00-0500', + '2022-09-01T00:00:00-0500', + '2022-10-01T00:00:00-0500', + '2022-11-01T00:00:00-0500', + '2022-12-01T00:00:00-0500', + '2023-01-01T00:00:00-0500', + '2023-02-01T00:00:00-0500', + ], ) self.assertEqual( result['graph']['datasets'], @@ -651,10 +696,22 @@ def test_statistics_tomatoes_year(self): { 'label': 'number_of_tomato', 'data': [ + {'x': '2022-02-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-03-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-04-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-05-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-06-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-07-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-08-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-09-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-10-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-11-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-12-01T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-01T00:00:00-0500', 'y': 0.0}, { 'x': '2023-02-01T00:00:00-0500', - 'y': 83.0 - } + 'y': 60.0 + }, ] } ], @@ -664,7 +721,7 @@ def test_statistics_tomatoes_month(self): """ Test we can get tomatoes statistics of current month """ - today = timezone.datetime(2023, 2, 26, 16, 0, 0) + today = timezone.datetime(2023, 2, 26, 0, 0, 0) last_month = today - timedelta(days=32) limit_last_month = last_month + timedelta(days=1) @@ -722,9 +779,38 @@ def test_statistics_tomatoes_month(self): self.assertEqual( result['graph']['labels'], [ + '2023-01-25T00:00:00-0500', + '2023-01-26T00:00:00-0500', + '2023-01-27T00:00:00-0500', + '2023-01-28T00:00:00-0500', + '2023-01-29T00:00:00-0500', + '2023-01-30T00:00:00-0500', + '2023-01-31T00:00:00-0500', + '2023-02-01T00:00:00-0500', + '2023-02-02T00:00:00-0500', + '2023-02-03T00:00:00-0500', + '2023-02-04T00:00:00-0500', + '2023-02-05T00:00:00-0500', + '2023-02-06T00:00:00-0500', + '2023-02-07T00:00:00-0500', + '2023-02-08T00:00:00-0500', + '2023-02-09T00:00:00-0500', + '2023-02-10T00:00:00-0500', + '2023-02-11T00:00:00-0500', + '2023-02-12T00:00:00-0500', + '2023-02-13T00:00:00-0500', + '2023-02-14T00:00:00-0500', + '2023-02-15T00:00:00-0500', + '2023-02-16T00:00:00-0500', + '2023-02-17T00:00:00-0500', + '2023-02-18T00:00:00-0500', + '2023-02-19T00:00:00-0500', + '2023-02-20T00:00:00-0500', + '2023-02-21T00:00:00-0500', + '2023-02-22T00:00:00-0500', '2023-02-23T00:00:00-0500', '2023-02-24T00:00:00-0500', - '2023-02-25T00:00:00-0500' + '2023-02-25T00:00:00-0500', ], ) self.assertEqual( @@ -733,14 +819,40 @@ def test_statistics_tomatoes_month(self): { 'label': 'number_of_tomato', 'data': [ + {'x': '2023-01-25T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-26T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-27T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-28T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-29T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-30T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-31T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-01T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-02T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-03T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-04T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-05T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-06T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-07T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-08T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-09T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-10T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-11T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-12T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-13T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-14T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-15T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-16T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-17T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-18T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-19T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-20T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-21T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-22T00:00:00-0500', 'y': 0.0}, { 'x': '2023-02-23T00:00:00-0500', 'y': 45.0 }, - { - 'x': '2023-02-24T00:00:00-0500', - 'y': 23.0 - }, + {'x': '2023-02-24T00:00:00-0500', 'y': 0.0}, { 'x': '2023-02-25T00:00:00-0500', 'y': 15.0 @@ -754,7 +866,7 @@ def test_statistics_tomatoes_week(self): """ Test we can get tomatoes statistics of current week """ - today = timezone.datetime(2023, 2, 26, 16, 0, 0) + today = timezone.datetime(2023, 2, 26, 0, 0, 0) last_week = today - timedelta(days=7) limit_last_week = last_week + timedelta(days=1) @@ -812,9 +924,13 @@ def test_statistics_tomatoes_week(self): self.assertEqual( result['graph']['labels'], [ + '2023-02-19T00:00:00-0500', + '2023-02-20T00:00:00-0500', + '2023-02-21T00:00:00-0500', + '2023-02-22T00:00:00-0500', '2023-02-23T00:00:00-0500', '2023-02-24T00:00:00-0500', - '2023-02-25T00:00:00-0500' + '2023-02-25T00:00:00-0500', ], ) self.assertEqual( @@ -823,19 +939,35 @@ def test_statistics_tomatoes_week(self): { 'label': 'number_of_tomato', 'data': [ + { + 'x': '2023-02-19T00:00:00-0500', + 'y': 0.0, + }, + { + 'x': '2023-02-20T00:00:00-0500', + 'y': 0.0, + }, + { + 'x': '2023-02-21T00:00:00-0500', + 'y': 0.0, + }, + { + 'x': '2023-02-22T00:00:00-0500', + 'y': 0.0, + }, { 'x': '2023-02-23T00:00:00-0500', 'y': 45.0 }, { 'x': '2023-02-24T00:00:00-0500', - 'y': 23.0 + 'y': 0.0 }, { 'x': '2023-02-25T00:00:00-0500', 'y': 15.0 } ] - } + }, ], ) diff --git a/tomato/views.py b/tomato/views.py index 0b209bd6..3b2d218f 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -1,5 +1,6 @@ import pytz import asyncio +from django.conf import settings from django.utils.dateparse import parse_datetime from tomato.models import ( Message, @@ -336,6 +337,10 @@ def _get_graph_data(self, start_date, end_date): 'HTTP_REQUEST_TIMEZONE', 'America/Montreal', ) + + start_date = start_date.astimezone(pytz.timezone(self.timezone)) + end_date = end_date.astimezone(pytz.timezone(self.timezone)) + queryset = Tomato.objects.filter(user=self.request.user) if end_date: @@ -347,9 +352,11 @@ def _get_graph_data(self, start_date, end_date): interval=self._trunc_interval(interval_param), ) + labels = self._get_intervals(start_date, end_date, interval_param) + response_data = { - 'labels': self._get_intervals(queryset), - 'datasets': self._get_datasets(queryset) + 'labels': labels, + 'datasets': self._get_datasets(queryset, labels) } return response_data @@ -370,37 +377,43 @@ def _define_interval(start_date, end_date): return 'month' def _trunc_interval(self, interval_param): - if interval_param == 'hour': - # To handle Daylight date changes AmbiguousTimeError we can't - # Truncate by hour. But Dataset is made by hour, so we just use - # the hour_stamp - return F('acquisition_date') - trunc_function = TruncMonth if interval_param == 'day': trunc_function = TruncDay - if interval_param == 'month': - trunc_function = TruncMonth - return trunc_function( 'acquisition_date', tzinfo=pytz.timezone(self.timezone) ) @staticmethod - def _get_intervals(queryset): - labels = set( - [ - data.interval.strftime('%Y-%m-%dT%H:%M:%S%z') - for data in queryset - ] - ) + def _get_intervals(start, end, interval_param): + labels = set() + + date = start.replace(hour=0, minute=0, second=0) + + if interval_param == 'day': + while end >= date: + labels.add( + date.strftime("%Y-%m-%dT%H:%M:%S%z") + ) + date += timedelta(days=1) + else: + date = date.replace(day=1) + end = end.replace(day=1, hour=0, minute=0, second=0) + while end >= date: + labels.add( + date.strftime("%Y-%m-%dT%H:%M:%S%z") + ) + # Get first day of next month + date += timedelta(days=32) + date = date.replace(day=1) + labels = list(labels) labels.sort() return labels - def _get_datasets(self, queryset): + def _get_datasets(self, queryset, labels): queryset_by_interval = queryset.values('interval').annotate( number_of_tomato=(Sum('number_of_tomato')), ) @@ -413,6 +426,7 @@ def _get_datasets(self, queryset): 'data': self._get_data( queryset_by_interval, data_set_type, + labels, ) } data_sets.append(data_set) @@ -420,10 +434,29 @@ def _get_datasets(self, queryset): return data_sets @staticmethod - def _get_data(queryset, data_set_type): + def _get_data(queryset, data_set_type, labels): + results = list() + + non_covered_labels = labels.copy() + for data in queryset: + label = data['interval'].strftime('%Y-%m-%dT%H:%M:%S%z') + results.append( + { + 'x': label, + 'y': data.get(data_set_type), + } + ) + non_covered_labels.remove(label) + + # Adding missing datasets + for label in non_covered_labels: + results.append( + { + 'x': label, + 'y': 0.0, + } + ) + + results.sort(key=lambda d: parse_datetime(d['x'])) - return [dict( - {'x': data['interval'].strftime('%Y-%m-%dT%H:%M:%S%z'), - 'y': data.get(data_set_type) - }) - for data in queryset] + return results From daebd1cb5a6a082f1d04f06e2de68bd1d6c582ac Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Thu, 2 Mar 2023 14:59:57 -0500 Subject: [PATCH 15/19] refactor tomatoes user function --- blitz_api/models.py | 88 ++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 61 deletions(-) diff --git a/blitz_api/models.py b/blitz_api/models.py index 0259a3df..f238e0f8 100644 --- a/blitz_api/models.py +++ b/blitz_api/models.py @@ -202,102 +202,68 @@ def send_new_activation_email(self): ) def get_number_of_past_tomatoes(self): - timeslots = self.get_nb_tomatoes_timeslot() - virtual_retreats = self.get_nb_tomatoes_virtual_retreat() - physical_retreats = self.get_nb_tomatoes_physical_retreat() - - past_count = timeslots['past'] + \ - virtual_retreats['past'] + \ - physical_retreats['past'] - - custom_tomatoes = Tomato.objects.filter( + today = timezone.now() + nb_tomatoes = Tomato.objects.filter( user=self, - ).aggregate( - Sum('number_of_tomato'), - ) - - if custom_tomatoes['number_of_tomato__sum'] is not None: - past_count += custom_tomatoes['number_of_tomato__sum'] + acquisition_date__lte=today + ).aggregate(Sum('number_of_tomato'))['number_of_tomato__sum'] + nb_tomatoes = nb_tomatoes if nb_tomatoes else 0 - return past_count + return nb_tomatoes def get_number_of_future_tomatoes(self): timeslots = self.get_nb_tomatoes_timeslot() - virtual_retreats = self.get_nb_tomatoes_virtual_retreat() - physical_retreats = self.get_nb_tomatoes_physical_retreat() + retreats = self.get_nb_tomatoes_retreat() - future_count = timeslots['future'] + \ - virtual_retreats['future'] + \ - physical_retreats['future'] + future_count = timeslots['future'] + retreats['future'] return future_count def get_nb_tomatoes_timeslot(self): from workplace.models import Reservation as TimeslotReservation + today = timezone.now() + nb_tomatoes = Tomato.objects.filter( + user=self, + source=Tomato.TOMATO_SOURCE_TIMESLOT, + acquisition_date__lte=today + ).aggregate(Sum('number_of_tomato'))['number_of_tomato__sum'] + past_count = nb_tomatoes if nb_tomatoes else 0 reservations = TimeslotReservation.objects.filter( user=self, is_active=True, ) - - past_count = 0 future_count = 0 - for reservation in reservations: - if reservation.timeslot.end_time < timezone.now(): - past_count += settings.NUMBER_OF_TOMATOES_TIMESLOT - else: - future_count += settings.NUMBER_OF_TOMATOES_TIMESLOT + if reservation.timeslot.end_time >= timezone.now(): + future_count += reservation.timeslot.number_of_tomatoes return { 'past': past_count, 'future': future_count, } - def get_nb_tomatoes_virtual_retreat(self): + def get_nb_tomatoes_retreat(self): from retirement.models import Reservation as RetreatReservation - - reservations = RetreatReservation.objects.filter( + today = timezone.now() + nb_tomatoes = Tomato.objects.filter( user=self, - is_active=True, - retreat__type__is_virtual=True, - ) - - past_count = 0 - future_count = 0 - - for reservation in reservations: - if reservation.retreat.end_time < timezone.now(): - past_count += reservation.retreat.get_number_of_tomatoes() - else: - future_count += reservation.retreat.get_number_of_tomatoes() - - return { - 'past': past_count, - 'future': future_count, - } - - def get_nb_tomatoes_physical_retreat(self): - from retirement.models import Reservation as RetreatReservation + source=Tomato.TOMATO_SOURCE_RETREAT, + acquisition_date__lte=today + ).aggregate(Sum('number_of_tomato'))['number_of_tomato__sum'] + past_count = nb_tomatoes if nb_tomatoes else 0 reservations = RetreatReservation.objects.filter( user=self, is_active=True, - retreat__type__is_virtual=False, ) - - past_count = 0 - future_count = 0 - + all_count = 0 for reservation in reservations: - if reservation.retreat.end_time < timezone.now(): - past_count += reservation.retreat.get_number_of_tomatoes() - else: - future_count += reservation.retreat.get_number_of_tomatoes() + all_count += reservation.retreat.get_number_of_tomatoes() return { 'past': past_count, - 'future': future_count, + 'future': all_count - past_count, } def get_active_membership(self): From b74d8ea7355136c769d32d2506cd2cee631b3008 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Thu, 2 Mar 2023 15:00:20 -0500 Subject: [PATCH 16/19] create missing factories --- blitz_api/factories.py | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/blitz_api/factories.py b/blitz_api/factories.py index 8714d2e1..95aa729c 100644 --- a/blitz_api/factories.py +++ b/blitz_api/factories.py @@ -24,6 +24,12 @@ OrderLineBaseProduct, Coupon, ) +from workplace.models import ( + TimeSlot, + Reservation as TimeSlotReservation, + Workplace, + Period, +) from faker import Faker User = get_user_model() @@ -182,3 +188,47 @@ class Meta: end_time = "2020-01-06T15:11:06-05:00" max_use = 100 max_use_per_user = 2 + + +class WorkplaceFactory(DjangoModelFactory): + class Meta: + model = Workplace + + name = factory.Sequence(lambda n: f'Blitz {n}') + seats = factory.fuzzy.FuzzyInteger(0) + details = "short_description" + address_line1 = "123 random street" + postal_code = "123 456" + state_province = "Random state" + country = "Random country" + + +class PeriodFactory(DjangoModelFactory): + class Meta: + model = Period + + name = factory.Sequence(lambda n: f'Period {n}') + workplace = factory.SubFactory(WorkplaceFactory) + start_date = datetime(2130, 1, 1, 1) + end_date = datetime(2130, 12, 12, 12) + price = factory.fuzzy.FuzzyDecimal(0, 1000, 2) + is_active = True + + +class TimeSlotFactory(DjangoModelFactory): + class Meta: + model = TimeSlot + + period = factory.SubFactory(PeriodFactory) + price = factory.fuzzy.FuzzyDecimal(0, 1000, 2) + start_time = datetime(2130, 1, 15, 8) + end_time = datetime(2130, 1, 15, 12) + + +class TimeSlotReservationFactory(DjangoModelFactory): + class Meta: + model = TimeSlotReservation + + user = factory.SubFactory(UserFactory) + timeslot = factory.SubFactory(TimeSlotFactory) + is_active = True From baebeed8856729a3fa64d2d9981c596704af86ea Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Thu, 2 Mar 2023 15:02:51 -0500 Subject: [PATCH 17/19] update tests --- blitz_api/tests/tests_model_User.py | 230 ++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) diff --git a/blitz_api/tests/tests_model_User.py b/blitz_api/tests/tests_model_User.py index 73140bad..63da2ed8 100644 --- a/blitz_api/tests/tests_model_User.py +++ b/blitz_api/tests/tests_model_User.py @@ -1,9 +1,18 @@ from django.utils import timezone +from datetime import datetime from rest_framework.test import APITestCase from blitz_api.factories import UserFactory from tomato.factories import TomatoFactory from tomato.models import Tomato +from blitz_api.factories import ( + RetreatFactory, + RetreatTypeFactory, + RetreatDateFactory, + ReservationFactory, + TimeSlotFactory, + TimeSlotReservationFactory, +) class UserTests(APITestCase): @@ -69,3 +78,224 @@ def test_property_current_month_tomatoes(self): ) self.assertEqual(user3.current_month_tomatoes, 0) + + def test_get_nb_tomatoes_retreat(self): + """ + Ensure we get the correct value for property get_nb_tomatoes_retreat + """ + today = timezone.now() + user1 = UserFactory() + user2 = UserFactory() + type = RetreatTypeFactory() + r = RetreatFactory( + number_of_tomatoes=10, + type=type, + display_start_time=datetime(1990, 1, 15, 8) + ) + date = RetreatDateFactory(retreat=r) + resa = ReservationFactory( + retreat=r, + user=user1, + is_active=True + ) + r2 = RetreatFactory( + number_of_tomatoes=30, + type=type, + display_start_time=datetime(1990, 1, 15, 8) + ) + date2 = RetreatDateFactory(retreat=r2) + resa2 = ReservationFactory( + retreat=r2, + user=user1, + is_active=True + ) + r3 = RetreatFactory( + number_of_tomatoes=15, + type=type, + display_start_time=datetime(1990, 1, 15, 8) + ) + date3 = RetreatDateFactory( + retreat=r3, + start_time=datetime(1990, 1, 15, 8), + end_time=datetime(1990, 1, 17, 12) + ) + resa3 = ReservationFactory( + retreat=r3, + user=user1, + is_active=True + ) + t1_1 = TomatoFactory( + user=user1, + number_of_tomato=r3.number_of_tomatoes, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_RETREAT) + t1_2 = TomatoFactory( + user=user1, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_CHRONO) + t1_3 = TomatoFactory( + user=user1, + number_of_tomato=12.5, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_MANUAL) + t1_4 = TomatoFactory( + user=user1, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_TIMESLOT) + TomatoFactory( + user=user1, + acquisition_date=today - timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_TIMESLOT) + TomatoFactory( + user=user1, + acquisition_date=today + timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_CHRONO) + self.assertEqual( + user1.get_nb_tomatoes_retreat(), + { + 'past': t1_1.number_of_tomato, + 'future': r.number_of_tomatoes + r2.number_of_tomatoes, + }) + self.assertEqual( + user2.get_nb_tomatoes_retreat(), + { + 'past': 0, + 'future': 0, + }) + + def test_get_nb_tomatoes_timeslot(self): + """ + Ensure we get the correct value for property get_nb_tomatoes_timeslot + """ + today = timezone.now() + user1 = UserFactory() + user2 = UserFactory() + user3 = UserFactory() + t1_1 = TomatoFactory( + user=user1, + number_of_tomato=15, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_RETREAT) + t1_2 = TomatoFactory( + user=user1, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_CHRONO) + t1_3 = TomatoFactory( + user=user1, + number_of_tomato=12.5, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_MANUAL) + t1_4 = TomatoFactory( + user=user1, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_TIMESLOT) + t1_5 = TomatoFactory( + user=user1, + acquisition_date=today - timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_TIMESLOT) + TomatoFactory( + user=user1, + acquisition_date=today + timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_CHRONO) + + TomatoFactory( + user=user2, + acquisition_date=today - timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_TIMESLOT) + TomatoFactory( + user=user2, + acquisition_date=today + timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_CHRONO) + TomatoFactory( + user=user2, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_MANUAL) + TomatoFactory( + user=user2, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_TIMESLOT) + + t1 = TimeSlotFactory() + r1 = TimeSlotReservationFactory(timeslot=t1, user=user1) + + t2 = TimeSlotFactory() + r2 = TimeSlotReservationFactory( + timeslot=t2, is_active=False, user=user1) + + t3 = TimeSlotFactory( + start_time=datetime(1990, 1, 15, 8), + end_time=datetime(1990, 1, 16, 8)) + r3 = TimeSlotReservationFactory(timeslot=t3, user=user1) + + self.assertEqual( + user1.get_nb_tomatoes_timeslot(), + { + 'past': t1_4.number_of_tomato + t1_5.number_of_tomato, + 'future': t1.number_of_tomatoes, + }) + self.assertEqual( + user3.get_nb_tomatoes_timeslot(), + { + 'past': 0, + 'future': 0, + }) + + def test_get_number_of_past_tomatoes(self): + """ + Ensure we get the correct value for property + get_number_of_past_tomatoes + """ + today = timezone.now() + user1 = UserFactory() + user2 = UserFactory() + user3 = UserFactory() + t1_1 = TomatoFactory( + user=user1, + number_of_tomato=15, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_RETREAT) + t1_2 = TomatoFactory( + user=user1, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_CHRONO) + t1_3 = TomatoFactory( + user=user1, + number_of_tomato=12.5, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_MANUAL) + t1_4 = TomatoFactory( + user=user1, + acquisition_date=today - timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_TIMESLOT) + TomatoFactory( + user=user1, + acquisition_date=today + timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_CHRONO) + + TomatoFactory( + user=user2, + acquisition_date=today - timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_TIMESLOT) + TomatoFactory( + user=user2, + acquisition_date=today + timezone.timedelta(days=31), + source=Tomato.TOMATO_SOURCE_CHRONO) + TomatoFactory( + user=user2, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_MANUAL) + TomatoFactory( + user=user2, + acquisition_date=today, + source=Tomato.TOMATO_SOURCE_TIMESLOT) + self.assertEqual( + # convert float to avoid Decimal(x), handled by serializer + user1.get_number_of_past_tomatoes(), + sum([ + t1_1.number_of_tomato, + t1_2.number_of_tomato, + t1_3.number_of_tomato, + t1_4.number_of_tomato]) + ) + + self.assertEqual(user3.get_number_of_past_tomatoes(), 0) From 155166c8e6bdce623a7ebcb7744e029827a8c9e7 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Wed, 8 Mar 2023 15:54:50 -0500 Subject: [PATCH 18/19] update future tomato method --- blitz_api/models.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/blitz_api/models.py b/blitz_api/models.py index f238e0f8..168c0ed0 100644 --- a/blitz_api/models.py +++ b/blitz_api/models.py @@ -244,7 +244,10 @@ def get_nb_tomatoes_timeslot(self): } def get_nb_tomatoes_retreat(self): - from retirement.models import Reservation as RetreatReservation + from retirement.models import ( + Reservation as RetreatReservation, + RetreatDate, + ) today = timezone.now() nb_tomatoes = Tomato.objects.filter( user=self, @@ -257,13 +260,18 @@ def get_nb_tomatoes_retreat(self): user=self, is_active=True, ) - all_count = 0 - for reservation in reservations: - all_count += reservation.retreat.get_number_of_tomatoes() + future_dates = RetreatDate.objects.filter( + end_time__gte=today, + tomatoes_assigned=False, + retreat__reservations__in=reservations, + ) + future_count = 0 + for date in future_dates: + future_count += date.number_of_tomatoes return { 'past': past_count, - 'future': all_count - past_count, + 'future': future_count, } def get_active_membership(self): From e1d9522031ec575f54f999d0cb5c2a17d519ad37 Mon Sep 17 00:00:00 2001 From: RomainFayolle Date: Thu, 9 Mar 2023 14:36:00 -0500 Subject: [PATCH 19/19] remove usage of timezone because of issue with time change --- tomato/tests/tests_viewset_Tomato.py | 260 +++++++++++++-------------- tomato/views.py | 6 +- 2 files changed, 133 insertions(+), 133 deletions(-) diff --git a/tomato/tests/tests_viewset_Tomato.py b/tomato/tests/tests_viewset_Tomato.py index da4b4f13..3cd6881f 100644 --- a/tomato/tests/tests_viewset_Tomato.py +++ b/tomato/tests/tests_viewset_Tomato.py @@ -574,19 +574,19 @@ def test_statistics_tomatoes_empty_data(self): self.assertEqual( result['graph']['labels'], [ - '2022-02-01T00:00:00-0500', - '2022-03-01T00:00:00-0500', - '2022-04-01T00:00:00-0500', - '2022-05-01T00:00:00-0500', - '2022-06-01T00:00:00-0500', - '2022-07-01T00:00:00-0500', - '2022-08-01T00:00:00-0500', - '2022-09-01T00:00:00-0500', - '2022-10-01T00:00:00-0500', - '2022-11-01T00:00:00-0500', - '2022-12-01T00:00:00-0500', - '2023-01-01T00:00:00-0500', - '2023-02-01T00:00:00-0500', + '2022-02-01T00:00:00', + '2022-03-01T00:00:00', + '2022-04-01T00:00:00', + '2022-05-01T00:00:00', + '2022-06-01T00:00:00', + '2022-07-01T00:00:00', + '2022-08-01T00:00:00', + '2022-09-01T00:00:00', + '2022-10-01T00:00:00', + '2022-11-01T00:00:00', + '2022-12-01T00:00:00', + '2023-01-01T00:00:00', + '2023-02-01T00:00:00', ], ) self.assertEqual( @@ -595,19 +595,19 @@ def test_statistics_tomatoes_empty_data(self): { 'label': 'number_of_tomato', 'data': [ - {'x': '2022-02-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-03-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-04-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-05-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-06-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-07-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-08-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-09-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-10-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-11-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-12-01T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-01T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-02-01T00:00:00', 'y': 0.0}, + {'x': '2022-03-01T00:00:00', 'y': 0.0}, + {'x': '2022-04-01T00:00:00', 'y': 0.0}, + {'x': '2022-05-01T00:00:00', 'y': 0.0}, + {'x': '2022-06-01T00:00:00', 'y': 0.0}, + {'x': '2022-07-01T00:00:00', 'y': 0.0}, + {'x': '2022-08-01T00:00:00', 'y': 0.0}, + {'x': '2022-09-01T00:00:00', 'y': 0.0}, + {'x': '2022-10-01T00:00:00', 'y': 0.0}, + {'x': '2022-11-01T00:00:00', 'y': 0.0}, + {'x': '2022-12-01T00:00:00', 'y': 0.0}, + {'x': '2023-01-01T00:00:00', 'y': 0.0}, + {'x': '2023-02-01T00:00:00', 'y': 0.0}, ] } ], @@ -675,19 +675,19 @@ def test_statistics_tomatoes_year(self): self.assertEqual( result['graph']['labels'], [ - '2022-02-01T00:00:00-0500', - '2022-03-01T00:00:00-0500', - '2022-04-01T00:00:00-0500', - '2022-05-01T00:00:00-0500', - '2022-06-01T00:00:00-0500', - '2022-07-01T00:00:00-0500', - '2022-08-01T00:00:00-0500', - '2022-09-01T00:00:00-0500', - '2022-10-01T00:00:00-0500', - '2022-11-01T00:00:00-0500', - '2022-12-01T00:00:00-0500', - '2023-01-01T00:00:00-0500', - '2023-02-01T00:00:00-0500', + '2022-02-01T00:00:00', + '2022-03-01T00:00:00', + '2022-04-01T00:00:00', + '2022-05-01T00:00:00', + '2022-06-01T00:00:00', + '2022-07-01T00:00:00', + '2022-08-01T00:00:00', + '2022-09-01T00:00:00', + '2022-10-01T00:00:00', + '2022-11-01T00:00:00', + '2022-12-01T00:00:00', + '2023-01-01T00:00:00', + '2023-02-01T00:00:00', ], ) self.assertEqual( @@ -696,20 +696,20 @@ def test_statistics_tomatoes_year(self): { 'label': 'number_of_tomato', 'data': [ - {'x': '2022-02-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-03-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-04-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-05-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-06-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-07-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-08-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-09-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-10-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-11-01T00:00:00-0500', 'y': 0.0}, - {'x': '2022-12-01T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-01T00:00:00-0500', 'y': 0.0}, + {'x': '2022-02-01T00:00:00', 'y': 0.0}, + {'x': '2022-03-01T00:00:00', 'y': 0.0}, + {'x': '2022-04-01T00:00:00', 'y': 0.0}, + {'x': '2022-05-01T00:00:00', 'y': 0.0}, + {'x': '2022-06-01T00:00:00', 'y': 0.0}, + {'x': '2022-07-01T00:00:00', 'y': 0.0}, + {'x': '2022-08-01T00:00:00', 'y': 0.0}, + {'x': '2022-09-01T00:00:00', 'y': 0.0}, + {'x': '2022-10-01T00:00:00', 'y': 0.0}, + {'x': '2022-11-01T00:00:00', 'y': 0.0}, + {'x': '2022-12-01T00:00:00', 'y': 0.0}, + {'x': '2023-01-01T00:00:00', 'y': 0.0}, { - 'x': '2023-02-01T00:00:00-0500', + 'x': '2023-02-01T00:00:00', 'y': 60.0 }, ] @@ -779,38 +779,38 @@ def test_statistics_tomatoes_month(self): self.assertEqual( result['graph']['labels'], [ - '2023-01-25T00:00:00-0500', - '2023-01-26T00:00:00-0500', - '2023-01-27T00:00:00-0500', - '2023-01-28T00:00:00-0500', - '2023-01-29T00:00:00-0500', - '2023-01-30T00:00:00-0500', - '2023-01-31T00:00:00-0500', - '2023-02-01T00:00:00-0500', - '2023-02-02T00:00:00-0500', - '2023-02-03T00:00:00-0500', - '2023-02-04T00:00:00-0500', - '2023-02-05T00:00:00-0500', - '2023-02-06T00:00:00-0500', - '2023-02-07T00:00:00-0500', - '2023-02-08T00:00:00-0500', - '2023-02-09T00:00:00-0500', - '2023-02-10T00:00:00-0500', - '2023-02-11T00:00:00-0500', - '2023-02-12T00:00:00-0500', - '2023-02-13T00:00:00-0500', - '2023-02-14T00:00:00-0500', - '2023-02-15T00:00:00-0500', - '2023-02-16T00:00:00-0500', - '2023-02-17T00:00:00-0500', - '2023-02-18T00:00:00-0500', - '2023-02-19T00:00:00-0500', - '2023-02-20T00:00:00-0500', - '2023-02-21T00:00:00-0500', - '2023-02-22T00:00:00-0500', - '2023-02-23T00:00:00-0500', - '2023-02-24T00:00:00-0500', - '2023-02-25T00:00:00-0500', + '2023-01-25T00:00:00', + '2023-01-26T00:00:00', + '2023-01-27T00:00:00', + '2023-01-28T00:00:00', + '2023-01-29T00:00:00', + '2023-01-30T00:00:00', + '2023-01-31T00:00:00', + '2023-02-01T00:00:00', + '2023-02-02T00:00:00', + '2023-02-03T00:00:00', + '2023-02-04T00:00:00', + '2023-02-05T00:00:00', + '2023-02-06T00:00:00', + '2023-02-07T00:00:00', + '2023-02-08T00:00:00', + '2023-02-09T00:00:00', + '2023-02-10T00:00:00', + '2023-02-11T00:00:00', + '2023-02-12T00:00:00', + '2023-02-13T00:00:00', + '2023-02-14T00:00:00', + '2023-02-15T00:00:00', + '2023-02-16T00:00:00', + '2023-02-17T00:00:00', + '2023-02-18T00:00:00', + '2023-02-19T00:00:00', + '2023-02-20T00:00:00', + '2023-02-21T00:00:00', + '2023-02-22T00:00:00', + '2023-02-23T00:00:00', + '2023-02-24T00:00:00', + '2023-02-25T00:00:00', ], ) self.assertEqual( @@ -819,42 +819,42 @@ def test_statistics_tomatoes_month(self): { 'label': 'number_of_tomato', 'data': [ - {'x': '2023-01-25T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-26T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-27T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-28T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-29T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-30T00:00:00-0500', 'y': 0.0}, - {'x': '2023-01-31T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-01T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-02T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-03T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-04T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-05T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-06T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-07T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-08T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-09T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-10T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-11T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-12T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-13T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-14T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-15T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-16T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-17T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-18T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-19T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-20T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-21T00:00:00-0500', 'y': 0.0}, - {'x': '2023-02-22T00:00:00-0500', 'y': 0.0}, + {'x': '2023-01-25T00:00:00', 'y': 0.0}, + {'x': '2023-01-26T00:00:00', 'y': 0.0}, + {'x': '2023-01-27T00:00:00', 'y': 0.0}, + {'x': '2023-01-28T00:00:00', 'y': 0.0}, + {'x': '2023-01-29T00:00:00', 'y': 0.0}, + {'x': '2023-01-30T00:00:00', 'y': 0.0}, + {'x': '2023-01-31T00:00:00', 'y': 0.0}, + {'x': '2023-02-01T00:00:00', 'y': 0.0}, + {'x': '2023-02-02T00:00:00', 'y': 0.0}, + {'x': '2023-02-03T00:00:00', 'y': 0.0}, + {'x': '2023-02-04T00:00:00', 'y': 0.0}, + {'x': '2023-02-05T00:00:00', 'y': 0.0}, + {'x': '2023-02-06T00:00:00', 'y': 0.0}, + {'x': '2023-02-07T00:00:00', 'y': 0.0}, + {'x': '2023-02-08T00:00:00', 'y': 0.0}, + {'x': '2023-02-09T00:00:00', 'y': 0.0}, + {'x': '2023-02-10T00:00:00', 'y': 0.0}, + {'x': '2023-02-11T00:00:00', 'y': 0.0}, + {'x': '2023-02-12T00:00:00', 'y': 0.0}, + {'x': '2023-02-13T00:00:00', 'y': 0.0}, + {'x': '2023-02-14T00:00:00', 'y': 0.0}, + {'x': '2023-02-15T00:00:00', 'y': 0.0}, + {'x': '2023-02-16T00:00:00', 'y': 0.0}, + {'x': '2023-02-17T00:00:00', 'y': 0.0}, + {'x': '2023-02-18T00:00:00', 'y': 0.0}, + {'x': '2023-02-19T00:00:00', 'y': 0.0}, + {'x': '2023-02-20T00:00:00', 'y': 0.0}, + {'x': '2023-02-21T00:00:00', 'y': 0.0}, + {'x': '2023-02-22T00:00:00', 'y': 0.0}, { - 'x': '2023-02-23T00:00:00-0500', + 'x': '2023-02-23T00:00:00', 'y': 45.0 }, - {'x': '2023-02-24T00:00:00-0500', 'y': 0.0}, + {'x': '2023-02-24T00:00:00', 'y': 0.0}, { - 'x': '2023-02-25T00:00:00-0500', + 'x': '2023-02-25T00:00:00', 'y': 15.0 } ] @@ -924,13 +924,13 @@ def test_statistics_tomatoes_week(self): self.assertEqual( result['graph']['labels'], [ - '2023-02-19T00:00:00-0500', - '2023-02-20T00:00:00-0500', - '2023-02-21T00:00:00-0500', - '2023-02-22T00:00:00-0500', - '2023-02-23T00:00:00-0500', - '2023-02-24T00:00:00-0500', - '2023-02-25T00:00:00-0500', + '2023-02-19T00:00:00', + '2023-02-20T00:00:00', + '2023-02-21T00:00:00', + '2023-02-22T00:00:00', + '2023-02-23T00:00:00', + '2023-02-24T00:00:00', + '2023-02-25T00:00:00', ], ) self.assertEqual( @@ -940,31 +940,31 @@ def test_statistics_tomatoes_week(self): 'label': 'number_of_tomato', 'data': [ { - 'x': '2023-02-19T00:00:00-0500', + 'x': '2023-02-19T00:00:00', 'y': 0.0, }, { - 'x': '2023-02-20T00:00:00-0500', + 'x': '2023-02-20T00:00:00', 'y': 0.0, }, { - 'x': '2023-02-21T00:00:00-0500', + 'x': '2023-02-21T00:00:00', 'y': 0.0, }, { - 'x': '2023-02-22T00:00:00-0500', + 'x': '2023-02-22T00:00:00', 'y': 0.0, }, { - 'x': '2023-02-23T00:00:00-0500', + 'x': '2023-02-23T00:00:00', 'y': 45.0 }, { - 'x': '2023-02-24T00:00:00-0500', + 'x': '2023-02-24T00:00:00', 'y': 0.0 }, { - 'x': '2023-02-25T00:00:00-0500', + 'x': '2023-02-25T00:00:00', 'y': 15.0 } ] diff --git a/tomato/views.py b/tomato/views.py index 3b2d218f..cdf7b7d0 100644 --- a/tomato/views.py +++ b/tomato/views.py @@ -395,7 +395,7 @@ def _get_intervals(start, end, interval_param): if interval_param == 'day': while end >= date: labels.add( - date.strftime("%Y-%m-%dT%H:%M:%S%z") + date.strftime("%Y-%m-%dT%H:%M:%S") ) date += timedelta(days=1) else: @@ -403,7 +403,7 @@ def _get_intervals(start, end, interval_param): end = end.replace(day=1, hour=0, minute=0, second=0) while end >= date: labels.add( - date.strftime("%Y-%m-%dT%H:%M:%S%z") + date.strftime("%Y-%m-%dT%H:%M:%S") ) # Get first day of next month date += timedelta(days=32) @@ -439,7 +439,7 @@ def _get_data(queryset, data_set_type, labels): non_covered_labels = labels.copy() for data in queryset: - label = data['interval'].strftime('%Y-%m-%dT%H:%M:%S%z') + label = data['interval'].strftime('%Y-%m-%dT%H:%M:%S') results.append( { 'x': label,