diff --git a/lemarche/siaes/admin.py b/lemarche/siaes/admin.py index 5375f13d2..20a55ccab 100644 --- a/lemarche/siaes/admin.py +++ b/lemarche/siaes/admin.py @@ -740,7 +740,7 @@ class SiaeActivityAdmin(admin.ModelAdmin): search_fields = ["id", "siae__id", "siae__name"] search_help_text = "Cherche sur les champs : ID, Structure (ID, Nom)" - autocomplete_fields = ["siae", "sectors", "location"] + autocomplete_fields = ["siae", "sectors", "locations"] readonly_fields = ["created_at", "updated_at"] fieldsets = ( @@ -759,7 +759,7 @@ class SiaeActivityAdmin(admin.ModelAdmin): ( "Localisation et périmètre d'intervention", { - "fields": ("location", "geo_range", "geo_range_custom_distance"), + "fields": ("locations", "geo_range", "geo_range_custom_distance"), }, ), ("Dates", {"fields": ("created_at", "updated_at")}), diff --git a/lemarche/siaes/constants.py b/lemarche/siaes/constants.py index c6a2c0f92..cb3bd4deb 100644 --- a/lemarche/siaes/constants.py +++ b/lemarche/siaes/constants.py @@ -80,6 +80,7 @@ GEO_RANGE_REGION = "REGION" GEO_RANGE_CUSTOM = "CUSTOM" GEO_RANGE_COUNTRY = "COUNTRY" +GEO_RANGE_ZONES = "ZONES" GEO_RANGE_CHOICES = ( (GEO_RANGE_COUNTRY, "France entière"), @@ -88,6 +89,12 @@ (GEO_RANGE_CUSTOM, "Distance en kilomètres"), ) +ACTIVITIES_GEO_RANGE_CHOICES = ( + (GEO_RANGE_COUNTRY, "France entière"), + (GEO_RANGE_CUSTOM, "Distance en kilomètres"), + (GEO_RANGE_ZONES, "Zone(s) d'intervention personnalisée(s)"), +) + SOURCE_ASP = "ASP" SOURCE_GEIQ = "GEIQ" SOURCE_EA_EATT = "EA_EATT" diff --git a/lemarche/siaes/factories.py b/lemarche/siaes/factories.py index 46d19cc55..3e750f316 100644 --- a/lemarche/siaes/factories.py +++ b/lemarche/siaes/factories.py @@ -3,6 +3,7 @@ import factory.fuzzy from factory.django import DjangoModelFactory +from lemarche.sectors.factories import SectorGroupFactory from lemarche.siaes import constants as siae_constants from lemarche.siaes.models import ( Siae, @@ -69,13 +70,28 @@ class SiaeActivityFactory(DjangoModelFactory): class Meta: model = SiaeActivity - presta_type = factory.List([factory.fuzzy.FuzzyChoice([key for (key, value) in siae_constants.PRESTA_CHOICES])]) + class Params: + with_country_perimeter = factory.Trait(geo_range=siae_constants.GEO_RANGE_COUNTRY) + with_custom_distance_perimeter = factory.Trait( + geo_range=siae_constants.GEO_RANGE_CUSTOM, + geo_range_custom_distance=factory.fuzzy.FuzzyInteger(1, 100), + ) + with_zones_perimeter = factory.Trait(geo_range=siae_constants.GEO_RANGE_ZONES) + + sector_group = factory.SubFactory(SectorGroupFactory) + + presta_type = factory.List([factory.fuzzy.FuzzyChoice([key for (key, _) in siae_constants.PRESTA_CHOICES])]) @factory.post_generation def sectors(self, create, extracted, **kwargs): if extracted: self.sectors.add(*extracted) + @factory.post_generation + def locations(self, create, extracted, **kwargs): + if extracted: + self.locations.add(*extracted) + class SiaeOfferFactory(DjangoModelFactory): class Meta: diff --git a/lemarche/siaes/migrations/0077_remove_siaeactivity_location_siaeactivity_locations_and_more.py b/lemarche/siaes/migrations/0077_remove_siaeactivity_location_siaeactivity_locations_and_more.py new file mode 100644 index 000000000..10dfe9291 --- /dev/null +++ b/lemarche/siaes/migrations/0077_remove_siaeactivity_location_siaeactivity_locations_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.15 on 2024-10-07 14:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("perimeters", "0005_alter_perimeter_post_codes"), + ("siaes", "0076_siaeactivity"), + ] + + operations = [ + migrations.RemoveField( + model_name="siaeactivity", + name="location", + ), + migrations.AddField( + model_name="siaeactivity", + name="locations", + field=models.ManyToManyField( + blank=True, related_name="siae_activities", to="perimeters.perimeter", verbose_name="Localisations" + ), + ), + migrations.AlterField( + model_name="siaeactivity", + name="geo_range", + field=models.CharField( + blank=True, + choices=[ + ("COUNTRY", "France entière"), + ("CUSTOM", "Distance en kilomètres"), + ("ZONES", "Zone(s) d'intervention personnalisée(s)"), + ], + db_index=True, + max_length=20, + verbose_name="Périmètre d'intervention", + ), + ), + ] diff --git a/lemarche/siaes/models.py b/lemarche/siaes/models.py index 83d8c8230..d319bd6ce 100644 --- a/lemarche/siaes/models.py +++ b/lemarche/siaes/models.py @@ -1428,25 +1428,19 @@ class SiaeActivity(models.Model): null=True, db_index=True, ) - location: Perimeter = models.ForeignKey( - to="perimeters.Perimeter", - verbose_name="Localisation", - related_name="siae_activities", - on_delete=models.DO_NOTHING, - blank=True, - null=True, + locations = models.ManyToManyField( + "perimeters.Perimeter", verbose_name="Localisations", related_name="siae_activities", blank=True ) geo_range = models.CharField( verbose_name="Périmètre d'intervention", max_length=20, - choices=siae_constants.GEO_RANGE_CHOICES, + choices=siae_constants.ACTIVITIES_GEO_RANGE_CHOICES, blank=True, db_index=True, ) geo_range_custom_distance = models.IntegerField( verbose_name="Distance en kilomètres (périmètre d'intervention)", blank=True, null=True ) - created_at = models.DateTimeField(verbose_name="Date de création", default=timezone.now) updated_at = models.DateTimeField(verbose_name="Date de modification", auto_now=True) @@ -1463,28 +1457,13 @@ def presta_type_display(self) -> str: def geo_range_pretty_display(self): if self.geo_range == siae_constants.GEO_RANGE_COUNTRY: return self.get_geo_range_display() - elif self.geo_range == siae_constants.GEO_RANGE_REGION: - return f"{self.get_geo_range_display().lower()} ({self.siae.region})" - elif self.geo_range == siae_constants.GEO_RANGE_DEPARTMENT: - return f"{self.get_geo_range_display().lower()} ({self.siae.department})" + elif self.geo_range == siae_constants.GEO_RANGE_ZONES: + return f"{self.get_geo_range_display()} : {', '.join(self.locations.values_list('name', flat=True))}" elif self.geo_range == siae_constants.GEO_RANGE_CUSTOM: if self.geo_range_custom_distance: - return f"{self.geo_range_custom_distance} km" + return f"{self.geo_range_custom_distance} km de {self.siae.city}" return "non disponible" - @property - def geo_range_pretty_title(self): - if self.geo_range == siae_constants.GEO_RANGE_COUNTRY: - return self.geo_range_pretty_display - elif self.geo_range == siae_constants.GEO_RANGE_REGION: - return self.siae.region - elif self.geo_range == siae_constants.GEO_RANGE_DEPARTMENT: - return self.siae.get_department_display() - elif self.geo_range == siae_constants.GEO_RANGE_CUSTOM: - if self.geo_range_custom_distance: - return f"{self.geo_range_pretty_display} de {self.siae.city}" - return self.geo_range_pretty_display - class SiaeOffer(models.Model): name = models.CharField(verbose_name="Nom", max_length=255) diff --git a/lemarche/static/js/siae_activity_delete.js b/lemarche/static/js/siae_activity_delete.js index 17b546ee0..672ec0147 100644 --- a/lemarche/static/js/siae_activity_delete.js +++ b/lemarche/static/js/siae_activity_delete.js @@ -1,11 +1,9 @@ document.addEventListener('alpine:init', function () { Alpine.data('activityItem', () => ({ - siaeSlug: null, siaeActivityId: null, siaeActivityNameDisplay: null, - initOptions(siaeSlug, siaeActivityId, siaeActivityNameDisplay) { - this.siaeSlug = siaeSlug; + initOptions(siaeActivityId, siaeActivityNameDisplay) { this.siaeActivityId = siaeActivityId; this.siaeActivityNameDisplay = siaeActivityNameDisplay; }, @@ -20,13 +18,8 @@ document.addEventListener('alpine:init', function () { if (modal.querySelector('#siae-activity-name-display')) { modal.querySelector('#siae-activity-name-display').textContent = this.siaeActivityNameDisplay; } - - let formActionUrl = new URL(modalForm.getAttribute('data-action')); - formActionUrl.pathname = formActionUrl.pathname - .replace('siae-slug-to-replace', encodeURIComponent(this.siaeSlug)) - .replace('siae-activity-id-to-replace', encodeURIComponent(this.siaeActivityId)); - - modalForm.setAttribute('action', formActionUrl.toString()); + let formActionUrl = escapeHtml(modalForm.getAttribute('data-action')); + modalForm.setAttribute('action', formActionUrl.replace('siae-activity-id-to-replace', this.siaeActivityId.replace(/\D/g, ''))); const modalDialog = document.getElementById(modalID); dsfr(modalDialog).modal.disclose(); diff --git a/lemarche/static/js/utils.js b/lemarche/static/js/utils.js index a1a89f8d6..67b070422 100644 --- a/lemarche/static/js/utils.js +++ b/lemarche/static/js/utils.js @@ -29,7 +29,7 @@ window.addEventListener('DOMContentLoaded', function () { initSuperBadges(); // some elements have their url in data-url attribute - $(document).on("click", ".with-data-url", function(e) { + $(document).on("click", ".with-data-url", function (e) { window.location.href = $(this).data("url"); }); }); @@ -57,13 +57,6 @@ let toggleInputElement = (toggle, element, required = undefined) => { } } -const initModalMessages = () => { - var elements = document.getElementsByClassName('modal-message-alert'); - for (var i = 0; i < elements.length; i++) { - OpenBootstrapModal(elements[i]); - } -} - const initSuperBadges = () => { $('.super-badge-badge').each(function (element) { $(this).tooltip( @@ -103,8 +96,11 @@ const initSuperBadges = () => { }) } -function OpenBootstrapModal(elmt) { - setTimeout(function () { - $(elmt).modal('show'); - }, 1000); -} +function escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} \ No newline at end of file diff --git a/lemarche/templates/dashboard/_siae_activity_delete_modal.html b/lemarche/templates/dashboard/_siae_activity_delete_modal.html index 301df3e10..6824793c5 100644 --- a/lemarche/templates/dashboard/_siae_activity_delete_modal.html +++ b/lemarche/templates/dashboard/_siae_activity_delete_modal.html @@ -22,7 +22,7 @@

Supprimer