349 lines
13 KiB
Python
349 lines
13 KiB
Python
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)
|