diff --git a/dora/core/management/commands/import_structures.py b/dora/core/management/commands/import_structures.py index b592ce0d..26a32848 100644 --- a/dora/core/management/commands/import_structures.py +++ b/dora/core/management/commands/import_structures.py @@ -6,7 +6,7 @@ from dora.core.models import ModerationStatus from dora.core.notify import send_moderation_notification -from dora.core.validators import validate_siret +from dora.core.validators import validate_phone_number, validate_siret from dora.services.models import ServiceModel from dora.services.utils import instantiate_model from dora.sirene.models import Establishment @@ -26,8 +26,8 @@ # Les administrateurs proposés ne seront ajoutés que s’il n’y a pas déjà # un administrateur # -# Format du CSV attendu: -# | nom | siret | siret_parent | courriels_administrateurs | labels | modeles | +# Format du CSV attendu (entête): +# | nom | siret | siret_parent | courriels_administrateurs | labels | modeles | telephone | courriel_structure def to_string_array(strings_list): @@ -44,15 +44,20 @@ class ImportSerializer(serializers.Serializer): admins = serializers.ListField(child=serializers.EmailField(), allow_empty=True) labels = serializers.ListField(child=serializers.CharField(), allow_empty=True) models = serializers.ListField(child=serializers.CharField(), allow_empty=True) + phone = serializers.CharField(allow_blank=True, validators=[validate_phone_number]) + email = serializers.EmailField(allow_blank=True) - def _clean_siret(self, siret: str): - return "".join([c for c in siret if c.isdigit()]) + def _clean_siret_or_phone(self, siret_or_phone: str): + if not siret_or_phone: + return "" + return "".join([c for c in siret_or_phone if c.isdigit()]) def to_internal_value(self, data): # nettoyage pré-validation data |= { - "siret": self._clean_siret(data["siret"]), - "parent_siret": self._clean_siret(data["parent_siret"]), + "siret": self._clean_siret_or_phone(data["siret"]), + "phone": self._clean_siret_or_phone(data["phone"]), + "parent_siret": self._clean_siret_or_phone(data["parent_siret"]), } return super().to_internal_value(data) @@ -154,6 +159,10 @@ def handle(self, *args, **options): "admins": to_string_array(row["courriels_administrateurs"]), "labels": to_string_array(row["labels"]), "models": to_string_array(row["modeles"]), + # champs optionnels correspondant directement + # à un champ du modèle structure + "phone": row.get("telephone", ""), + "email": row.get("courriel_structure", ""), } ) @@ -169,6 +178,8 @@ def handle(self, *args, **options): data["name"], data["siret"], data["parent_siret"], + phone=data.get("phone"), + email=data.get("email"), ) self.stdout.write(f"{structure.get_frontend_url()}") self.invite_users(structure, data["admins"]) @@ -184,14 +195,17 @@ def get_or_create_structure( name, siret, parent_siret, + **kwargs, ): if parent_siret: parent_structure = self._get_or_create_structure_from_siret( - parent_siret, is_parent=True + parent_siret, is_parent=True, **kwargs + ) + structure = self._get_or_create_branch( + name, siret, parent_structure, **kwargs ) - structure = self._get_or_create_branch(name, siret, parent_structure) else: - structure = self._get_or_create_structure_from_siret(siret) + structure = self._get_or_create_structure_from_siret(siret, **kwargs) return structure @@ -246,7 +260,7 @@ def create_services(self, structure, models): f"Ajout du service {service.name} ({service.get_frontend_url()})" ) - def _get_or_create_branch(self, name, siret, parent_structure): + def _get_or_create_branch(self, name, siret, parent_structure, **kwargs): try: if siret: branch = Structure.objects.get(siret=siret) @@ -260,12 +274,13 @@ def _get_or_create_branch(self, name, siret, parent_structure): if siret: establishment = Establishment.objects.get(siret=siret) branch = Structure.objects.create_from_establishment( - establishment, name, parent_structure + establishment, name, parent_structure, **kwargs ) else: branch = Structure.objects.create( name=name, parent=parent_structure, + **kwargs, ) parent_structure.post_create_branch(branch, self.bot_user, self.source) @@ -280,7 +295,7 @@ def _get_or_create_branch(self, name, siret, parent_structure): ) return branch - def _get_or_create_structure_from_siret(self, siret, is_parent=False): + def _get_or_create_structure_from_siret(self, siret, is_parent=False, **kwargs): try: structure = Structure.objects.get(siret=siret) self.stdout.write( @@ -288,7 +303,9 @@ def _get_or_create_structure_from_siret(self, siret, is_parent=False): ) except Structure.DoesNotExist: establishment = Establishment.objects.get(siret=siret) - structure = Structure.objects.create_from_establishment(establishment) + structure = Structure.objects.create_from_establishment( + establishment, **kwargs + ) structure.creator = self.bot_user structure.last_editor = self.bot_user structure.source = self.source diff --git a/dora/core/tests/test_structures_import.py b/dora/core/tests/test_structures_import.py index eae21705..7b17aa33 100644 --- a/dora/core/tests/test_structures_import.py +++ b/dora/core/tests/test_structures_import.py @@ -33,6 +33,8 @@ def create_csv(self, file): "courriels_administrateurs", "labels", "modeles", + "telephone", + "courriel_structure", ] ) return writer @@ -58,7 +60,7 @@ def call_command(self): # Validité des sirets def test_unknown_siret_wont_create_anything(self): - self.add_row(["foo", "12345678901234", "", "foo@buzz.com", "", ""]) + self.add_row(["foo", "12345678901234", "", "foo@buzz.com", "", "", "", ""]) out, err = self.call_command() self.assertIn("Siret inconnu", err) self.assertFalse(Structure.objects.filter(siret="12345678901234").exists()) @@ -66,7 +68,7 @@ def test_unknown_siret_wont_create_anything(self): self.assertEqual(len(mail.outbox), 0) def test_invalid_siret_wont_create_anything(self): - self.add_row(["foo", "1234", "", "foo@buzz.com", "", ""]) + self.add_row(["foo", "1234", "", "foo@buzz.com", "", "", "", ""]) out, err = self.call_command() self.assertIn("Le numéro SIRET doit être composé de 14 chiffres.", err) self.assertIn("siret", err) @@ -75,25 +77,25 @@ def test_invalid_siret_wont_create_anything(self): self.assertEqual(len(mail.outbox), 0) def test_invalid_parent_siret_error(self): - self.add_row(["foo", "", "1234", "foo@buzz.com", "", ""]) + self.add_row(["foo", "", "1234", "foo@buzz.com", "", "", "", ""]) out, err = self.call_command() self.assertIn("Le numéro SIRET doit être composé de 14 chiffres.", err) self.assertIn("parent_siret", err) def test_unknown_parent_siret_error(self): - self.add_row(["foo", "", "12345678901234", "foo@buzz.com", "", ""]) + self.add_row(["foo", "", "12345678901234", "foo@buzz.com", "", "", "", ""]) out, err = self.call_command() self.assertIn("SIRET parent inconnu", err) def test_missing_siret_error(self): - self.add_row(["foo", "", "", "foo@buzz.com", "", ""]) + self.add_row(["foo", "", "", "foo@buzz.com", "", "", "", ""]) out, err = self.call_command() self.assertIn("`siret` ou `parent_siret` sont requis", err) def test_no_recursive_branch(self): parent = make_structure() make_structure(parent=parent, siret="12345678901234") - self.add_row(["foo", "", "12345678901234", "foo@buzz.com", "", ""]) + self.add_row(["foo", "", "12345678901234", "foo@buzz.com", "", "", "", ""]) out, err = self.call_command() self.assertIn( "Le SIRET 12345678901234 est une antenne, il ne peut pas être utilisé comme parent", @@ -103,7 +105,9 @@ def test_no_recursive_branch(self): # def test_can_invite_new_user(self): structure = make_structure() - self.add_row([structure.name, structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo@buzz.com", "", "", "", ""] + ) self.call_command() user = User.objects.filter(email="foo@buzz.com").first() self.assertIsNotNone(user) @@ -122,7 +126,16 @@ def test_can_invite_new_user(self): def test_can_invite_multiple_users(self): structure = make_structure() self.add_row( - [structure.name, structure.siret, "", "foo@buzz.com,bar@buzz.com", "", ""] + [ + structure.name, + structure.siret, + "", + "foo@buzz.com,bar@buzz.com", + "", + "", + "", + "", + ] ) self.call_command() self.assertTrue(User.objects.filter(email="foo@buzz.com").exists()) @@ -133,7 +146,9 @@ def test_can_invite_even_if_theres_already_an_admin(self): structure = make_structure() user = make_user() baker.make(StructureMember, user=user, structure=structure, is_admin=True) - self.add_row([structure.name, structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo@buzz.com", "", "", "", "", ""] + ) out, err = self.call_command() self.assertTrue( StructurePutativeMember.objects.filter( @@ -144,7 +159,9 @@ def test_can_invite_even_if_theres_already_an_admin(self): def test_new_users_are_automatically_accepted(self): structure = make_structure() - self.add_row([structure.name, structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo@buzz.com", "", "", "", ""] + ) self.call_command() self.assertTrue( StructurePutativeMember.objects.filter( @@ -154,7 +171,9 @@ def test_new_users_are_automatically_accepted(self): def test_idempotent(self): structure = make_structure() - self.add_row([structure.name, structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo@buzz.com", "", "", "", ""] + ) self.call_command() self.assertEqual(len(mail.outbox), 1) out, err = self.call_command() @@ -165,7 +184,9 @@ def test_idempotent(self): def test_invitee_is_admin(self): structure = make_structure() - self.add_row([structure.name, structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo@buzz.com", "", "", "", ""] + ) self.call_command() self.assertTrue( StructurePutativeMember.objects.filter( @@ -178,7 +199,9 @@ def test_invitee_is_admin(self): def test_email_is_valid(self): structure = make_structure() - self.add_row([structure.name, structure.siret, "", "foo.buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo.buzz.com", "", "", "", ""] + ) out, err = self.call_command() self.assertIn("admins", err) self.assertIn("Saisissez une adresse e-mail valide.", err) @@ -187,7 +210,7 @@ def test_email_is_valid(self): def test_structure_name_is_valid(self): structure = make_structure() - self.add_row(["", structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row(["", structure.siret, "", "foo@buzz.com", "", "", "", ""]) out, err = self.call_command() self.assertIn("name", err) self.assertIn("Ce champ ne peut être vide.", err) @@ -196,14 +219,18 @@ def test_structure_name_is_valid(self): def test_invitee_not_a_valid_user_yet(self): structure = make_structure() - self.add_row([structure.name, structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo@buzz.com", "", "", "", ""] + ) self.call_command() user = User.objects.filter(email="foo@buzz.com").first() self.assertFalse(user.is_valid) def test_invitee_not_a_valid_member_yet(self): structure = make_structure() - self.add_row([structure.name, structure.siret, "", "foo@buzz.com", "", ""]) + self.add_row( + [structure.name, structure.siret, "", "foo@buzz.com", "", "", "", ""] + ) self.call_command() members = StructurePutativeMember.objects.filter( user__email="foo@buzz.com", structure=structure @@ -219,7 +246,7 @@ def test_can_invite_existing_user(self): user = baker.make( "users.User", first_name="foo", last_name="bar", is_valid=True ) - self.add_row([structure.name, structure.siret, "", user.email, "", ""]) + self.add_row([structure.name, structure.siret, "", user.email, "", "", "", ""]) self.call_command() self.assertEqual(User.objects.filter(email=user.email).count(), 1) user.refresh_from_db() @@ -242,7 +269,7 @@ def test_existing_user_stay_valid_user(self): user = baker.make( "users.User", first_name="foo", last_name="bar", is_valid=True ) - self.add_row([structure.name, structure.siret, "", user.email, "", ""]) + self.add_row([structure.name, structure.siret, "", user.email, "", "", "", ""]) self.call_command() user.refresh_from_db() self.assertTrue(user.is_valid) @@ -256,7 +283,7 @@ def test_existing_user_stay_valid_member(self): structure=structure, user=user, ) - self.add_row([structure.name, structure.siret, "", user.email, "", ""]) + self.add_row([structure.name, structure.siret, "", user.email, "", "", "", ""]) self.call_command() member.refresh_from_db() @@ -268,7 +295,7 @@ def test_member_can_be_promoted_to_admin(self): member = StructureMember.objects.create( structure=structure, user=user, is_admin=False ) - self.add_row([structure.name, structure.siret, "", user.email, "", ""]) + self.add_row([structure.name, structure.siret, "", user.email, "", "", "", ""]) self.call_command() member.refresh_from_db() self.assertTrue(member.is_admin) @@ -280,7 +307,7 @@ def test_create_structure_on_the_fly(self): name="My Establishment", parent_name="Parent", ) - self.add_row(["Foo", "12345678901234", "", "foo@buzz.com", "", ""]) + self.add_row(["Foo", "12345678901234", "", "foo@buzz.com", "", "", "", ""]) self.call_command() self.assertTrue( Structure.objects.filter( @@ -295,7 +322,7 @@ def test_create_parent_structure_on_the_fly(self): name="My Establishment", parent_name="Parent", ) - self.add_row(["Foo", "", "12345678901234", "foo@buzz.com", "", ""]) + self.add_row(["Foo", "", "12345678901234", "foo@buzz.com", "", "", "", ""]) self.call_command() self.assertTrue( Structure.objects.filter( @@ -305,7 +332,7 @@ def test_create_parent_structure_on_the_fly(self): def test_create_new_branch(self): structure = make_structure(name="My Structure") - self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", ""]) + self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", "", "", ""]) self.call_command() branches = Structure.objects.filter(parent=structure) self.assertEqual(branches.count(), 1) @@ -323,7 +350,7 @@ def test_find_existing_branch(self): structure = make_structure(name="My Structure", siret="12345678901234") branch = baker.make("Structure", name="Foo", siret=None, parent=structure) self.assertEqual(structure.branches.count(), 1) - self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", ""]) + self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", "", "", ""]) self.call_command() self.assertEqual(structure.branches.count(), 1) self.assertEqual(len(mail.outbox), 1) @@ -341,7 +368,9 @@ def test_find_existing_branch(self): def test_create_new_branch_with_siret(self): structure = make_structure(name="My Structure") baker.make("Establishment", siret="12345678901234", name="My Establishment") - self.add_row(["Foo", "12345678901234", structure.siret, "foo@buzz.com", "", ""]) + self.add_row( + ["Foo", "12345678901234", structure.siret, "foo@buzz.com", "", "", "", ""] + ) self.call_command() branches = Structure.objects.filter(parent=structure) self.assertEqual(branches.count(), 1) @@ -356,7 +385,7 @@ def test_create_new_branch_with_siret(self): def test_user_belong_to_branch(self): structure = make_structure(name="My Structure") - self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", ""]) + self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", "", "", ""]) self.call_command() branch = Structure.objects.filter(parent=structure).first() @@ -373,7 +402,7 @@ def test_user_belong_to_branch(self): def test_user_doesnt_belong_to_parent(self): structure = make_structure(name="My Structure") - self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", ""]) + self.add_row(["Foo", "", structure.siret, "foo@buzz.com", "", "", "", ""]) self.call_command() self.assertEqual( @@ -386,7 +415,7 @@ def test_user_doesnt_belong_to_parent(self): def test_parent_admin_are_branch_admins(self): parent_structure = make_structure() parent_admin = make_user(structure=parent_structure, is_admin=True) - self.add_row(["branch", "", parent_structure.siret, "", "", ""]) + self.add_row(["branch", "", parent_structure.siret, "", "", "", "", ""]) self.call_command() branches = Structure.objects.filter(parent=parent_structure, name="branch") self.assertEqual(branches.count(), 1) @@ -403,7 +432,7 @@ def test_add_labels(self): baker.make("StructureNationalLabel", value="l1") baker.make("StructureNationalLabel", value="l2") self.add_row( - [structure.name, structure.siret, "", "foo@buzz.com", "l1, l2", ""] + [structure.name, structure.siret, "", "foo@buzz.com", "l1, l2", "", "", ""] ) self.call_command() self.assertTrue(structure.national_labels.filter(value="l1").exists()) @@ -412,14 +441,14 @@ def test_add_labels(self): def test_add_services(self): model = make_model() structure = make_structure() - self.add_row([structure.name, structure.siret, "", "", "", model.slug]) + self.add_row([structure.name, structure.siret, "", "", "", model.slug, "", ""]) self.call_command() self.assertTrue(structure.services.filter(model=model).exists()) def test_labels_must_exist(self): structure = make_structure() self.add_row( - [structure.name, structure.siret, "", "foo@buzz.com", "l1, l2", ""] + [structure.name, structure.siret, "", "foo@buzz.com", "l1, l2", "", "", ""] ) out, err = self.call_command() self.assertIn("Label inconnu l1", err) @@ -427,7 +456,16 @@ def test_labels_must_exist(self): def test_models_must_exist(self): structure = make_structure() self.add_row( - [structure.name, structure.siret, "", "foo@buzz.com", "", "mod1,mod2"] + [ + structure.name, + structure.siret, + "", + "foo@buzz.com", + "", + "mod1,mod2", + "", + "", + ] ) out, err = self.call_command() self.assertIn("Modèle inconnu mod1", err) @@ -437,7 +475,7 @@ def test_wont_duplicate_labels(self): structure = make_structure() structure.national_labels.add(l1) self.assertEqual(structure.national_labels.filter(value="l1").count(), 1) - self.add_row([structure.name, structure.siret, "", "", "l1", ""]) + self.add_row([structure.name, structure.siret, "", "", "l1", "", ""]) self.call_command() self.assertEqual(structure.national_labels.filter(value="l1").count(), 1) @@ -446,6 +484,39 @@ def test_wont_duplicate_services(self): structure = make_structure() make_service(structure=structure, model=model) self.assertEqual(structure.services.filter(model=model).count(), 1) - self.add_row([structure.name, structure.siret, "", "", "", model.slug]) + self.add_row([structure.name, structure.siret, "", "", "", model.slug, ""]) self.call_command() self.assertEqual(structure.services.filter(model=model).count(), 1) + + # champs optionnels : + + def test_add_phone_number(self): + baker.make( + "Establishment", + siret="12345678901234", + name="My Establishment", + ) + + self.add_row(["Test", "12345678901234", "", "", "", "", "0123456789", ""]) + self.call_command() + + structure = Structure.objects.get(siret="12345678901234") + + assert structure.phone == "0123456789" + + def test_add_structure_email(self): + baker.make( + "Establishment", + siret="12345678900000", + name="My Establishment", + ) + + self.add_row( + ["Test", "12345678900000", "", "", "", "", "", "email@structure.com"] + ) + out, err = self.call_command() + print(out, err) + + structure = Structure.objects.get(siret="12345678900000") + + assert structure.email == "email@structure.com"