diff --git a/lemarche/templates/tenders/survey_transactioned_detail.html b/lemarche/templates/tenders/survey_transactioned_detail.html
index 7bb7ebcf0..b4d155436 100644
--- a/lemarche/templates/tenders/survey_transactioned_detail.html
+++ b/lemarche/templates/tenders/survey_transactioned_detail.html
@@ -12,7 +12,7 @@
- Accueil
- Tableau de bord
- - Mes besoins
+ - {{ parent_title }}
- {{ tender.title|truncatechars:25 }}
- Avez-vous contractualisé ?
@@ -43,6 +43,14 @@
+
+
+
+
+ Pour le besoin {{ tender.title }}
+
+
+
{% if tender.survey_transactioned_answer == None or tender.survey_transactioned_answer == True %}
diff --git a/lemarche/utils/mixins.py b/lemarche/utils/mixins.py
index a211673dd..1098c71d9 100644
--- a/lemarche/utils/mixins.py
+++ b/lemarche/utils/mixins.py
@@ -72,7 +72,7 @@ class SiaeMemberRequiredMixin(LoginRequiredUserPassesTestMixin):
def test_func(self):
user = self.request.user
- siae_slug = self.kwargs.get("slug")
+ siae_slug = self.kwargs.get("siae_slug") or self.kwargs.get("slug")
return user.is_authenticated and (siae_slug in user.siaes.values_list("slug", flat=True))
def handle_no_permission(self):
@@ -206,3 +206,16 @@ def test_func(self):
def handle_no_permission(self):
return HttpResponseRedirect(reverse_lazy("tenders:detail", args=[self.kwargs.get("slug")]))
+
+
+class SesameSiaeMemberRequiredMixin(SesameTokenRequiredUserPassesTestMixin):
+ """
+ Restrict access to the Tender's author
+ """
+
+ def test_func(self):
+ siae_slug = self.kwargs.get("siae_slug") or self.kwargs.get("slug")
+ return siae_slug in self.request.user.siaes.values_list("slug", flat=True)
+
+ def handle_no_permission(self):
+ return HttpResponseRedirect(reverse_lazy("dashboard:home"))
diff --git a/lemarche/www/tenders/forms.py b/lemarche/www/tenders/forms.py
index 26dbc1d54..076d65c5f 100644
--- a/lemarche/www/tenders/forms.py
+++ b/lemarche/www/tenders/forms.py
@@ -5,7 +5,7 @@
from lemarche.sectors.models import Sector
from lemarche.tenders import constants as tender_constants
-from lemarche.tenders.models import Tender
+from lemarche.tenders.models import Tender, TenderSiae
from lemarche.users.models import User
from lemarche.utils.fields import GroupedModelMultipleChoiceField
@@ -315,3 +315,28 @@ def __init__(self, tender_survey_transactioned_answer=None, *args, **kwargs):
self.fields["survey_transactioned_answer"].disabled = True
if tender_survey_transactioned_answer is False:
self.fields["survey_transactioned_amount"].widget = forms.HiddenInput()
+
+
+class TenderSiaeSurveyTransactionedForm(forms.ModelForm):
+ class Meta:
+ model = TenderSiae
+ fields = [
+ "survey_transactioned_answer",
+ "survey_transactioned_amount",
+ "survey_transactioned_feedback",
+ ]
+
+ def __init__(self, tender_survey_transactioned_answer=None, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields["survey_transactioned_answer"].label = "Avez-vous contractualisé avec le client ?"
+ self.fields["survey_transactioned_amount"].label = "Quel est le montant de la transaction ? (facultatif)"
+ self.fields["survey_transactioned_feedback"].label = "Partagez-nous votre retour d'expérience (facultatif)"
+ self.fields["survey_transactioned_feedback"].widget.attrs.update(
+ {
+ "placeholder": "Lors de mon expérience avec le Marché de l'inclusion :\n- j'ai apprécié ...\n- j'ai moins aimé ...\n- vous pourriez vous améliorer dans ..." # noqa
+ }
+ )
+ if tender_survey_transactioned_answer is not None:
+ self.fields["survey_transactioned_answer"].disabled = True
+ if tender_survey_transactioned_answer is False:
+ self.fields["survey_transactioned_amount"].widget = forms.HiddenInput()
diff --git a/lemarche/www/tenders/tests.py b/lemarche/www/tenders/tests.py
index c832de4fd..43e17f5d7 100644
--- a/lemarche/www/tenders/tests.py
+++ b/lemarche/www/tenders/tests.py
@@ -1696,10 +1696,10 @@ def setUpTestData(cls):
cls.user_admin = UserFactory(kind=User.KIND_ADMIN)
cls.tender = TenderFactory(kind=tender_constants.KIND_TENDER, author=cls.user_buyer_1, siaes=[cls.siae])
cls.user_buyer_1_sesame_query_string = sesame_get_query_string(cls.user_buyer_1)
+ cls.url = reverse("tenders:detail-survey-transactioned", kwargs={"slug": cls.tender.slug})
def test_anonymous_user_cannot_call_tender_survey_transactioned(self):
- url = reverse("tenders:detail-survey-transactioned", kwargs={"slug": self.tender.slug})
- response = self.client.post(url)
+ response = self.client.post(self.url)
self.assertEqual(response.status_code, 403)
def test_only_tender_author_with_sesame_token_can_call_tender_survey_transactioned(self):
@@ -1713,28 +1713,20 @@ def test_only_tender_author_with_sesame_token_can_call_tender_survey_transaction
self.user_admin,
]:
self.client.force_login(user)
- url = reverse("tenders:detail-survey-transactioned", kwargs={"slug": self.tender.slug})
- response = self.client.get(url)
+ response = self.client.get(self.url)
self.assertEqual(response.status_code, 403)
# logout the last user to be sure
self.client.logout()
# authorized
user_sesame_query_string = sesame_get_query_string(self.user_buyer_1)
- url = (
- reverse("tenders:detail-survey-transactioned", kwargs={"slug": self.tender.slug})
- + user_sesame_query_string
- )
+ url = self.url + user_sesame_query_string
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200)
# full form displayed (but should never happen)
def test_update_tender_stats_on_tender_survey_transactioned_answer_true(self):
# load with answer 'True': partial form
- url = (
- reverse("tenders:detail-survey-transactioned", kwargs={"slug": self.tender.slug})
- + self.user_buyer_1_sesame_query_string
- + "&answer=True"
- )
+ url = self.url + self.user_buyer_1_sesame_query_string + "&answer=True"
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertTrue(Tender.objects.get(id=self.tender.id).survey_transactioned_answer)
@@ -1749,11 +1741,7 @@ def test_update_tender_stats_on_tender_survey_transactioned_answer_true(self):
self.assertTrue(Tender.objects.get(id=self.tender.id).survey_transactioned_answer)
self.assertEqual(Tender.objects.get(id=self.tender.id).survey_transactioned_amount, 1000)
# reload with answer, ignore changes and redirect
- url = (
- reverse("tenders:detail-survey-transactioned", kwargs={"slug": self.tender.slug})
- + self.user_buyer_1_sesame_query_string
- + "&answer=False"
- )
+ url = self.url + self.user_buyer_1_sesame_query_string + "&answer=False"
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200) # redirect
self.assertRedirects(response, reverse("tenders:detail", kwargs={"slug": self.tender.slug}))
@@ -1763,11 +1751,7 @@ def test_update_tender_stats_on_tender_survey_transactioned_answer_true(self):
def test_update_tender_stats_on_tender_survey_transactioned_answer_false(self):
# load with answer 'False': partial form
- url = (
- reverse("tenders:detail-survey-transactioned", kwargs={"slug": self.tender.slug})
- + self.user_buyer_1_sesame_query_string
- + "&answer=False"
- )
+ url = self.url + self.user_buyer_1_sesame_query_string + "&answer=False"
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertFalse(Tender.objects.get(id=self.tender.id).survey_transactioned_answer)
@@ -1780,14 +1764,98 @@ def test_update_tender_stats_on_tender_survey_transactioned_answer_false(self):
self.assertFalse(Tender.objects.get(id=self.tender.id).survey_transactioned_answer)
self.assertIsNone(Tender.objects.get(id=self.tender.id).survey_transactioned_amount)
# reload with answer, ignore changes
- url = (
- reverse("tenders:detail-survey-transactioned", kwargs={"slug": self.tender.slug})
- + self.user_buyer_1_sesame_query_string
- + "&answer=True"
- )
+ url = self.url + self.user_buyer_1_sesame_query_string + "&answer=True"
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200) # redirect
self.assertRedirects(response, reverse("tenders:detail", kwargs={"slug": self.tender.slug}))
self.assertContains(response, "Votre réponse a déjà été prise en compte")
self.assertFalse(Tender.objects.get(id=self.tender.id).survey_transactioned_answer)
self.assertFalse(Tender.objects.get(id=self.tender.id).siae_transactioned)
+
+
+class TenderDetailSiaeSurveyTransactionedViewTest(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.siae = SiaeFactory(name="ZZ ESI")
+ cls.siae_user_1 = UserFactory(kind=User.KIND_SIAE, siaes=[cls.siae])
+ cls.siae_user_2 = UserFactory(kind=User.KIND_SIAE)
+ cls.user_buyer_1 = UserFactory(kind=User.KIND_BUYER)
+ cls.user_buyer_2 = UserFactory(kind=User.KIND_BUYER)
+ cls.user_partner = UserFactory(kind=User.KIND_PARTNER)
+ cls.user_admin = UserFactory(kind=User.KIND_ADMIN)
+ cls.tender = TenderFactory(kind=tender_constants.KIND_TENDER, author=cls.user_buyer_1)
+ cls.tendersiae = TenderSiae.objects.create(tender=cls.tender, siae=cls.siae)
+ cls.url = reverse(
+ "tenders:detail-siae-survey-transactioned", kwargs={"slug": cls.tender.slug, "siae_slug": cls.siae.slug}
+ )
+ cls.user_siae_1_sesame_query_string = sesame_get_query_string(cls.siae_user_1)
+
+ def test_anonymous_user_cannot_call_tender_siae_survey_transactioned(self):
+ response = self.client.post(self.url)
+ self.assertEqual(response.status_code, 403)
+
+ def test_only_tender_author_with_sesame_token_can_call_tender_siae_survey_transactioned(self):
+ # forbidden
+ for user in [
+ self.siae_user_1,
+ self.siae_user_2,
+ self.user_buyer_1,
+ self.user_buyer_2,
+ self.user_partner,
+ self.user_admin,
+ ]:
+ self.client.force_login(user)
+ response = self.client.get(self.url)
+ self.assertEqual(response.status_code, 403)
+ # logout the last user to be sure
+ self.client.logout()
+ # authorized
+ user_sesame_query_string = sesame_get_query_string(self.siae_user_1)
+ url = self.url + user_sesame_query_string
+ response = self.client.get(url, follow=True)
+ self.assertEqual(response.status_code, 200)
+ # full form displayed (but should never happen)
+
+ def test_update_tender_stats_on_tender_siae_survey_transactioned_answer_true(self):
+ # load with answer 'True': partial form
+ url = self.url + self.user_siae_1_sesame_query_string + "&answer=True"
+ response = self.client.get(url, follow=True)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_answer)
+ # fill in form
+ response = self.client.post(
+ url, data={"survey_transactioned_amount": 1000, "survey_transactioned_feedback": "Feedback"}, follow=True
+ )
+ self.assertEqual(response.status_code, 200) # redirect
+ self.assertRedirects(response, reverse("tenders:detail", kwargs={"slug": self.tender.slug}))
+ self.assertContains(response, "Merci pour votre réponse")
+ self.assertTrue(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_answer)
+ self.assertEqual(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_amount, 1000)
+ # reload with answer, ignore changes and redirect
+ url = self.url + self.user_siae_1_sesame_query_string + "&answer=False"
+ response = self.client.get(url, follow=True)
+ self.assertEqual(response.status_code, 200) # redirect
+ self.assertRedirects(response, reverse("tenders:detail", kwargs={"slug": self.tender.slug}))
+ self.assertContains(response, "Votre réponse a déjà été prise en compte")
+ self.assertTrue(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_answer)
+
+ def test_update_tender_stats_on_tender_siae_survey_transactioned_answer_false(self):
+ # load with answer 'False': partial form
+ url = self.url + self.user_siae_1_sesame_query_string + "&answer=False"
+ response = self.client.get(url, follow=True)
+ self.assertEqual(response.status_code, 200)
+ self.assertFalse(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_answer)
+ # fill in form
+ response = self.client.post(url, data={"survey_transactioned_feedback": "Feedback"}, follow=True)
+ self.assertEqual(response.status_code, 200) # redirect
+ self.assertRedirects(response, reverse("tenders:detail", kwargs={"slug": self.tender.slug}))
+ self.assertContains(response, "Merci pour votre réponse")
+ self.assertFalse(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_answer)
+ self.assertIsNone(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_amount)
+ # reload with answer, ignore changes
+ url = self.url + self.user_siae_1_sesame_query_string + "&answer=True"
+ response = self.client.get(url, follow=True)
+ self.assertEqual(response.status_code, 200) # redirect
+ self.assertRedirects(response, reverse("tenders:detail", kwargs={"slug": self.tender.slug}))
+ self.assertContains(response, "Votre réponse a déjà été prise en compte")
+ self.assertFalse(TenderSiae.objects.get(tender=self.tender, siae=self.siae).survey_transactioned_answer)
diff --git a/lemarche/www/tenders/urls.py b/lemarche/www/tenders/urls.py
index 02446598b..93f7fff2c 100644
--- a/lemarche/www/tenders/urls.py
+++ b/lemarche/www/tenders/urls.py
@@ -6,6 +6,7 @@
TenderDetailCocontractingClickView,
TenderDetailContactClickStatView,
TenderDetailNotInterestedClickView,
+ TenderDetailSiaeSurveyTransactionedView,
TenderDetailSurveyTransactionedView,
TenderDetailView,
TenderListView,
@@ -49,4 +50,9 @@
TenderDetailSurveyTransactionedView.as_view(),
name="detail-survey-transactioned",
),
+ path(
+ "/prestataires//sondage-transaction",
+ TenderDetailSiaeSurveyTransactionedView.as_view(),
+ name="detail-siae-survey-transactioned",
+ ),
]
diff --git a/lemarche/www/tenders/views.py b/lemarche/www/tenders/views.py
index f646fa2da..058765d23 100644
--- a/lemarche/www/tenders/views.py
+++ b/lemarche/www/tenders/views.py
@@ -19,6 +19,7 @@
from lemarche.users.models import User
from lemarche.utils.data import get_choice
from lemarche.utils.mixins import (
+ SesameSiaeMemberRequiredMixin,
SesameTenderAuthorRequiredMixin,
SiaeUserRequiredOrSiaeIdParamMixin,
TenderAuthorOrAdminRequiredIfNotSentMixin,
@@ -31,6 +32,7 @@
TenderCreateStepDetailForm,
TenderCreateStepGeneralForm,
TenderCreateStepSurveyForm,
+ TenderSiaeSurveyTransactionedForm,
TenderSurveyTransactionedForm,
)
from lemarche.www.tenders.tasks import ( # , send_tender_emails_to_siaes
@@ -624,6 +626,79 @@ def get(self, request, *args, **kwargs):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["tender"] = self.object
+ context["parent_title"] = TITLE_DETAIL_PAGE_OTHERS
+ return context
+
+ def get_form_kwargs(self):
+ kwargs = super().get_form_kwargs()
+ kwargs["tender_survey_transactioned_answer"] = self.object.survey_transactioned_answer
+ return kwargs
+
+ def form_valid(self, form):
+ super().form_valid(form)
+ messages.add_message(self.request, messages.SUCCESS, self.get_success_message())
+ return HttpResponseRedirect(self.get_success_url())
+
+ def get_success_url(self):
+ success_url = reverse_lazy("tenders:detail", args=[self.kwargs.get("slug")])
+ return success_url
+
+ def get_success_message(self, already_answered=False):
+ if already_answered:
+ return "Votre réponse a déjà été prise en compte."
+ return "Merci pour votre réponse !"
+
+
+class TenderDetailSiaeSurveyTransactionedView(SesameSiaeMemberRequiredMixin, UpdateView):
+ """
+ Endpoint to store the tender siae survey transactioned answer
+ """
+
+ template_name = "tenders/survey_transactioned_detail.html" # same template as author survey
+ form_class = TenderSiaeSurveyTransactionedForm
+ queryset = TenderSiae.objects.all()
+ # success_message (see get_success_message() below)
+ # success_url (see get_success_url() below)
+
+ def get(self, request, *args, **kwargs):
+ """
+ TenderSiae.survey_transactioned_answer field is updated only if:
+ - the user should be the tender author (thanks to SesameTenderAuthorRequiredMixin)
+ - the field is None in the database (first time answering)
+ - the GET parameter 'answer' is passed
+ """
+ self.object = self.get_object()
+ survey_transactioned_answer = request.GET.get("answer", None)
+ # first time answering
+ if self.object.survey_transactioned_answer is None:
+ if survey_transactioned_answer in ["True", "False"]:
+ # transform survey_transactioned_answer into bool
+ survey_transactioned_answer = survey_transactioned_answer == "True"
+ # update survey_transactioned_answer
+ TenderSiae.objects.filter(id=self.object.id).update(
+ survey_transactioned_answer=survey_transactioned_answer,
+ survey_transactioned_answer_date=timezone.now(),
+ updated_at=timezone.now(),
+ )
+ else:
+ pass
+ # TODO or not? "answer" should always be passed
+ return super().get(request, *args, **kwargs)
+ # already answered
+ else:
+ messages.add_message(self.request, messages.WARNING, self.get_success_message(already_answered=True))
+ return HttpResponseRedirect(self.get_success_url())
+
+ def get_object(self):
+ self.tender = Tender.objects.get(slug=self.kwargs.get("slug"))
+ self.siae = Siae.objects.get(slug=self.kwargs.get("siae_slug"))
+ return get_object_or_404(TenderSiae, tender=self.tender, siae=self.siae)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context["tender"] = self.tender
+ context["siae"] = self.siae
+ context["parent_title"] = TITLE_DETAIL_PAGE_SIAE
return context
def get_form_kwargs(self):