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 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 assets.models.outdoor import ( OutdoorLocation, OutdoorAsset, OutdoorLocationDocument, SITE_TYPE_CHOICES, OUTDOOR_ASSET_TYPE_CHOICES, NORM_CHOICES ) from assets.permissions import can_edit_asset def outdoor_locations_geojson(request): """ Vue GeoJSON renvoyant la collection des localisations d'espaces publics d'extérieur 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) site_types = data.get('site_type') or [] if isinstance(site_types, str): site_types = [site_types] is_main_loc = data.get('is_main_location') pmr = data.get('pmr_accessible') water = data.get('has_water_point') power = data.get('has_power_supply') fenced = data.get('is_fenced') 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: site_types = request.GET.getlist('site_type', []) is_main_loc = request.GET.get('is_main_location') pmr = request.GET.get('pmr_accessible') water = request.GET.get('has_water_point') power = request.GET.get('has_power_supply') fenced = request.GET.get('is_fenced') filter_geom = None search_q = request.GET.get('q', '').strip() qs = OutdoorLocation.objects.all() if site_types: qs = qs.filter(site_type__in=site_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 pmr in ['true', '1', True]: qs = qs.filter(pmr_accessible=True) if water in ['true', '1', True]: qs = qs.filter(has_water_point=True) if power in ['true', '1', True]: qs = qs.filter(has_power_supply=True) if fenced in ['true', '1', True]: qs = qs.filter(is_fenced=True) 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 loc in qs: geometry = None if loc.geojson: try: geometry = json.loads(loc.geojson) except Exception: pass if not geometry and loc.geom: geom_4326 = loc.geom.clone() geom_4326.transform(4326) centroid = geom_4326.centroid geometry = {"type": "Point", "coordinates": [centroid.x, centroid.y]} elif not geometry and loc.lon is not None and loc.lat is not None: geometry = {"type": "Point", "coordinates": [loc.lon, loc.lat]} detail_url = reverse('assets:outdoor_location_detail', kwargs={'pk': loc.pk}) features.append({ "type": "Feature", "id": loc.id, "geometry": geometry, "properties": { "code": loc.code, "name": loc.get_name(), "site_type": loc.site_type, "site_type_display": loc.get_site_type_display(), "status": "active", "owner": loc.owner or '', "pmr_accessible": loc.pmr_accessible, "has_water_point": loc.has_water_point, "has_power_supply": loc.has_power_supply, "is_fenced": loc.is_fenced, "lon": loc.lon, "lat": loc.lat, "color": "#198754", "display_color": "#198754", "asset_type": "outdoorlocation", "detail_url": detail_url, "element_type": get_element_type_label("outdoorlocation"), "thematic": "outdoorfacilities", "is_location": True, } }) return JsonResponse({"type": "FeatureCollection", "features": features}) def outdoor_assets_geojson(request): """ Vue GeoJSON renvoyant la collection des assets d'extérieur (structures de jeu, fitness, skate modules...) 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] norms = data.get('norm') or [] if isinstance(norms, str): norms = [norms] 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', []) norms = request.GET.getlist('norm', []) search_q = request.GET.get('q', '').strip() qs = OutdoorAsset.objects.select_related('site', '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 norms: qs = qs.filter(norm__in=norms) 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.location and a.location.lon is not None and a.location.lat is not None: geometry = {"type": "Point", "coordinates": [a.location.lon, a.location.lat]} elif not geometry and a.site and a.site.lon is not None and a.site.lat is not None: geometry = {"type": "Point", "coordinates": [a.site.lon, a.site.lat]} detail_url = reverse('assets:outdoor_assets_detail', kwargs={'asset_model': 'outdoorasset', '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(), "obsolescence": a.obsolescence or '', "obsolescence_display": a.get_obsolescence_display() if hasattr(a, 'get_obsolescence_display') else '', "norm": a.norm or '', "norm_display": a.get_norm_display() if a.norm else '', "site_code": a.site.code if a.site else '', "site_name": a.site.get_name() if a.site else '', "location_code": a.location.code if a.location else '', "location_name": a.location.get_name() if a.location else '', "lon": a.lon, "lat": a.lat, "color": "#198754", "display_color": "#198754", "detail_url": detail_url, "element_type": get_element_type_label("outdoorasset"), "thematic": "outdoorfacilities", "is_location": False, } }) return JsonResponse({"type": "FeatureCollection", "features": features}) def build_outdoor_asset_node(asset): return { 'asset': asset, 'children': [build_outdoor_asset_node(c) for c in asset.child_assets.all()] } def build_outdoor_location_node(loc): if loc.parent_location is None: loc_assets = loc.location_assets.filter(parent_asset__isnull=True) | loc.all_outdoor_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_outdoor_location_node(c) for c in loc.child_locations.all().prefetch_related('child_locations', 'location_assets')], 'assets': [build_outdoor_asset_node(a) for a in loc_assets], } @login_required def outdoor_location_detail(request, pk): """ Vue détaillée d'une Localisation Espace Public Extérieur 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 location = get_object_or_404(OutdoorLocation.objects.select_related('parent_location', 'manager'), pk=pk) can_edit = can_edit_asset(request.user, location) location_tree = build_outdoor_location_node(location) parent_location = location.parent_location documents = location.documents.all() content_type = ContentType.objects.get_for_model(OutdoorLocation) base_interventions = filter_viewable_interventions_for_user(request.user) interventions = base_interventions.filter( interventionasset__object_id=location.id, interventionasset__content_type=content_type ).distinct().order_by('-creation_time') location_geojson = None center_lon, center_lat = None, None if location.geom: try: geom_4326 = location.geom.clone() geom_4326.transform(4326) location_geojson = json.loads(geom_4326.json) centroid = geom_4326.centroid center_lon, center_lat = centroid.x, centroid.y except Exception: pass elif location.lon is not None and location.lat is not None: center_lon, center_lat = location.lon, location.lat context = { 'location': location, 'can_edit': can_edit, 'location_tree': location_tree, 'parent_location': parent_location, 'equipments': location.get_all_assets(), 'documents': documents, 'interventions': interventions, 'content_type': content_type, 'location_geojson_json': json.dumps(location_geojson) if location_geojson else 'null', 'center_lon': center_lon, 'center_lat': center_lat, } return render(request, 'assets/outdoor/outdoorlocation_detail.html', context) @login_required def outdoor_assets_detail(request, asset_model, asset_id): 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)