Skip to content

Commit

Permalink
feat: Ajout d'attributs de contact pour les acheteurs inscrits (#1468)
Browse files Browse the repository at this point in the history
  • Loading branch information
chloend authored Nov 7, 2024
1 parent acaedec commit e508900
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 24 deletions.
68 changes: 60 additions & 8 deletions lemarche/api/tenders/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from unittest.mock import patch

from django.conf import settings
from django.test import TestCase
from django.urls import reverse

Expand Down Expand Up @@ -44,11 +47,29 @@ class TenderCreateApiTest(TestCase):
def setUpTestData(cls):
cls.url = reverse("api:tenders-list") + "?token=admin"
cls.user = UserFactory()
cls.user_buyer = UserFactory(kind=User.KIND_BUYER, company_name="Entreprise Buyer")
cls.user_with_token = UserFactory(email="[email protected]", api_key="admin")
cls.perimeter = PerimeterFactory()
cls.sector_1 = SectorFactory()
cls.sector_2 = SectorFactory()

@patch("lemarche.api.tenders.views.get_or_create_user_from_anonymous_content")
def setup_mock_user_and_tender_creation(self, mock_get_user, user=None, title="Test Tally", extra_data=None):
"""Helper method to setup mock user and create a tender."""
user = user if user else self.user
mock_get_user.return_value = user

# Tender data
tender_data = TENDER_JSON.copy()
tender_data["title"] = title
tender_data["extra_data"] = extra_data or {}

# Tender creation
response = self.client.post(self.url, data=tender_data, content_type="application/json")
tender = Tender.objects.get(title=title)

return response, tender, user

def test_anonymous_user_cannot_create_tender(self):
url = reverse("api:tenders-list")
response = self.client.post(url, data=TENDER_JSON, content_type="application/json")
Expand All @@ -67,7 +88,7 @@ def test_user_with_valid_api_key_can_create_tender(self):
self.assertEqual(response.status_code, 201)
self.assertIn("slug", response.data.keys())
tender = Tender.objects.get(title="Test author 1")
self.assertEqual(User.objects.count(), 2 + 1) # created a new user
self.assertEqual(User.objects.count(), 3 + 1) # created a new user
self.assertEqual(tender.author.email, USER_CONTACT_EMAIL)
self.assertEqual(tender.status, tender_constants.STATUS_PUBLISHED)
self.assertEqual(tender.source, tender_constants.SOURCE_API)
Expand All @@ -81,7 +102,7 @@ def test_user_with_valid_api_key_can_create_tender(self):
self.assertEqual(response.status_code, 201)
self.assertIn("slug", response.data.keys())
tender = Tender.objects.get(title="Test author 2")
self.assertEqual(User.objects.count(), 3) # did not create a new user
self.assertEqual(User.objects.count(), 4) # did not create a new user
self.assertEqual(tender.author, self.user_with_token)
self.assertEqual(tender.status, tender_constants.STATUS_PUBLISHED)
self.assertEqual(tender.source, tender_constants.SOURCE_API)
Expand Down Expand Up @@ -135,14 +156,45 @@ def test_create_tender_with_sectors(self):
response = self.client.post(self.url, data=tender_data)
self.assertEqual(response.status_code, 400)

def test_create_tender_with_tally_source(self):
tender_data = TENDER_JSON.copy()
tender_data["title"] = "Test tally"
tender_data["extra_data"] = {"source": "TALLY"}
response = self.client.post(self.url, data=tender_data, content_type="application/json")
@patch("lemarche.api.tenders.views.add_to_contact_list")
def test_create_tender_with_tally_source(self, mock_add_to_contact_list):
extra_data = {"source": "TALLY"}
response, tender, user = self.setup_mock_user_and_tender_creation(title="Test tally", extra_data=extra_data)

mock_add_to_contact_list.assert_called_once()
args, kwargs = mock_add_to_contact_list.call_args

self.assertEqual(response.status_code, 201)
tender = Tender.objects.get(title="Test tally")
self.assertEqual(tender.source, tender_constants.SOURCE_TALLY)
# Check other arguments like user, type, and source
self.assertEqual(kwargs["user"], user)
self.assertEqual(kwargs["type"], "signup")
self.assertEqual(kwargs["source"], user_constants.SOURCE_TALLY_FORM)
# Verify that `tender` is an instance of Tender
self.assertIsInstance(
kwargs.get("tender"), Tender, "Expected an instance of Tender for the 'tender' argument."
)

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CreateContact")
def test_create_contact_call_has_user_buyer_attributes(self, mock_create_contact):
"""Test CreateContact call contains user buyer attributes"""
extra_data = {"source": "TALLY"}
_, tender, user = self.setup_mock_user_and_tender_creation(
title="Test tally", user=self.user_buyer, extra_data=extra_data
)
sectors = tender.sectors.all()

