diff --git a/streetup/assets/static/assets/inspection_map.js b/streetup/assets/static/assets/inspection_map.js index 3b9fb1a..9a796c4 100644 --- a/streetup/assets/static/assets/inspection_map.js +++ b/streetup/assets/static/assets/inspection_map.js @@ -292,7 +292,7 @@ const iconDisp = document.getElementById('thematic-icon-display'); const nameDisp = document.getElementById('thematic-name-display'); - if (iconDisp) iconDisp.className = `bi ${selectedIcon} text-primary`; + if (iconDisp) iconDisp.className = `bi ${selectedIcon} text-primary me-2`; if (nameDisp) nameDisp.textContent = selectedThematicName; const categorySelect = document.getElementById('filter-category'); diff --git a/streetup/assets/templates/assets/inspections_list.html b/streetup/assets/templates/assets/inspections_list.html index ac88198..261bca0 100644 --- a/streetup/assets/templates/assets/inspections_list.html +++ b/streetup/assets/templates/assets/inspections_list.html @@ -12,11 +12,13 @@

{% translate "Suivi et historique des contrôles et vérifications sur le terrain" %}

+ {% if can_inspect %}
{% translate "Nouvelle inspection (Carte GPS)" %}
+ {% endif %} diff --git a/streetup/assets/templates/assets/quick_inspection_map.html b/streetup/assets/templates/assets/quick_inspection_map.html index 3ef8f1b..feeae04 100644 --- a/streetup/assets/templates/assets/quick_inspection_map.html +++ b/streetup/assets/templates/assets/quick_inspection_map.html @@ -143,14 +143,14 @@
diff --git a/streetup/assets/tests_inspections_views.py b/streetup/assets/tests_inspections_views.py index 9952553..06dd0ca 100644 --- a/streetup/assets/tests_inspections_views.py +++ b/streetup/assets/tests_inspections_views.py @@ -91,3 +91,33 @@ class InspectionViewsTestCase(TestCase): interv = Intervention.objects.filter(code=data['created_intervention_code']).first() self.assertIsNotNone(interv) self.assertEqual(interv.status, 'in_preparation') + + def test_quick_inspection_map_permissions_can_inspect(self): + from common.models import UserConfig, UserThematics, Thematic + t_parking = Thematic.objects.get(code='parking') + t_other, _ = Thematic.objects.get_or_create(code='publiclighting', defaults={'name_fr': 'Éclairage public'}) + + user_config, _ = UserConfig.objects.get_or_create(user=self.user) + UserThematics.objects.create(user_config=user_config, thematic=t_parking, can_inspect=True) + UserThematics.objects.create(user_config=user_config, thematic=t_other, can_inspect=False) + + response = self.client.get(reverse('assets:quick_inspection_map')) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'parking') + self.assertNotContains(response, 'publiclighting') + + def test_no_inspectable_thematic_hides_button_and_returns_403(self): + from common.models import UserConfig, UserThematics, Thematic + t_parking = Thematic.objects.get(code='parking') + + user_config, _ = UserConfig.objects.get_or_create(user=self.user) + UserThematics.objects.create(user_config=user_config, thematic=t_parking, can_inspect=False) + + # 1. Page liste des inspections -> Bouton 'Nouvelle inspection' masqué + resp_list = self.client.get(reverse('assets:inspections_list')) + self.assertEqual(resp_list.status_code, 200) + self.assertNotContains(resp_list, reverse('assets:quick_inspection_map')) + + # 2. Tentative d'accès direct à quick_inspection_map -> HTTP 403 Forbidden + resp_map = self.client.get(reverse('assets:quick_inspection_map')) + self.assertEqual(resp_map.status_code, 403) diff --git a/streetup/assets/views/inspections.py b/streetup/assets/views/inspections.py index 82fa8ee..eb569f9 100644 --- a/streetup/assets/views/inspections.py +++ b/streetup/assets/views/inspections.py @@ -15,6 +15,15 @@ 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. + """ + if hasattr(user, 'config'): + return user.config.get_inspectable_thematics().order_by('name_fr') + return Thematic.objects.all().order_by('name_fr') + + @login_required def inspections_list(request): """ @@ -54,7 +63,8 @@ def inspections_list(request): Q(inspector__username__icontains=search_q) ) - thematics = Thematic.objects.all().order_by('name_fr') + thematics = get_user_inspectable_thematics(request.user) + can_inspect = thematics.exists() is_mobile = request.path.startswith('/mobile/') base_template = "mobile/mobile_base.html" if is_mobile else "base.html" @@ -70,6 +80,7 @@ def inspections_list(request): 'selected_nature': nature_filter, 'search_q': search_q, 'thematics': thematics, + 'can_inspect': can_inspect, 'result_choices': INSPECTION_RESULT_CHOICES, 'nature_choices': INSPECTION_NATURE_CHOICES, } @@ -81,8 +92,11 @@ def quick_inspection_map(request): """ Vue cartographique d'inspection rapide optimisée mobile & GPS. """ - thematics = Thematic.objects.all().order_by('name_fr') - categories = AssetCategory.objects.select_related('thematic').all().order_by('name_fr') + thematics = get_user_inspectable_thematics(request.user) + if 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') is_mobile = request.path.startswith('/mobile/') base_template = "mobile/mobile_base.html" if is_mobile else "base.html" diff --git a/streetup/common/admin.py b/streetup/common/admin.py index 795a082..6ec5b2e 100644 --- a/streetup/common/admin.py +++ b/streetup/common/admin.py @@ -489,7 +489,7 @@ class UserThematicsInline(admin.TabularInline): model = UserThematics extra = 1 fields = [ - 'thematic', 'can_view_assets', 'can_edit_assets', 'can_validate_assets', + 'thematic', 'can_view_assets', 'can_edit_assets', 'can_validate_assets', 'can_inspect', 'can_view_interventions', 'can_edit_interventions', 'can_edit_locations', 'can_process_observations', 'can_view_projects', 'can_edit_projects', @@ -584,7 +584,7 @@ class UserAssetTypeAccessInline(admin.TabularInline): @admin.register(UserThematics) class UserThematicsAdmin(admin.ModelAdmin): - list_display = ('user_config', 'thematic', 'can_view_interventions', 'can_edit_interventions', 'can_view_assets', 'can_edit_assets', 'can_validate_assets', 'can_process_observations') + list_display = ('user_config', 'thematic', 'can_view_interventions', 'can_edit_interventions', 'can_view_assets', 'can_edit_assets', 'can_validate_assets', 'can_inspect', 'can_process_observations') list_filter = ('thematic',) search_fields = ('user_config__user__username', 'thematic__name_fr', 'thematic__name_nl') raw_id_fields = ['user_config'] diff --git a/streetup/common/migrations/0006_userthematics_can_inspect.py b/streetup/common/migrations/0006_userthematics_can_inspect.py new file mode 100644 index 0000000..a47f3c9 --- /dev/null +++ b/streetup/common/migrations/0006_userthematics_can_inspect.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.7 on 2026-08-07 14:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0005_userconfig_regional_standard'), + ] + + operations = [ + migrations.AddField( + model_name='userthematics', + name='can_inspect', + field=models.BooleanField(default=True, verbose_name='Peut réaliser des inspections'), + ), + ] diff --git a/streetup/common/migrations/0007_alter_userthematics_can_inspect.py b/streetup/common/migrations/0007_alter_userthematics_can_inspect.py new file mode 100644 index 0000000..3046a2a --- /dev/null +++ b/streetup/common/migrations/0007_alter_userthematics_can_inspect.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.7 on 2026-08-07 14:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0006_userthematics_can_inspect'), + ] + + operations = [ + migrations.AlterField( + model_name='userthematics', + name='can_inspect', + field=models.BooleanField(default=False, verbose_name='Peut réaliser des inspections'), + ), + ] diff --git a/streetup/common/models.py b/streetup/common/models.py index a2e336d..9fed840 100644 --- a/streetup/common/models.py +++ b/streetup/common/models.py @@ -447,6 +447,30 @@ class UserConfig(models.Model): return True return self.userwarehouses.filter(can_administer=True).exists() + 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 + if self.user.is_superuser or self.has_role('admin'): + return Thematic.objects.all() + + if not self.userthematics.exists(): + return Thematic.objects.all() + + return Thematic.objects.filter( + userthematics__user_config=self, + userthematics__can_inspect=True + ).distinct() + + def can_inspect_thematic(self, thematic): + """Vérifie si l'utilisateur peut réaliser des inspections pour une thématique donnée.""" + if self.user.is_superuser or self.has_role('admin'): + return True + + if not self.userthematics.exists(): + return True + + return self.userthematics.filter(thematic=thematic, can_inspect=True).exists() + class UserThematics(models.Model): @@ -462,6 +486,7 @@ class UserThematics(models.Model): can_view_projects = models.BooleanField(default=False, verbose_name="Peut voir les projets") # Peut voir les projets de la thématique can_edit_projects = models.BooleanField(default=False, verbose_name="Peut éditer les projets") # Peut créer/modifier les projets de la thématique can_validate_assets = models.BooleanField(default=False, verbose_name="Peut valider les assets") + can_inspect = models.BooleanField(default=False, verbose_name="Peut réaliser des inspections") class Meta: unique_together = ("user_config", "thematic") # Empêche les doublons diff --git a/streetup/mobile/templates/mobile/mobile_index.html b/streetup/mobile/templates/mobile/mobile_index.html index 0bb1cd3..344330b 100644 --- a/streetup/mobile/templates/mobile/mobile_index.html +++ b/streetup/mobile/templates/mobile/mobile_index.html @@ -20,10 +20,12 @@ {% endif %} + {% if can_inspect %} {% translate "Nouvelle inspection" %} + {% endif %} {% if can_add_structures_repair %} diff --git a/streetup/mobile/views.py b/streetup/mobile/views.py index 3d5428f..e022ccb 100644 --- a/streetup/mobile/views.py +++ b/streetup/mobile/views.py @@ -110,6 +110,8 @@ def index(request): inspection_configs.append(config) break + can_inspect = user_config.get_inspectable_thematics().exists() + return render(request, "mobile/mobile_index.html", { 'can_edit_green_surfaces': can_edit_green_surfaces, 'can_access_stock': can_access_stock, @@ -117,6 +119,7 @@ def index(request): 'can_add_intervention_mobile': can_add_intervention_mobile, 'is_inspector': is_inspector, 'inspection_configs': inspection_configs, + 'can_inspect': can_inspect, }) @login_not_required