import json from django.shortcuts import render, get_object_or_404, redirect from django.contrib import messages from django.contrib.auth.decorators import login_required from django.db.models import Q, F from django.http import JsonResponse, HttpResponseForbidden from django.urls import reverse from django.utils.translation import gettext as _, get_language from django.contrib.gis.geos import GEOSGeometry from common.models import UserThematics from common.utils import check_thematic_access, get_regional_terms from assets.permissions import can_edit_asset from assets.forms import PublicBuildingForm, BuildingAssetForm from assets.models.buildings import ( PublicBuilding, BuildingAsset, BuildingEquipment, PublicBuildingDocument, USAGE_TYPE_CHOICES, BUILDING_STATUS_CHOICES, DPE_RATING_CHOICES, ERP_CATEGORY_CHOICES, LOCATION_TYPE_CHOICES, BUILDING_ASSET_TYPE_CHOICES ) def publicbuildings_locations_geojson(request): """ Vue GeoJSON renvoyant la collection des localisations de bâtiments publics pour la carte unifiée. """ lang = get_language() if get_language() in ('fr', 'nl') else 'fr' if request.method == "POST": try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse({"error": "Invalid JSON"}, status=400) statuses = data.get('asset_status', []) location_types = data.get('location_type') or [] if isinstance(location_types, str): location_types = [location_types] usage_types = data.get('usage_type') or [] if isinstance(usage_types, str): usage_types = [usage_types] is_main_loc = data.get('is_main_location') is_erp_val = data.get('is_erp') filter_geom_raw = data.get("filter_geom") if filter_geom_raw: try: filter_geom = GEOSGeometry(json.dumps(filter_geom_raw['geometry']), srid=4326) filter_geom.transform(3812) except Exception as e: return JsonResponse({"error": f"Invalid geometry: {str(e)}"}, status=400) else: filter_geom = None search_q = (data.get('q') or '').strip() else: statuses = request.GET.getlist('asset_status') location_types = request.GET.getlist('location_type', []) usage_types = request.GET.getlist('usage_type', []) is_main_loc = request.GET.get('is_main_location') is_erp_val = request.GET.get('is_erp') filter_geom = None search_q = request.GET.get('q', '').strip() qs = PublicBuilding.objects.all() if location_types: qs = qs.filter(location_type__in=location_types) if usage_types: qs = qs.filter(usage_type__in=usage_types) if is_main_loc in ['true', '1', True]: qs = qs.filter(is_main_location=True) elif is_main_loc in ['false', '0', False]: qs = qs.filter(is_main_location=False) if is_erp_val in ['true', '1', True]: qs = qs.filter(is_erp=True) elif is_erp_val in ['false', '0', False]: qs = qs.filter(is_erp=False) if statuses: qs = qs.filter(building_status__in=statuses) if filter_geom: qs = qs.filter(geom__intersects=filter_geom) if search_q: qs = qs.filter( Q(code__icontains=search_q) | Q(name_fr__icontains=search_q) | Q(name_nl__icontains=search_q) | Q(address__icontains=search_q) | Q(owner__icontains=search_q) ) features = [] from assets.views.core import get_element_type_label for b in qs: geometry = None if b.geojson: try: geometry = json.loads(b.geojson) except Exception: pass if not geometry and b.geom: geom_4326 = b.geom.clone() geom_4326.transform(4326) centroid = geom_4326.centroid geometry = {"type": "Point", "coordinates": [centroid.x, centroid.y]} elif not geometry and b.lon is not None and b.lat is not None: geometry = {"type": "Point", "coordinates": [b.lon, b.lat]} detail_url = reverse('assets:publicbuilding_detail', kwargs={'pk': b.pk}) features.append({ "type": "Feature", "id": b.id, "geometry": geometry, "properties": { "code": b.code, "name": b.get_name(), "location_type": b.location_type, "location_type_display": b.get_location_type_display(), "status": b.building_status, "building_status": b.building_status, "building_status_display": b.get_building_status_display(), "usage_type": b.usage_type, "usage_type_display": b.get_usage_type_display(), "owner": b.owner or '', "is_erp": b.is_erp, "erp_category": b.erp_category or '', "dpe_rating": b.dpe_rating or '', "lon": b.lon, "lat": b.lat, "color": "#2B579A", "display_color": "#2B579A", "asset_type": "publicbuilding", "detail_url": detail_url, "element_type": get_element_type_label("publicbuilding"), "thematic": "publicbuildings", "is_location": True, } }) return JsonResponse({"type": "FeatureCollection", "features": features}) def publicbuildings_assets_geojson(request): """ Vue GeoJSON renvoyant la collection des assets physiques de bâtiments (Génie civil, Façades, CVC, Ascenseurs...) pour la carte unifiée. """ lang = get_language() if get_language() in ('fr', 'nl') else 'fr' if request.method == "POST": try: data = json.loads(request.body) except json.JSONDecodeError: return JsonResponse({"error": "Invalid JSON"}, status=400) statuses = data.get('asset_status', []) categories = data.get('category', []) or data.get('categories', []) if isinstance(categories, (str, int)): categories = [categories] asset_types = data.get('asset_type') or [] if isinstance(asset_types, str): asset_types = [asset_types] search_q = (data.get('q') or '').strip() else: statuses = request.GET.getlist('asset_status') categories = request.GET.getlist('category') asset_types = request.GET.getlist('asset_type', []) search_q = request.GET.get('q', '').strip() qs = BuildingAsset.objects.select_related('building', 'location', 'parent_asset', 'category').all() if categories: qs = qs.filter(category_id__in=categories) if asset_types: qs = qs.filter(asset_type__in=asset_types) if statuses: qs = qs.filter(status__in=statuses) if search_q: qs = qs.filter( Q(code__icontains=search_q) | Q(name_fr__icontains=search_q) | Q(name_nl__icontains=search_q) ) features = [] from assets.views.core import get_element_type_label for a in qs: geometry = None if a.geojson: try: geometry = json.loads(a.geojson) except Exception: pass if not geometry and a.geom: geom_4326 = a.geom.clone() geom_4326.transform(4326) geometry = {"type": "Point", "coordinates": [geom_4326.x, geom_4326.y]} elif not geometry and a.lon is not None and a.lat is not None: geometry = {"type": "Point", "coordinates": [a.lon, a.lat]} elif not geometry and a.building and a.building.lon is not None and a.building.lat is not None: geometry = {"type": "Point", "coordinates": [a.building.lon, a.building.lat]} detail_url = reverse('assets:publicbuildings_assets_detail', kwargs={'asset_model': 'buildingasset', 'asset_id': a.pk}) features.append({ "type": "Feature", "id": a.id, "geometry": geometry, "properties": { "code": a.code, "name": a.get_name(), "asset_type": a.asset_type, "asset_type_display": a.get_asset_type_display(), "category_id": a.category_id, "category_name": a.category.get_name() if a.category else '', "status": a.status, "status_display": a.get_status_display(), "building_code": a.building.code if a.building else '', "building_name": a.building.get_name() if a.building else '', "parent_asset_id": a.parent_asset_id, "color": "#17a2b8", "display_color": "#17a2b8", "detail_url": detail_url, "element_type": get_element_type_label("buildingasset"), "thematic": "publicbuildings", "is_location": False, } }) return JsonResponse({"type": "FeatureCollection", "features": features}) @login_required def buildings_index(request): """ Vue principale (Index / Liste) des Localisations Bâtiments Publics. """ lang = get_language() buildings = PublicBuilding.objects.select_related('parent_location', 'manager').all() # Search filter search_query = request.GET.get('q', '').strip() if search_query: buildings = buildings.filter( Q(code__icontains=search_query) | Q(name_fr__icontains=search_query) | Q(name_nl__icontains=search_query) | Q(address__icontains=search_query) | Q(owner__icontains=search_query) ) # Usage filter usage_filter = request.GET.get('usage_type') if usage_filter: buildings = buildings.filter(usage_type=usage_filter) # Status filter status_filter = request.GET.get('building_status') if status_filter: buildings = buildings.filter(building_status=status_filter) # Location type filter location_type_filter = request.GET.get('location_type') if location_type_filter: buildings = buildings.filter(location_type=location_type_filter) # ERP filter erp_filter = request.GET.get('is_erp') if erp_filter in ['true', '1']: buildings = buildings.filter(is_erp=True) elif erp_filter in ['false', '0']: buildings = buildings.filter(is_erp=False) # DPE rating filter dpe_filter = request.GET.get('dpe_rating') if dpe_filter: buildings = buildings.filter(dpe_rating=dpe_filter) building_list = [] for b in buildings: b.can_edit = can_edit_asset(request.user, b) building_list.append(b) context = { 'buildings': building_list, 'search_query': search_query, 'usage_type_choices': USAGE_TYPE_CHOICES, 'building_status_choices': BUILDING_STATUS_CHOICES, 'location_type_choices': LOCATION_TYPE_CHOICES, 'dpe_rating_choices': DPE_RATING_CHOICES, 'selected_usage': usage_filter, 'selected_status': status_filter, 'selected_location_type': location_type_filter, 'selected_erp': erp_filter, 'selected_dpe': dpe_filter, 'active_tab': 'buildings', } return render(request, 'assets/buildings/publicbuilding_list.html', context) def build_asset_node(asset): return { 'asset': asset, 'children': [build_asset_node(c) for c in asset.child_assets.all()] } def build_location_node(loc): if loc.parent_location is None: loc_assets = loc.location_assets.filter(parent_asset__isnull=True) | loc.all_building_assets.filter(location__isnull=True, parent_asset__isnull=True) loc_assets = loc_assets.distinct().prefetch_related('child_assets') else: loc_assets = loc.location_assets.filter(parent_asset__isnull=True).prefetch_related('child_assets') return { 'loc': loc, 'children': [build_location_node(c) for c in loc.child_locations.all().prefetch_related('child_locations', 'location_assets')], 'assets': [build_asset_node(a) for a in loc_assets], } @login_required def building_detail(request, pk): """ Vue détaillée d'une Localisation Bâtiment Public avec son arborescence unifiée de localisations et d'assets physiques. """ from django.contrib.contenttypes.models import ContentType from interventions.models import Intervention from interventions.permissions import filter_viewable_interventions_for_user building = get_object_or_404(PublicBuilding.objects.select_related('parent_location', 'manager'), pk=pk) can_edit = can_edit_asset(request.user, building) regional_terms = get_regional_terms(getattr(building, '_regional_standard', 'BE')) # Arborescence unifiée (Localisation courante + sous-localisations + assets rattachés) location_tree = build_location_node(building) parent_location = building.parent_location documents = building.documents.all() content_type = ContentType.objects.get_for_model(PublicBuilding) # Récupération des interventions associées au bâtiment base_interventions = filter_viewable_interventions_for_user(request.user) interventions = base_interventions.filter( interventionasset__object_id=building.id, interventionasset__content_type=content_type ).distinct().order_by('-creation_time') # GeoJSON pour la carte de localisation building_geojson = None center_lon, center_lat = None, None if building.geom: try: geom_4326 = building.geom.clone() geom_4326.transform(4326) building_geojson = json.loads(geom_4326.json) centroid = geom_4326.centroid center_lon, center_lat = centroid.x, centroid.y except Exception: pass elif building.lon is not None and building.lat is not None: center_lon, center_lat = building.lon, building.lat context = { 'building': building, 'can_edit': can_edit, 'regional_terms': regional_terms, 'location_tree': location_tree, 'parent_location': parent_location, 'equipments': building.get_all_assets(), 'documents': documents, 'interventions': interventions, 'content_type': content_type, 'building_geojson_json': json.dumps(building_geojson) if building_geojson else 'null', 'center_lon': center_lon, 'center_lat': center_lat, } return render(request, 'assets/buildings/publicbuilding_detail.html', context) @login_required def publicbuilding_edit(request, pk): """ Vue d'édition d'une Localisation Bâtiment Public réservée aux utilisateurs autorisés. """ building = get_object_or_404(PublicBuilding, pk=pk) if not can_edit_asset(request.user, building): return HttpResponseForbidden(_("Vous n'avez pas la permission de modifier ce bâtiment.")) if request.method == 'POST': form = PublicBuildingForm(request.POST, instance=building) if form.is_valid(): saved_building = form.save() messages.success(request, _("La localisation '%s' a été mise à jour avec succès.") % saved_building.code) return redirect('assets:publicbuilding_detail', pk=saved_building.pk) else: form = PublicBuildingForm(instance=building) regional_terms = get_regional_terms(getattr(building, '_regional_standard', 'BE')) center_lon, center_lat = None, None if building.lon is not None and building.lat is not None: center_lon, center_lat = building.lon, building.lat elif building.geom: try: geom_4326 = building.geom.clone() geom_4326.transform(4326) centroid = geom_4326.centroid center_lon, center_lat = centroid.x, centroid.y except Exception: pass context = { 'building': building, 'form': form, 'regional_terms': regional_terms, 'center_lon': center_lon, 'center_lat': center_lat, } return render(request, 'assets/buildings/publicbuilding_form.html', context) @login_required def publicbuildings_assets_detail(request, asset_model, asset_id): """ Vue générique pour le détail d'un asset/équipement physique d'un bâtiment public. """ from django.contrib.contenttypes.models import ContentType from assets.views.core import get_asset_edit_context from interventions.models import Intervention from interventions.permissions import filter_viewable_interventions_for_user content_type = get_object_or_404(ContentType, app_label='assets', model=asset_model.lower()) model_class = content_type.model_class() asset = get_object_or_404(model_class, pk=asset_id) base_interventions = filter_viewable_interventions_for_user(request.user) interventions = base_interventions.filter( interventionasset__object_id=asset.id, interventionasset__content_type=content_type ).distinct().order_by('-creation_time') context = { 'asset': asset, 'content_type': content_type, 'interventions': interventions, } context.update(get_asset_edit_context(request, asset, content_type)) return render(request, "assets/detail_generic.html", context)