mock_create_contact.assert_called_once()
args, kwargs = mock_create_contact.call_args
attributes = kwargs["attributes"]

self.assertEqual(kwargs["email"], user.email)
self.assertIn(settings.BREVO_CL_SIGNUP_BUYER_ID, kwargs["list_ids"])
self.assertEqual(attributes["MONTANT_BESOIN_ACHETEUR"], tender.amount_int)
self.assertEqual(attributes["TYPE_BESOIN_ACHETEUR"], tender.kind)

if sectors.exists():
attributes["TYPE_VERTICALE_ACHETEUR"] = sectors.first().name

def test_create_tender_with_different_contact_data(self):
tender_data = TENDER_JSON.copy()
Expand Down
4 changes: 3 additions & 1 deletion lemarche/api/tenders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from lemarche.tenders import constants as tender_constants
from lemarche.tenders.models import Tender
from lemarche.users import constants as user_constants
from lemarche.utils.emails import add_to_contact_list
from lemarche.www.tenders.utils import get_or_create_user_from_anonymous_content


Expand Down Expand Up @@ -73,13 +74,14 @@ def perform_create(self, serializer: TenderSerializer):
serializer.validated_data.pop("contact_kind", None)
serializer.validated_data.pop("contact_buyer_kind_detail", None)
# create Tender
serializer.save(
tender = serializer.save(
author=user,
status=tender_constants.STATUS_PUBLISHED,
published_at=timezone.now(),
source=tender_source,
import_raw_object=self.request.data,
)
add_to_contact_list(user=user, type="signup", source=user_source, tender=tender)


class TenderKindViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
Expand Down
47 changes: 37 additions & 10 deletions lemarche/utils/apis/api_brevo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

import sib_api_v3_sdk
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from huey.contrib.djhuey import task
from sib_api_v3_sdk.rest import ApiException

from lemarche.tenders import constants as tender_constants
from lemarche.utils.constants import EMAIL_SUBJECT_PREFIX
from lemarche.utils.data import sanitize_to_send_by_email
from lemarche.utils.urls import get_object_admin_url, get_object_share_url
Expand All @@ -28,26 +30,51 @@ def get_api_client():
return sib_api_v3_sdk.ApiClient(config)


def create_contact(user, list_id: int):
def create_contact(user, list_id: int, tender=None):
"""
Brevo docs
- Python library: https://github.com/sendinblue/APIv3-python-library/blob/master/docs/CreateContact.md
- API: https://developers.brevo.com/reference/createcontact
"""
api_client = get_api_client()
api_instance = sib_api_v3_sdk.ContactsApi(api_client)

attributes = {
"NOM": sanitize_to_send_by_email(user.last_name.capitalize()),
"PRENOM": sanitize_to_send_by_email(user.first_name.capitalize()),
"DATE_INSCRIPTION": user.created_at,
"TYPE_ORGANISATION": user.buyer_kind_detail,
"NOM_ENTREPRISE": sanitize_to_send_by_email(user.company_name.capitalize()),
"SMS": sanitize_to_send_by_email(user.phone_display),
"MONTANT_BESOIN_ACHETEUR": None,
"TYPE_BESOIN_ACHETEUR": None,
"TYPE_VERTICALE_ACHETEUR": None,
# WHATSAPP, TYPE_ORGANISATION, LIEN_FICHE_COMMERCIALE, TAUX_DE_COMPLETION
}

try:
tender = user.tenders.get(id=tender.id)
first_sector = tender.sectors.first()
attributes["MONTANT_BESOIN_ACHETEUR"] = tender.amount_int
attributes["TYPE_BESOIN_ACHETEUR"] = tender.kind

# Check if there is one sector whose tender source is TALLY
if tender.source == tender_constants.SOURCE_TALLY and first_sector:
attributes["TYPE_VERTICALE_ACHETEUR"] = first_sector.name
else:
attributes["TYPE_VERTICALE_ACHETEUR"] = None

except ObjectDoesNotExist:
print("L'objet Tender demandé n'existe pas pour cet utilisateur.")
except AttributeError as e:
print(f"Erreur d'attribut : {e}")
except Exception as e:
print(f"Une erreur inattendue est survenue : {e}")

new_contact = sib_api_v3_sdk.CreateContact(
email=user.email,
list_ids=[list_id],
attributes={
"NOM": sanitize_to_send_by_email(user.last_name.capitalize()),
"PRENOM": sanitize_to_send_by_email(user.first_name.capitalize()),
"DATE_INSCRIPTION": user.created_at,
"TYPE_ORGANISATION": user.buyer_kind_detail,
"NOM_ENTREPRISE": sanitize_to_send_by_email(user.company_name.capitalize()),
"SMS": sanitize_to_send_by_email(user.phone_display),
# WHATSAPP, TYPE_ORGANISATION, LIEN_FICHE_COMMERCIALE, TAUX_DE_COMPLETION
},
attributes=attributes,
ext_id=str(user.id),
update_enabled=True,
)
Expand Down
4 changes: 2 additions & 2 deletions lemarche/utils/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def whitelist_recipient_list(recipient_list):
return [email for email in recipient_list if (email and email.endswith("beta.gouv.fr"))]


def add_to_contact_list(user, type: str, source: str = user_constants.SOURCE_SIGNUP_FORM):
def add_to_contact_list(user, type: str, tender=None, source: str = user_constants.SOURCE_SIGNUP_FORM):
"""Add user to contactlist
Args:
Expand All @@ -65,7 +65,7 @@ def add_to_contact_list(user, type: str, source: str = user_constants.SOURCE_SIG
if type == "signup":
contact_list_id = api_mailjet.get_mailjet_cl_on_signup(user, source)
if user.kind == user.KIND_BUYER:
api_brevo.create_contact(user=user, list_id=settings.BREVO_CL_SIGNUP_BUYER_ID)
api_brevo.create_contact(user=user, list_id=settings.BREVO_CL_SIGNUP_BUYER_ID, tender=tender)
elif user.kind == user.KIND_SIAE:
api_brevo.create_contact(user=user, list_id=settings.BREVO_CL_SIGNUP_SIAE_ID)
elif type == "buyer_search":
Expand Down
2 changes: 2 additions & 0 deletions lemarche/www/pages/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from lemarche.tenders.models import Tender, TenderStepsData
from lemarche.users import constants as user_constants
from lemarche.users.models import User
from lemarche.utils.emails import add_to_contact_list
from lemarche.utils.tracker import track
from lemarche.www.pages.forms import (
CompanyReferenceCalculatorForm,
Expand Down Expand Up @@ -342,6 +343,7 @@ def csrf_failure(request, reason=""): # noqa C901
# create tender
if is_adding:
tender: Tender = create_tender_from_dict(tender_dict)
add_to_contact_list(user=user, type="signup", source=user_constants.SOURCE_TENDER_FORM, tender=tender)
elif is_update:
slug = request.path.split("/")[-1]
tender: Tender = Tender.objects.get(slug=slug)
Expand Down
50 changes: 50 additions & 0 deletions lemarche/www/tenders/tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from datetime import timedelta
from unittest import mock
from unittest.mock import patch

from django.conf import settings
from django.contrib.gis.geos import Point
Expand All @@ -21,6 +22,7 @@
from lemarche.tenders.enums import SurveyDoesNotExistQuestionChoices, SurveyScaleQuestionChoices
from lemarche.tenders.factories import TenderFactory, TenderQuestionFactory
from lemarche.tenders.models import Tender, TenderSiae, TenderStepsData
from lemarche.users import constants as user_constants
from lemarche.users.factories import UserFactory
from lemarche.users.models import User
from lemarche.utils import constants
Expand All @@ -30,6 +32,7 @@
class TenderCreateViewTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = UserFactory()
cls.user_siae = UserFactory(kind=User.KIND_SIAE)
cls.user_buyer = UserFactory(kind=User.KIND_BUYER, company_name="Entreprise Buyer")
cls.sectors = [SectorFactory().slug for _ in range(3)]
Expand Down Expand Up @@ -101,6 +104,18 @@ def _check_every_step(self, tenders_step_data, final_redirect_page: str = revers
tender_step_data.steps_data[-1]["tender_create_multi_step_view-current_step"],
)

@patch("lemarche.www.tenders.views.get_or_create_user")
def setup_mock_user_and_tender_creation(self, mock_get_user, user=None, title="Test Tender Form"):
"""Helper method to setup mock user"""
user = user if user else self.user
mock_get_user.return_value = user

tenders_step_data = self._generate_fake_data_form({"general-title": title})
self._check_every_step(tenders_step_data, final_redirect_page=reverse("siae:search_results"))
tender = Tender.objects.get(title=title)

return tender, user

def test_anyone_can_access_create_tender(self):
# anonymous user
url = reverse("tenders:create")
Expand Down Expand Up @@ -249,6 +264,41 @@ def test_tender_wizard_form_questions_list(self):
self.assertEqual(tender.questions.count(), len(initial_data_questions_list)) # count is 2
self.assertEqual(tender.questions_list()[0].get("text"), initial_data_questions_list[0].get("text"))

@patch("lemarche.www.tenders.views.add_to_contact_list")
def test_args_in_add_to_contact_list_call(self, mock_add_to_contact_list):
"""Test arguments in `add_to_contact_list` call"""
tender, user = self.setup_mock_user_and_tender_creation()

mock_add_to_contact_list.assert_called_once()
args, kwargs = mock_add_to_contact_list.call_args

# Check arguments like user, type, and source
self.assertEqual(kwargs["user"], user)
self.assertEqual(kwargs["type"], "signup")
self.assertEqual(kwargs["source"], user_constants.SOURCE_TENDER_FORM)
# Verify that `tender` is an instance of Tender
self.assertIsInstance(
kwargs.get("tender"), Tender, "Expected an instance of Tender for the 'tender' argument."
)

@patch("lemarche.utils.apis.api_brevo.sib_api_v3_sdk.CreateContact")
def test_create_contact_call_has_user_buyer_attributes(self, mock_create_contact):
"""Test CreateContact call contains user buyer attributes"""
tender, user = self.setup_mock_user_and_tender_creation(user=self.user_buyer)
tender.save()

mock_create_contact.assert_called_once()
args, kwargs = mock_create_contact.call_args
attributes = kwargs["attributes"]

self.assertEqual(kwargs["email"], user.email)
self.assertIn(settings.BREVO_CL_SIGNUP_BUYER_ID, kwargs["list_ids"])
self.assertEqual(attributes["MONTANT_BESOIN_ACHETEUR"], tender.amount_int)
self.assertEqual(attributes["TYPE_BESOIN_ACHETEUR"], tender.kind)
self.assertIsNone(
attributes["TYPE_VERTICALE_ACHETEUR"], "Expected TYPE_VERTICALE_ACHETEUR to be None for non-TALLY sources"
)


class TenderMatchingTest(TestCase):
@classmethod
Expand Down
2 changes: 0 additions & 2 deletions lemarche/www/tenders/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from lemarche.tenders.models import Tender, TenderQuestion
from lemarche.users import constants as user_constants
from lemarche.users.models import User
from lemarche.utils.emails import add_to_contact_list
from lemarche.www.auth.tasks import send_new_user_password_reset_link


Expand Down Expand Up @@ -79,7 +78,6 @@ def get_or_create_user_from_anonymous_content(
)
if created and settings.BITOUBI_ENV == "prod":
send_new_user_password_reset_link(user)
add_to_contact_list(user=user, type="signup", source=source)
return user


Expand Down
4 changes: 3 additions & 1 deletion lemarche/www/tenders/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from lemarche.users.models import User
from lemarche.utils import constants, settings_context_processors
from lemarche.utils.data import get_choice
from lemarche.utils.emails import add_to_contact_list
from lemarche.utils.mixins import (
SesameSiaeMemberRequiredMixin,
SesameTenderAuthorRequiredMixin,
Expand Down Expand Up @@ -229,7 +230,6 @@ def done(self, _, form_dict, **kwargs):
tender_dict = cleaned_data | {"author": user, "source": tender_constants.SOURCE_FORM}
is_draft: bool = self.request.POST.get("is_draft", False)
self.save_instance_tender(tender_dict=tender_dict, form_dict=form_dict, is_draft=is_draft)

# remove steps data
uuid = self.request.session.get("tender_steps_data_uuid", None)
if uuid:
Expand All @@ -254,6 +254,8 @@ def done(self, _, form_dict, **kwargs):
message=self.get_success_message(cleaned_data, self.instance, is_draft=is_draft),
extra_tags="modal_message_bizdev",
)
tender = self.instance
add_to_contact_list(user=user, type="signup", source=user_constants.SOURCE_TENDER_FORM, tender=tender)
return redirect(self.get_success_url())

def get_success_url(self):
Expand Down

0 comments on commit e508900

Please sign in to comment.