From cea8a54e12b29064fe7151edd9203b8ec3d18dcb Mon Sep 17 00:00:00 2001 From: Marjolaine Date: Thu, 26 Sep 2024 15:48:32 +0200 Subject: [PATCH] pages recaps demande habilitation - WIP --- aidants_connect_common/constants.py | 47 +++++- ...eric-habilitation-request-profile-card.css | 7 +- ...ric-habilitation-request-profile-card.html | 16 ++- aidants_connect_habilitation/forms.py | 16 ++- .../0033_alter_organisationrequest_status.py | 18 +++ aidants_connect_habilitation/models.py | 31 ++-- .../_formation_registration_button.html | 17 +-- .../common}/_recap_general_info.html | 40 +++--- .../common/_recap_personnes.html | 87 +++++++++++ .../forms/validation.html | 12 ++ .../_habilitation-request-profile-card.html | 17 +++ .../validation_form.html | 68 +++++++++ .../templates/issuer_space.html | 15 +- .../templates/validation_form.html | 136 ------------------ .../templates/view_organisation_request.html | 65 ++++----- .../test_modify_request_form.py | 9 +- .../tests/test_views.py | 2 +- aidants_connect_habilitation/views.py | 106 +++++++++----- .../usagers/usager_row.html | 6 +- pyproject.toml | 3 + 20 files changed, 437 insertions(+), 281 deletions(-) create mode 100644 aidants_connect_habilitation/migrations/0033_alter_organisationrequest_status.py rename aidants_connect_habilitation/templates/{ => aidants_connect_habilitation/common}/_recap_general_info.html (72%) create mode 100644 aidants_connect_habilitation/templates/aidants_connect_habilitation/common/_recap_personnes.html create mode 100644 aidants_connect_habilitation/templates/aidants_connect_habilitation/forms/validation.html create mode 100644 aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/_habilitation-request-profile-card.html create mode 100644 aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/validation_form.html delete mode 100644 aidants_connect_habilitation/templates/validation_form.html diff --git a/aidants_connect_common/constants.py b/aidants_connect_common/constants.py index 7684e742f..76cbcf240 100644 --- a/aidants_connect_common/constants.py +++ b/aidants_connect_common/constants.py @@ -6,7 +6,14 @@ from django.db.models import Choices, IntegerChoices, TextChoices from django.db.models.enums import ChoicesMeta as DjangoChoicesMeta from django.utils.functional import Promise, classproperty +from django.utils.safestring import mark_safe from django.utils.timezone import now +from django.utils.version import PY311 + +if PY311: + from enum import property as enum_property +else: + from types import DynamicClassAttribute as enum_property __all__ = [ "DictChoices", @@ -253,13 +260,45 @@ class RequestOriginConstants(IntegerChoices): class RequestStatusConstants(TextChoicesEnum): NEW = "Brouillon" - AC_VALIDATION_PROCESSING = "En attente de validation par Aidants Connect" - VALIDATED = "Validée" - REFUSED = "Refusée" + AC_VALIDATION_PROCESSING = mark_safe( + "En attente de validation d’éligibilité avant inscription en " + "formation des aidants" + ) + VALIDATED = "Éligibilité validée" + REFUSED = "Éligibilité Refusée" CLOSED = "Clôturée" - CHANGES_REQUIRED = "Modifications demandées" + CHANGES_REQUIRED = "Demande de modifications par l’équipe Aidants Connect" CHANGES_PROPOSED = "Modifications proposées par Aidants Connect" + @enum_property + def description(self): + match self: + case self.AC_VALIDATION_PROCESSING: + return mark_safe( + "

Votre demande d’habilitation est en cours d’instruction " + "par nos équipes. Vous serez prochainement notifié de la " + "décision de nos équipes concernant votre dossier." + ) + case self.VALIDATED: + return mark_safe( + "

Félicitations, votre demande d’habilitation a été acceptée par " + "Aidants Connect !

" + "

Vous pouvez désormais inscrire le référent sur un webinaire " + "d’information dédié aux référents et inscrire les aidants en " + "formation.

" + ) + case self.CHANGES_REQUIRED: + return mark_safe( + "

L'équipe Aidants Connect a étudié votre demande d’habilitation " + "et souhaite que vous y apportiez des modifications. N’oubliez pas " + "de valider à nouveau votre demande d’habilitation en cliquant sur " + "le bouton « Soumettre la demande » pour que l'équipe Aidants " + "Connect prenne en compte vos modifications et valide votre " + "demande

" + ) + case _: + return "" + @classproperty def aidant_registrable(cls): """Statuses that allow to add new aidants to an habilitation request""" diff --git a/aidants_connect_common/static/css/generic-habilitation-request-profile-card.css b/aidants_connect_common/static/css/generic-habilitation-request-profile-card.css index b445c09a1..b16ce47ac 100644 --- a/aidants_connect_common/static/css/generic-habilitation-request-profile-card.css +++ b/aidants_connect_common/static/css/generic-habilitation-request-profile-card.css @@ -28,10 +28,5 @@ details.request-card-details .details-content { padding: inherit; position: absolute; width: 100%; - z-index: 0; -} - -details.request-card-details .details-content .fr-btns-group .fr-btn { - margin-bottom: 0; - margin-top: 0; + z-index: 11; } diff --git a/aidants_connect_common/templates/habilitation/generic-habilitation-request-profile-card.html b/aidants_connect_common/templates/habilitation/generic-habilitation-request-profile-card.html index 41d53bdae..587d1b2ad 100644 --- a/aidants_connect_common/templates/habilitation/generic-habilitation-request-profile-card.html +++ b/aidants_connect_common/templates/habilitation/generic-habilitation-request-profile-card.html @@ -3,7 +3,11 @@ {# Prevent messing with the absolute positionning of the design #} {# See https://stackoverflow.com/questions/17115344/absolute-positioning-ignoring-padding-of-parent #}
-
+
{% block details_introduction %}{% endblock %}

{{ habilitation_request.user.full_name }}

- {% if habilitation_request.user.email %} -

{{ habilitation_request.user.email }}

- {% endif %} + {% block details_subtitles %} + {% if habilitation_request.user.email %} +

{{ habilitation_request.user.email }}

+ {% endif %} + {% endblock details_subtitles %}
{% endfor %} -
diff --git a/aidants_connect_habilitation/templates/aidants_connect_habilitation/common/_recap_personnes.html b/aidants_connect_habilitation/templates/aidants_connect_habilitation/common/_recap_personnes.html new file mode 100644 index 000000000..69da3be53 --- /dev/null +++ b/aidants_connect_habilitation/templates/aidants_connect_habilitation/common/_recap_personnes.html @@ -0,0 +1,87 @@ +{% load static %} + +
+

Personnes impliquées

+
+ {% if organisation.status in organisation.Status.aidant_registrable %} + + Ajouter un aidant à la demande + + {% endif %} +
+
+

+ Il vous est encore possible d’ajouter ou de supprimer des aidants de votre demande. Vous pouvez également + vérifier les informations saisies ci-dessous. +

+
+
+

Référents

+
+ {% if organisation.manager %} +
+ +
+ {{ organisation.manager.get_full_name }} +
+
+
+

référent

+ {% if organisation.manager.is_aidant %} +

aidant

+ {% endif %} +
+
Email
+
{{ organisation.manager.email }}
+
Profession
+
{{ organisation.manager.profession }}
+
Conseiller numérique
+
{{ organisation.manager.conseiller_numerique|yesno:"Oui,Non" }}
+
Organisation
+
{{ organisation.manager.organisation }}
+ + + {% else %} + + {% endif %} +
+
+
+

Aidants

+ {% if habilitation_requests %} +
+ {% for habilitation_request in habilitation_requests %} + {% include "aidants_connect_habilitation/validation_request_form_view/_habilitation-request-profile-card.html" %} + {% endfor %} +
+ {% else %} +
+ Vous n'avez pas encore ajouté d'aidant à votre demande +
+ {% endif %} +
+
diff --git a/aidants_connect_habilitation/templates/aidants_connect_habilitation/forms/validation.html b/aidants_connect_habilitation/templates/aidants_connect_habilitation/forms/validation.html new file mode 100644 index 000000000..7b5626d67 --- /dev/null +++ b/aidants_connect_habilitation/templates/aidants_connect_habilitation/forms/validation.html @@ -0,0 +1,12 @@ +{% load dsfr_tags %} +
+ Conditions et modalités d’utilisation + {% dsfr_form_field form.cgu %} + {% dsfr_form_field form.not_free %} +
+
+ Personnes impliquées + {% dsfr_form_field form.dpo %} + {% dsfr_form_field form.professionals_only %} + {% dsfr_form_field form.without_elected %} +
diff --git a/aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/_habilitation-request-profile-card.html b/aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/_habilitation-request-profile-card.html new file mode 100644 index 000000000..86abfc63e --- /dev/null +++ b/aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/_habilitation-request-profile-card.html @@ -0,0 +1,17 @@ +{% extends "habilitation/generic-habilitation-request-profile-card.html" %} +{% load ac_common ac_extras dsfr_tags static %} + +{% block details_subtitles %} + {% if organisation.manager.aidant.last_login %} + + Gérer les formations sur mon espace référent + + {% elif aidant.habilitation_request.formations.exists %} + {% dsfr_badge label="Cette personne est inscrite" extra_classes="fr-badge--sm fr-badge--success" %} + {% else %} + {{ block.super }} + {% endif %} +{% endblock details_subtitles %} diff --git a/aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/validation_form.html b/aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/validation_form.html new file mode 100644 index 000000000..6138d0d86 --- /dev/null +++ b/aidants_connect_habilitation/templates/aidants_connect_habilitation/validation_request_form_view/validation_form.html @@ -0,0 +1,68 @@ +{% extends 'layouts/main.html' %} +{% load ac_common dsfr_tags form_extras static %} + +{% block title %} + Demande d'habilitation pour {{ organisation.name }} - Aidants Connect +{% endblock title %} + +{% block extracss %} + +{% endblock extracss %} + +{% block content %} + +
+ {% if organisation.status == organisation.Status.NEW %} +

Récapitulatif de la demande

+ {% else %} +

Demande n° {{ organisation.data_pass_id }}

+
+

Statut : {{ organisation.status_enum.label }}

+ {% if organisation.status_enum.description %} +
{{ organisation.status_enum.description }}
+ {% endif %} +
+ {% endif %} + {% include 'aidants_connect_habilitation/common/_recap_general_info.html' with show_edit_buttons=True %} +
+

Une question ?

+

+ Si vous souhaitez ajouter une précision sur votre formulaire ou nous poser une question, + vous pouvez nous contacter à l’adresse suivante :
+ {% mailto "contact@aidantsconnect.beta.gouv.fr" %} +

+
+
+ {% include 'aidants_connect_habilitation/common/_recap_personnes.html' %} +
+
+ +
+

Validation de la demande

+
+
+ {% csrf_token %} + {{ form }} +
+ +
+
+{% endblock content %} diff --git a/aidants_connect_habilitation/templates/issuer_space.html b/aidants_connect_habilitation/templates/issuer_space.html index 8997fbb12..3b97c6e34 100644 --- a/aidants_connect_habilitation/templates/issuer_space.html +++ b/aidants_connect_habilitation/templates/issuer_space.html @@ -1,9 +1,9 @@ {% extends 'layouts/main-habilitation.html' %} -{% load static form_extras %} +{% load form_extras static %} {% block title %} Aidants Connect - Mon espace habilitation -{% endblock %} +{% endblock title %} {% block content %}
@@ -72,11 +72,16 @@

{{ organisation.name }} - {{ organisation.status_label }}<

{% if organisation.status == "NEW" %} - Soumettre la demande + {% elif organisation.status == "CHANGES_REQUIRED" %} + + Voir + {% else %} + href='{% url "habilitation_organisation_view" issuer_id=issuer.issuer_id uuid=organisation.uuid %}'> Voir {% endif %} @@ -98,4 +103,4 @@

{{ organisation.name }} - {{ organisation.status_label }}< {% include "_more-info.html" %}

-{% endblock %} +{% endblock content %} diff --git a/aidants_connect_habilitation/templates/validation_form.html b/aidants_connect_habilitation/templates/validation_form.html deleted file mode 100644 index 9dd2755ce..000000000 --- a/aidants_connect_habilitation/templates/validation_form.html +++ /dev/null @@ -1,136 +0,0 @@ -{% extends 'layouts/main.html' %} -{% load static ac_common form_extras dsfr_tags %} - -{% block title %} - Récapitulatif pour {{ organisation.name }} - Aidants Connect -{% endblock %} - -{% block extracss %} - -{% endblock %} - -{% block content %} - -
-

Récapitulatif de la demande

- {% include '_recap_general_info.html' with show_edit_buttons=True %} -
-

Une question ?

-

- Si vous souhaitez ajouter une précision sur votre formulaire ou nous poser une question, - vous pouvez nous contacter à l’adresse suivante :
- {% mailto "contact@aidantsconnect.beta.gouv.fr" %} -

-
-
-
-

Personnes impliquées

-
- {% if organisation.status in organisation.Status.aidant_registrable %} - - Ajouter un aidant à la demande - - {% endif %} -
-
-

Il vous est encore possible d’ajouter ou de supprimer des aidants de votre demande. Vous pouvez également - vérifier - les informations saisies ci-dessous.

-
-
-

Référents

-
- {% if organisation.manager %} -
- -
- {{ organisation.manager.get_full_name }} -
-
-
-

référent

- {% if organisation.manager.is_aidant %} -

aidant

- {% endif %} -
-
Email
-
{{ organisation.manager.email }}
-
Profession
-
{{ organisation.manager.profession }}
-
Conseiller numérique
-
{{ organisation.manager.conseiller_numerique|yesno:"Oui,Non" }}
-
Organisation
-
{{ organisation.manager.organisation }}
- - - {% else %} - - {% endif %} -
-
-
-

Aidants

- {% if habilitation_requests %} -
- {% for habilitation_request in habilitation_requests %} - {% include "habilitation/generic-habilitation-request-profile-card.html" %} - {% endfor %} -
- {% else %} -
- Vous n'avez pas encore ajouté d'aidant à votre demande -
- {% endif %} -
-
- -
-
- -
-

Validation de la demande

-
-
- {% csrf_token %} -

Conditions et modalités d’utilisation

- {% dsfr_form_field form.cgu %} - {% dsfr_form_field form.not_free %} -

Personnes impliquées

- {% dsfr_form_field form.dpo %} - {% dsfr_form_field form.professionals_only %} - {% dsfr_form_field form.without_elected %} -
- - Revenir à l’étape précédente - - -
-
-{% endblock %} diff --git a/aidants_connect_habilitation/templates/view_organisation_request.html b/aidants_connect_habilitation/templates/view_organisation_request.html index 7cb358845..c76f4910a 100644 --- a/aidants_connect_habilitation/templates/view_organisation_request.html +++ b/aidants_connect_habilitation/templates/view_organisation_request.html @@ -1,47 +1,40 @@ -{% extends 'layouts/main-habilitation.html' %} -{% load static form_extras ac_common %} +{% extends 'layouts/main.html' %} +{% load ac_common form_extras static %} {% block title %} - Aidants Connect — Demande d'habilitation pour {{ organisation.name }} -{% endblock %} + Demande d'habilitation pour {{ organisation.name }} - Aidants Connect +{% endblock title %} + +{% block extracss %} + +{% endblock extracss %} {% block content %}
-

Demande d’habilitation n° {{ organisation.data_pass_id }}

-

- -

-

Votre demande

-

- Votre demande est actuellement dans l'état « {{ organisation.status_label }} ». -
- {% include "edito/_aide_etats_demandes_habilitation.html" with status=organisation.status %} -

-

Rappel de votre saisie

- {% if organisation.status in organisation.Status.validatable %} - - Modifier votre demande - - {% endif %} - - {% include "_display_org_request.html" with show_edit_buttons=False %} - -
-

- Pour modifier cette demande (ajouter un aidant, etc.), envoyez votre requête par mail à lʼadresse - {% mailto AC_CONTACT_EMAIL %}. +

Demande n° {{ organisation.data_pass_id }}

+
+
+
+

Statut : {{ organisation.status_enum.label }}

+ {% if organisation.status_enum.description %} +
{{ organisation.status_enum.description }}
+ {% endif %} +
+
+
+ {% include 'aidants_connect_habilitation/common/_recap_general_info.html' %} +
+

Une question ?

+

+ Si vous souhaitez ajouter une précision sur votre formulaire ou nous poser une question, + vous pouvez nous contacter à l’adresse suivante : {% mailto "contact@aidantsconnect.beta.gouv.fr" %}

- -
{# fr-container #} -{% endblock %} - + {% include 'aidants_connect_habilitation/common/_recap_personnes.html' %} +
+{% endblock content %} {% block extrajs %} {% stimulusjs %} -{% endblock %} +{% endblock extrajs %} diff --git a/aidants_connect_habilitation/tests/test_functionnal/test_modify_request_form.py b/aidants_connect_habilitation/tests/test_functionnal/test_modify_request_form.py index 050eaa6ba..02ef38fc0 100644 --- a/aidants_connect_habilitation/tests/test_functionnal/test_modify_request_form.py +++ b/aidants_connect_habilitation/tests/test_functionnal/test_modify_request_form.py @@ -19,6 +19,9 @@ @tag("functional") class AddAidantsRequestViewTests(FunctionalTestCase): + def setUp(self): + self.add_aidant_css = "#add-aidants-btn" + def test_add_aidant_button_shown_in_readonly_view_under_correct_conditions(self): unauthorized_statuses = set(RequestStatusConstants) - set( RequestStatusConstants.aidant_registrable @@ -29,9 +32,7 @@ def test_add_aidant_button_shown_in_readonly_view_under_correct_conditions(self) status=status ) self.__open_readonly_view_url(organisation) - - self.selenium.find_element(By.CSS_SELECTOR, ".fr-container") - self.assertElementNotFound(By.CSS_SELECTOR, "#add-aidants-btn") + self.assertElementNotFound(By.CSS_SELECTOR, self.add_aidant_css) for status in RequestStatusConstants.aidant_registrable: organisation: OrganisationRequest = OrganisationRequestFactory( @@ -39,7 +40,7 @@ def test_add_aidant_button_shown_in_readonly_view_under_correct_conditions(self) ) self.__open_readonly_view_url(organisation) - self.selenium.find_element(By.CSS_SELECTOR, "#add-aidants-btn").click() + self.selenium.find_element(By.CSS_SELECTOR, self.add_aidant_css).click() path = reverse( "habilitation_organisation_add_aidants", diff --git a/aidants_connect_habilitation/tests/test_views.py b/aidants_connect_habilitation/tests/test_views.py index 1072d9eb8..d245ece59 100644 --- a/aidants_connect_habilitation/tests/test_views.py +++ b/aidants_connect_habilitation/tests/test_views.py @@ -867,7 +867,7 @@ class ValidationRequestFormViewTests(TestCase): def setUpTestData(cls): cls.client = Client() cls.pattern_name = "habilitation_validation" - cls.template_name = "validation_form.html" + cls.template_name = "aidants_connect_habilitation/validation_request_form_view/validation_form.html" # noqa: E501 cls.organisation: OrganisationRequest = DraftOrganisationRequestFactory( manager=ManagerFactory() ) diff --git a/aidants_connect_habilitation/views.py b/aidants_connect_habilitation/views.py index 89f97f03a..34bd40f95 100644 --- a/aidants_connect_habilitation/views.py +++ b/aidants_connect_habilitation/views.py @@ -28,6 +28,7 @@ OrganisationRequestForm, PersonnelForm, RequestMessageForm, + RequestViewForm, ValidationForm, ) from aidants_connect_habilitation.models import ( @@ -164,6 +165,39 @@ def define_html_attributes(self, form: PatchedModelForm): """Real views""" +class ProfileCardAidantRequestPresenter: + def __init__(self, req: AidantRequest): + self.req = req + + @property + def obj(self): + return self + + @property + def user(self): + return { + "full_name": self.req.get_full_name(), + "email": self.req.email, + "edit_href": reverse( + "habilitation_new_aidants", + kwargs={ + "issuer_id": self.req.organisation.issuer.issuer_id, + "uuid": self.req.organisation.uuid, + }, + ), + "details_fields": [ + # email profession conseiller_numerique organisation + {"label": "Email", "value": self.req.email}, + {"label": "Profession", "value": self.req.profession}, + { + "label": "Conseiller numérique", + "value": yesno(self.req.conseiller_numerique, "Oui,Non"), + }, + {"label": "Organisation", "value": self.req.organisation}, + ], + } + + class NewHabilitationView(RedirectView): permanent = True pattern_name = "habilitation_new_issuer" @@ -393,7 +427,7 @@ def get_success_url(self): class ValidationRequestFormView(OnlyNewRequestsView, FormView): - template_name = "validation_form.html" + template_name = "aidants_connect_habilitation/validation_request_form_view/validation_form.html" # noqa: E501 form_class = ValidationForm @property @@ -401,37 +435,18 @@ def step(self) -> HabilitationFormStep: return HabilitationFormStep.SUMMARY def get_context_data(self, **kwargs): - return { - **super().get_context_data(**kwargs), - "organisation": self.organisation, - "habilitation_requests": [ - { - "user": { - "full_name": it.get_full_name(), - "email": it.email, - "edit_href": reverse( - "habilitation_new_aidants", - kwargs={ - "issuer_id": it.organisation.issuer.issuer_id, - "uuid": it.organisation.uuid, - }, - ), - "details_fields": [ - # email profession conseiller_numerique organisation - {"label": "Email", "value": it.email}, - {"label": "Profession", "value": it.profession}, - { - "label": "Conseiller numérique", - "value": yesno(it.conseiller_numerique, "Oui,Non"), - }, - {"label": "Organisation", "value": it.organisation}, - ], - } - } - for it in self.organisation.aidant_requests.all() - ], - "type_other": RequestOriginConstants.OTHER.value, - } + kwargs.update( + { + "organisation": self.organisation, + # using a generator to avoid unneccessary computations + "habilitation_requests": ( + ProfileCardAidantRequestPresenter(it) + for it in self.organisation.aidant_requests.all + ), + "type_other": RequestOriginConstants.OTHER.value, + } + ) + return super().get_context_data(**kwargs) def get_success_url(self): return reverse( @@ -442,6 +457,15 @@ def get_success_url(self): }, ) + def get_initial(self): + return { + "cgu": self.organisation.cgu, + "not_free": self.organisation.not_free, + "dpo": self.organisation.dpo, + "professionals_only": self.organisation.professionals_only, + "without_elected": self.organisation.without_elected, + } + def form_valid(self, form): form.save(self.organisation) return super().form_valid(form) @@ -467,7 +491,11 @@ def get_context_data(self, **kwargs): return { **super().get_context_data(**kwargs), "organisation": self.organisation, - "aidants": self.organisation.aidant_requests, + # using a generator to avoid unneccessary computations + "habilitation_requests": ( + ProfileCardAidantRequestPresenter(it) + for it in self.organisation.aidant_requests.all() + ), } def get_success_url(self): @@ -493,6 +521,18 @@ def form_valid(self, form): return super().form_valid(form) + def post(self, request, *args, **kwargs): + form: RequestViewForm = self.get_form() + if self.organisation.manager is None: + form.add_error( + None, + "Veuillez ajouter le ou la référente de la structure avant validation.", + ) + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + class AddAidantsRequestView(LateStageRequestView, FormView): template_name = "add_aidants_request.html" diff --git a/aidants_connect_web/templates/aidants_connect_web/usagers/usager_row.html b/aidants_connect_web/templates/aidants_connect_web/usagers/usager_row.html index 1da6e9dd9..094ec3eb8 100644 --- a/aidants_connect_web/templates/aidants_connect_web/usagers/usager_row.html +++ b/aidants_connect_web/templates/aidants_connect_web/usagers/usager_row.html @@ -12,7 +12,7 @@ {{ usager.family_name }} - {{ usager.birthdate |date:"d F" }} + {{ usager.birthdate|date:"d F" }} {% if mandats|length > 1 %} @@ -44,7 +44,7 @@ {% else %} -
+
@@ -94,7 +94,7 @@ {% else %} -
+
diff --git a/pyproject.toml b/pyproject.toml index 47528a07e..163d449da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,6 @@ +[project] +requires-python = ">=3.11" + [tool.black] force-exclude = ''' /(