refactor: replace ContentType lookup with robust filtering and validation across asset and intervention views
This commit is contained in:
parent
27056b61fa
commit
33ef94902b
17 changed files with 175 additions and 67 deletions
|
|
@ -688,10 +688,20 @@ def asset_edit_permission_required(view_func):
|
|||
return HttpResponseForbidden(_("Missing asset information"))
|
||||
|
||||
try:
|
||||
content_type = ContentType.objects.get(model=asset_model.lower())
|
||||
model_class = content_type.model_class()
|
||||
cts = ContentType.objects.filter(model=asset_model.lower())
|
||||
if not cts.exists():
|
||||
return HttpResponseForbidden(_("Invalid asset type"))
|
||||
cts_sorted = sorted(cts, key=lambda ct: (0 if ct.app_label == 'assets' else 1))
|
||||
model_class = None
|
||||
for ct in cts_sorted:
|
||||
mc = ct.model_class()
|
||||
if mc is not None:
|
||||
model_class = mc
|
||||
break
|
||||
if model_class is None:
|
||||
return HttpResponseForbidden(_("Invalid asset type"))
|
||||
asset = get_object_or_404(model_class, pk=asset_id)
|
||||
except ContentType.DoesNotExist:
|
||||
except Exception:
|
||||
return HttpResponseForbidden(_("Invalid asset type"))
|
||||
|
||||
if not can_edit_asset(request.user, asset):
|
||||
|
|
|
|||
|
|
@ -4029,6 +4029,77 @@ class ReplaceAssetStructureGeoAssetTest(TestCase):
|
|||
self.assertEqual(new_asset.name_fr, "Asset Original")
|
||||
|
||||
|
||||
class ExportAssetsTest(TestCase):
|
||||
def setUp(self):
|
||||
from django.contrib.auth import get_user_model
|
||||
from common.models import Thematic, UserConfig, UserThematics
|
||||
from assets.models import AssetCategory, Structure, StructureLocation, StructureAssetModel, StructureGeoAsset
|
||||
|
||||
self.user = get_user_model().objects.create_user(
|
||||
username="test_export_user",
|
||||
email="export_test@example.com",
|
||||
password="testpassword",
|
||||
is_staff=True,
|
||||
)
|
||||
self.user_config = UserConfig.objects.create(user=self.user)
|
||||
self.thematic = Thematic.objects.get_or_create(code="structures", defaults={"name_fr": "Structures"})[0]
|
||||
UserThematics.objects.create(
|
||||
user_config=self.user_config,
|
||||
thematic=self.thematic,
|
||||
can_view_assets=True,
|
||||
can_edit_assets=True,
|
||||
)
|
||||
|
||||
self.struct = Structure.objects.create(code="EXP01", name_fr="Pont Export")
|
||||
self.loc = StructureLocation.objects.create(code="EXP01A", name_fr="Loc Export", structure=self.struct)
|
||||
self.category = AssetCategory.objects.create(thematic=self.thematic, code="CAT_EXP", name_fr="Cat Export")
|
||||
self.model = StructureAssetModel.objects.create(code="MOD_EXP", name_fr="Mod Export", category=self.category)
|
||||
self.asset = StructureGeoAsset.objects.create(
|
||||
code="EXP01A-000001",
|
||||
name_fr="Asset Pour Export",
|
||||
structure=self.struct,
|
||||
location=self.loc,
|
||||
model=self.model,
|
||||
category=self.category,
|
||||
status="active"
|
||||
)
|
||||
|
||||
def test_export_assets_csv_success(self):
|
||||
from django.urls import reverse
|
||||
|
||||
self.client.force_login(self.user)
|
||||
url = reverse('assets:export_assets')
|
||||
ids_json = json.dumps([f"structuregeoasset:{self.asset.id}"])
|
||||
|
||||
response = self.client.post(url, {'ids': ids_json, 'format': 'csv'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-Type'], 'text/csv; charset=utf-8')
|
||||
content = response.content.decode('utf-8')
|
||||
self.assertIn("EXP01A-000001", content)
|
||||
self.assertIn("Asset Pour Export", content)
|
||||
|
||||
def test_export_assets_with_duplicate_content_types(self):
|
||||
from django.urls import reverse
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
# Create a second duplicate ContentType with another app_label to simulate MultipleObjectsReturned
|
||||
ContentType.objects.create(
|
||||
app_label='other_app',
|
||||
model='structuregeoasset'
|
||||
)
|
||||
|
||||
self.client.force_login(self.user)
|
||||
url = reverse('assets:export_assets')
|
||||
ids_json = json.dumps([f"structuregeoasset:{self.asset.id}"])
|
||||
|
||||
# This should not raise MultipleObjectsReturned and succeed
|
||||
response = self.client.post(url, {'ids': ids_json, 'format': 'csv'})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = response.content.decode('utf-8')
|
||||
self.assertIn("EXP01A-000001", content)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -725,8 +725,12 @@ def _generate_archive_asset_code(old_asset):
|
|||
if location and hasattr(location, 'get_direct_child_assets'):
|
||||
existing_codes = set()
|
||||
assets_dict = location.get_direct_child_assets()
|
||||
for key, queryset in assets_dict.items():
|
||||
for asset in queryset:
|
||||
if isinstance(assets_dict, dict):
|
||||
for key, queryset in assets_dict.items():
|
||||
for asset in queryset:
|
||||
existing_codes.add(asset.code)
|
||||
elif hasattr(assets_dict, '__iter__'):
|
||||
for asset in assets_dict:
|
||||
existing_codes.add(asset.code)
|
||||
|
||||
if new_code not in existing_codes:
|
||||
|
|
|
|||
|
|
@ -468,10 +468,11 @@ def clean_assets_geojson(request):
|
|||
|
||||
@check_thematic_access('clean')
|
||||
def clean_assets_detail(request, asset_model, asset_id):
|
||||
try:
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(_("Unknown model: %(asset_model)s") % {'asset_model': asset_model})
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(_("Unknown model: %(asset_model)s") % {'asset_model': asset_model})
|
||||
|
||||
asset = get_object_or_404(model_class, id=asset_id)
|
||||
|
|
|
|||
|
|
@ -520,11 +520,11 @@ def controlcenters_locations_detail(request, location_id):
|
|||
|
||||
@check_thematic_access('controlcenters')
|
||||
def controlcenters_assets_detail(request, asset_model, asset_id):
|
||||
try:
|
||||
# Récupérer le ContentType pour le modèle fourni
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(_("Modèle '%(model)s' inconnu.") % {"model": asset_model})
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(_("Modèle '%(model)s' inconnu.") % {"model": asset_model})
|
||||
|
||||
# Récupérer l’objet asset
|
||||
|
|
|
|||
|
|
@ -152,14 +152,22 @@ def export_assets(request):
|
|||
items: list[tuple[str, object]] = []
|
||||
for model_name, asset_ids in groups.items():
|
||||
try:
|
||||
ct = ContentType.objects.get(model=model_name)
|
||||
model_class = ct.model_class()
|
||||
cts = ContentType.objects.filter(model=model_name.lower())
|
||||
if not cts.exists():
|
||||
continue
|
||||
cts_sorted = sorted(cts, key=lambda ct: (0 if ct.app_label == 'assets' else 1))
|
||||
model_class = None
|
||||
for ct in cts_sorted:
|
||||
mc = ct.model_class()
|
||||
if mc is not None:
|
||||
model_class = mc
|
||||
break
|
||||
if model_class is None:
|
||||
continue
|
||||
for asset in model_class.objects.filter(pk__in=asset_ids):
|
||||
if can_view_asset(request.user, asset):
|
||||
items.append((model_name, asset))
|
||||
except ContentType.DoesNotExist:
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
timestamp = localtime(now()).strftime('%Y%m%d_%H%M%S')
|
||||
|
|
|
|||
|
|
@ -523,11 +523,11 @@ def its_locations_detail(request, location_id):
|
|||
|
||||
@check_thematic_access('its')
|
||||
def its_assets_detail(request, asset_model, asset_id):
|
||||
try:
|
||||
# Récupérer le ContentType pour le modèle fourni
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(f"Modèle '{asset_model}' inconnu.")
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(f"Modèle '{asset_model}' inconnu.")
|
||||
|
||||
# Récupérer l’objet asset
|
||||
|
|
|
|||
|
|
@ -215,13 +215,12 @@ def nature_assets(request):
|
|||
|
||||
@check_thematic_access('nature')
|
||||
def nature_assets_detail(request, asset_model, asset_id):
|
||||
|
||||
try:
|
||||
# Récupérer le ContentType pour le modèle fourni
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % (asset_model))
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
|
||||
# Récupérer l’objet asset
|
||||
asset = get_object_or_404(model_class, id=asset_id)
|
||||
|
|
|
|||
|
|
@ -257,12 +257,12 @@ def publiclighting_assets(request):
|
|||
|
||||
@check_thematic_access('publiclighting')
|
||||
def publiclighting_assets_detail(request, asset_model, asset_id):
|
||||
try:
|
||||
# Récupérer le ContentType pour le modèle fourni (ex: 'trafficlightpole')
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % (asset_model))
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
|
||||
# Récupérer l’objet asset
|
||||
asset = get_object_or_404(model_class, id=asset_id)
|
||||
|
|
|
|||
|
|
@ -520,12 +520,12 @@ def roads_streets_detail(request, street_id):
|
|||
|
||||
@check_thematic_access('roads')
|
||||
def roads_assets_detail(request, asset_model, asset_id):
|
||||
try:
|
||||
# Récupérer le ContentType pour le modèle fourni
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % (asset_model))
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
|
||||
# Récupérer l’objet asset
|
||||
asset = get_object_or_404(model_class, id=asset_id)
|
||||
|
|
|
|||
|
|
@ -1354,12 +1354,12 @@ def trafficlights_assets_geojson(request):
|
|||
|
||||
@check_thematic_access('trafficlights')
|
||||
def trafficlights_assets_detail(request, asset_model, asset_id):
|
||||
try:
|
||||
# Récupérer le ContentType pour le modèle fourni (ex: 'trafficlightpole')
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % (asset_model))
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
|
||||
# Récupérer l’objet asset
|
||||
asset = get_object_or_404(model_class, id=asset_id)
|
||||
|
|
|
|||
|
|
@ -527,10 +527,11 @@ def zirw_detail(request, location_id):
|
|||
|
||||
@check_thematic_access('water')
|
||||
def water_assets_detail(request, asset_model, asset_id):
|
||||
try:
|
||||
content_type = ContentType.objects.get(model=asset_model)
|
||||
model_class = content_type.model_class()
|
||||
except ContentType.DoesNotExist:
|
||||
content_type = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not content_type:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
model_class = content_type.model_class()
|
||||
if not model_class:
|
||||
raise Http404(_("Modèle %(asset_model)s inconnu.") % {'asset_model': asset_model})
|
||||
|
||||
asset = get_object_or_404(model_class, id=asset_id)
|
||||
|
|
|
|||
|
|
@ -518,9 +518,10 @@ def _get_content_type_ids_for_user(user, *, edit_only: bool) -> list[int]:
|
|||
model_names = _THEMATIC_MODEL_MAPPING.get(thematic_code, [])
|
||||
for model_name in model_names:
|
||||
try:
|
||||
ct = ContentType.objects.get(model=model_name)
|
||||
result.append(ct.id)
|
||||
except ContentType.DoesNotExist:
|
||||
ct = ContentType.objects.filter(app_label='assets', model=model_name.lower()).first() or ContentType.objects.filter(model=model_name.lower()).first()
|
||||
if ct:
|
||||
result.append(ct.id)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not hasattr(user, '_folder_cache'):
|
||||
|
|
|
|||
|
|
@ -1849,10 +1849,9 @@ def interventions_add(request, thematic_code):
|
|||
if equip_ct and equip_id:
|
||||
asset_pairs = [(int(equip_ct), int(equip_id))]
|
||||
elif equip_type and equip_id:
|
||||
# ATTENTION: .get(model=...) suppose unicité du model name à travers les apps
|
||||
# Préfère envoyer equip_ct depuis le front si possible.
|
||||
ct = ContentType.objects.get(model=equip_type.lower())
|
||||
asset_pairs = [(ct.id, int(equip_id))]
|
||||
ct = ContentType.objects.filter(app_label='assets', model=equip_type.lower()).first() or ContentType.objects.filter(model=equip_type.lower()).first()
|
||||
if ct:
|
||||
asset_pairs = [(ct.id, int(equip_id))]
|
||||
|
||||
first_asset_obj = None
|
||||
for ct_id, obj_id in asset_pairs:
|
||||
|
|
@ -3446,8 +3445,8 @@ def check_asset_templates_for_intervention(request, intervention_id):
|
|||
}, status=400)
|
||||
|
||||
# Récupérer l'asset
|
||||
ct = ContentType.objects.get(model=asset_type)
|
||||
model_class = ct.model_class()
|
||||
ct = ContentType.objects.filter(app_label='assets', model=asset_type.lower()).first() or ContentType.objects.filter(model=asset_type.lower()).first()
|
||||
model_class = ct.model_class() if ct else None
|
||||
if not model_class:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
|
|
@ -3700,8 +3699,8 @@ def create_operations_from_selected_template(request, intervention_id):
|
|||
}, status=400)
|
||||
|
||||
# Récupérer l'asset
|
||||
ct = ContentType.objects.get(model=asset_type)
|
||||
model_class = ct.model_class()
|
||||
ct = ContentType.objects.filter(app_label='assets', model=asset_type.lower()).first() or ContentType.objects.filter(model=asset_type.lower()).first()
|
||||
model_class = ct.model_class() if ct else None
|
||||
if not model_class:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
|
|
|
|||
|
|
@ -1282,8 +1282,12 @@ def ajax_get_replacement_models_by_category(request):
|
|||
|
||||
try:
|
||||
# Get the asset ContentType
|
||||
ct = ContentType.objects.get(model=asset_model)
|
||||
ct = ContentType.objects.filter(app_label='assets', model=asset_model.lower()).first() or ContentType.objects.filter(model=asset_model.lower()).first()
|
||||
if not ct:
|
||||
return JsonResponse({'error': f"Unknown model: {asset_model}"}, status=400)
|
||||
model_class = ct.model_class()
|
||||
if not model_class:
|
||||
return JsonResponse({'error': f"Unknown model: {asset_model}"}, status=400)
|
||||
|
||||
# Get the asset instance
|
||||
asset_obj = model_class.objects.get(pk=asset_id)
|
||||
|
|
|
|||
|
|
@ -3163,8 +3163,12 @@ def batch_fetch_assets_with_locations(selected_assets_array):
|
|||
|
||||
models_map = {}
|
||||
for model_name, ids in by_model.items():
|
||||
ct = ContentType.objects.get(model=model_name)
|
||||
ct = ContentType.objects.filter(app_label='assets', model=model_name.lower()).first() or ContentType.objects.filter(model=model_name.lower()).first()
|
||||
if not ct:
|
||||
continue
|
||||
Model = ct.model_class()
|
||||
if not Model:
|
||||
continue
|
||||
|
||||
# détecter quelles FKs de localisation existent réellement sur le modèle
|
||||
related_fields = []
|
||||
|
|
|
|||
|
|
@ -555,7 +555,9 @@ def intervention_add_preventive(request, thematic_code):
|
|||
asset_id = a['id']
|
||||
|
||||
# ContentType de l'asset
|
||||
ct_asset = ContentType.objects.get(model=model_name)
|
||||
ct_asset = ContentType.objects.filter(app_label='assets', model=model_name.lower()).first() or ContentType.objects.filter(model=model_name.lower()).first()
|
||||
if not ct_asset:
|
||||
continue
|
||||
|
||||
# Objet asset préchargé (avec ses FKs)
|
||||
asset_obj = assets_map.get(model_name, {}).get(asset_id)
|
||||
|
|
@ -671,7 +673,9 @@ def intervention_add_preventive(request, thematic_code):
|
|||
for a in selected_assets_array:
|
||||
model_name = a['asset_type']
|
||||
asset_id = a['id']
|
||||
ct_asset = ContentType.objects.get(model=model_name)
|
||||
ct_asset = ContentType.objects.filter(app_label='assets', model=model_name.lower()).first() or ContentType.objects.filter(model=model_name.lower()).first()
|
||||
if not ct_asset:
|
||||
continue
|
||||
|
||||
InterventionAsset.objects.get_or_create(
|
||||
intervention=intervention,
|
||||
|
|
@ -783,7 +787,9 @@ def intervention_add_preventive(request, thematic_code):
|
|||
for a in selected_assets_array:
|
||||
model_name = a['asset_type']
|
||||
asset_id = a['id']
|
||||
ct_asset = ContentType.objects.get(model=model_name)
|
||||
ct_asset = ContentType.objects.filter(app_label='assets', model=model_name.lower()).first() or ContentType.objects.filter(model=model_name.lower()).first()
|
||||
if not ct_asset:
|
||||
continue
|
||||
asset_obj = assets_map.get(model_name, {}).get(asset_id)
|
||||
|
||||
# Filtrer les templates applicables à cet asset
|
||||
|
|
|
|||
Loading…
Reference in a new issue