territories-dashboard-lib 0.1.33b1__py3-none-any.whl → 1.1.1.dev7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. territories_dashboard_lib/geo_lib/admin.py +3 -1
  2. territories_dashboard_lib/geo_lib/migrations/0002_geoelement_linked_to_indicator.py +18 -0
  3. territories_dashboard_lib/geo_lib/migrations/0003_geofeature_color_column_geofeature_size_column.py +23 -0
  4. territories_dashboard_lib/geo_lib/models.py +17 -0
  5. territories_dashboard_lib/geo_lib/payloads.py +7 -21
  6. territories_dashboard_lib/geo_lib/views.py +58 -53
  7. territories_dashboard_lib/indicators_lib/enums.py +61 -37
  8. territories_dashboard_lib/indicators_lib/methodo_pdf.py +6 -1
  9. territories_dashboard_lib/indicators_lib/migrations/0004_alter_indicator_min_mesh.py +18 -0
  10. territories_dashboard_lib/indicators_lib/migrations/0005_auto_20251203_1621.py +124 -0
  11. territories_dashboard_lib/indicators_lib/models.py +7 -4
  12. territories_dashboard_lib/indicators_lib/payloads.py +14 -1
  13. territories_dashboard_lib/indicators_lib/query/commons.py +90 -104
  14. territories_dashboard_lib/indicators_lib/query/comparison.py +8 -3
  15. territories_dashboard_lib/indicators_lib/query/details.py +8 -13
  16. territories_dashboard_lib/indicators_lib/query/histogram.py +6 -1
  17. territories_dashboard_lib/indicators_lib/query/indicator_card.py +12 -7
  18. territories_dashboard_lib/indicators_lib/query/top_10.py +13 -12
  19. territories_dashboard_lib/indicators_lib/query/utils.py +9 -0
  20. territories_dashboard_lib/indicators_lib/table.py +15 -12
  21. territories_dashboard_lib/indicators_lib/views.py +49 -59
  22. territories_dashboard_lib/superset_lib/logic.py +24 -25
  23. territories_dashboard_lib/superset_lib/migrations/0002_alter_filter_mesh.py +18 -0
  24. territories_dashboard_lib/tracking_lib/enums.py +1 -0
  25. territories_dashboard_lib/tracking_lib/migrations/0005_alter_page_cmp_territory_mesh_alter_page_submesh_and_more.py +28 -0
  26. territories_dashboard_lib/tracking_lib/migrations/0006_alter_event_name.py +18 -0
  27. territories_dashboard_lib/tracking_lib/payloads.py +4 -2
  28. territories_dashboard_lib/tracking_lib/views.py +7 -6
  29. territories_dashboard_lib/website_lib/conf.py +28 -0
  30. territories_dashboard_lib/website_lib/context_processors.py +5 -6
  31. territories_dashboard_lib/website_lib/migrations/0005_mainconf_meshes.py +20 -0
  32. territories_dashboard_lib/website_lib/models.py +12 -0
  33. territories_dashboard_lib/website_lib/params.py +34 -22
  34. territories_dashboard_lib/website_lib/serializers.py +1 -0
  35. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/anchor.mjs +45 -0
  36. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/page.mjs +5 -9
  37. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/histogram.mjs +4 -1
  38. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/page.mjs +2 -7
  39. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/proportions.mjs +3 -2
  40. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/top10.mjs +4 -2
  41. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/utils.mjs +14 -5
  42. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/dom.mjs +0 -15
  43. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/enums.mjs +13 -10
  44. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/side_panel.mjs +1 -15
  45. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/page.mjs +5 -9
  46. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/indicatorMap.bundle.js +1 -1
  47. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/sankeyGraph.bundle.js +1 -1
  48. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/vendors.bundle.js +1 -1
  49. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/base.css +12 -0
  50. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/header.html +1 -1
  51. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/page.html +4 -3
  52. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/anchor.html +14 -0
  53. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/geo_params.html +3 -3
  54. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/indicator-card.html +14 -8
  55. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/select_territory.html +32 -0
  56. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_geo.html +8 -35
  57. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/histogram.html +1 -0
  58. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/no-data.html +1 -0
  59. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/proportions.html +1 -0
  60. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/top10.html +1 -0
  61. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/map.css +3 -2
  62. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.css +8 -0
  63. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.html +9 -8
  64. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/methodo/methodo.js +28 -0
  65. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/methodo/page.html +40 -0
  66. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/page.html +4 -3
  67. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/sitemap/page.html +9 -0
  68. territories_dashboard_lib/website_lib/templatetags/other_filters.py +6 -3
  69. territories_dashboard_lib/website_lib/views.py +100 -0
  70. {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/METADATA +3 -3
  71. {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/RECORD +74 -62
  72. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.css +0 -29
  73. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.html +0 -45
  74. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.js +0 -19
  75. {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/WHEEL +0 -0
  76. {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/licenses/licence.md +0 -0
  77. {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ from territories_dashboard_lib.commons.decorators import use_payload
9
9
  from territories_dashboard_lib.tracking_lib.enums import EventType, GraphType
10
10
  from territories_dashboard_lib.tracking_lib.logic import track_event
11
11
 
12
- from .enums import MESH_TITLES
12
+ from .enums import MESHES_SHORT_TITLES
13
13
  from .export import export_to_csv
14
14
  from .format import format_data, format_indicator_value
15
15
  from .models import Indicator
@@ -19,9 +19,11 @@ from .payloads import (
19
19
  FlowsPayload,
20
20
  IndicatorTablePayload,
21
21
  OptionalComparisonQueryPayload,
22
+ SubMeshOnlyPayload,
22
23
  SubMeshPayload,
23
24
  )
24
25
  from .query.commons import (
26
+ get_mesh_column_name,
25
27
  get_sub_territories,
26
28
  get_territory_name,
27
29
  get_values_for_territory,
@@ -62,6 +64,11 @@ def download_indicator_methodo_view(request, name):
62
64
  @cache_control(max_age=3600)
63
65
  @use_payload(SubMeshPayload)
64
66
  def indicator_statistics_view(request, name, payload):
67
+ if payload.submesh == payload.territory.mesh:
68
+ return HttpResponse(
69
+ "les statiques ne sont pas disponibles si la maille est du même niveau que le territoire.",
70
+ status=400,
71
+ )
65
72
  indicator = get_object_or_404(Indicator, name=name)
66
73
  filters = get_filters(request, indicator)
67
74
  query = get_geography_statistics_values_for_indicator(
@@ -193,7 +200,7 @@ def indicator_proportions_export_view(request, name, payload):
193
200
 
194
201
 
195
202
  @cache_control(max_age=3600)
196
- @use_payload(SubMeshPayload)
203
+ @use_payload(SubMeshOnlyPayload)
197
204
  def indicator_histogram_view(request, name, payload):
198
205
  indicator = get_object_or_404(Indicator, name=name)
199
206
  filters = get_filters(request, indicator)
@@ -207,7 +214,7 @@ def indicator_histogram_view(request, name, payload):
207
214
 
208
215
 
209
216
  @cache_control(max_age=3600)
210
- @use_payload(SubMeshPayload)
217
+ @use_payload(SubMeshOnlyPayload)
211
218
  def indicator_histogram_export_view(request, name, payload):
212
219
  indicator = get_object_or_404(Indicator, name=name)
213
220
  filters = get_filters(request, indicator)
@@ -237,7 +244,7 @@ def indicator_histogram_export_view(request, name, payload):
237
244
 
238
245
 
239
246
  @cache_control(max_age=3600)
240
- @use_payload(SubMeshPayload)
247
+ @use_payload(SubMeshOnlyPayload)
241
248
  def indicator_top_10_view(request, name, payload):
242
249
  indicator = get_object_or_404(Indicator, name=name)
243
250
  filters = get_filters(request, indicator)
@@ -251,7 +258,7 @@ def indicator_top_10_view(request, name, payload):
251
258
 
252
259
 
253
260
  @require_GET
254
- @use_payload(SubMeshPayload)
261
+ @use_payload(SubMeshOnlyPayload)
255
262
  def indicator_top_10_export_view(request, name, payload):
256
263
  indicator = get_object_or_404(Indicator, name=name)
257
264
  filters = get_filters(request, indicator)
@@ -274,28 +281,10 @@ def flows_view(request, payload):
274
281
  last_year_query = f"SELECT DISTINCT annee FROM {flows_table} ORDER BY annee DESC"
275
282
  last_year = run_custom_query(last_year_query)[0]["annee"]
276
283
 
277
- mapped_geo_level = (
278
- "DEPCOM" if payload.territory.mesh == "com" else payload.territory.mesh.upper()
279
- )
280
- mapped_mesh_level = (
281
- "DEPCOM" if payload.submesh == "com" else payload.submesh.upper()
284
+ territories = get_sub_territories(
285
+ submesh=payload.submesh, territory=payload.territory, with_center=True
282
286
  )
283
-
284
- codes = ", ".join(f"'{code.strip()}'" for code in payload.territory.id.split(","))
285
-
286
- # Geo query
287
- geo_query = f"""
288
- SELECT DISTINCT
289
- arbo."{mapped_mesh_level}" as territory_id,
290
- arbo."NOM_{mapped_mesh_level}" as territory_name,
291
- ST_ASGEOJSON(ST_CENTROID(contours.geometry)) as center
292
- FROM arborescence_geo AS arbo
293
- JOIN contours_simplified_{payload.submesh} as contours
294
- ON arbo."{mapped_mesh_level}" = contours.code
295
- WHERE arbo."{mapped_geo_level}" IN ({codes})
296
- """
297
- territories = run_custom_query(geo_query)
298
- territories_ids = ", ".join(f"'{t['territory_id'].strip()}'" for t in territories)
287
+ territories_ids = ", ".join(f"'{t['code'].strip()}'" for t in territories)
299
288
 
300
289
  dimension_value = (
301
290
  f"{payload.dimension} as dimension"
@@ -303,25 +292,28 @@ def flows_view(request, payload):
303
292
  else "'all' as dimension"
304
293
  )
305
294
 
295
+ mesh_col = get_mesh_column_name(payload.submesh)
306
296
  # Values query
307
297
  values_query = f"""
308
298
  SELECT
309
- code_{payload.submesh}_1 as territory_1_id,
310
- code_{payload.submesh}_2 as territory_2_id,
299
+ {mesh_col}_1 as territory_1_id,
300
+ {mesh_col}_2 as territory_2_id,
311
301
  CAST(valeur AS int) as value,
312
302
  {dimension_value}
313
303
  FROM {flows_table} flows
314
304
  WHERE annee = {last_year}
315
305
  AND (
316
- code_{payload.submesh}_1 IN ({territories_ids})
317
- OR code_{payload.submesh}_2 IN ({territories_ids})
306
+ {mesh_col}_1 IN ({territories_ids})
307
+ OR {mesh_col}_2 IN ({territories_ids})
318
308
  )
309
+ AND {mesh_col}_1 IS NOT NULL
310
+ AND {mesh_col}_2 IS NOT NULL
319
311
  """
320
312
  row_values = run_custom_query(values_query)
321
313
  territories_dict = {
322
- t["territory_id"]: {
323
- "name": t["territory_name"],
324
- "code": t["territory_id"],
314
+ t["code"]: {
315
+ "name": t["name"],
316
+ "code": t["code"],
325
317
  "center": json.loads(t["center"]),
326
318
  }
327
319
  for t in territories
@@ -333,26 +325,16 @@ def flows_view(request, payload):
333
325
  external_territories_ids.add(row["territory_1_id"])
334
326
  if row["territory_2_id"] not in territories_dict:
335
327
  external_territories_ids.add(row["territory_2_id"])
336
- external_territories_ids
337
328
 
338
329
  external_territories = []
339
330
  if external_territories_ids:
340
- codes = ", ".join([f"'{tid}'" for tid in external_territories_ids])
341
- geo_query = f"""
342
- SELECT DISTINCT
343
- arbo."{mapped_mesh_level}" as territory_id,
344
- arbo."NOM_{mapped_mesh_level}" as territory_name,
345
- ST_ASGEOJSON(ST_CENTROID(contours.geometry)) as center
346
- FROM arborescence_geo AS arbo
347
- JOIN contours_simplified_{payload.submesh} as contours
348
- ON arbo."{mapped_mesh_level}" = contours.code
349
- WHERE arbo."{mapped_mesh_level}" IN ({codes})
350
- """
351
- external_territories = run_custom_query(geo_query)
331
+ external_territories = get_sub_territories(
332
+ submesh=payload.submesh, codes=external_territories_ids, with_center=True
333
+ )
352
334
  for t in external_territories:
353
- territories_dict[t["territory_id"]] = {
354
- "name": f"{t['territory_name']} (externe)",
355
- "code": t["territory_id"],
335
+ territories_dict[t["code"]] = {
336
+ "name": f"{t['name']} (externe)",
337
+ "code": t["code"],
356
338
  "center": json.loads(t["center"]),
357
339
  }
358
340
 
@@ -367,8 +349,12 @@ def flows_view(request, payload):
367
349
  def comparison_histogram_view(request, name, payload):
368
350
  indicator = get_object_or_404(Indicator, name=name)
369
351
  filters = get_filters(request, indicator)
370
- territories = get_sub_territories(payload.territory, payload.submesh)
371
- cmp_territories = get_sub_territories(payload.cmp_territory, payload.submesh)
352
+ territories = get_sub_territories(
353
+ submesh=payload.submesh, territory=payload.territory
354
+ )
355
+ cmp_territories = get_sub_territories(
356
+ submesh=payload.submesh, territory=payload.cmp_territory
357
+ )
372
358
  values, cmp_values, buckets = get_comparison_values_and_buckets(
373
359
  indicator, payload.submesh, territories, cmp_territories, filters
374
360
  )
@@ -386,8 +372,12 @@ def comparison_histogram_view(request, name, payload):
386
372
  def comparison_histogram_export_view(request, name, payload):
387
373
  indicator = get_object_or_404(Indicator, name=name)
388
374
  filters = get_filters(request, indicator)
389
- territories = get_sub_territories(payload.territory, payload.submesh)
390
- cmp_territories = get_sub_territories(payload.cmp_territory, payload.submesh)
375
+ territories = get_sub_territories(
376
+ submesh=payload.submesh, territory=payload.territory
377
+ )
378
+ cmp_territories = get_sub_territories(
379
+ submesh=payload.submesh, territory=payload.cmp_territory
380
+ )
391
381
  values, cmp_values, buckets = get_comparison_values_and_buckets(
392
382
  indicator, payload.submesh, territories, cmp_territories, filters
393
383
  )
@@ -399,12 +389,12 @@ def comparison_histogram_export_view(request, name, payload):
399
389
  row["Décile"] = (
400
390
  f"{format_indicator_value(bucket[0])} - {format_indicator_value(bucket[1])}"
401
391
  )
402
- row[f"{territory_name} - Nombre de {MESH_TITLES[payload.submesh]}s"] = len(
403
- values[index + 1]
404
- )
405
- row[f"{cmp_territory_name} - Nombre de {MESH_TITLES[payload.submesh]}s"] = len(
406
- cmp_values[index + 1]
392
+ row[f"{territory_name} - Nombre de {MESHES_SHORT_TITLES[payload.submesh]}s"] = (
393
+ len(values[index + 1])
407
394
  )
395
+ row[
396
+ f"{cmp_territory_name} - Nombre de {MESHES_SHORT_TITLES[payload.submesh]}s"
397
+ ] = len(cmp_values[index + 1])
408
398
  row[f"{territory_name} - échantillon de dix territoires"] = " | ".join(
409
399
  values[index + 1][:10]
410
400
  )
@@ -418,7 +408,7 @@ def comparison_histogram_export_view(request, name, payload):
418
408
  def get_label(props, indicator, key):
419
409
  labels = {
420
410
  "annee": "Année",
421
- "lieu": "Lieu",
411
+ "name": "Lieu",
422
412
  "territory_1": "Origine",
423
413
  "territory_2": "Destination",
424
414
  "valeur": "flux" if props.flows else indicator.unite,
@@ -9,46 +9,45 @@ def get_territory_meshes(territory_id: str, territory_mesh: MeshLevel):
9
9
  current_dep = None
10
10
  current_epci = None
11
11
  current_com = None
12
- if territory_mesh != MeshLevel.National:
13
- mapped_mesh = "DEPCOM" if territory_mesh == "com" else territory_mesh.upper()
12
+ if territory_mesh != MeshLevel.fr:
14
13
  query = f"""
15
14
  SELECT
16
- "NOM_REG" as reg_name,
17
- "NOM_DEP" as dep_name,
18
- "NOM_EPCI" as epci_name,
19
- "NOM_DEPCOM" as com_name
15
+ name_reg,
16
+ name_dep,
17
+ name_epci,
18
+ name_com
20
19
  FROM arborescence_geo
21
- WHERE "{mapped_mesh}" = '{territory_id}'
20
+ WHERE code_{territory_mesh} = '{territory_id}'
22
21
  LIMIT 1
23
22
  """
24
23
  row = run_custom_query(query)[0]
25
24
  if territory_mesh in [
26
- MeshLevel.Region,
27
- MeshLevel.Department,
28
- MeshLevel.Epci,
29
- MeshLevel.Town,
25
+ MeshLevel.reg,
26
+ MeshLevel.dep,
27
+ MeshLevel.epci,
28
+ MeshLevel.com,
30
29
  ]:
31
- current_reg = row["reg_name"]
30
+ current_reg = row["name_reg"]
32
31
  if territory_mesh in [
33
- MeshLevel.Department,
34
- MeshLevel.Epci,
35
- MeshLevel.Town,
32
+ MeshLevel.dep,
33
+ MeshLevel.epci,
34
+ MeshLevel.com,
36
35
  ]:
37
- current_dep = row["dep_name"]
36
+ current_dep = row["name_dep"]
38
37
  if territory_mesh in [
39
- MeshLevel.Epci,
40
- MeshLevel.Town,
38
+ MeshLevel.epci,
39
+ MeshLevel.com,
41
40
  ]:
42
- current_epci = row["epci_name"]
41
+ current_epci = row["name_epci"]
43
42
  if territory_mesh in [
44
- MeshLevel.Town,
43
+ MeshLevel.com,
45
44
  ]:
46
- current_com = row["com_name"]
45
+ current_com = row["name_com"]
47
46
  territory_meshes = {
48
- MeshLevel.Region: current_reg,
49
- MeshLevel.Department: current_dep,
50
- MeshLevel.Epci: current_epci,
51
- MeshLevel.Town: current_com,
47
+ MeshLevel.reg: current_reg,
48
+ MeshLevel.dep: current_dep,
49
+ MeshLevel.epci: current_epci,
50
+ MeshLevel.com: current_com,
52
51
  }
53
52
  return territory_meshes
54
53
 
@@ -0,0 +1,18 @@
1
+ # Generated by Django 5.2.8 on 2025-12-03 13:30
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('superset_lib', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='filter',
15
+ name='mesh',
16
+ field=models.TextField(blank=True, choices=[('fr', 'Fr'), ('reg', 'Reg'), ('dep', 'Dep'), ('epci', 'Epci'), ('com', 'Com'), ('aom', 'Aom')], help_text="Maille du territoire sur lequel s'effectue le filtre.", null=True),
17
+ ),
18
+ ]
@@ -5,6 +5,7 @@ TRACKING_COOKIE_NAME = "omnibus"
5
5
 
6
6
  class EventType(models.TextChoices):
7
7
  download = "download"
8
+ view_indicator_card = "vue-resume-indicateur"
8
9
 
9
10
 
10
11
  class GraphType(models.TextChoices):
@@ -0,0 +1,28 @@
1
+ # Generated by Django 5.2.8 on 2025-12-03 13:30
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('tracking_lib', '0004_cookieinfo_ip_address'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='page',
15
+ name='cmp_territory_mesh',
16
+ field=models.TextField(blank=True, choices=[('fr', 'Fr'), ('reg', 'Reg'), ('dep', 'Dep'), ('epci', 'Epci'), ('com', 'Com'), ('aom', 'Aom')], help_text="Maille du territoire de comparaison, null s'il ne s'agit pas de la page comparaison.", null=True),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='page',
20
+ name='submesh',
21
+ field=models.TextField(blank=True, choices=[('fr', 'Fr'), ('reg', 'Reg'), ('dep', 'Dep'), ('epci', 'Epci'), ('com', 'Com'), ('aom', 'Aom')], help_text="Maille d'analyse sélectionnée.", null=True),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name='page',
25
+ name='territory_mesh',
26
+ field=models.TextField(blank=True, choices=[('fr', 'Fr'), ('reg', 'Reg'), ('dep', 'Dep'), ('epci', 'Epci'), ('com', 'Com'), ('aom', 'Aom')], help_text='Maille du territoire principal sélectionné.', null=True),
27
+ ),
28
+ ]
@@ -0,0 +1,18 @@
1
+ # Generated by Django 5.2.9 on 2026-01-06 16:48
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('tracking_lib', '0005_alter_page_cmp_territory_mesh_alter_page_submesh_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='event',
15
+ name='name',
16
+ field=models.TextField(choices=[('download', 'Download'), ('vue-resume-indicateur', 'View Indicator Card')]),
17
+ ),
18
+ ]
@@ -1,3 +1,5 @@
1
+ from typing import Optional
2
+
1
3
  from pydantic import BaseModel
2
4
 
3
5
  from territories_dashboard_lib.tracking_lib.enums import EventType
@@ -6,5 +8,5 @@ from territories_dashboard_lib.tracking_lib.enums import EventType
6
8
  class EventPayload(BaseModel):
7
9
  indicator: str
8
10
  event: EventType
9
- objet: str
10
- type: str
11
+ objet: Optional[str] = None
12
+ type: Optional[str] = None
@@ -26,18 +26,19 @@ def track_event_view(request):
26
26
  return JsonResponse({"error": e.errors()}, status=422)
27
27
  if (
28
28
  Event.objects.filter(created_at__gte=timezone.now() - timedelta(days=1)).count()
29
- > 1000
29
+ > 100_000
30
30
  ):
31
31
  return HttpResponse(status=429)
32
32
  response = HttpResponse()
33
+ data = {"indicator": payload.indicator}
34
+ if payload.objet:
35
+ data["objet"] = payload.objet
36
+ if payload.type:
37
+ data["type"] = payload.type
33
38
  track_event(
34
39
  request=request,
35
40
  response=response,
36
41
  event_name=payload.event,
37
- data={
38
- "indicator": payload.indicator,
39
- "objet": payload.objet,
40
- "type": payload.type,
41
- },
42
+ data=data,
42
43
  )
43
44
  return HttpResponse()
@@ -0,0 +1,28 @@
1
+ from territories_dashboard_lib.indicators_lib.enums import (
2
+ ALL_MESHES_ABSOLUTE,
3
+ MESHES_ORDERED_FOR_PRESENTATION,
4
+ )
5
+ from territories_dashboard_lib.website_lib.models import MainConf
6
+
7
+
8
+ class MissingMainConf(Exception):
9
+ def __init__(self):
10
+ super().__init__()
11
+ self.message = "Configuration principale du site (MainConf) manquante, veuillez la créer via le backoffice ou le shell."
12
+
13
+
14
+ def get_meshes_for_current_project():
15
+ main_conf = get_main_conf()
16
+ return [m for m in ALL_MESHES_ABSOLUTE if m in main_conf.meshes]
17
+
18
+
19
+ def get_ordered_meshes_for_current_project():
20
+ main_conf = get_main_conf()
21
+ return [m for m in MESHES_ORDERED_FOR_PRESENTATION if m in main_conf.meshes]
22
+
23
+
24
+ def get_main_conf():
25
+ main_conf = MainConf.objects.first()
26
+ if main_conf is None:
27
+ raise MissingMainConf
28
+ return main_conf
@@ -2,20 +2,19 @@ from django.conf import settings
2
2
 
3
3
  from territories_dashboard_lib.superset_lib.models import Dashboard
4
4
  from territories_dashboard_lib.superset_lib.serializers import serialize_dashboard
5
- from territories_dashboard_lib.website_lib.models import (
6
- MainConf,
7
- NoticeBanner,
8
- )
5
+ from territories_dashboard_lib.website_lib.conf import get_main_conf
6
+ from territories_dashboard_lib.website_lib.models import NoticeBanner
9
7
 
10
8
 
11
9
  def default(request):
12
- main_conf = MainConf.objects.first()
10
+ main_conf = get_main_conf()
13
11
  notice = NoticeBanner.objects.first()
12
+ view_name = request.resolver_match.view_name if request.resolver_match else None
14
13
  context = {
15
14
  "ENABLE_SUPERSET": settings.ENABLE_SUPERSET,
16
15
  "ANALYTICS_ID": settings.ANALYTICS_ID,
17
16
  "ENVIRONMENT": settings.ENVIRONMENT,
18
- "view_name": request.resolver_match.view_name,
17
+ "view_name": view_name,
19
18
  "absolute_uri": request.build_absolute_uri(),
20
19
  "main_conf": main_conf,
21
20
  "notice": notice,
@@ -0,0 +1,20 @@
1
+ # Generated by Django 5.2.8 on 2025-12-03 13:42
2
+
3
+ import django.contrib.postgres.fields
4
+ import territories_dashboard_lib.website_lib.models
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('website_lib', '0004_mainconf_description_mainconf_social_image_url'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='mainconf',
17
+ name='meshes',
18
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(choices=[('fr', 'Fr'), ('reg', 'Reg'), ('dep', 'Dep'), ('epci', 'Epci'), ('com', 'Com'), ('aom', 'Aom')]), default=territories_dashboard_lib.website_lib.models.get_default_meshes, help_text="Liste des mailles territoriales activées sur la plateforme, la liste des mailles disponible est : ['fr', 'reg', 'dep', 'epci', 'com', 'aom'].<br/>L'ajout de nouvelles mailles demande une modification de la base de données des indicateurs ainsi que du code.", size=None, verbose_name='Mailles'),
19
+ ),
20
+ ]
@@ -1,7 +1,9 @@
1
+ from django.contrib.postgres.fields import ArrayField
1
2
  from django.db import models
2
3
  from martor.models import MartorField
3
4
 
4
5
  from territories_dashboard_lib.commons.models import CommonModel
6
+ from territories_dashboard_lib.indicators_lib.enums import STANDARD_MESHES, MeshLevel
5
7
  from territories_dashboard_lib.website_lib.navigation import (
6
8
  parse_footer_navigation,
7
9
  parse_header_navigation,
@@ -12,6 +14,10 @@ from territories_dashboard_lib.website_lib.static_content import (
12
14
  )
13
15
 
14
16
 
17
+ def get_default_meshes():
18
+ return STANDARD_MESHES
19
+
20
+
15
21
  class MainConf(CommonModel):
16
22
  title = models.TextField(
17
23
  verbose_name="Titre principal du site", default="Tableau de bord"
@@ -74,6 +80,12 @@ class MainConf(CommonModel):
74
80
  verbose_name="Image de description",
75
81
  help_text="Lien d'une image (idéalement 1200x630px). Elle sera utilisée lors du partage sur les réseaux sociaux.",
76
82
  )
83
+ meshes = ArrayField(
84
+ models.TextField(choices=MeshLevel),
85
+ default=get_default_meshes,
86
+ verbose_name="Mailles",
87
+ help_text=f"Liste des mailles territoriales activées sur la plateforme, la liste des mailles disponible est : {[m.value for m in MeshLevel]}.<br/>L'ajout de nouvelles mailles demande une modification de la base de données des indicateurs ainsi que du code.",
88
+ )
77
89
 
78
90
  @property
79
91
  def entity_breaklines(self):
@@ -2,23 +2,29 @@ from functools import wraps
2
2
 
3
3
  from territories_dashboard_lib.indicators_lib.enums import (
4
4
  FRANCE_GEOLEVEL_TITLES,
5
- MESH_TITLES,
5
+ MESHES_LONG_TITLES,
6
+ MESHES_SHORT_TITLES,
7
+ STANDARD_MESHES,
6
8
  FranceGeoLevel,
7
9
  MeshLevel,
8
- get_all_meshes,
9
10
  get_allow_same_mesh,
11
+ order_meshes_for_presentation,
10
12
  )
11
13
  from territories_dashboard_lib.indicators_lib.query.utils import run_custom_query
14
+ from territories_dashboard_lib.website_lib.conf import (
15
+ get_meshes_for_current_project,
16
+ get_ordered_meshes_for_current_project,
17
+ )
12
18
 
13
19
  TERRITORY_DEFAULT = {
14
20
  "id": FranceGeoLevel.METRO,
15
- "mesh": MeshLevel.National,
21
+ "mesh": MeshLevel.fr,
16
22
  "name": FRANCE_GEOLEVEL_TITLES[FranceGeoLevel.METRO],
17
23
  }
18
24
 
19
25
  CMP_TERRITORY_DEFAULT = {
20
26
  "id": FranceGeoLevel.All,
21
- "mesh": MeshLevel.National,
27
+ "mesh": MeshLevel.fr,
22
28
  "name": FRANCE_GEOLEVEL_TITLES[FranceGeoLevel.All],
23
29
  }
24
30
 
@@ -32,9 +38,6 @@ class BadParam(Exception):
32
38
  pass
33
39
 
34
40
 
35
- # TODO coverage
36
- # appeler la page détail d'un indicateur (page html, pas api) avec plus de combinaisons de paramètres GET
37
- # tester les valeurs du context du template
38
41
  class ParamsHandler:
39
42
  def __init__(self, request):
40
43
  self.request = request
@@ -47,6 +50,7 @@ class ParamsHandler:
47
50
  self.mesh = None
48
51
  self.meshes = []
49
52
  self.comparison = request.resolver_match.view_name == "website:comparison"
53
+ self.all_meshes = get_meshes_for_current_project()
50
54
 
51
55
  ######################## Territory
52
56
 
@@ -66,12 +70,12 @@ class ParamsHandler:
66
70
  return parts[0], parts[1]
67
71
 
68
72
  def get_territory_name(self, territory_id, territory_mesh):
69
- if territory_mesh == MeshLevel.National:
73
+ # TODO use common -> get_territory_name, need Territory
74
+ if territory_mesh == MeshLevel.fr:
70
75
  return FRANCE_GEOLEVEL_TITLES[territory_id]
71
- mesh = "DEPCOM" if territory_mesh == "com" else territory_mesh.upper()
72
76
  query = f"""
73
- SELECT DISTINCT "NOM_{mesh}" AS name FROM arborescence_geo
74
- WHERE "{mesh}" = '{territory_id}'
77
+ SELECT DISTINCT name FROM arbo_{territory_mesh}
78
+ WHERE code = '{territory_id}'
75
79
  """
76
80
  try:
77
81
  results = run_custom_query(query)
@@ -144,29 +148,34 @@ class ParamsHandler:
144
148
  ######################## Mesh
145
149
 
146
150
  def get_max_territory_mesh(self):
147
- meshes = get_all_meshes()
148
151
  if self.comparison is False:
149
152
  return self.territory_mesh
150
- if meshes.index(self.cmp_territory_mesh) > meshes.index(self.territory_mesh):
153
+ if self.all_meshes.index(self.cmp_territory_mesh) > self.all_meshes.index(
154
+ self.territory_mesh
155
+ ):
151
156
  return self.cmp_territory_mesh
152
157
  return self.territory_mesh
153
158
 
154
159
  def is_not_valid_mesh(self, mesh):
155
160
  if mesh is None:
156
161
  return True
157
- meshes = get_all_meshes()
158
162
  max_territory_mesh = self.get_max_territory_mesh()
159
163
  allow_same_mesh = get_allow_same_mesh()
160
164
  is_not_valid = (
161
- meshes.index(max_territory_mesh) > meshes.index(mesh)
165
+ self.all_meshes.index(max_territory_mesh) > self.all_meshes.index(mesh)
162
166
  if allow_same_mesh
163
- else meshes.index(max_territory_mesh) >= meshes.index(mesh)
167
+ else self.all_meshes.index(max_territory_mesh)
168
+ >= self.all_meshes.index(mesh)
164
169
  )
165
170
  return is_not_valid
166
171
 
167
172
  def get_default_mesh(self):
168
173
  max_territory_mesh = self.get_max_territory_mesh()
169
- meshes = get_all_meshes()
174
+ meshes = (
175
+ STANDARD_MESHES
176
+ if max_territory_mesh in STANDARD_MESHES
177
+ else self.all_meshes
178
+ )
170
179
  if meshes.index(max_territory_mesh) == len(meshes) - 1:
171
180
  default_mesh = meshes[-1]
172
181
  else:
@@ -201,13 +210,15 @@ class ParamsHandler:
201
210
  max_territory_mesh = self.get_max_territory_mesh()
202
211
  meshes = [
203
212
  m
204
- for m in get_all_meshes()
205
- if not (m == MeshLevel.Town and max_territory_mesh == MeshLevel.National)
213
+ for m in self.all_meshes
214
+ if not (m == MeshLevel.com and max_territory_mesh == MeshLevel.fr)
206
215
  ]
207
216
  min_mesh_index = meshes.index(max_territory_mesh)
208
217
  if get_allow_same_mesh() is False:
209
218
  min_mesh_index += 1
210
- self.meshes = meshes[min(min_mesh_index, len(meshes) - 1) :]
219
+ self.meshes = order_meshes_for_presentation(
220
+ meshes[min(min_mesh_index, len(meshes) - 1) :]
221
+ )
211
222
 
212
223
  ######################## Commons
213
224
 
@@ -258,8 +269,9 @@ class ParamsHandler:
258
269
  "cmp_territory_name": self.cmp_territory_name,
259
270
  "mesh": self.mesh,
260
271
  "meshes": self.meshes,
261
- "meshes_titles": MESH_TITLES,
262
- "all_meshes": get_all_meshes(),
272
+ "meshes_short_titles": MESHES_SHORT_TITLES,
273
+ "meshes_long_titles": MESHES_LONG_TITLES,
274
+ "ordered_meshes": get_ordered_meshes_for_current_project(),
263
275
  "url_params": "&".join(url_params),
264
276
  }
265
277