From 86eb4935f5f8963eda9dfe08edbca8975315eaed Mon Sep 17 00:00:00 2001 From: ssorin Date: Mon, 16 Dec 2024 17:29:03 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(dashboard)=20add=20signals=20on=20del?= =?UTF-8?q?ivery=20point=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented a signal to automatically create a Consent object when a new DeliveryPoint is saved. Added corresponding tests to ensure functionality and updated related Pipfile dependencies. Centralized consent end date logic in a new app settings file (CONSENT_END_DATE) and updated all related signals, factories, and tests to use it. Updated documentation and code comments to reflect these changes. --- src/dashboard/CHANGELOG.md | 3 + src/dashboard/apps/consent/apps.py | 4 + src/dashboard/apps/consent/factories.py | 6 +- .../apps/consent/fixtures/consent.py | 29 ++++-- src/dashboard/apps/consent/models.py | 23 ++++- src/dashboard/apps/consent/settings.py | 13 +++ src/dashboard/apps/consent/signals.py | 18 ++++ .../apps/consent/tests/test_models.py | 86 +++++++++++----- .../apps/consent/tests/test_signals.py | 24 +++++ .../apps/consent/tests/test_utils.py | 33 +++++++ .../apps/consent/tests/test_views.py | 97 +++++++------------ src/dashboard/apps/consent/utils.py | 21 ++++ src/dashboard/apps/core/factories.py | 1 + src/dashboard/apps/core/tests/test_models.py | 82 ++++++++-------- src/dashboard/dashboard/settings.py | 15 ++- src/dashboard/readme.md | 7 ++ 16 files changed, 319 insertions(+), 143 deletions(-) create mode 100644 src/dashboard/apps/consent/settings.py create mode 100644 src/dashboard/apps/consent/signals.py create mode 100644 src/dashboard/apps/consent/tests/test_signals.py create mode 100644 src/dashboard/apps/consent/tests/test_utils.py create mode 100644 src/dashboard/apps/consent/utils.py diff --git a/src/dashboard/CHANGELOG.md b/src/dashboard/CHANGELOG.md index a67f69f2..c69c51bf 100644 --- a/src/dashboard/CHANGELOG.md +++ b/src/dashboard/CHANGELOG.md @@ -20,6 +20,9 @@ and this project adheres to - add consent form to manage consents of one or many entities - integration of custom 403, 404 and 500 pages - sentry integration +- added a signal on the creation of a delivery point. This signal allows the creation +of the consent corresponding to the delivery point + [unreleased]: https://github.com/MTES-MCT/qualicharge/compare/main...bootstrap-dashboard-project diff --git a/src/dashboard/apps/consent/apps.py b/src/dashboard/apps/consent/apps.py index 74d5d1f6..e3494bd8 100644 --- a/src/dashboard/apps/consent/apps.py +++ b/src/dashboard/apps/consent/apps.py @@ -11,3 +11,7 @@ class ConsentConfig(AppConfig): name = "apps.consent" label = "qcd_consent" verbose_name = _("Consent") + + def ready(self): + """Register signals.""" + from .signals import handle_new_delivery_point # noqa: F401 diff --git a/src/dashboard/apps/consent/factories.py b/src/dashboard/apps/consent/factories.py index 61d012d6..34131d39 100644 --- a/src/dashboard/apps/consent/factories.py +++ b/src/dashboard/apps/consent/factories.py @@ -1,7 +1,5 @@ """Dashboard consent factories.""" -from datetime import timedelta - import factory from django.utils import timezone from factory import fuzzy @@ -10,6 +8,8 @@ from apps.core.factories import DeliveryPointFactory from .models import Consent +from .settings import CONSENT_NUMBER_DAYS_END_DATE +from .utils import calculate_consent_end_date class ConsentFactory(factory.django.DjangoModelFactory): @@ -18,7 +18,7 @@ class ConsentFactory(factory.django.DjangoModelFactory): delivery_point = factory.SubFactory(DeliveryPointFactory) created_by = factory.SubFactory(UserFactory) start = fuzzy.FuzzyDateTime(timezone.now()) - end = factory.LazyAttribute(lambda o: o.start + timedelta(days=90)) + end = calculate_consent_end_date(CONSENT_NUMBER_DAYS_END_DATE) class Meta: # noqa: D106 model = Consent diff --git a/src/dashboard/apps/consent/fixtures/consent.py b/src/dashboard/apps/consent/fixtures/consent.py index 742c2105..17d5cc6a 100644 --- a/src/dashboard/apps/consent/fixtures/consent.py +++ b/src/dashboard/apps/consent/fixtures/consent.py @@ -1,6 +1,11 @@ """Dashboard consent dev fixture.""" +from datetime import timedelta + +from django.utils import timezone + from apps.auth.factories import UserFactory +from apps.consent import VALIDATED from apps.consent.factories import ConsentFactory from apps.core.factories import DeliveryPointFactory, EntityFactory from apps.core.models import DeliveryPoint @@ -29,12 +34,18 @@ def seed_consent(): entity4 = EntityFactory(users=(user5,)) # create delivery points - for _ in range(1, 4): - DeliveryPointFactory(entity=entity1) - DeliveryPointFactory(entity=entity2) - DeliveryPointFactory(entity=entity3) - DeliveryPointFactory(entity=entity4) - - # create awaiting consents - for delivery_point in DeliveryPoint.objects.all(): - ConsentFactory(delivery_point=delivery_point, created_by=user1) + size = 4 + DeliveryPointFactory.create_batch(size, entity=entity1) + DeliveryPointFactory.create_batch(size, entity=entity2) + DeliveryPointFactory.create_batch(size, entity=entity3) + DeliveryPointFactory.create_batch(size, entity=entity4) + + # create past consents with validated status + for dl in DeliveryPoint.objects.filter(entity=entity1): + ConsentFactory( + delivery_point=dl, + created_by=user1, + status=VALIDATED, + start=timezone.now() - timedelta(days=300), + end=timezone.now() - timedelta(days=270), + ) diff --git a/src/dashboard/apps/consent/models.py b/src/dashboard/apps/consent/models.py index 59053879..0eb40706 100644 --- a/src/dashboard/apps/consent/models.py +++ b/src/dashboard/apps/consent/models.py @@ -8,6 +8,8 @@ from . import AWAITING, CONSENT_STATUS_CHOICE, REVOKED from .managers import ConsentManager +from .settings import CONSENT_NUMBER_DAYS_END_DATE +from .utils import calculate_consent_end_date class Consent(DashboardBase): @@ -46,8 +48,8 @@ class Consent(DashboardBase): end = models.DateTimeField(_("end date")) revoked_at = models.DateTimeField(_("revoked at"), null=True, blank=True) - active_objects = ConsentManager() objects = models.Manager() + active_objects = ConsentManager() class Meta: # noqa: D106 ordering = ["delivery_point"] @@ -56,7 +58,24 @@ def __str__(self): # noqa: D105 return f"{self.delivery_point} - {self.updated_at}: {self.status}" def save(self, *args, **kwargs): - """Update the revoked_at timestamps if the consent is revoked.""" + """Saves with custom logic applied before persistency. + + - If the consent status is revoked, `revoked_at` is updated to the current time. + - When creating a new consent instance, if the `start` and `end` dates are not + provided, they are set to default values: the `start` date is set to the current + date, and the `end` date is set to a computed date based on + `CONSENT_NUMBER_DAYS_END_DATE`. + """ + # Update the `revoked_at` timestamps if the consent is revoked. if self.status == REVOKED: self.revoked_at = timezone.now() + + # if the current instance is newly created, + # set default values for `start` and end `dates` if they are not provided. + if self._state.adding: + self.start = self.start or timezone.now() + self.end = self.end or calculate_consent_end_date( + CONSENT_NUMBER_DAYS_END_DATE + ) + return super(Consent, self).save(*args, **kwargs) diff --git a/src/dashboard/apps/consent/settings.py b/src/dashboard/apps/consent/settings.py new file mode 100644 index 00000000..41a3d854 --- /dev/null +++ b/src/dashboard/apps/consent/settings.py @@ -0,0 +1,13 @@ +"""Dashboard consent app settings.""" + +from django.conf import settings + +# CONSENT_NUMBER_DAYS_END_DATE allows to calculate the end date of a consent period by +# adding a number of days to the current date. +# If the value is None, the end date of the period will correspond to the last day of +# the current year +# More details on the calculation in the function: `utils.calculate_consent_end_date()` +# ie: +# CONSENT_NUMBER_DAYS_END_DATE = 90 will return the current date + 90 days. +# CONSENT_NUMBER_DAYS_END_DATE = None will return 2024-12-31 23:59:59. +CONSENT_NUMBER_DAYS_END_DATE = getattr(settings, "CONSENT_NUMBER_DAYS_END_DATE", None) diff --git a/src/dashboard/apps/consent/signals.py b/src/dashboard/apps/consent/signals.py new file mode 100644 index 00000000..6c24ab5e --- /dev/null +++ b/src/dashboard/apps/consent/signals.py @@ -0,0 +1,18 @@ +"""Dashboard consent app signals.""" + +from django.db.models.signals import post_save +from django.dispatch import receiver + +from apps.core.models import DeliveryPoint + +from .models import Consent + + +@receiver(post_save, sender=DeliveryPoint, dispatch_uid="handle_new_delivery_point") +def handle_new_delivery_point(sender, instance, created, **kwargs): + """Signal triggered after a new DeliveryPoint is saved. + + Create a new Consent object for the delivery point. + """ + if created: + Consent.objects.create(delivery_point=instance) diff --git a/src/dashboard/apps/consent/tests/test_models.py b/src/dashboard/apps/consent/tests/test_models.py index ae81bd12..bc8866e0 100644 --- a/src/dashboard/apps/consent/tests/test_models.py +++ b/src/dashboard/apps/consent/tests/test_models.py @@ -1,39 +1,40 @@ """Dashboard consent models tests.""" -from datetime import timedelta - import pytest -from django.utils import formats +from django.db.models import signals -from apps.auth.factories import UserFactory from apps.consent import AWAITING, REVOKED, VALIDATED from apps.consent.factories import ConsentFactory +from apps.consent.models import Consent +from apps.consent.signals import handle_new_delivery_point +from apps.consent.utils import calculate_consent_end_date from apps.core.factories import DeliveryPointFactory +from apps.core.models import DeliveryPoint @pytest.mark.django_db def test_create_consent(): """Tests the creation of a consent.""" - user1 = UserFactory() + assert Consent.objects.count() == 0 + + # create one `delivery_point` and consequently one `consent` delivery_point = DeliveryPointFactory() - consent = ConsentFactory( - delivery_point=delivery_point, - created_by=user1, - ) + assert Consent.objects.count() == 1 + + # get the created consent + consent = Consent.objects.get(delivery_point=delivery_point) assert consent.delivery_point == delivery_point - assert consent.created_by == user1 + assert consent.created_by == delivery_point.entity.users.first() assert consent.status == AWAITING assert consent.revoked_at is None assert consent.start is not None assert consent.end is not None - # test consent.end is 90 days later than the consent.start - end_date = consent.start + timedelta(days=90) - consent_start = formats.date_format(end_date, "Y/m/d") - consent_end = formats.date_format(consent.end, "Y/m/d") - assert consent_start == consent_end + # test consent.end is the last day of the year + expected_end_date = calculate_consent_end_date().replace(microsecond=0) + assert consent.end.replace(microsecond=0) == expected_end_date # test created_at and updated_at have been updated. assert consent.created_at is not None @@ -41,23 +42,51 @@ def test_create_consent(): @pytest.mark.django_db -def test_update_consent_status(): - """Tests updating a consent status.""" - user1 = UserFactory() - delivery_point = DeliveryPointFactory() +def test_create_consent_with_custom_period_date(): + """Tests the creation of a consent with a custom period date (`start` / `end`).""" + expected_start_date = "2024-12-20" + expected_end_date = "2024-12-22" - consent = ConsentFactory( - delivery_point=delivery_point, - created_by=user1, + # Create one consent with custom start / end date + # we have to disconnect the signals that allow the creation of a consent after the + # creation of a delivery point: + # `ConsentFactory` creates a new `delivery_point` which itself creates a new + # `consent`. + assert Consent.objects.count() == 0 + signals.post_save.disconnect( + receiver=handle_new_delivery_point, + sender=DeliveryPoint, + dispatch_uid="handle_new_delivery_point", ) - new_updated_at = consent.updated_at + consent = ConsentFactory(start=expected_start_date, end=expected_end_date) + + signals.post_save.connect( + receiver=handle_new_delivery_point, + sender=DeliveryPoint, + dispatch_uid="handle_new_delivery_point", + ) + assert Consent.objects.count() == 1 + + assert consent.start == expected_start_date + assert consent.end == expected_end_date + + +@pytest.mark.django_db +def test_update_consent_status_and_period_date(): + """Tests updating a consent status.""" + # create one `delivery_point` and consequently one `consent` + delivery_point = DeliveryPointFactory() + + # get the created consent + consent = Consent.objects.get(delivery_point=delivery_point) + consent_updated_at = consent.updated_at # update status to VALIDATED consent.status = VALIDATED consent.save() assert consent.status == VALIDATED - assert consent.updated_at > new_updated_at + assert consent.updated_at > consent_updated_at assert consent.revoked_at is None new_updated_at = consent.updated_at @@ -67,3 +96,12 @@ def test_update_consent_status(): assert consent.status == REVOKED assert consent.updated_at > new_updated_at assert consent.revoked_at is not None + + # update start and end date + expected_start_date = "2024-12-20" + expected_end_date = "2024-12-22" + consent.start = expected_start_date + consent.end = expected_end_date + consent.save() + assert consent.start == expected_start_date + assert consent.end == expected_end_date diff --git a/src/dashboard/apps/consent/tests/test_signals.py b/src/dashboard/apps/consent/tests/test_signals.py new file mode 100644 index 00000000..4bcc9258 --- /dev/null +++ b/src/dashboard/apps/consent/tests/test_signals.py @@ -0,0 +1,24 @@ +"""Dashboard consent signals tests.""" + +import pytest + +from apps.consent.models import Consent +from apps.consent.utils import calculate_consent_end_date +from apps.core.factories import DeliveryPointFactory + + +@pytest.mark.django_db +def test_signal_triggered_on_delivery_point_creation(): + """Tests if the signal is triggered when creating a DeliveryPoint.""" + assert Consent.objects.count() == 0 + + # Create new delivery point + delivery_point = DeliveryPointFactory() + + # check if consent was created with signals after delivery_point creation. + assert Consent.objects.count() == 1 + + consent = Consent.objects.first() + assert consent.delivery_point == delivery_point + expected_date = calculate_consent_end_date() + assert consent.end.replace(microsecond=0) == expected_date.replace(microsecond=0) diff --git a/src/dashboard/apps/consent/tests/test_utils.py b/src/dashboard/apps/consent/tests/test_utils.py new file mode 100644 index 00000000..2a4bb077 --- /dev/null +++ b/src/dashboard/apps/consent/tests/test_utils.py @@ -0,0 +1,33 @@ +"""Dashboard consent utils tests.""" + +from datetime import datetime, timedelta, timezone + +import pytest + +from apps.consent.utils import calculate_consent_end_date + + +@pytest.mark.django_db +def test_calculate_consent_end_date_with_days(): + """Test `calculate_consent_end_date` function when days argument is provided.""" + days = 10 + end_date = calculate_consent_end_date(days=days).replace(microsecond=0) + expected_date = datetime.now().replace(tzinfo=timezone.utc) + timedelta(days=days) + assert end_date == expected_date.replace(microsecond=0) + + +@pytest.mark.django_db +def test_calculate_consent_end_date_without_days(): + """Test `calculate_consent_end_date` function when days argument is not provided.""" + end_date = calculate_consent_end_date().replace(microsecond=0) + current_year = datetime.now().year + expected_date = datetime( + year=current_year, + month=12, + day=31, + hour=23, + minute=59, + second=59, + tzinfo=timezone.utc, + ) + assert end_date == expected_date.replace(microsecond=0) diff --git a/src/dashboard/apps/consent/tests/test_views.py b/src/dashboard/apps/consent/tests/test_views.py index 9743a9ec..41e1ca14 100644 --- a/src/dashboard/apps/consent/tests/test_views.py +++ b/src/dashboard/apps/consent/tests/test_views.py @@ -1,5 +1,6 @@ """Dashboard consent views tests.""" +import uuid from http import HTTPStatus import pytest @@ -10,12 +11,12 @@ from apps.consent.factories import ConsentFactory from apps.consent.models import Consent from apps.consent.views import ConsentFormView -from apps.core.factories import EntityFactory +from apps.core.factories import DeliveryPointFactory, EntityFactory @pytest.mark.django_db def test_bulk_update_consent_status_without_ids(rf): - """Test that no status is updated if no id is passed.""" + """Test no status is updated if no ID is passed.""" request = rf.get(reverse("consent:manage")) request.user = UserFactory() @@ -23,7 +24,7 @@ def test_bulk_update_consent_status_without_ids(rf): view.setup(request) size = 4 - ConsentFactory.create_batch(size) + DeliveryPointFactory.create_batch(size) # check data before update assert all(c == AWAITING for c in Consent.objects.values_list("status", flat=True)) @@ -37,7 +38,7 @@ def test_bulk_update_consent_status_without_ids(rf): @pytest.mark.django_db def test_bulk_update_consent_status(rf): - """Test that all consents are correctly updated.""" + """Test all consents are correctly updated.""" user = UserFactory() request = rf.get(reverse("consent:manage")) @@ -49,8 +50,8 @@ def test_bulk_update_consent_status(rf): # create entity for the user and consents for the entity size = 3 entity = EntityFactory(users=(user,)) - consents = ConsentFactory.create_batch(size, delivery_point__entity=entity) - ids = [c.id for c in consents] + DeliveryPointFactory.create_batch(size, entity=entity) + ids = list(Consent.objects.values_list("id", flat=True)) # check data before update assert all(c == AWAITING for c in Consent.objects.values_list("status", flat=True)) @@ -64,7 +65,7 @@ def test_bulk_update_consent_status(rf): @pytest.mark.django_db def test_bulk_update_consent_status_with_fake_id(rf): - """Test update with wrong ID in list of ids to update.""" + """Test update with wrong ID in list of IDs to update.""" user = UserFactory() request = rf.get(reverse("consent:manage")) @@ -76,10 +77,10 @@ def test_bulk_update_consent_status_with_fake_id(rf): # create entity for the user and consents for the entity size = 3 entity = EntityFactory(users=(user,)) - consents = ConsentFactory.create_batch(size, delivery_point__entity=entity) - ids = [c.id for c in consents] + DeliveryPointFactory.create_batch(size, entity=entity) + ids = list(Consent.objects.values_list("id", flat=True)) - # add a fake id to the ids to update + # add a fake ID to the IDs to update ids.append("fa62cf1d-c510-498a-b428-fdf72fa35651") # check data before update @@ -107,15 +108,15 @@ def test_bulk_update_consent_without_user_perms(rf): # create entity for the user and consents for the entity size = 3 entity = EntityFactory(users=(user,)) - consents = ConsentFactory.create_batch(size, delivery_point__entity=entity) - ids = [c.id for c in consents] + DeliveryPointFactory.create_batch(size, entity=entity) + ids = list(Consent.objects.values_list("id", flat=True)) # create wrong consent wrong_user = UserFactory() wrong_entity = EntityFactory(users=(wrong_user,)) wrong_consent = ConsentFactory(delivery_point__entity=wrong_entity) - # add wrong_id to ids + # add wrong_id to IDs ids.append(wrong_consent.id) assert len(ids) == size + 1 assert wrong_consent.id in ids @@ -124,7 +125,7 @@ def test_bulk_update_consent_without_user_perms(rf): assert all(c == AWAITING for c in Consent.objects.values_list("status", flat=True)) # bulk update to VALIDATED, - # and check all records have been updated except the wrong id. + # and check all records have been updated except the wrong ID. assert view._bulk_update_consent(ids, VALIDATED) == size # and checks that the data has changed to VALIDATED after the update. @@ -153,22 +154,18 @@ def test_get_awaiting_ids_with_bad_parameters(rf): view = ConsentFormView() view.setup(request) - # create entity for the user and consents for the entity - size = 3 - entity = EntityFactory(users=(user,)) - consents = ConsentFactory.create_batch(size, delivery_point__entity=entity) - # create a list of QuerySet instead of str - ids = [c.id for c in consents] + # create a list of UUID instead of str + ids = [uuid.uuid4(), uuid.uuid4(), uuid.uuid4()] # check _get_awaiting_ids() raise exception - # (ids must be a list of string not of QuerySet) + # (IDs must be a list of string not of UUID) with pytest.raises(ValueError): view._get_awaiting_ids(validated_ids=ids) @pytest.mark.django_db def test_get_awaiting_ids(rf): - """Test getting of awaiting ids inferred from validated consents.""" + """Test getting of awaiting IDs inferred from validated consents.""" user = UserFactory() request = rf.get(reverse("consent:manage")) @@ -180,8 +177,10 @@ def test_get_awaiting_ids(rf): # create entity for the user and consents for the entity size = 3 entity = EntityFactory(users=(user,)) - consents = ConsentFactory.create_batch(size, delivery_point__entity=entity) - ids = [str(c.id) for c in consents] + DeliveryPointFactory.create_batch(size, entity=entity) + ids = [ + str(c.id) for c in Consent.active_objects.filter(delivery_point__entity=entity) + ] # removes one `id` from the list `ids`, # this is the one we must find with _get_awaiting_ids() @@ -196,7 +195,7 @@ def test_get_awaiting_ids(rf): @pytest.mark.django_db def test_templates_render_without_entities(rf): - """Test the templates are rendered without entities.""" + """Test the templates are rendered without entities, and with expected content.""" user = UserFactory() request = rf.get(reverse("consent:manage")) @@ -213,20 +212,7 @@ def test_templates_render_without_entities(rf): response = view.dispatch(request) assert response.status_code == HTTPStatus.OK - -@pytest.mark.django_db -def test_templates_render_html_content_without_entities(rf): - """Test the html content of the templates without entities.""" - user = UserFactory() - - request = rf.get(reverse("consent:manage")) - request.user = user - - view = ConsentFormView() - view.setup(request) - - # Get response object and force template rendering - response = view.dispatch(request) + # force template rendering rendered = response.render() html = rendered.content.decode() @@ -240,8 +226,8 @@ def test_templates_render_html_content_without_entities(rf): @pytest.mark.django_db -def test_templates_render_with_entities(rf): - """Test the templates are rendered.""" +def test_templates_render_with_entities_without_consents(rf): + """Test the templates without consents are rendered with expected content.""" user = UserFactory() request = rf.get(reverse("consent:manage")) @@ -257,27 +243,11 @@ def test_templates_render_with_entities(rf): context = view.get_context_data() assert context["entities"] == [entity] - # Get response object + # get response object response = view.dispatch(request) assert response.status_code == HTTPStatus.OK - -@pytest.mark.django_db -def test_templates_render_html_content_with_entities(rf): - """Test the html content of the templates with entities but no consents.""" - user = UserFactory() - - request = rf.get(reverse("consent:manage")) - request.user = user - - view = ConsentFormView() - view.setup(request) - - # create entity - EntityFactory(users=(user,)) - - # Get response object and force template rendering - response = view.dispatch(request) + # force template rendering rendered = response.render() html = rendered.content.decode() @@ -307,7 +277,7 @@ def test_templates_render_with_slug(rf): context = view.get_context_data() assert context["entities"] == [entity] - # Get response object + # get response object response = view.dispatch(request) assert response.status_code == HTTPStatus.OK @@ -326,15 +296,16 @@ def test_templates_render_html_content_with_consents(rf): # create entity size = 3 entity = EntityFactory(users=(user,)) - consents = ConsentFactory.create_batch(size, delivery_point__entity=entity) + DeliveryPointFactory.create_batch(size, entity=entity) + consents = Consent.objects.filter(delivery_point__entity=entity) - # Get response object and force template rendering + # get response object and force template rendering response = view.dispatch(request) rendered = response.render() html = rendered.content.decode() assert (entity.name in html) is True - assert all(str(c.id) in html for c in consents) + assert all(str(dl.id) in html for dl in consents) @pytest.mark.django_db diff --git a/src/dashboard/apps/consent/utils.py b/src/dashboard/apps/consent/utils.py new file mode 100644 index 00000000..170329d0 --- /dev/null +++ b/src/dashboard/apps/consent/utils.py @@ -0,0 +1,21 @@ +"""Dashboard consent app utils.""" + +from datetime import datetime, timedelta, timezone + + +def calculate_consent_end_date(days: int | None = None) -> datetime: + """Calculate the end date of the consent period. + + Returns a specific end date based on the number of days provided, + or the default end of the year date if no argument is passed. + + Parameters: + days (int | None): An optional number of days to calculate the future date. + If None, the function defaults to the end of the current year. + """ + if days: + return datetime.now().replace(tzinfo=timezone.utc) + timedelta(days=days) + + return datetime.now().replace( + month=12, day=31, hour=23, minute=59, second=59, tzinfo=timezone.utc + ) diff --git a/src/dashboard/apps/core/factories.py b/src/dashboard/apps/core/factories.py index 4d0d0b4b..c69bbe9f 100644 --- a/src/dashboard/apps/core/factories.py +++ b/src/dashboard/apps/core/factories.py @@ -14,6 +14,7 @@ class EntityFactory(factory.django.DjangoModelFactory): class Meta: # noqa: D106 model = Entity + skip_postgeneration_save = True @factory.post_generation def users(self, create, extracted, **kwargs): diff --git a/src/dashboard/apps/core/tests/test_models.py b/src/dashboard/apps/core/tests/test_models.py index 3f16baf4..1a8df131 100644 --- a/src/dashboard/apps/core/tests/test_models.py +++ b/src/dashboard/apps/core/tests/test_models.py @@ -12,7 +12,7 @@ from apps.consent.factories import ConsentFactory from apps.consent.models import Consent from apps.core.factories import DeliveryPointFactory, EntityFactory -from apps.core.models import DeliveryPoint, Entity +from apps.core.models import Entity @pytest.mark.django_db @@ -173,26 +173,22 @@ def test_count_validated_consents(): DeliveryPointFactory(provider_assigned_id=f"entity2_{i}", entity=entity2) DeliveryPointFactory(provider_assigned_id=f"entity3_{i}", entity=entity3) - # create awaiting consent for each delivery points - for delivery_point in DeliveryPoint.objects.all(): - ConsentFactory(delivery_point=delivery_point, created_by=user1, status=AWAITING) - # create validated consent for entity1 dl = DeliveryPointFactory(provider_assigned_id="entity3_validated", entity=entity1) - ConsentFactory(delivery_point=dl, created_by=user1, status=VALIDATED) + ConsentFactory(delivery_point=dl, status=VALIDATED) - # create awainting consents for entity1 in past period - dl = DeliveryPointFactory(provider_assigned_id="entity3_past", entity=entity1) + # create awaiting consents for entity1 in past period ConsentFactory( delivery_point=dl, - created_by=user1, status=AWAITING, start=timezone.now() - timedelta(days=300), end=timezone.now() - timedelta(days=270), ) assert ( - Consent.objects.filter(status=AWAITING, delivery_point__entity=entity1).count() + Consent.active_objects.filter( + status=AWAITING, delivery_point__entity=entity1 + ).count() == 4 # noqa: PLR2004 ) assert entity1.count_validated_consents() == 1 @@ -217,22 +213,20 @@ def test_count_awaiting_consents(): DeliveryPointFactory(provider_assigned_id=f"entity2_{i}", entity=entity2) DeliveryPointFactory(provider_assigned_id=f"entity3_{i}", entity=entity3) - # create awaiting consent for each delivery points - for delivery_point in DeliveryPoint.objects.all(): - ConsentFactory(delivery_point=delivery_point, created_by=user1, status=AWAITING) - # create validated consent for entity1 dl = DeliveryPointFactory(provider_assigned_id="entity3_validated", entity=entity1) - ConsentFactory(delivery_point=dl, created_by=user1, status=VALIDATED) + Consent.objects.filter(delivery_point=dl).update(status=VALIDATED) # create awainting consents for entity1 in past period dl = DeliveryPointFactory(provider_assigned_id="entity3_past", entity=entity1) - ConsentFactory( - delivery_point=dl, - created_by=user1, - status=AWAITING, - start=timezone.now() - timedelta(days=300), - end=timezone.now() - timedelta(days=270), + ( + Consent.objects.filter( + delivery_point=dl, + status=AWAITING, + ).update( + start=timezone.now() - timedelta(days=300), + end=timezone.now() - timedelta(days=270), + ) ) assert ( @@ -265,16 +259,18 @@ def test_get_consents(): dl3_1 = DeliveryPointFactory(provider_assigned_id="entity3_1", entity=entity3) dl3_2 = DeliveryPointFactory(provider_assigned_id="entity3_2", entity=entity3) - # create awaiting consents - c1_1 = ConsentFactory(delivery_point=dl1_1, created_by=user1, status=AWAITING) - c1_2 = ConsentFactory(delivery_point=dl1_2, created_by=user1, status=AWAITING) - c2_1 = ConsentFactory(delivery_point=dl2_1, created_by=user2, status=AWAITING) - c2_2 = ConsentFactory(delivery_point=dl2_2, created_by=user2, status=AWAITING) - c3_1 = ConsentFactory(delivery_point=dl3_1, created_by=user3, status=AWAITING) - c3_2 = ConsentFactory(delivery_point=dl3_2, created_by=user3, status=AWAITING) + # get awaiting consents + c1_1 = Consent.objects.get(delivery_point=dl1_1, status=AWAITING) + c1_2 = Consent.objects.get(delivery_point=dl1_2, status=AWAITING) + c2_1 = Consent.objects.get(delivery_point=dl2_1, status=AWAITING) + c2_2 = Consent.objects.get(delivery_point=dl2_2, status=AWAITING) + c3_1 = Consent.objects.get(delivery_point=dl3_1, status=AWAITING) + c3_2 = Consent.objects.get(delivery_point=dl3_2, status=AWAITING) - # create validated consent for entity1 - c1_3 = ConsentFactory(delivery_point=dl1_3, created_by=user1, status=VALIDATED) + # update consent c1_3 to validated + c1_3 = Consent.objects.get(delivery_point=dl1_3) + c1_3.status = VALIDATED + c1_3.save() # create awaiting consents for entity1 in past period ConsentFactory( @@ -285,7 +281,9 @@ def test_get_consents(): end=timezone.now() - timedelta(days=270), ) - assertQuerySetEqual(entity1.get_consents(), [c1_1, c1_2, c1_3]) + assertQuerySetEqual( + entity1.get_consents().order_by("delivery_point"), [c1_1, c1_2, c1_3] + ) assertQuerySetEqual(entity2.get_consents(), [c2_1, c2_2]) assertQuerySetEqual(entity3.get_consents(), [c3_1, c3_2]) @@ -311,16 +309,18 @@ def test_get_awaiting_consents(): dl3_1 = DeliveryPointFactory(provider_assigned_id="entity3_1", entity=entity3) dl3_2 = DeliveryPointFactory(provider_assigned_id="entity3_2", entity=entity3) - # create awaiting consents - c1_1 = ConsentFactory(delivery_point=dl1_1, created_by=user1, status=AWAITING) - c1_2 = ConsentFactory(delivery_point=dl1_2, created_by=user1, status=AWAITING) - c2_1 = ConsentFactory(delivery_point=dl2_1, created_by=user2, status=AWAITING) - c2_2 = ConsentFactory(delivery_point=dl2_2, created_by=user2, status=AWAITING) - c3_1 = ConsentFactory(delivery_point=dl3_1, created_by=user3, status=AWAITING) - c3_2 = ConsentFactory(delivery_point=dl3_2, created_by=user3, status=AWAITING) - - # create validated consent for entity1 - ConsentFactory(delivery_point=dl1_3, created_by=user1, status=VALIDATED) + # get awaiting consents + c1_1 = Consent.objects.get(delivery_point=dl1_1, status=AWAITING) + c1_2 = Consent.objects.get(delivery_point=dl1_2, status=AWAITING) + c2_1 = Consent.objects.get(delivery_point=dl2_1, status=AWAITING) + c2_2 = Consent.objects.get(delivery_point=dl2_2, status=AWAITING) + c3_1 = Consent.objects.get(delivery_point=dl3_1, status=AWAITING) + c3_2 = Consent.objects.get(delivery_point=dl3_2, status=AWAITING) + + # update consent c1_3 to validated + c1_3 = Consent.objects.get(delivery_point=dl1_3) + c1_3.status = VALIDATED + c1_3.save() # create awaiting consents for entity1 in past period ConsentFactory( diff --git a/src/dashboard/dashboard/settings.py b/src/dashboard/dashboard/settings.py index 1396a792..b2606ed3 100644 --- a/src/dashboard/dashboard/settings.py +++ b/src/dashboard/dashboard/settings.py @@ -174,7 +174,20 @@ send_default_pii=True, ) -# Debug-toolbar +## Consent app + +# CONSENT_NUMBER_DAYS_END_DATE allows to calculate the end date of a consent period by +# adding a number of days to the current date. +# If the value is None, the end date of the period will correspond to the last day of +# the current year +# More details on the calculation in the function: `utils.calculate_consent_end_date()` +# ie: +# CONSENT_NUMBER_DAYS_END_DATE = 90 will return the current date + 90 days. +# CONSENT_NUMBER_DAYS_END_DATE = None will return 2024-12-31 23:59:59. +CONSENT_NUMBER_DAYS_END_DATE = None + + +## Debug-toolbar # Despite the `DEBUG` being set to `False`, for some tests, # pytest seems to consider `DEBUG` to be `True`. diff --git a/src/dashboard/readme.md b/src/dashboard/readme.md index 1e64ff88..2349a2e7 100644 --- a/src/dashboard/readme.md +++ b/src/dashboard/readme.md @@ -51,6 +51,13 @@ class HomeConfig(AppConfig): label = "qcd_home" # prefix app name with 'qcd_' ``` +## Signals + +### apps.consent.signals.handle_new_delivery_point() +There is a signal on the creation of a `delivery point` (`apps.core.models.DeliveryPoint`). +This signal allows the creation of a `consent` (`apps.consent.models.Consent`) +corresponding to the `delivery_point`. + ## License This work is released under the MIT License (see LICENSE).