From 41aa78e38296451d0047b82c02cc913ea008e564 Mon Sep 17 00:00:00 2001 From: kdeterme Date: Tue, 11 Aug 2026 15:47:17 +0200 Subject: [PATCH] feat: implement GIEP inspection question framework with localized labels and template filters --- .../assets/locale/nl/LC_MESSAGES/django.mo | Bin 95866 -> 95862 bytes .../0019_naturerwiasset_location.py | 29 + streetup/assets/models/nature.py | 19 +- streetup/assets/permissions.py | 1 + .../assets/components/asset_inspections.html | 64 +- .../components/inspection_form_modal.html | 6 +- .../templates/assets/naturerwiz_detail.html | 4 + streetup/assets/templatetags/asset_tags.py | 6 +- streetup/assets/views/water.py | 10 +- .../locale/fr/LC_MESSAGES/django.mo | Bin 2423 -> 11197 bytes .../locale/fr/LC_MESSAGES/django.po | 301 +++++++++ streetup/inspections/models.py | 7 +- streetup/inspections/registry.py | 4 +- .../components/asset_details/_naturerwiz.html | 284 ++++++++- .../components/asset_fields/_naturerwiz.html | 588 +++++++++++++++++- .../components/inspection_form_modal.html | 6 +- .../templatetags/inspections_tags.py | 95 +++ streetup/inspections/tests.py | 41 +- streetup/inspections/views.py | 2 +- 19 files changed, 1405 insertions(+), 62 deletions(-) create mode 100644 streetup/assets/migrations/0019_naturerwiasset_location.py diff --git a/streetup/assets/locale/nl/LC_MESSAGES/django.mo b/streetup/assets/locale/nl/LC_MESSAGES/django.mo index be3b2f5dd81b677f3bb10d3e681a4c65f4be82e8..2aee0e27dd5ca3914c43d0c76d103f13aa9b1817 100644 GIT binary patch delta 9808 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

