Skip to content

Commit

Permalink
Dépôt de besoin : sondage aux prestataires : formulaire et vue (#1072)
Browse files Browse the repository at this point in the history
* New TenderDetailSiaeSurveyTransactionedView. Add form & mixin

* Update tests. Improve breadcrumb, add tender info
  • Loading branch information
raphodn authored Feb 8, 2024
1 parent 8c59339 commit ef46d20
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 31 deletions.
10 changes: 9 additions & 1 deletion lemarche/templates/tenders/survey_transactioned_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'wagtail_serve' '' %}">Accueil</a></li>
<li class="breadcrumb-item"><a href="{% url 'dashboard:home' %}">Tableau de bord</a></li>
<li class="breadcrumb-item"><a href="{% url 'tenders:list' %}">Mes besoins</a></li>
<li class="breadcrumb-item"><a href="{% url 'tenders:list' %}">{{ parent_title }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'tenders:detail' tender.slug %}" title="{{ tender.title }}">{{ tender.title|truncatechars:25 }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Avez-vous contractualisé ?</li>
</ol>
Expand Down Expand Up @@ -43,6 +43,14 @@ <h1 class="h1 mb-3 mb-lg-5">
</fieldset>
</div>
</div>
<div class="col-12 col-lg-4">
<div class="alert alert-info mt-3 mt-lg-0" role="alert">
<p class="mb-0">
<i class="ri-information-line ri-lg"></i>
Pour le besoin <strong>{{ tender.title }}</strong>
</p>
</div>
</div>
</div>

{% if tender.survey_transactioned_answer == None or tender.survey_transactioned_answer == True %}
Expand Down
15 changes: 14 additions & 1 deletion lemarche/utils/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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"))
27 changes: 26 additions & 1 deletion lemarche/www/tenders/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
124 changes: 96 additions & 28 deletions lemarche/www/tenders/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
Expand All @@ -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}))
Expand All @@ -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)
Expand All @@ -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)
6 changes: 6 additions & 0 deletions lemarche/www/tenders/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
TenderDetailCocontractingClickView,
TenderDetailContactClickStatView,
TenderDetailNotInterestedClickView,
TenderDetailSiaeSurveyTransactionedView,
TenderDetailSurveyTransactionedView,
TenderDetailView,
TenderListView,
Expand Down Expand Up @@ -49,4 +50,9 @@
TenderDetailSurveyTransactionedView.as_view(),
name="detail-survey-transactioned",
),
path(
"<str:slug>/prestataires/<str:siae_slug>/sondage-transaction",
TenderDetailSiaeSurveyTransactionedView.as_view(),
name="detail-siae-survey-transactioned",
),
]
75 changes: 75 additions & 0 deletions lemarche/www/tenders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -31,6 +32,7 @@
TenderCreateStepDetailForm,
TenderCreateStepGeneralForm,
TenderCreateStepSurveyForm,
TenderSiaeSurveyTransactionedForm,
TenderSurveyTransactionedForm,
)
from lemarche.www.tenders.tasks import ( # , send_tender_emails_to_siaes
Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit ef46d20

Please sign in to comment.