- {% 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 %}
-
-
- Déjà {{ tender.siae_detail_display_date_count_all }} prestataire{{ tender.siae_detail_display_date_count_all|pluralize }} inclusif{{ tender.siae_detail_display_date_count_all|pluralize }}
- {{ tender.siae_detail_display_date_count_all|pluralize:"a,ont" }} vu le besoin de ce client.
-
-
- {% endif %}
- {% if user.is_authenticated %}
- {% if user.kind == user.KIND_PARTNER %}
- {% if not user_partner_can_display_tender_contact_details %}
-
-
-
- Comment contacter le client ?
-
-
- Contactez Sofiane via
- {{ CONTACT_EMAIL }}
- pour être mis en relation avec le client.
-
-
+ {% else %}
+ {% if not tender.deadline_date_outdated %}
+ {% if tender.siae_detail_display_date_count_all > 0 %}
+
+
+ Déjà {{ tender.siae_detail_display_date_count_all }} prestataire{{ tender.siae_detail_display_date_count_all|pluralize }} inclusif{{ tender.siae_detail_display_date_count_all|pluralize }}
+ {{ tender.siae_detail_display_date_count_all|pluralize:"a,ont" }} vu le besoin de ce client.
+
+
+ {% endif %}
+ {% if user.is_authenticated %}
+ {% if user.kind == user.KIND_PARTNER %}
+ {% if not user_partner_can_display_tender_contact_details %}
+
+
+
+ Comment contacter le client ?
+
+
+ Contactez Sofiane via
+ {{ CONTACT_EMAIL }}
+ pour être mis en relation avec le client.
+
+
+ {% endif %}
+ {% elif user.kind == user.KIND_SIAE %}
+ {% if not user.has_siae %}
+
+
+
+ Comment contacter le client ?
+
+
+ Pour accéder aux coordonnées du client, veuillez d'abord vous rattacher à votre structure.
+
+
+ Besoin d'aide ? contacter le support via le chat en ligne qui se trouve en bas à droite.
+
+
+ {% 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 %}
-
-
-
- Comment contacter le client ?
-
-
- Pour accéder aux coordonnées du client, veuillez d'abord vous rattacher à votre structure.
-
-
- Besoin d'aide ? contacter le support via le chat en ligne qui se trouve en bas à droite.
-
-
- {% elif not user_siae_has_detail_contact_click_date %}
- {% include "tenders/_detail_cta.html" with user=user tender=tender %}
-
- {% include "tenders/_detail_contact.html" with 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):