From 8467453c55ad9f58bbba12b63b59ee79423b39b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Reuiller?= Date: Tue, 19 Sep 2023 18:01:30 +0200 Subject: [PATCH 1/5] tender form : remove first_name and last_name field --- .../tenders/create_step_contact.html | 14 +++---- lemarche/www/tenders/forms.py | 42 +++++++++---------- lemarche/www/tenders/tests.py | 7 +++- lemarche/www/tenders/utils.py | 4 +- lemarche/www/tenders/views.py | 13 ++++++ 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/lemarche/templates/tenders/create_step_contact.html b/lemarche/templates/tenders/create_step_contact.html index e739a9fbd..79bb8655b 100644 --- a/lemarche/templates/tenders/create_step_contact.html +++ b/lemarche/templates/tenders/create_step_contact.html @@ -9,15 +9,13 @@ {% csrf_token %}
-
- {% bootstrap_field form.contact_first_name form_group_class="form-group col-12 col-md-6" %} - {% bootstrap_field form.contact_last_name form_group_class="form-group col-12 col-md-6" %} -
{% bootstrap_field form.contact_company_name %} -
- {% bootstrap_field form.contact_email form_group_class="form-group col-12 col-md-6" %} - {% bootstrap_field form.contact_phone form_group_class="form-group col-12 col-md-6" %} -
+ {% if not user.is_authenticated %} +
+ {% bootstrap_field form.contact_email form_group_class="form-group col-12 col-md-6" %} + {% bootstrap_field form.contact_phone form_group_class="form-group col-12 col-md-6" %} +
+ {% endif %} {% bootstrap_field form.response_kind %} {% bootstrap_field form.deadline_date %}
diff --git a/lemarche/www/tenders/forms.py b/lemarche/www/tenders/forms.py index 388dce1a4..891f07f9f 100644 --- a/lemarche/www/tenders/forms.py +++ b/lemarche/www/tenders/forms.py @@ -135,8 +135,6 @@ class TenderCreateStepContactForm(forms.ModelForm): class Meta: model = Tender fields = [ - "contact_first_name", - "contact_last_name", "contact_email", "contact_phone", "response_kind", @@ -150,26 +148,22 @@ def __init__(self, max_deadline_date, external_link, user: User, *args, **kwargs super().__init__(*args, **kwargs) self.max_deadline_date = max_deadline_date self.external_link = external_link - user_is_anonymous = not user.is_authenticated + self.user_is_anonymous = not user.is_authenticated if self.instance.deadline_date: self.initial["deadline_date"] = self.instance.deadline_date.isoformat() # required fields - self.fields["contact_first_name"].required = True - self.fields["contact_last_name"].required = True self.fields["response_kind"].required = True self.fields["deadline_date"].required = True - if user_is_anonymous: + if self.user_is_anonymous: self.fields["contact_email"].required = True self.fields["contact_phone"].required = True else: - self.initial["contact_first_name"] = user.first_name - self.initial["contact_last_name"] = user.last_name - self.initial["contact_email"] = user.email - self.initial["contact_phone"] = user.phone + del self.fields["contact_email"] + del self.fields["contact_phone"] - user_does_not_have_company_name = user_is_anonymous or not user.company_name + user_does_not_have_company_name = self.user_is_anonymous or not user.company_name if user_does_not_have_company_name: self.fields["contact_company_name"].widget = forms.TextInput() # HiddenInput() by default self.fields["contact_company_name"].required = True @@ -194,18 +188,20 @@ def clean(self): self.add_error( "deadline_date", "La date de clôture des réponses ne doit pas être antérieure à aujourd'hui." ) - # contact_email must be filled if RESPONSE_KIND_TEL - if self.cleaned_data.get("response_kind") and ( - Tender.RESPONSE_KIND_EMAIL in self.cleaned_data.get("response_kind") - and not self.cleaned_data.get("contact_email") - ): - self.add_error("response_kind", "E-mail sélectionné mais aucun e-mail renseigné.") - # contact_phone must be filled if RESPONSE_KIND_TEL - if self.cleaned_data.get("response_kind") and ( - Tender.RESPONSE_KIND_TEL in self.cleaned_data.get("response_kind") - and not self.cleaned_data.get("contact_phone") - ): - self.add_error("response_kind", "Téléphone sélectionné mais aucun téléphone renseigné.") + + if self.user_is_anonymous: + # contact_email must be filled if RESPONSE_KIND_EMAIL + if self.cleaned_data.get("response_kind") and ( + Tender.RESPONSE_KIND_EMAIL in self.cleaned_data.get("response_kind") + and not self.cleaned_data.get("contact_email") + ): + self.add_error("response_kind", "E-mail sélectionné mais aucun e-mail renseigné.") + # contact_phone must be filled if RESPONSE_KIND_TEL + if self.cleaned_data.get("response_kind") and ( + Tender.RESPONSE_KIND_TEL in self.cleaned_data.get("response_kind") + and not self.cleaned_data.get("contact_phone") + ): + self.add_error("response_kind", "Téléphone sélectionné mais aucun téléphone renseigné.") # external_link must be filled if RESPONSE_KIND_EXTERNAL if self.cleaned_data.get("response_kind") and ( Tender.RESPONSE_KIND_EXTERNAL in self.cleaned_data.get("response_kind") and not self.external_link diff --git a/lemarche/www/tenders/tests.py b/lemarche/www/tenders/tests.py index bdd6598c2..75c1c7cf5 100644 --- a/lemarche/www/tenders/tests.py +++ b/lemarche/www/tenders/tests.py @@ -55,8 +55,6 @@ def _generate_fake_data_form( } | _step_2 step_3 = { "tender_create_multi_step_view-current_step": "contact", - "contact-contact_first_name": tender_not_saved.contact_first_name, - "contact-contact_last_name": tender_not_saved.contact_last_name, "contact-contact_email": tender_not_saved.contact_email, "contact-contact_phone": "0123456789", "contact-contact_company_name": "TEST", @@ -119,6 +117,11 @@ def test_tender_wizard_form_all_good_authenticated(self): TenderCreateMultiStepView, tenders_step_data, tender, is_draft=False ), ) + self.assertEqual(tender.contact_first_name, self.user_buyer.first_name) + self.assertEqual(tender.contact_last_name, self.user_buyer.last_name) + self.assertEqual(tender.contact_email, self.user_buyer.email) + self.assertEqual(tender.contact_phone, self.user_buyer.phone) + def test_tender_wizard_form_not_created(self): self.client.force_login(self.user_buyer) diff --git a/lemarche/www/tenders/utils.py b/lemarche/www/tenders/utils.py index 4f0bc0dfe..4bece7925 100644 --- a/lemarche/www/tenders/utils.py +++ b/lemarche/www/tenders/utils.py @@ -56,8 +56,8 @@ def get_or_create_user_from_anonymous_content(tender_dict: dict, source: str = U user, created = User.objects.get_or_create( email=email, defaults={ - "first_name": tender_dict.get("contact_first_name"), - "last_name": tender_dict.get("contact_last_name"), + "first_name": "", + "last_name": "", "phone": tender_dict.get("contact_phone"), "company_name": tender_dict.pop("contact_company_name") if tender_dict.get("contact_company_name") diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py index 3738cd6e6..55a2f2990 100644 --- a/lemarche/www/tenders/views.py +++ b/lemarche/www/tenders/views.py @@ -177,9 +177,22 @@ def save_instance_tender(self, tender_dict: dict, form_dict: dict, is_draft: boo elif step == self.STEP_SURVEY: setattr(self.instance, "scale_marche_useless", tender_dict.get("scale_marche_useless")) self.instance.extra_data.update(tender_dict.get("extra_data")) + if self.request.user.is_authenticated: + self.instance.contact_first_name = self.request.user.first_name + self.instance.contact_last_name = self.request.user.last_name + self.instance.contact_email = self.request.user.email + self.instance.contact_phone = self.request.user.phone + self.instance.save() else: tender_dict |= {"status": tender_status, "published_at": tender_published_at} + if self.request.user.is_authenticated: + tender_dict |= { + "contact_first_name": self.request.user.first_name, + "contact_last_name": self.request.user.last_name, + "contact_email": self.request.user.email, + "contact_phone": self.request.user.phone, + } self.instance = create_tender_from_dict(tender_dict) def done(self, _, form_dict, **kwargs): From d06aabf858424794874811d861bd5cecf49aab29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Reuiller?= Date: Wed, 20 Sep 2023 10:28:02 +0200 Subject: [PATCH 2/5] introduce match to have beautiful code and avoid too complexe flake8 alert --- lemarche/www/tenders/tests.py | 1 - lemarche/www/tenders/views.py | 49 ++++++++++++++++------------------- pyproject.toml | 2 +- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/lemarche/www/tenders/tests.py b/lemarche/www/tenders/tests.py index 75c1c7cf5..2b7c0db1c 100644 --- a/lemarche/www/tenders/tests.py +++ b/lemarche/www/tenders/tests.py @@ -122,7 +122,6 @@ def test_tender_wizard_form_all_good_authenticated(self): self.assertEqual(tender.contact_email, self.user_buyer.email) self.assertEqual(tender.contact_phone, self.user_buyer.phone) - def test_tender_wizard_form_not_created(self): self.client.force_login(self.user_buyer) tenders_step_data = self._generate_fake_data_form() diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py index 55a2f2990..8feeee1cb 100644 --- a/lemarche/www/tenders/views.py +++ b/lemarche/www/tenders/views.py @@ -152,6 +152,15 @@ def get_context_data(self, form, **kwargs): def save_instance_tender(self, tender_dict: dict, form_dict: dict, is_draft: bool): tender_status = tender_constants.STATUS_DRAFT if is_draft else tender_constants.STATUS_PUBLISHED tender_published_at = None if is_draft else timezone.now() + + if self.request.user.is_authenticated: + tender_dict |= { + "contact_first_name": self.request.user.first_name, + "contact_last_name": self.request.user.last_name, + "contact_email": self.request.user.email, + "contact_phone": self.request.user.phone, + } + if self.instance.id: # update self.instance.status = tender_status @@ -161,38 +170,26 @@ def save_instance_tender(self, tender_dict: dict, form_dict: dict, is_draft: boo if model_form.has_changed(): if step != self.STEP_SURVEY: for attribute in model_form.changed_data: - if attribute == "sectors": - sectors = tender_dict.get("sectors", None) - self.instance.sectors.set(sectors) - elif attribute == "location": - location = tender_dict.get("location") - self.instance.location = location - self.instance.perimeters.set([location]) - elif attribute == "questions_list": - update_or_create_questions_list( - tender=self.instance, questions_list=tender_dict.get("questions_list") - ) - else: - setattr(self.instance, attribute, tender_dict.get(attribute)) + match attribute: + case "sectors": + sectors = tender_dict.get("sectors", None) + self.instance.sectors.set(sectors) + case "location": + location = tender_dict.get("location") + self.instance.location = location + self.instance.perimeters.set([location]) + case "questions_list": + update_or_create_questions_list( + tender=self.instance, questions_list=tender_dict.get("questions_list") + ) + case _: + setattr(self.instance, attribute, tender_dict.get(attribute)) elif step == self.STEP_SURVEY: setattr(self.instance, "scale_marche_useless", tender_dict.get("scale_marche_useless")) self.instance.extra_data.update(tender_dict.get("extra_data")) - if self.request.user.is_authenticated: - self.instance.contact_first_name = self.request.user.first_name - self.instance.contact_last_name = self.request.user.last_name - self.instance.contact_email = self.request.user.email - self.instance.contact_phone = self.request.user.phone - self.instance.save() else: tender_dict |= {"status": tender_status, "published_at": tender_published_at} - if self.request.user.is_authenticated: - tender_dict |= { - "contact_first_name": self.request.user.first_name, - "contact_last_name": self.request.user.last_name, - "contact_email": self.request.user.email, - "contact_phone": self.request.user.phone, - } self.instance = create_tender_from_dict(tender_dict) def done(self, _, form_dict, **kwargs): diff --git a/pyproject.toml b/pyproject.toml index 9b80c88a5..ff077b0d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ use_parentheses = true [tool.black] line-length = 119 -target-version = ['py38', 'py39'] +target-version = ['py310'] include = '\.pyi?$' [tool.pytest.ini_options] From 9c39b88d8de1a0c6a35d99fb171156fad5b934b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Reuiller?= Date: Mon, 25 Sep 2023 16:01:13 +0200 Subject: [PATCH 3/5] anonymous users must fill their first and last name --- lemarche/templates/tenders/create_step_contact.html | 6 ++++++ lemarche/www/tenders/forms.py | 6 ++++++ lemarche/www/tenders/tests.py | 2 ++ lemarche/www/tenders/utils.py | 4 ++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lemarche/templates/tenders/create_step_contact.html b/lemarche/templates/tenders/create_step_contact.html index 79bb8655b..ec83009f6 100644 --- a/lemarche/templates/tenders/create_step_contact.html +++ b/lemarche/templates/tenders/create_step_contact.html @@ -9,6 +9,12 @@ {% csrf_token %}
+ {% if not user.is_authenticated %} +
+ {% bootstrap_field form.contact_first_name form_group_class="form-group col-12 col-md-6" %} + {% bootstrap_field form.contact_last_name form_group_class="form-group col-12 col-md-6" %} +
+ {% endif %} {% bootstrap_field form.contact_company_name %} {% if not user.is_authenticated %}
diff --git a/lemarche/www/tenders/forms.py b/lemarche/www/tenders/forms.py index 891f07f9f..440eca7a0 100644 --- a/lemarche/www/tenders/forms.py +++ b/lemarche/www/tenders/forms.py @@ -135,6 +135,8 @@ class TenderCreateStepContactForm(forms.ModelForm): class Meta: model = Tender fields = [ + "contact_first_name", + "contact_last_name", "contact_email", "contact_phone", "response_kind", @@ -157,9 +159,13 @@ def __init__(self, max_deadline_date, external_link, user: User, *args, **kwargs self.fields["response_kind"].required = True self.fields["deadline_date"].required = True if self.user_is_anonymous: + self.fields["contact_first_name"].required = True + self.fields["contact_last_name"].required = True self.fields["contact_email"].required = True self.fields["contact_phone"].required = True else: + del self.fields["contact_first_name"] + del self.fields["contact_last_name"] del self.fields["contact_email"] del self.fields["contact_phone"] diff --git a/lemarche/www/tenders/tests.py b/lemarche/www/tenders/tests.py index 2b7c0db1c..28696406c 100644 --- a/lemarche/www/tenders/tests.py +++ b/lemarche/www/tenders/tests.py @@ -55,6 +55,8 @@ def _generate_fake_data_form( } | _step_2 step_3 = { "tender_create_multi_step_view-current_step": "contact", + "contact-contact_first_name": tender_not_saved.contact_first_name, + "contact-contact_last_name": tender_not_saved.contact_last_name, "contact-contact_email": tender_not_saved.contact_email, "contact-contact_phone": "0123456789", "contact-contact_company_name": "TEST", diff --git a/lemarche/www/tenders/utils.py b/lemarche/www/tenders/utils.py index 4bece7925..4f0bc0dfe 100644 --- a/lemarche/www/tenders/utils.py +++ b/lemarche/www/tenders/utils.py @@ -56,8 +56,8 @@ def get_or_create_user_from_anonymous_content(tender_dict: dict, source: str = U user, created = User.objects.get_or_create( email=email, defaults={ - "first_name": "", - "last_name": "", + "first_name": tender_dict.get("contact_first_name"), + "last_name": tender_dict.get("contact_last_name"), "phone": tender_dict.get("contact_phone"), "company_name": tender_dict.pop("contact_company_name") if tender_dict.get("contact_company_name") From e846f66155efd8f8e70779b07621838b6607748a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Reuiller?= Date: Mon, 25 Sep 2023 16:16:56 +0200 Subject: [PATCH 4/5] lighten the contact labels --- lemarche/www/tenders/forms.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lemarche/www/tenders/forms.py b/lemarche/www/tenders/forms.py index 440eca7a0..47960962d 100644 --- a/lemarche/www/tenders/forms.py +++ b/lemarche/www/tenders/forms.py @@ -145,6 +145,12 @@ class Meta: widgets = { "deadline_date": forms.widgets.DateInput(attrs={"class": "form-control", "type": "date"}), } + labels = { + "contact_first_name": "Prénom", + "contact_last_name": "Nom", + "contact_email": "E-mail", + "contact_phone": "Téléphone", + } def __init__(self, max_deadline_date, external_link, user: User, *args, **kwargs): super().__init__(*args, **kwargs) From c4eb7853a5d2703e3050f383e7de806e77b36f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Reuiller?= Date: Mon, 25 Sep 2023 16:48:47 +0200 Subject: [PATCH 5/5] raise error if the user does not have a phone --- lemarche/www/tenders/forms.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lemarche/www/tenders/forms.py b/lemarche/www/tenders/forms.py index 47960962d..7fee4bc25 100644 --- a/lemarche/www/tenders/forms.py +++ b/lemarche/www/tenders/forms.py @@ -156,7 +156,8 @@ def __init__(self, max_deadline_date, external_link, user: User, *args, **kwargs super().__init__(*args, **kwargs) self.max_deadline_date = max_deadline_date self.external_link = external_link - self.user_is_anonymous = not user.is_authenticated + self.user = user + user_is_anonymous = not user.is_authenticated if self.instance.deadline_date: self.initial["deadline_date"] = self.instance.deadline_date.isoformat() @@ -164,7 +165,7 @@ def __init__(self, max_deadline_date, external_link, user: User, *args, **kwargs # required fields self.fields["response_kind"].required = True self.fields["deadline_date"].required = True - if self.user_is_anonymous: + if user_is_anonymous: self.fields["contact_first_name"].required = True self.fields["contact_last_name"].required = True self.fields["contact_email"].required = True @@ -175,7 +176,7 @@ def __init__(self, max_deadline_date, external_link, user: User, *args, **kwargs del self.fields["contact_email"] del self.fields["contact_phone"] - user_does_not_have_company_name = self.user_is_anonymous or not user.company_name + user_does_not_have_company_name = user_is_anonymous or not user.company_name if user_does_not_have_company_name: self.fields["contact_company_name"].widget = forms.TextInput() # HiddenInput() by default self.fields["contact_company_name"].required = True @@ -201,7 +202,7 @@ def clean(self): "deadline_date", "La date de clôture des réponses ne doit pas être antérieure à aujourd'hui." ) - if self.user_is_anonymous: + if not self.user.is_authenticated: # contact_email must be filled if RESPONSE_KIND_EMAIL if self.cleaned_data.get("response_kind") and ( Tender.RESPONSE_KIND_EMAIL in self.cleaned_data.get("response_kind") @@ -214,6 +215,14 @@ def clean(self): and not self.cleaned_data.get("contact_phone") ): self.add_error("response_kind", "Téléphone sélectionné mais aucun téléphone renseigné.") + elif not self.user.phone: + if self.cleaned_data.get("response_kind") and ( + Tender.RESPONSE_KIND_TEL in self.cleaned_data.get("response_kind") + ): + self.add_error( + "response_kind", "Téléphone sélectionné mais aucun téléphone renseigné dans votre profil." + ) + # external_link must be filled if RESPONSE_KIND_EXTERNAL if self.cleaned_data.get("response_kind") and ( Tender.RESPONSE_KIND_EXTERNAL in self.cleaned_data.get("response_kind") and not self.external_link