diff --git a/streetup/assets/templates/assets/inspections_list.html b/streetup/assets/templates/assets/inspections_list.html index 0f6aef6..dbb47b0 100644 --- a/streetup/assets/templates/assets/inspections_list.html +++ b/streetup/assets/templates/assets/inspections_list.html @@ -44,7 +44,7 @@
- +
diff --git a/streetup/assets/tests_inspections_views.py b/streetup/assets/tests_inspections_views.py index da18b44..9642207 100644 --- a/streetup/assets/tests_inspections_views.py +++ b/streetup/assets/tests_inspections_views.py @@ -18,6 +18,8 @@ class InspectionViewsTestCase(TestCase): first_name='Jean', last_name='Dupont' ) + from common.models import UserConfig + UserConfig.objects.get_or_create(user=self.user) self.client = Client() self.client.login(username='test_inspector', password='password123') @@ -167,3 +169,41 @@ class InspectionViewsTestCase(TestCase): self.assertEqual(data['type'], 'FeatureCollection') self.assertEqual(data['features'][0]['properties']['inspections'][0]['id'], insp.id) self.assertEqual(data['features'][0]['properties']['inspections_count'], 1) + + def test_inspections_list_search_by_asset_code(self): + self.spot.code = "SPOT-TEST-SEARCH-99" + self.spot.save() + + insp = AssetInspection.objects.create( + asset_content_type=self.spot_ct, + asset_object_id=self.spot.id, + inspector=self.user, + result_status='compliant', + nature='visual' + ) + + response = self.client.get(reverse('assets:inspections_list') + '?tab=all&q=SEARCH-99') + self.assertEqual(response.status_code, 200) + self.assertIn(insp, response.context['inspections']) + + def test_inspections_list_filter_thematics_uses_can_view_assets(self): + from common.models import UserConfig, UserThematics, Thematic + t_parking = Thematic.objects.get(code='parking') + t_lighting, _ = Thematic.objects.get_or_create(code='publiclighting', defaults={'name_fr': 'Éclairage public'}) + + user_config, _ = UserConfig.objects.get_or_create(user=self.user) + # L'utilisateur a les droits de voir les assets parking mais pas d'inspecter, et pas de voir l'éclairage public + UserThematics.objects.create(user_config=user_config, thematic=t_parking, can_view_assets=True, can_inspect=False) + UserThematics.objects.create(user_config=user_config, thematic=t_lighting, can_view_assets=False, can_inspect=False) + + resp = self.client.get(reverse('assets:inspections_list')) + self.assertEqual(resp.status_code, 200) + self.assertIn(t_parking, resp.context['thematics']) + self.assertNotIn(t_lighting, resp.context['thematics']) + self.assertFalse(resp.context['can_inspect']) + + def test_user_without_config_returns_none(self): + from assets.views.inspections import get_user_viewable_thematics, get_user_inspectable_thematics + user_no_config = User.objects.create_user(username='no_config_user', password='pwd') + self.assertIsNone(get_user_viewable_thematics(user_no_config)) + self.assertIsNone(get_user_inspectable_thematics(user_no_config)) diff --git a/streetup/assets/views/inspections.py b/streetup/assets/views/inspections.py index 4d8428c..a02dbc8 100644 --- a/streetup/assets/views/inspections.py +++ b/streetup/assets/views/inspections.py @@ -18,10 +18,21 @@ from assets.models.inspection import ( def get_user_inspectable_thematics(user): """ Retourne les thématiques pour lesquelles l'utilisateur détient la permission can_inspect. + Si l'utilisateur n'a pas de UserConfig, retourne None. """ - if hasattr(user, 'config'): + if hasattr(user, 'config') and user.config: return user.config.get_inspectable_thematics().order_by('name_fr') - return Thematic.objects.all().order_by('name_fr') + return None + + +def get_user_viewable_thematics(user): + """ + Retourne les thématiques pour lesquelles l'utilisateur détient la permission de visualisation (can_view_assets). + Si l'utilisateur n'a pas de UserConfig, retourne None. + """ + if hasattr(user, 'config') and user.config: + return user.config.get_viewable_assets_thematics().order_by('name_fr') + return None @login_required @@ -56,15 +67,41 @@ def inspections_list(request): qs = qs.filter(nature=nature_filter) if search_q: - qs = qs.filter( + search_filter = ( Q(notes__icontains=search_q) | Q(inspector__first_name__icontains=search_q) | Q(inspector__last_name__icontains=search_q) | Q(inspector__username__icontains=search_q) ) - thematics = get_user_inspectable_thematics(request.user) - can_inspect = thematics.exists() + asset_q_conditions = Q() + for ct in ContentType.objects.filter(app_label='assets'): + ModelClass = ct.model_class() + if not ModelClass: + continue + + model_fields = [f.name for f in ModelClass._meta.get_fields()] + asset_search = Q() + if 'code' in model_fields: + asset_search |= Q(code__icontains=search_q) + if 'name' in model_fields: + asset_search |= Q(name__icontains=search_q) + if 'name_fr' in model_fields: + asset_search |= Q(name_fr__icontains=search_q) + + if asset_search: + matching_ids = list(ModelClass.objects.filter(asset_search).values_list('pk', flat=True)[:500]) + if matching_ids: + asset_q_conditions |= Q(asset_content_type=ct, asset_object_id__in=matching_ids) + + if asset_q_conditions: + search_filter |= asset_q_conditions + + qs = qs.filter(search_filter) + + thematics = get_user_viewable_thematics(request.user) + insp_thematics = get_user_inspectable_thematics(request.user) + can_inspect = bool(insp_thematics and insp_thematics.exists()) is_mobile = request.path.startswith('/mobile/') base_template = "mobile/mobile_base.html" if is_mobile else "base.html" @@ -93,7 +130,7 @@ def quick_inspection_map(request): Vue cartographique d'inspection rapide optimisée mobile & GPS. """ thematics = get_user_inspectable_thematics(request.user) - if not thematics.exists(): + if not thematics or not thematics.exists(): return HttpResponseForbidden(_("Vous ne disposez d'aucune thématique autorisée pour réaliser des inspections.")) categories = AssetCategory.objects.filter(thematic__in=thematics).select_related('thematic').order_by('name_fr') diff --git a/streetup/common/models.py b/streetup/common/models.py index 9fed840..2f082a2 100644 --- a/streetup/common/models.py +++ b/streetup/common/models.py @@ -447,6 +447,20 @@ class UserConfig(models.Model): return True return self.userwarehouses.filter(can_administer=True).exists() + def get_viewable_assets_thematics(self): + """Retourne le QuerySet des thématiques pour lesquelles l'utilisateur est autorisé à voir les assets.""" + from common.models import Thematic + if self.user.is_superuser or self.has_role('admin'): + return Thematic.objects.all() + + if not self.userthematics.exists(): + return None + + return Thematic.objects.filter( + userthematics__user_config=self, + userthematics__can_view_assets=True + ).distinct() + def get_inspectable_thematics(self): """Retourne le QuerySet des thématiques pour lesquelles l'utilisateur est autorisé à réaliser des inspections.""" from common.models import Thematic