feat: implement user preference persistence for assets index thematics and filters

This commit is contained in:
kdeterme 2026-07-28 15:08:12 +02:00
parent abf4470441
commit 9c822719b5
5 changed files with 57 additions and 3 deletions

View file

@ -586,7 +586,7 @@ function applyVisibleColumnsPreference(visibleColumns) {
* explicit thematic selection (GET params / locked_thematic_code). * explicit thematic selection (GET params / locked_thematic_code).
*/ */
function applyInitialPreferences() { function applyInitialPreferences() {
if (PREVENTIVE_DRAFT_ID || LOCKED_THEMATIC_CODE || SELECTED_THEMATICS.length > 0) return; if (PREVENTIVE_DRAFT_ID || LOCKED_THEMATIC_CODE || (typeof HAS_URL_THEMATICS !== 'undefined' && HAS_URL_THEMATICS)) return;
if (ASSETS_INDEX_PREFS && typeof ASSETS_INDEX_PREFS === 'object' && Object.keys(ASSETS_INDEX_PREFS).length > 0) { if (ASSETS_INDEX_PREFS && typeof ASSETS_INDEX_PREFS === 'object' && Object.keys(ASSETS_INDEX_PREFS).length > 0) {
const prefs = ASSETS_INDEX_PREFS; const prefs = ASSETS_INDEX_PREFS;

View file

@ -718,6 +718,7 @@
const THEMATIC_CATEGORIES = {{ thematic_categories_json|safe }}; const THEMATIC_CATEGORIES = {{ thematic_categories_json|safe }};
const ADD_MODAL_CONFIG = {{ add_modal_config_json|safe }}; const ADD_MODAL_CONFIG = {{ add_modal_config_json|safe }};
const SELECTED_THEMATICS = {{ selected_thematics|safe }}; const SELECTED_THEMATICS = {{ selected_thematics|safe }};
const HAS_URL_THEMATICS = {% if has_url_thematics %}true{% else %}false{% endif %};
const SELECTED_CATEGORIES = {{ selected_categories_json|safe }}; const SELECTED_CATEGORIES = {{ selected_categories_json|safe }};
const PREVENTIVE_DRAFT_ID = "{{ preventive_draft_id|default:'' }}"; const PREVENTIVE_DRAFT_ID = "{{ preventive_draft_id|default:'' }}";
const LOCKED_THEMATIC_CODE = "{{ locked_thematic_code|default:'' }}"; const LOCKED_THEMATIC_CODE = "{{ locked_thematic_code|default:'' }}";

View file

@ -938,10 +938,17 @@ def unified_assets_index(request):
selected_locations_ids = draft_data.get('locations', []) or [] selected_locations_ids = draft_data.get('locations', []) or []
# Thématiques présélectionnées (GET, intervention, thématique par défaut ou toutes les thématiques accessibles) # Thématiques présélectionnées (GET, intervention, thématique par défaut ou toutes les thématiques accessibles)
if 'thematic' in request.GET: has_url_thematics = 'thematic' in request.GET
if has_url_thematics:
selected_thematics = request.GET.getlist('thematic') selected_thematics = request.GET.getlist('thematic')
elif locked_thematic_code: elif locked_thematic_code:
selected_thematics = [locked_thematic_code] selected_thematics = [locked_thematic_code]
elif user_config and user_config.assets_index_preferences and user_config.assets_index_preferences.get('thematics'):
saved_thematics = user_config.assets_index_preferences.get('thematics')
accessible_codes = {t.code for t in accessible_thematics}
selected_thematics = [code for code in saved_thematics if code in accessible_codes]
if not selected_thematics and accessible_thematics:
selected_thematics = [t.code for t in accessible_thematics]
elif user_config and user_config.default_thematic and user_config.default_thematic in accessible_thematics: elif user_config and user_config.default_thematic and user_config.default_thematic in accessible_thematics:
selected_thematics = [user_config.default_thematic.code] selected_thematics = [user_config.default_thematic.code]
elif accessible_thematics: elif accessible_thematics:
@ -991,6 +998,7 @@ def unified_assets_index(request):
'asset_validation_status_choices': list(ASSET_VALIDATION_STATUS_CHOICES.items()), 'asset_validation_status_choices': list(ASSET_VALIDATION_STATUS_CHOICES.items()),
'add_modal_config_json': json.dumps(add_modal_config), 'add_modal_config_json': json.dumps(add_modal_config),
'selected_thematics': selected_thematics, 'selected_thematics': selected_thematics,
'has_url_thematics': has_url_thematics,
'selected_categories_json': json.dumps(selected_categories), 'selected_categories_json': json.dumps(selected_categories),
'preventive_draft_id': preventive_draft_id, 'preventive_draft_id': preventive_draft_id,
'locked_thematic_code': locked_thematic_code, 'locked_thematic_code': locked_thematic_code,

View file

@ -5,6 +5,8 @@ from django.urls import reverse
from django.conf import settings from django.conf import settings
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
import json
from common.models import UserConfig from common.models import UserConfig
@ -249,4 +251,41 @@ class HelpPanelTests(TestCase):
self.assertIn("Ajouter un contrôle", data["html"]) self.assertIn("Ajouter un contrôle", data["html"])
class AssetsIndexPreferencesTests(TestCase):
def setUp(self):
from common.models import Thematic
self.superuser = User.objects.create_superuser(username="superprefuser", password="password123")
Thematic.objects.create(code="sign", name_fr="Signalisation")
Thematic.objects.create(code="roads", name_fr="Voirie")
def test_save_and_restore_assets_index_preferences_for_superuser(self):
self.client.force_login(self.superuser)
save_url = reverse("common:save_assets_index_preferences")
payload = {
"thematics": ["sign", "roads"],
"categories": [1, 2],
"models": [],
"conditional_filters": {},
"visible_columns": ["code", "status"],
"type_filter": "assets",
"status_filters": ["installed"],
"validation_status_filters": ["validated"],
"include_no_geom": False,
}
response = self.client.post(save_url, json.dumps(payload), content_type="application/json")
self.assertEqual(response.status_code, 200)
self.assertTrue(response.json().get("success"))
# Verify unified_assets_index restores saved thematics and sets has_url_thematics to False
index_url = reverse("assets:index")
response = self.client.get(index_url)
self.assertEqual(response.status_code, 200)
self.assertFalse(response.context["has_url_thematics"])
self.assertEqual(response.context["selected_thematics"], ["sign", "roads"])
saved_prefs = json.loads(response.context["assets_index_prefs_json"])
self.assertEqual(saved_prefs["thematics"], ["sign", "roads"])
self.assertEqual(saved_prefs["validation_status_filters"], ["validated"])

View file

@ -432,6 +432,8 @@ def save_assets_index_preferences(request):
visible_columns = data.get('visible_columns') visible_columns = data.get('visible_columns')
type_filter = data.get('type_filter', 'all') type_filter = data.get('type_filter', 'all')
status_filters = data.get('status_filters', []) status_filters = data.get('status_filters', [])
validation_status_filters = data.get('validation_status_filters', [])
include_no_geom = data.get('include_no_geom', True)
map_center = data.get('map_center') map_center = data.get('map_center')
map_zoom = data.get('map_zoom') map_zoom = data.get('map_zoom')
@ -449,9 +451,11 @@ def save_assets_index_preferences(request):
return JsonResponse({'error': _('Format de filtre type invalide')}, status=400) return JsonResponse({'error': _('Format de filtre type invalide')}, status=400)
if not isinstance(status_filters, list): if not isinstance(status_filters, list):
return JsonResponse({'error': _('Format de filtres statut invalide')}, status=400) return JsonResponse({'error': _('Format de filtres statut invalide')}, status=400)
if not isinstance(validation_status_filters, list):
return JsonResponse({'error': _('Format de filtres statut de validation invalide')}, status=400)
try: try:
user_config = request.user.config user_config, _ = UserConfig.objects.get_or_create(user=request.user)
existing_prefs = user_config.assets_index_preferences or {} existing_prefs = user_config.assets_index_preferences or {}
existing_visible_columns = existing_prefs.get('visible_columns', []) if isinstance(existing_prefs, dict) else [] existing_visible_columns = existing_prefs.get('visible_columns', []) if isinstance(existing_prefs, dict) else []
if not isinstance(existing_visible_columns, list): if not isinstance(existing_visible_columns, list):
@ -465,6 +469,8 @@ def save_assets_index_preferences(request):
'visible_columns': visible_columns if visible_columns is not None else existing_visible_columns, 'visible_columns': visible_columns if visible_columns is not None else existing_visible_columns,
'type_filter': type_filter, 'type_filter': type_filter,
'status_filters': status_filters, 'status_filters': status_filters,
'validation_status_filters': validation_status_filters,
'include_no_geom': include_no_geom,
'map_center': map_center, 'map_center': map_center,
'map_zoom': map_zoom, 'map_zoom': map_zoom,
} }