loko/streetup/assets/views/outdoor.py

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)