diff --git a/lemarche/tenders/admin.py b/lemarche/tenders/admin.py index 69cbaeb31..5c5ce1715 100644 --- a/lemarche/tenders/admin.py +++ b/lemarche/tenders/admin.py @@ -1,6 +1,5 @@ from ckeditor.widgets import CKEditorWidget from django import forms -from django.conf import settings from django.contrib import admin from django.contrib.contenttypes.admin import GenericTabularInline from django.core.exceptions import ValidationError @@ -256,6 +255,23 @@ class Meta: model = Tender fields = "__all__" + is_followed_by_us = forms.BooleanField( + widget=forms.CheckboxInput(), + required=False, + label=Tender._meta.get_field("is_followed_by_us").verbose_name, + ) + proj_resulted_in_reserved_tender = forms.BooleanField( + widget=forms.CheckboxInput(), + required=False, + label=Tender._meta.get_field("proj_resulted_in_reserved_tender").verbose_name, + ) + + is_reserved_tender = forms.BooleanField( + widget=forms.CheckboxInput(), + required=False, + label=Tender._meta.get_field("is_reserved_tender").verbose_name, + ) + def clean(self): """ Add validation on form rules: @@ -501,8 +517,8 @@ class TenderAdmin(FieldsetsInlineMixin, admin.ModelAdmin): "admins", "is_followed_by_us", "proj_resulted_in_reserved_tender", - "proj_link_to_tender", "is_reserved_tender", + "proj_link_to_tender", ) }, ), @@ -789,7 +805,7 @@ def response_change(self, request, obj: Tender): return HttpResponseRedirect("./#structures") # redirect to structures sections if request.POST.get("_validate_send_to_siaes"): obj.set_validated() - if obj.amount_int > settings.BREVO_TENDERS_MIN_AMOUNT_TO_SEND: + if obj.is_followed_by_us: try: api_brevo.create_deal(tender=obj, owner_email=request.user.email) # we link deal(tender) with author contact diff --git a/lemarche/tenders/models.py b/lemarche/tenders/models.py index d9ce7eb0b..e370c2a33 100644 --- a/lemarche/tenders/models.py +++ b/lemarche/tenders/models.py @@ -1,10 +1,9 @@ -import random from datetime import datetime, timedelta from uuid import uuid4 from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation -from django.db import IntegrityError, models, transaction +from django.db import IntegrityError, models from django.db.models import ( BooleanField, Case, @@ -757,16 +756,7 @@ def save(self, *args, **kwargs): self.set_last_updated_fields() try: self.set_slug() - # generate random status for is_followed_by_us - if ( - not self.pk - and self.kind == tender_constants.KIND_PROJECT - and self.is_followed_by_us is None - and self.amount_int > settings.BREVO_TENDERS_MIN_AMOUNT_TO_SEND - ): - self.is_followed_by_us = random.random() < 0.5 # 50% True, 50% False - with transaction.atomic(): - super().save(*args, **kwargs) + super().save(*args, **kwargs) except IntegrityError as e: # check that it's a slug conflict # Full message expected: duplicate key value violates unique constraint "tenders_tender_slug_0f0b821f_uniq" DETAIL: Key (slug)=(...) already exists. # noqa diff --git a/lemarche/tenders/tests/test_admin.py b/lemarche/tenders/tests/test_admin.py new file mode 100644 index 000000000..44e8f6f54 --- /dev/null +++ b/lemarche/tenders/tests/test_admin.py @@ -0,0 +1,89 @@ +from unittest.mock import patch + +from django.contrib.admin.sites import AdminSite +from django.contrib.messages import get_messages +from django.contrib.messages.storage.fallback import FallbackStorage +from django.contrib.sessions.middleware import SessionMiddleware +from django.http import HttpResponse +from django.test import RequestFactory, TestCase + +from lemarche.tenders.admin import TenderAdmin +from lemarche.tenders.factories import TenderFactory +from lemarche.tenders.models import Tender +from lemarche.users.factories import UserFactory + + +# Create a dummy response function for the middleware +def get_response(request): + return HttpResponse("OK") + + +class TenderAdminTestCase(TestCase): + def setUp(self): + # Initialize required objects for testing + self.factory = RequestFactory() + self.admin_site = AdminSite() + self.user_admin = UserFactory(is_superuser=True) # Admin user + self.admin = TenderAdmin(Tender, self.admin_site) + + def setUpRequest(self): + request = self.factory.post("/", data={"_validate_send_to_siaes": "1"}) + request.user = self.user_admin + + # Initialize and apply SessionMiddleware + middleware = SessionMiddleware(get_response) + middleware.process_request(request) + + # Attach a message storage system to the request + setattr(request, "_messages", FallbackStorage(request)) + + return request + + @patch("lemarche.tenders.admin.api_brevo.create_deal") # Mock the create_deal API call + @patch("lemarche.tenders.admin.api_brevo.link_deal_with_contact_list") # Mock the link_deal API call + def test_validate_send_to_siaes_not_synch_brevo(self, mock_link_deal, mock_create_deal): + tender = TenderFactory(is_followed_by_us=False) # Tender object + request = self.setUpRequest() + + # Call the response_change method without the validation action enabled + response = self.admin.response_change(request, tender) + + # Check that the create_deal and link_deal functions were not called + mock_create_deal.assert_not_called() + mock_link_deal.assert_not_called() + + # Verify the response + self.assertEqual(response.status_code, 302) + self.assertIn(".", response.url) + + # Verify the flash messages + messages = list(get_messages(request)) + self.assertEqual(len(messages), 1) # Expecting only one message + self.assertEqual(str(messages[0]), "Ce dépôt de besoin a été validé. Il sera envoyé en temps voulu :)") + + @patch("lemarche.tenders.admin.api_brevo.create_deal") # Mock the create_deal API call + @patch("lemarche.tenders.admin.api_brevo.link_deal_with_contact_list") # Mock the link_deal API call + def test_validate_send_to_siaes_with_sync_brevo(self, mock_link_deal, mock_create_deal): + tender = TenderFactory(is_followed_by_us=True) # Tender object + request = self.setUpRequest() + + # Call the response_change method + response = self.admin.response_change(request, tender) + + # Verify that the tender is marked as validated + tender.refresh_from_db() + self.assertTrue(tender.is_validated) + + # Check if the create_deal and link_deal API methods were called + mock_create_deal.assert_called_once_with(tender=tender, owner_email=self.user_admin.email) + mock_link_deal.assert_called_once_with(tender=tender) + + # Ensure the response redirects correctly + self.assertEqual(response.status_code, 302) + self.assertIn(".", response.url) + + # Verify flash messages + messages = list(get_messages(request)) + self.assertEqual(len(messages), 2) # Expecting two messages + self.assertEqual(str(messages[0]), "Ce dépôt de besoin a été synchronisé avec Brevo") + self.assertEqual(str(messages[1]), "Ce dépôt de besoin a été validé. Il sera envoyé en temps voulu :)")