From 73378b15a354533bf7f5ef057d80dc6eb63145be Mon Sep 17 00:00:00 2001 From: Raphael Odini Date: Thu, 14 Sep 2023 11:02:23 +0200 Subject: [PATCH] =?UTF-8?q?D=C3=A9p=C3=B4t=20de=20besoin=20:=20permettre?= =?UTF-8?q?=20=C3=A0=20certains=20utilisateurs=20anonyme=20de=20se=20mettr?= =?UTF-8?q?e=20en=20relation=20(#899)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * TenderSiae contact click: allow anon with siae_id * Fix template param replace * Mixin: check that siae_id is numeric. Add test --- .../_login_or_signup_siae_tender_modal.html | 3 + lemarche/templates/tenders/_detail_card.html | 10 +- .../templates/tenders/_detail_contact.html | 2 +- .../_detail_contact_click_confirm_modal.html | 21 ++- lemarche/templates/tenders/_detail_cta.html | 4 +- lemarche/templates/tenders/detail.html | 135 +++++++++--------- lemarche/utils/mixins.py | 9 ++ lemarche/www/tenders/tests.py | 63 +++++++- lemarche/www/tenders/views.py | 34 +++-- 9 files changed, 194 insertions(+), 87 deletions(-) diff --git a/lemarche/templates/auth/_login_or_signup_siae_tender_modal.html b/lemarche/templates/auth/_login_or_signup_siae_tender_modal.html index 1441a11b1..00aca420a 100644 --- a/lemarche/templates/auth/_login_or_signup_siae_tender_modal.html +++ b/lemarche/templates/auth/_login_or_signup_siae_tender_modal.html @@ -1,5 +1,6 @@ {% load static %} {% load theme_inclusion %} + + + diff --git a/lemarche/templates/tenders/_detail_cta.html b/lemarche/templates/tenders/_detail_cta.html index 4c30ea74f..9891c8a7b 100644 --- a/lemarche/templates/tenders/_detail_cta.html +++ b/lemarche/templates/tenders/_detail_cta.html @@ -4,8 +4,8 @@

{{ tender.cta_card_title_text|safe }}

{{ tender.cta_card_paragraph_text|safe }}

- {% if user.is_authenticated %} - {% else %} diff --git a/lemarche/templates/tenders/detail.html b/lemarche/templates/tenders/detail.html index 5885d7d62..927bdf45d 100644 --- a/lemarche/templates/tenders/detail.html +++ b/lemarche/templates/tenders/detail.html @@ -28,34 +28,37 @@
{# Afficher les contacts en haut + conseil #} - {% if user_siae_has_detail_contact_click_date and not tender.deadline_date_outdated %} -
-
-
- {% include "tenders/_detail_contact.html" with tender=tender source="alert" %} + {% if not tender.deadline_date_outdated %} + {% if user_siae_has_detail_contact_click_date or siae_has_detail_contact_click_date %} +
+
+
+ {% include "tenders/_detail_contact.html" with tender=tender source="alert" %} +
-
-
- -
+ {% endif %} {% endif %}
+ {# Main tender card #}
{% include "tenders/_detail_card.html" with tender=tender %}
- {# Sidebar "actions" #} + {# Sidebar with actions #}
- {% if tender.author == request.user %} + {% if user == tender.author %} {% if is_draft %}
@@ -104,54 +107,58 @@ {{ tender.siae_detail_contact_click_date_count }} prestataire{{ tender.siae_detail_contact_click_date_count|pluralize }} intéressé{{ tender.siae_detail_contact_click_date_count|pluralize }} {% endif %} - {% endif %} - {% if tender.author != request.user and not tender.deadline_date_outdated %} - {% if tender.siae_detail_display_date_count_all > 0 %} - - {% endif %} - {% if user.is_authenticated %} - {% if user.kind == user.KIND_PARTNER %} - {% if not user_partner_can_display_tender_contact_details %} - + {% else %} + {% if not tender.deadline_date_outdated %} + {% if tender.siae_detail_display_date_count_all > 0 %} + + {% endif %} + {% if user.is_authenticated %} + {% if user.kind == user.KIND_PARTNER %} + {% if not user_partner_can_display_tender_contact_details %} + + {% endif %} + {% elif user.kind == user.KIND_SIAE %} + {% if not user.has_siae %} + + {% elif not user_siae_has_detail_contact_click_date %} + {% include "tenders/_detail_cta.html" with tender=tender user_can_click=True %} + {% endif %} {% endif %} - {% elif user.kind == user.KIND_SIAE %} - {% if not user.has_siae %} - - {% elif not user_siae_has_detail_contact_click_date %} - {% include "tenders/_detail_cta.html" with user=user tender=tender %} - + {% elif siae_id %} + {% if not siae_has_detail_contact_click_date %} + {% include "tenders/_detail_cta.html" with tender=tender user_can_click=True siae_id=siae_id %} + {% else %} + {% include "tenders/_detail_contact.html" with tender=tender %} {% endif %} + {% else %} + {% include "tenders/_detail_cta.html" with tender=tender user_can_click=False %} {% endif %} - {% else %} - {% include "tenders/_detail_cta.html" with user=user tender=tender %} {% endif %} {% endif %}
diff --git a/lemarche/utils/mixins.py b/lemarche/utils/mixins.py index 852321968..3894a30ea 100644 --- a/lemarche/utils/mixins.py +++ b/lemarche/utils/mixins.py @@ -171,6 +171,15 @@ def handle_no_permission(self): return HttpResponseRedirect(reverse_lazy("wagtail_serve", args=("",))) +class SiaeUserRequiredOrSiaeIdParamMixin(UserPassesTestMixin): + def test_func(self): + siae_id = self.request.GET.get("siae_id", None) + return SiaeUserRequiredMixin.test_func(self) or (siae_id and siae_id.isnumeric()) + + def handle_no_permission(self): + return HttpResponseForbidden() + + class SesameTokenRequiredUserPassesTestMixin(UserPassesTestMixin): """ Custom mixin that checks that a valid django-sesame token is passed diff --git a/lemarche/www/tenders/tests.py b/lemarche/www/tenders/tests.py index 9bb11827b..dfebfaa93 100644 --- a/lemarche/www/tenders/tests.py +++ b/lemarche/www/tenders/tests.py @@ -843,6 +843,16 @@ def test_tender_contact_details_display(self): self.assertNotContains(response, settings.TEAM_CONTACT_EMAIL) self.assertNotContains(response, "Voir l'appel d'offres") self.assertContains(response, "Lien partagé") + # tender_4 siae user interested but logged out (with siae_id parameter) + self.client.logout() + url = reverse("tenders:detail", kwargs={"slug": tender_4.slug}) + f"?siae_id={self.siae_1.id}" + response = self.client.get(url) + self.assertContains(response, "Contactez le client dès maintenant") + self.assertNotContains(response, tender_4.contact_email) + self.assertNotContains(response, tender_4.contact_phone) + self.assertNotContains(response, settings.TEAM_CONTACT_EMAIL) + self.assertNotContains(response, "Voir l'appel d'offres") + self.assertContains(response, "Lien partagé") class TenderDetailContactClickStatViewViewTest(TestCase): @@ -860,10 +870,9 @@ def setUpTestData(cls): def test_anonymous_user_cannot_call_tender_contact_click(self): url = reverse("tenders:detail-contact-click-stat", kwargs={"slug": self.tender.slug}) response = self.client.post(url) - self.assertEqual(response.status_code, 302) - self.assertTrue(response.url.startswith("/accounts/login/")) + self.assertEqual(response.status_code, 403) - def test_only_siae_user_can_call_tender_contact_click(self): + def test_only_siae_user_or_with_siae_id_param_can_call_tender_contact_click(self): # forbidden for user in [self.user_buyer_1, self.user_buyer_2, self.user_partner, self.user_admin]: self.client.force_login(user) @@ -876,6 +885,19 @@ def test_only_siae_user_can_call_tender_contact_click(self): url = reverse("tenders:detail-contact-click-stat", kwargs={"slug": self.tender.slug}) response = self.client.post(url, data={"detail_contact_click_confirm": "false"}) self.assertEqual(response.status_code, 302) + # authorized with siae_id parameter + self.client.logout() + url = ( + reverse("tenders:detail-contact-click-stat", kwargs={"slug": self.tender.slug}) + + f"?siae_id={self.siae.id}" + ) + response = self.client.post(url, data={"detail_contact_click_confirm": "false"}) + self.assertEqual(response.status_code, 302) + # forbidden because wrong siae_id parameter + self.client.logout() + url = reverse("tenders:detail-contact-click-stat", kwargs={"slug": self.tender.slug}) + "?siae_id=test" + response = self.client.post(url, data={"detail_contact_click_confirm": "false"}) + self.assertEqual(response.status_code, 403) def test_update_tendersiae_stats_on_tender_contact_click(self): siae_2 = SiaeFactory(name="ABC Insertion") @@ -903,7 +925,7 @@ def test_update_tendersiae_stats_on_tender_contact_click(self): response = self.client.get(url) self.assertContains(response, "contactez dès maintenant le client") # clicking again on the button doesn't update detail_contact_click_date - # Note: button will disappear on reload + # Note: button will disappear on reload anyway url = reverse("tenders:detail-contact-click-stat", kwargs={"slug": self.tender.slug}) response = self.client.post(url, data={"detail_contact_click_confirm": "false"}) self.assertEqual(response.status_code, 302) @@ -911,6 +933,39 @@ def test_update_tendersiae_stats_on_tender_contact_click(self): self.tender.tendersiae_set.first().detail_contact_click_date, siae_2_detail_contact_click_date ) + def test_update_tendersiae_stats_on_tender_contact_click_with_siae_id_param(self): + siae_2 = SiaeFactory(name="ABC Insertion") + self.siae_user_2.siaes.add(siae_2) + self.tender.siaes.add(siae_2) + self.assertEqual(self.tender.tendersiae_set.count(), 2) + self.assertEqual(self.tender.tendersiae_set.first().siae, siae_2) + self.assertEqual(self.tender.tendersiae_set.last().siae, self.siae) + self.assertIsNone(self.tender.tendersiae_set.first().detail_contact_click_date) + self.assertIsNone(self.tender.tendersiae_set.last().detail_contact_click_date) + # first load + url = reverse("tenders:detail", kwargs={"slug": self.tender.slug}) + f"?siae_id={siae_2.id}" + response = self.client.get(url) + self.assertNotContains(response, "contactez dès maintenant le client") + # click on button + url = reverse("tenders:detail-contact-click-stat", kwargs={"slug": self.tender.slug}) + f"?siae_id={siae_2.id}" + response = self.client.post(url, data={"detail_contact_click_confirm": "true"}) + self.assertEqual(response.status_code, 302) + siae_2_detail_contact_click_date = self.tender.tendersiae_set.first().detail_contact_click_date + self.assertNotEqual(siae_2_detail_contact_click_date, None) + self.assertEqual(self.tender.tendersiae_set.last().detail_contact_click_date, None) + # reload page + url = reverse("tenders:detail", kwargs={"slug": self.tender.slug}) + f"?siae_id={siae_2.id}" + response = self.client.get(url) + self.assertContains(response, "contactez dès maintenant le client") + # clicking again on the button doesn't update detail_contact_click_date + # Note: button will disappear on reload anyway + url = reverse("tenders:detail-contact-click-stat", kwargs={"slug": self.tender.slug}) + f"?siae_id={siae_2.id}" + response = self.client.post(url, data={"detail_contact_click_confirm": "false"}) + self.assertEqual(response.status_code, 302) + self.assertEqual( + self.tender.tendersiae_set.first().detail_contact_click_date, siae_2_detail_contact_click_date + ) + # TODO: this test doesn't work anymore. find a way to test logging post-email in non-prod environments? # class TenderTasksTest(TestCase): diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py index 4e9af4515..3738cd6e6 100644 --- a/lemarche/www/tenders/views.py +++ b/lemarche/www/tenders/views.py @@ -19,6 +19,7 @@ from lemarche.utils.data import get_choice from lemarche.utils.mixins import ( SesameTenderAuthorRequiredMixin, + SiaeUserRequiredOrSiaeIdParamMixin, TenderAuthorOrAdminRequiredIfNotValidatedMixin, TenderAuthorOrAdminRequiredMixin, ) @@ -288,10 +289,10 @@ def get(self, request, *args, **kwargs): """ self.object = self.get_object() user = self.request.user - siae_id = request.GET.get("siae_id", None) + self.siae_id = request.GET.get("siae_id", None) # update 'email_link_click_date' - if siae_id: - TenderSiae.objects.filter(tender=self.object, siae_id=siae_id, email_link_click_date=None).update( + if self.siae_id: + TenderSiae.objects.filter(tender=self.object, siae_id=self.siae_id, email_link_click_date=None).update( email_link_click_date=timezone.now(), updated_at=timezone.now() ) # update 'detail_display_date' @@ -323,6 +324,11 @@ def get_context_data(self, **kwargs): if user_kind == User.KIND_SIAE and self.object.kind == tender_constants.KIND_PROJECT else self.object.get_kind_display() ) + if self.siae_id: + context["siae_id"] = self.siae_id + context["siae_has_detail_contact_click_date"] = TenderSiae.objects.filter( + tender=self.object, siae_id=int(self.siae_id), detail_contact_click_date__isnull=False + ).exists() if user.is_authenticated: if self.object.author == user: context["is_draft"] = self.object.status == tender_constants.STATUS_DRAFT @@ -341,7 +347,7 @@ def get_context_data(self, **kwargs): return context -class TenderDetailContactClickStatView(LoginRequiredMixin, UpdateView): +class TenderDetailContactClickStatView(SiaeUserRequiredOrSiaeIdParamMixin, UpdateView): """ Endpoint to track contact_clicks by interested Siaes We might also send a notification to the buyer @@ -357,12 +363,18 @@ def post(self, request, *args, **kwargs): self.object = self.get_object() user = self.request.user detail_contact_click_confirm = self.request.POST.get("detail_contact_click_confirm", False) == "true" - if user.kind == User.KIND_SIAE: + siae_id = request.GET.get("siae_id", None) + if (user.is_authenticated and user.kind == User.KIND_SIAE) or siae_id: if detail_contact_click_confirm: # update detail_contact_click_date - TenderSiae.objects.filter( - tender=self.object, siae__in=user.siaes.all(), detail_contact_click_date__isnull=True - ).update(detail_contact_click_date=timezone.now(), updated_at=timezone.now()) + if user.is_authenticated: + TenderSiae.objects.filter( + tender=self.object, siae__in=user.siaes.all(), detail_contact_click_date__isnull=True + ).update(detail_contact_click_date=timezone.now(), updated_at=timezone.now()) + else: + TenderSiae.objects.filter( + tender=self.object, siae_id=int(siae_id), detail_contact_click_date__isnull=True + ).update(detail_contact_click_date=timezone.now(), updated_at=timezone.now()) # notify the tender author send_siae_interested_email_to_author(self.object) messages.add_message( @@ -373,14 +385,16 @@ def post(self, request, *args, **kwargs): self.request, messages.WARNING, self.get_success_message(detail_contact_click_confirm) ) # redirect - return HttpResponseRedirect(self.get_success_url(detail_contact_click_confirm)) + return HttpResponseRedirect(self.get_success_url(detail_contact_click_confirm, siae_id)) else: return HttpResponseForbidden() - def get_success_url(self, detail_contact_click_confirm): + def get_success_url(self, detail_contact_click_confirm, siae_id=None): success_url = reverse_lazy("tenders:detail", args=[self.kwargs.get("slug")]) if detail_contact_click_confirm: success_url += "?nps=true" + if siae_id: + success_url += f"&siae_id={siae_id}" return success_url def get_success_message(self, detail_contact_click_confirm):