feat: implement structures management module with index views, location geojson endpoints, and modal for adding assets

This commit is contained in:
Karl Determe 2026-08-18 16:05:57 +02:00
parent ff4042b333
commit 360db5aaad
5 changed files with 99 additions and 12 deletions

View file

@ -20,6 +20,18 @@
const categoriesCache = {};
function populateCategorySelectIfEmpty(select, categories) {
if (!select || select.options.length > 1 || !categories || categories.length === 0) {
return;
}
categories.forEach(cat => {
const option = document.createElement('option');
option.value = cat.id;
option.textContent = cat.name;
select.appendChild(option);
});
}
function initializeForm(form, modalId) {
const thematicCode = form.dataset.thematic;
const categorySelect = form.querySelector('.asset-category-select');
@ -31,17 +43,22 @@
const submitBtn = form.querySelector(`#${modalId}Submit`);
// Fetch and cache categories for this thematic
if (thematicCode && !categoriesCache[thematicCode]) {
fetch(`/assets/${thematicCode}/categories/`)
.then(response => response.json())
.then(data => {
if (data.categories) {
categoriesCache[thematicCode] = data.categories;
}
})
.catch(error => {
console.error('Error prefetching categories:', error);
});
if (thematicCode) {
if (categoriesCache[thematicCode]) {
populateCategorySelectIfEmpty(categorySelect, categoriesCache[thematicCode]);
} else {
fetch(`/assets/${thematicCode}/categories/`)
.then(response => response.json())
.then(data => {
if (data.categories) {
categoriesCache[thematicCode] = data.categories;
populateCategorySelectIfEmpty(categorySelect, data.categories);
}
})
.catch(error => {
console.error('Error prefetching categories:', error);
});
}
}
async function onCategoryChange() {

View file

@ -23,6 +23,12 @@
{% if intervention_id %}
<input type="hidden" name="intervention_id" value="{{ intervention_id }}">
{% endif %}
{% if location %}
<input type="hidden" name="location_id" value="{{ location.id }}">
{% endif %}
{% if structure %}
<input type="hidden" name="structure_id" value="{{ structure.id }}">
{% endif %}
<input type="hidden" name="thematic_code" value="{{ thematic_code }}">
<div class="modal-body">

View file

@ -382,7 +382,7 @@
<!-- Add Asset Modal -->
{% if can_create_assets %}
{% include "assets/components/add_asset_modal.html" with modal_id="addAssetLocationModal" thematic_code="structures" location=location %}
{% include "assets/components/add_asset_modal.html" with modal_id="addAssetLocationModal" thematic_code="structures" location=location categories=categories %}
{% endif %}
{% endblock content%}

View file

@ -374,6 +374,65 @@ class CategoryAllowedModelTest(TestCase):
self.assertFalse(accessory_model["is_model_required"])
class StructuresLocationDetailAddAssetModalTest(TestCase):
def setUp(self):
from common.models import Role
from assets.models.structures import Structure, StructureLocation
from django.contrib.gis.geos import Point
self.user = User.objects.create_superuser(username="admin_struct", password="password123")
self.config = UserConfig.objects.create(user=self.user, is_intern=True)
admin_role, _ = Role.objects.get_or_create(name="admin")
self.config.roles.add(admin_role)
self.thematic, _ = Thematic.objects.get_or_create(
code="structures",
defaults={"name_fr": "Ouvrages d'art", "name_nl": "Kunstwerken"}
)
UserThematics.objects.create(
user_config=self.config,
thematic=self.thematic,
can_view_assets=True,
can_edit_assets=True
)
self.category, _ = AssetCategory.objects.get_or_create(
code="TU_BAL_TEST",
defaults={
"name_fr": "Balisage Test",
"name_nl": "Balisage Test NL",
"thematic": self.thematic
}
)
from django.contrib.gis.geos import MultiPolygon, Polygon
poly = Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
mpoly = MultiPolygon(poly, srid=3812)
self.structure = Structure.objects.create(
code="OA_TEST_01",
name_fr="Pont Test",
structure_type="bridge"
)
self.location = StructureLocation.objects.create(
code="LOC_TEST_01",
name_fr="Localisation Test",
structure=self.structure,
geom=mpoly
)
def test_structures_location_detail_contains_categories(self):
self.client.force_login(self.user)
response = self.client.get(reverse("assets:structures_locations_detail", args=[self.location.id]))
self.assertEqual(response.status_code, 200)
self.assertIn("categories", response.context)
categories_in_context = response.context["categories"]
self.assertTrue(any(c.id == self.category.id for c in categories_in_context))
self.assertContains(response, "Balisage Test")
self.assertContains(response, f'name="location_id" value="{self.location.id}"')
from assets.views.trafficlights import parse_dxf_poles
@tag('slow', 'dxf')

View file

@ -621,10 +621,15 @@ def structures_locations_detail(request, location_id):
geom = json.loads(location_geom_4326.geojson) if location_geom_4326 else None
# Récupérer les catégories d'assets pour la thématique "structures"
categories = list(AssetCategory.objects.filter(thematic__code='structures'))
categories.sort(key=lambda m: m.get_name().lower())
return render(request, "assets/structures_location_detail.html", {
"location": location,
"interventions": interventions, # toutes les interventions de l'arbre
"assets": assets,
"categories": categories,
"main_documents": main_documents,
"other_documents": other_docs,
"add_document_form": StructureDocumentForm(),