Y|6e6S|P|u&VdJKLuaB9F24g z`eGIxa4veN?vLk-u?h7T(bR51_k0h!^54ShSEX?qpcA%4$L)wNq$g&LbN~ep*>&iO z%i@KZ=rvh@c6=5M^wp@pkM{c%J^kOKTlq)Ss|`tKs3|&O4>Z7jXabm!bWphI7%aco?1MnQRo+ zqXT|}rg}g6;?a0s{py@dbLvgdOk9X=-KeNfj`{-h7X2Mf`FmLY2MIdiZgie1!&5z5 zKML*84t=rwn~tt%IGXBGG=MU6fQQhPJ&$hDn^=e&Fb@x+TUGa(^j;xa?~ESWi;%OH z&5WerfK%eZJoFR$BzhaxVGG=fO)+zAPNpLipojG;G$S{mXJ9V6W%HxH5)JTWbW7G@ z9{zwe{Qa+dU0P`!bnhCX0To2O6M7bk(Ze(Vo#>`;5*o;~sNahowuRCDFnUH_jOTBm z0l$m2z5mm^YifKG5Vy7KYp-rj~Dw#C>5-$CCyhW?#UYjoPmw&+%L zL2pwJbo{>P77iYrO)n0m!N^CWDV!27-hpOfUeq5(Gw?KenBG7G`WLpt-_dc}j!B2N z80|k84PZ2yiHYcfW@IVY;ofL?0`0I8J^gP*`xbP7@1wpq>ievwwAr*$Jb@GfkICyh&)=#Cz;E6~(kj|Myu z4Rjhh&TMqz$I-2M89nVEq8Z$SF5m!G_WoD8F%5VedKT)UFBG7Mr4#z&RDvyWEPB`; z#M5yjw!uH5z4cA0{hIIrJcH-&q2H6kXhv&}kKg~3DL8RU^o4Hdr?x+u+DYiyn1`

HA8lw{wqrYMoqXCUW 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 diff --git a/streetup/assets/migrations/0019_naturerwiasset_location.py b/streetup/assets/migrations/0019_naturerwiasset_location.py new file mode 100644 index 0000000..826e9b1 --- /dev/null +++ b/streetup/assets/migrations/0019_naturerwiasset_location.py @@ -0,0 +1,29 @@ +# Generated by Django 6.0.7 on 2026-08-11 12:13 + +import django.db.models.deletion +from django.db import migrations, models + + +def populate_locations(apps, schema_editor): + NatureRWIZ = apps.get_model('assets', 'NatureRWIZ') + NatureRWIAsset = apps.get_model('assets', 'NatureRWIAsset') + + zones = NatureRWIZ.objects.filter(geom__isnull=False) + for zone in zones: + NatureRWIAsset.objects.filter(geom__intersects=zone.geom, location__isnull=True).update(location=zone) + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0018_remove_parkingspot_scooter_capacity'), + ] + + operations = [ + migrations.AddField( + model_name='naturerwiasset', + name='location', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assets', to='assets.naturerwiz', verbose_name='Zone GIEP / ZIEP'), + ), + migrations.RunPython(populate_locations, reverse_code=migrations.RunPython.noop), + ] diff --git a/streetup/assets/models/nature.py b/streetup/assets/models/nature.py index 5d2ef66..7968d07 100644 --- a/streetup/assets/models/nature.py +++ b/streetup/assets/models/nature.py @@ -277,14 +277,10 @@ class NatureRWIZ(AbstractLocation): geom = models.MultiPolygonField(srid=3812, null=True,blank=True) def get_active_assets(self): - if not self.geom: - return NatureRWIAsset.objects.none() - return NatureRWIAsset.objects.filter(geom__intersects=self.geom, status='active') + return NatureRWIAsset.objects.filter(location=self, status='active') def get_all_assets(self): - if not self.geom: - return NatureRWIAsset.objects.none() - return NatureRWIAsset.objects.filter(geom__intersects=self.geom) + return NatureRWIAsset.objects.filter(location=self) def get_absolute_url(self): return reverse( @@ -451,6 +447,7 @@ GEOMETRY_TYPE_CHOICES = [ class NatureRWIAsset(AbstractGeoAsset): + location = models.ForeignKey(NatureRWIZ, on_delete=models.SET_NULL, null=True, blank=True, related_name="assets", verbose_name=_("Zone GIEP / ZIEP")) geom = models.GeometryField(srid=3812, null=True, blank=True) external_id = models.IntegerField(null=True, blank=True, verbose_name=_("External ID")) @@ -485,6 +482,16 @@ class NatureRWIAsset(AbstractGeoAsset): verbose_name = _("RWI Asset") verbose_name_plural = _("RWI Assets") + def save(self, *args, **kwargs): + if not self.location_id and self.geom: + try: + matching_rwiz = NatureRWIZ.objects.filter(geom__intersects=self.geom).first() + if matching_rwiz: + self.location = matching_rwiz + except Exception: + pass + super().save(*args, **kwargs) + def get_absolute_url(self): return reverse('assets:water_assets_detail', kwargs={'asset_model': 'naturerwiasset', 'asset_id': self.id}) diff --git a/streetup/assets/permissions.py b/streetup/assets/permissions.py index 42f2376..382f27b 100644 --- a/streetup/assets/permissions.py +++ b/streetup/assets/permissions.py @@ -298,6 +298,7 @@ def _get_asset_thematic(asset): # Fallback: deduce thematic from asset class name class_name = asset.__class__.__name__.lower() thematic_mappings = { + 'naturerwiz': 'water', 'naturerwiasset': 'water', 'trafficlight': 'trafficlights', 'publiclighting': 'publiclighting', diff --git a/streetup/assets/templates/assets/components/asset_inspections.html b/streetup/assets/templates/assets/components/asset_inspections.html index 8c00ff2..91c4d8d 100644 --- a/streetup/assets/templates/assets/components/asset_inspections.html +++ b/streetup/assets/templates/assets/components/asset_inspections.html @@ -18,8 +18,8 @@ data-object-id="{{ asset_pk }}" data-target-code="{{ asset.code|default:asset.pk }}" data-target-name="{{ asset.name_fr|default:asset.name_nl|default:asset }}" - data-target-type="{{ asset.category.name_fr|default:'Asset' }}" - data-target-status="{{ asset.get_status_display|default:asset.inspection_status|default:'-' }}"> + data-target-type="{% if asset.category %}{{ asset.category.name_fr }}{% else %}{% translate 'Zone / Équipement' %}{% endif %}" + data-target-status="{% if asset.get_status_display %}{{ asset.get_status_display }}{% else %}-{% endif %}"> {% translate "Nouvelle inspection" %} {% endif %} @@ -86,7 +86,7 @@