territories-dashboard-lib 0.1.39b2__py3-none-any.whl → 1.1.1.dev10__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.

Potentially problematic release.


This version of territories-dashboard-lib might be problematic. Click here for more details.

Files changed (66) hide show
  1. territories_dashboard_lib/geo_lib/admin.py +2 -0
  2. territories_dashboard_lib/geo_lib/migrations/0003_geofeature_color_column_geofeature_size_column.py +23 -0
  3. territories_dashboard_lib/geo_lib/models.py +12 -0
  4. territories_dashboard_lib/geo_lib/payloads.py +7 -21
  5. territories_dashboard_lib/geo_lib/views.py +58 -53
  6. territories_dashboard_lib/indicators_lib/enums.py +61 -37
  7. territories_dashboard_lib/indicators_lib/migrations/0004_alter_indicator_min_mesh.py +18 -0
  8. territories_dashboard_lib/indicators_lib/migrations/0005_auto_20251203_1621.py +2 -2
  9. territories_dashboard_lib/indicators_lib/models.py +9 -6
  10. territories_dashboard_lib/indicators_lib/payloads.py +14 -1
  11. territories_dashboard_lib/indicators_lib/query/commons.py +90 -104
  12. territories_dashboard_lib/indicators_lib/query/comparison.py +8 -3
  13. territories_dashboard_lib/indicators_lib/query/details.py +8 -13
  14. territories_dashboard_lib/indicators_lib/query/histogram.py +0 -1
  15. territories_dashboard_lib/indicators_lib/query/indicator_card.py +12 -7
  16. territories_dashboard_lib/indicators_lib/query/top_10.py +12 -12
  17. territories_dashboard_lib/indicators_lib/query/utils.py +9 -0
  18. territories_dashboard_lib/indicators_lib/table.py +15 -12
  19. territories_dashboard_lib/indicators_lib/views.py +49 -59
  20. territories_dashboard_lib/superset_lib/logic.py +24 -25
  21. territories_dashboard_lib/superset_lib/migrations/0002_alter_filter_mesh.py +18 -0
  22. territories_dashboard_lib/tracking_lib/enums.py +2 -0
  23. territories_dashboard_lib/tracking_lib/migrations/0005_alter_page_cmp_territory_mesh_alter_page_submesh_and_more.py +28 -0
  24. territories_dashboard_lib/tracking_lib/migrations/0006_alter_event_name.py +18 -0
  25. territories_dashboard_lib/tracking_lib/payloads.py +4 -2
  26. territories_dashboard_lib/tracking_lib/views.py +7 -6
  27. territories_dashboard_lib/website_lib/conf.py +14 -0
  28. territories_dashboard_lib/website_lib/context_processors.py +2 -1
  29. territories_dashboard_lib/website_lib/migrations/0005_mainconf_meshes.py +20 -0
  30. territories_dashboard_lib/website_lib/models.py +12 -0
  31. territories_dashboard_lib/website_lib/params.py +34 -22
  32. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/anchor.mjs +43 -0
  33. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/page.mjs +7 -9
  34. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/page.mjs +2 -7
  35. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/dom.mjs +0 -15
  36. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/enums.mjs +13 -10
  37. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/side_panel.mjs +1 -15
  38. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/page.mjs +7 -9
  39. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/track-visible-indicators.mjs +121 -0
  40. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/indicatorMap.bundle.js +2 -0
  41. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/sankeyGraph.bundle.js +2 -0
  42. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/vendors.bundle.js +2 -0
  43. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/base.css +12 -0
  44. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/page.html +4 -3
  45. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/anchor.html +14 -0
  46. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/geo_params.html +3 -3
  47. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/indicator-card.html +14 -8
  48. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/select_territory.html +32 -0
  49. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_geo.html +8 -35
  50. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/histogram.html +1 -1
  51. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.html +9 -8
  52. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/methodo/methodo.js +28 -0
  53. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/methodo/page.html +40 -0
  54. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/page.html +4 -5
  55. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/sitemap/page.html +9 -0
  56. territories_dashboard_lib/website_lib/templatetags/other_filters.py +6 -3
  57. territories_dashboard_lib/website_lib/views.py +100 -0
  58. {territories_dashboard_lib-0.1.39b2.dist-info → territories_dashboard_lib-1.1.1.dev10.dist-info}/METADATA +2 -2
  59. {territories_dashboard_lib-0.1.39b2.dist-info → territories_dashboard_lib-1.1.1.dev10.dist-info}/RECORD +62 -51
  60. territories_dashboard_lib/indicators_lib/migrations/0006_alter_theme_action_theme_alter_theme_objectif_theme.py +0 -23
  61. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.css +0 -29
  62. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.html +0 -45
  63. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.js +0 -19
  64. {territories_dashboard_lib-0.1.39b2.dist-info → territories_dashboard_lib-1.1.1.dev10.dist-info}/WHEEL +0 -0
  65. {territories_dashboard_lib-0.1.39b2.dist-info → territories_dashboard_lib-1.1.1.dev10.dist-info}/licenses/licence.md +0 -0
  66. {territories_dashboard_lib-0.1.39b2.dist-info → territories_dashboard_lib-1.1.1.dev10.dist-info}/top_level.txt +0 -0
@@ -30,6 +30,8 @@ class GeoFeatureForm(forms.ModelForm):
30
30
  "show_on_fr_level",
31
31
  "point_icon_svg",
32
32
  "svg_file",
33
+ "color_column",
34
+ "size_column",
33
35
  ]
34
36
 
35
37
  def clean(self):
@@ -0,0 +1,23 @@
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
+ ('geo_lib', '0002_geoelement_linked_to_indicator'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='geofeature',
15
+ name='color_column',
16
+ field=models.TextField(blank=True, help_text="Optionnel. Nom de la colonne qui définit une couleur par point. Les valeurs de la colonne doivent être sous le format #ffffff (hexadecimale). Si renseigné, le champ 'color' ne sera pas utilisé. Seulement pour les points.", null=True, verbose_name='Colonne de la couleur'),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='geofeature',
20
+ name='size_column',
21
+ field=models.TextField(blank=True, help_text="Optionnel. Nom de la colonne qui définit une taille par point. Les valeurs de la colonne doivent être 'sm' (small) ou 'md' (medium). Seulement pour les points.", null=True, verbose_name='Colonne de la taille'),
22
+ ),
23
+ ]
@@ -40,6 +40,18 @@ class GeoFeature(CommonModel):
40
40
  verbose_name="Afficher au niveau France entière",
41
41
  help_text="Décochez si la quantité de données est trop importante pour le niveau France entière.",
42
42
  )
43
+ color_column = models.TextField(
44
+ blank=True,
45
+ null=True,
46
+ verbose_name="Colonne de la couleur",
47
+ help_text="Optionnel. Nom de la colonne qui définit une couleur par point. Les valeurs de la colonne doivent être sous le format #ffffff (hexadecimale). Si renseigné, le champ 'color' ne sera pas utilisé. Seulement pour les points.",
48
+ )
49
+ size_column = models.TextField(
50
+ blank=True,
51
+ null=True,
52
+ verbose_name="Colonne de la taille",
53
+ help_text="Optionnel. Nom de la colonne qui définit une taille par point. Les valeurs de la colonne doivent être 'sm' (small) ou 'md' (medium). Seulement pour les points.",
54
+ )
43
55
 
44
56
  def __str__(self):
45
57
  return self.name
@@ -3,29 +3,18 @@ from typing import List, Optional
3
3
  from pydantic import BaseModel, field_validator
4
4
 
5
5
  from territories_dashboard_lib.commons.types import TerritoryCode
6
- from territories_dashboard_lib.indicators_lib.enums import (
7
- GeoLevel,
8
- MeshLevel,
9
- )
6
+ from territories_dashboard_lib.indicators_lib.enums import MeshLevel
7
+ from territories_dashboard_lib.indicators_lib.payloads import SubMeshPayload
10
8
 
11
9
 
12
- class GeoFeaturesParams(BaseModel):
13
- mesh: MeshLevel
14
- geo_level: GeoLevel
15
- main_territories: List[TerritoryCode]
10
+ class GeoFeaturesPayload(SubMeshPayload):
16
11
  last: Optional[int] = None
17
12
  limit: Optional[int] = 1000
18
13
  feature: int
19
14
 
20
- @field_validator("main_territories", mode="before")
21
- def split_main_territories(cls, v):
22
- if isinstance(v, str):
23
- return v.split(",")
24
- return v
25
-
26
15
 
27
16
  class MainTerritoryParams(BaseModel):
28
- geo_level: GeoLevel
17
+ geo_level: MeshLevel
29
18
  geo_id: List[TerritoryCode]
30
19
 
31
20
  @field_validator("geo_id", mode="before")
@@ -46,14 +35,11 @@ class TerritoriesParams(BaseModel):
46
35
  return v
47
36
 
48
37
 
49
- class TerritoryFeatureParams(BaseModel):
50
- mesh: MeshLevel
51
- geo_level: GeoLevel | None = None
52
- main_territories: List[TerritoryCode] | None = None
38
+ class TerritoryFeaturePayload(SubMeshPayload):
53
39
  codes: List[TerritoryCode] | None = None
54
40
 
55
- @field_validator("main_territories", "codes", mode="before")
56
- def split_main_territories(cls, v):
41
+ @field_validator("codes", mode="before")
42
+ def split_codes(cls, v):
57
43
  if isinstance(v, str):
58
44
  return v.split(",")
59
45
  return v
@@ -7,22 +7,26 @@ from django.http import HttpResponse, JsonResponse
7
7
  from django.shortcuts import get_object_or_404
8
8
  from django.views.decorators.cache import cache_control
9
9
  from django.views.decorators.http import require_GET
10
- from psycopg2.sql import SQL, Identifier, Literal
10
+ from psycopg2.sql import SQL
11
11
 
12
12
  from territories_dashboard_lib.geo_lib.payloads import (
13
- GeoFeaturesParams,
13
+ GeoFeaturesPayload,
14
14
  MainTerritoryParams,
15
15
  SearchTerritoriesParams,
16
16
  TerritoriesParams,
17
- TerritoryFeatureParams,
17
+ TerritoryFeaturePayload,
18
18
  )
19
19
  from territories_dashboard_lib.indicators_lib.enums import (
20
20
  FRANCE_GEOLEVEL_TITLES,
21
- MESH_DB,
22
21
  MeshLevel,
23
22
  )
24
- from territories_dashboard_lib.indicators_lib.query.commons import get_territories_ids
25
- from territories_dashboard_lib.indicators_lib.query.utils import run_custom_query
23
+ from territories_dashboard_lib.indicators_lib.query.commons import (
24
+ get_sub_territories,
25
+ )
26
+ from territories_dashboard_lib.indicators_lib.query.utils import (
27
+ format_sql_codes,
28
+ run_custom_query,
29
+ )
26
30
 
27
31
  from .enums import GeoFeatureType
28
32
  from .models import GeoFeature
@@ -42,14 +46,11 @@ class DateTimeEncoder(json.JSONEncoder):
42
46
  @require_GET
43
47
  @cache_control(max_age=3600)
44
48
  def geo_features_view(request):
45
- params = GeoFeaturesParams(**request.GET.dict())
49
+ payload = GeoFeaturesPayload(**request.GET.dict())
46
50
 
47
- geo_feature = get_object_or_404(GeoFeature, id=params.feature)
48
- geo_level = params.geo_level
49
- mesh = params.mesh
50
- main_territory_codes = params.main_territories
51
- last = params.last
52
- limit = params.limit
51
+ geo_feature = get_object_or_404(GeoFeature, id=payload.feature)
52
+ last = payload.last
53
+ limit = payload.limit
53
54
 
54
55
  last_year_query = (
55
56
  f"SELECT DISTINCT annee FROM {geo_feature.name} ORDER BY annee DESC"
@@ -57,10 +58,16 @@ def geo_features_view(request):
57
58
  years = run_custom_query(last_year_query)
58
59
  last_year = years[0].get("annee")
59
60
 
60
- territories_ids = get_territories_ids(main_territory_codes, geo_level, mesh)
61
-
62
- col_id_name = f"code_{mesh}"
61
+ territories = get_sub_territories(
62
+ submesh=payload.submesh, territory=payload.territory
63
+ )
64
+ territories_ids = [t["code"] for t in territories]
65
+ col_id_name = f"code_{payload.submesh}"
63
66
  columns = [el.name for el in geo_feature.items.all()]
67
+ if geo_feature.color_column:
68
+ columns.append(geo_feature.color_column)
69
+ if geo_feature.size_column:
70
+ columns.append(geo_feature.size_column)
64
71
 
65
72
  coma = ", " if columns else ""
66
73
  select_geo_code = (
@@ -73,8 +80,8 @@ def geo_features_view(request):
73
80
  where_clause = f"WHERE geo.{col_id_name} IN ('{"', '".join(territories_ids)}')"
74
81
  else:
75
82
  where_clause = (
76
- f"JOIN contours_simplified_{geo_level} contours ON ST_intersects(geo.geometry, contours.geometry)\n"
77
- f"WHERE contours.code IN ('{"', '".join(main_territory_codes)}')"
83
+ f"JOIN contours_simplified_{payload.territory.mesh} contours ON ST_intersects(geo.geometry, contours.geometry)\n"
84
+ f"WHERE contours.code IN {payload.territory.sql_codes}"
78
85
  )
79
86
  where_clause += f" AND annee = {last_year}"
80
87
 
@@ -91,16 +98,22 @@ def geo_features_view(request):
91
98
  results = run_custom_query(query)
92
99
 
93
100
  items = geo_feature.items.all()
94
- # Transform results
95
- data = [
96
- {
101
+ data = []
102
+ for r in results:
103
+ properties = {el.label: r.get(el.name) for el in items}
104
+ if geo_feature.color_column or geo_feature.size_column:
105
+ properties["display"] = {}
106
+ if geo_feature.color_column:
107
+ properties["display"]["color"] = r.get(geo_feature.color_column)
108
+ if geo_feature.size_column:
109
+ properties["display"]["size"] = r.get(geo_feature.size_column)
110
+ d = {
97
111
  "type": "Feature",
98
112
  "id": r.get("geo_code"),
99
- "properties": {el.label: r.get(el.name) for el in items},
113
+ "properties": properties,
100
114
  "geometry": json.loads(r.get("geojson")),
101
115
  }
102
- for r in results
103
- ]
116
+ data.append(d)
104
117
 
105
118
  last_queried_order_id = results[-1]["order_id"] if results else None
106
119
 
@@ -146,12 +159,10 @@ def precise_view(request):
146
159
  if not mesh_level:
147
160
  return JsonResponse({"error": "Missing 'mesh' parameter"}, status=400)
148
161
 
149
- mapped_mesh_level = "DEPCOM" if mesh_level == "com" else mesh_level.upper()
150
-
151
162
  query = f"""
152
- SELECT "{mapped_mesh_level}" AS id, ST_asgeojson(geometry) AS polygon
163
+ SELECT code AS id, ST_asgeojson(geometry) AS polygon
153
164
  FROM contours_geo_ign_{mesh_level}
154
- WHERE "{mapped_mesh_level}" IN ('{"', '".join(territories_ids)}')
165
+ WHERE code IN ('{"', '".join(territories_ids)}')
155
166
  """
156
167
 
157
168
  results = run_custom_query(query)
@@ -164,20 +175,20 @@ def precise_view(request):
164
175
  @require_GET
165
176
  @cache_control(max_age=3600)
166
177
  def territories_view(request):
167
- params = TerritoryFeatureParams(**request.GET.dict())
168
- geo_level = params.geo_level
169
- mesh = params.mesh
178
+ payload = TerritoryFeaturePayload(**request.GET.dict())
170
179
 
171
- if params.codes:
172
- territories_ids = params.codes
180
+ if payload.codes:
181
+ territories_ids = payload.codes
173
182
  else:
174
- main_territory_codes = params.main_territories
175
- territories_ids = get_territories_ids(main_territory_codes, geo_level, mesh)
183
+ territories = get_sub_territories(
184
+ submesh=payload.submesh, territory=payload.territory
185
+ )
186
+ territories_ids = [t["code"] for t in territories]
176
187
 
177
188
  query = f"""
178
189
  SELECT code AS id, ST_asgeojson(geometry) AS polygon
179
- FROM contours_simplified_{params.mesh}
180
- WHERE code IN ('{"', '".join(territories_ids)}')
190
+ FROM contours_simplified_{payload.submesh}
191
+ WHERE code IN {format_sql_codes(territories_ids)}
181
192
  """
182
193
 
183
194
  results = run_custom_query(query)
@@ -190,7 +201,7 @@ def territories_view(request):
190
201
 
191
202
 
192
203
  def _fill_territory_li(code, name, mesh):
193
- label = name if mesh == MeshLevel.National else f"{name} - {code}"
204
+ label = name if mesh == MeshLevel.fr else f"{name} - {code}"
194
205
  return f"""<li data-code="{code}" data-name="{name}" onclick="chooseTerritory(this)"><button aria-label="Choisir {label}">{label}</button></li>"""
195
206
 
196
207
 
@@ -201,27 +212,21 @@ def search_territories_view(request):
201
212
  mesh = params.mesh
202
213
  search = params.search
203
214
  offset = params.offset
204
- if mesh == MeshLevel.National:
215
+ if mesh == MeshLevel.fr:
205
216
  lis = []
206
217
  for code, name in FRANCE_GEOLEVEL_TITLES.items():
207
218
  li = _fill_territory_li(code, name, mesh)
208
219
  lis.append(li)
209
220
  return HttpResponse("\n".join(lis))
210
- mesh_db = MESH_DB[mesh]
221
+ table_name = f"arbo_{mesh}"
211
222
  pagination = 20
212
- query = SQL("""
213
- SELECT DISTINCT {code} as code, {name} as name FROM arborescence_geo
214
- WHERE unaccent({name}) || {code} ILIKE unaccent(%s)
215
- AND "FR" <> 'ETR'
216
- ORDER BY {name}, {code}
217
- LIMIT {limit} OFFSET {offset};
218
- ;
219
- """).format(
220
- code=Identifier(mesh_db),
221
- name=Identifier(f"NOM_{mesh_db}"),
222
- offset=Literal(offset * pagination),
223
- limit=Literal(pagination),
224
- )
223
+ query = SQL(f"""
224
+ SELECT DISTINCT code, name FROM {table_name}
225
+ WHERE unaccent(name) || code ILIKE unaccent(%s)
226
+ AND code !~ '^[A-Z]{{3}}$'
227
+ ORDER BY name, code
228
+ LIMIT {pagination} OFFSET {offset * pagination};
229
+ """)
225
230
  territories = run_custom_query(query, [f"%{search}%"])
226
231
  lis = []
227
232
  for territory in territories:
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from django.conf import settings
2
4
  from django.db import models
3
5
 
@@ -8,19 +10,48 @@ class AggregationFunctions(models.TextChoices):
8
10
 
9
11
 
10
12
  class MeshLevel(models.TextChoices):
11
- National = "fr"
12
- Region = "reg"
13
- Department = "dep"
14
- Epci = "epci"
15
- Town = "com"
16
-
17
-
18
- class GeoLevel(models.TextChoices):
19
- France = "fr"
20
- Region = "reg"
21
- Department = "dep"
22
- Epci = "epci"
23
- Town = "com"
13
+ fr = "fr"
14
+ reg = "reg"
15
+ dep = "dep"
16
+ epci = "epci"
17
+ com = "com"
18
+ aom = "aom"
19
+
20
+
21
+ STANDARD_MESHES = [
22
+ MeshLevel.fr,
23
+ MeshLevel.reg,
24
+ MeshLevel.dep,
25
+ MeshLevel.epci,
26
+ MeshLevel.com,
27
+ ]
28
+
29
+ ALL_MESHES_ABSOLUTE = [
30
+ MeshLevel.fr,
31
+ MeshLevel.reg,
32
+ MeshLevel.dep,
33
+ MeshLevel.aom,
34
+ MeshLevel.epci,
35
+ MeshLevel.com,
36
+ ]
37
+
38
+
39
+ MESHES_ORDERED_FOR_PRESENTATION = [
40
+ MeshLevel.fr,
41
+ MeshLevel.reg,
42
+ MeshLevel.dep,
43
+ MeshLevel.epci,
44
+ MeshLevel.com,
45
+ MeshLevel.aom,
46
+ ]
47
+
48
+
49
+ def order_meshes_for_presentation(meshes: List[MeshLevel]) -> List[MeshLevel]:
50
+ """
51
+ Orders a list of MeshLevel values according to MESHES_ORDERED_FOR_PRESENTATION.
52
+ """
53
+ order_index = {mesh: i for i, mesh in enumerate(MESHES_ORDERED_FOR_PRESENTATION)}
54
+ return sorted(meshes, key=lambda mesh: order_index[mesh])
24
55
 
25
56
 
26
57
  class FranceGeoLevel(models.TextChoices):
@@ -42,30 +73,33 @@ FRANCE_DB_VALUES = {
42
73
  }
43
74
 
44
75
 
45
- DEFAULT_MESH = MeshLevel.Region
76
+ DEFAULT_MESH = MeshLevel.reg
46
77
 
47
- MESH_TITLES = {
48
- MeshLevel.National: "France entière",
49
- MeshLevel.Region: "Région",
50
- MeshLevel.Department: "Département",
51
- MeshLevel.Epci: "Intercommunalité",
52
- MeshLevel.Town: "Commune",
78
+ MESHES_SHORT_TITLES = {
79
+ MeshLevel.fr: "France",
80
+ MeshLevel.reg: "Région",
81
+ MeshLevel.dep: "Département",
82
+ MeshLevel.epci: "Intercommunalité",
83
+ MeshLevel.com: "Commune",
84
+ MeshLevel.aom: "AOM",
53
85
  }
54
86
 
55
- MESH_DB = {
56
- MeshLevel.Region: "REG",
57
- MeshLevel.Department: "DEP",
58
- MeshLevel.Epci: "EPCI",
59
- MeshLevel.Town: "DEPCOM",
87
+ MESHES_LONG_TITLES = {
88
+ MeshLevel.fr: "France",
89
+ MeshLevel.reg: "Région",
90
+ MeshLevel.dep: "Département",
91
+ MeshLevel.epci: "Intercommunalité",
92
+ MeshLevel.com: "Commune",
93
+ MeshLevel.aom: "Autorité Organisatrice de la Mobilité",
60
94
  }
61
95
 
62
96
 
63
97
  def get_miminum_mesh():
64
98
  try:
65
99
  town_mesh_is_disabled = settings.DISABLE_TOWN_MESH
66
- return MeshLevel.Epci if town_mesh_is_disabled else MeshLevel.Town
100
+ return MeshLevel.epci if town_mesh_is_disabled else MeshLevel.com
67
101
  except AttributeError:
68
- return MeshLevel.Town
102
+ return MeshLevel.com
69
103
 
70
104
 
71
105
  def get_allow_same_mesh():
@@ -73,13 +107,3 @@ def get_allow_same_mesh():
73
107
  return bool(settings.ALLOW_SAME_MESH)
74
108
  except AttributeError:
75
109
  return False
76
-
77
-
78
- def get_all_meshes():
79
- min_mesh = get_miminum_mesh()
80
- meshes = []
81
- for m in MeshLevel:
82
- meshes.append(m)
83
- if m == min_mesh:
84
- break
85
- return meshes
@@ -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
+ ('indicators_lib', '0003_indicator_short_title'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='indicator',
15
+ name='min_mesh',
16
+ field=models.TextField(choices=[('fr', 'Fr'), ('reg', 'Reg'), ('dep', 'Dep'), ('epci', 'Epci'), ('com', 'Com'), ('aom', 'Aom')], default='com'),
17
+ ),
18
+ ]
@@ -115,8 +115,8 @@ def reset_indicators_methodo_pdf(apps, schema_editor):
115
115
 
116
116
  class Migration(migrations.Migration):
117
117
  dependencies = [
118
- ("indicators_lib", "0003_indicator_short_title"),
119
- ("website_lib", "0004_mainconf_description_mainconf_social_image_url"),
118
+ ("indicators_lib", "0004_alter_indicator_min_mesh"),
119
+ ("website_lib", "0005_mainconf_meshes"),
120
120
  ]
121
121
 
122
122
  operations = [
@@ -10,18 +10,15 @@ from territories_dashboard_lib.indicators_lib.refresh_filters import refresh_fil
10
10
 
11
11
 
12
12
  class Theme(CommonModel):
13
- # TODO: make it primary key
14
- # django.db.utils.OperationalError: foreign key mismatch -
15
- # "tdbmd_indicators_indicatorsubtheme" referencing "tdbmd_indicators_indicatortheme"
16
13
  ordering = models.IntegerField(default=0)
17
14
  ordering.verbose_name = "Ordre dans la sidebar"
18
15
  name = models.CharField(max_length=64, unique=True)
19
16
  name.verbose_name = "Nom (~id, URL)"
20
17
  title = models.CharField(max_length=128)
21
18
  title.verbose_name = "Titre (affiché)"
22
- objectif_theme = models.TextField(blank=True, null=True)
19
+ objectif_theme = models.TextField(blank=True)
23
20
  objectif_theme.verbose_name = "Objectif"
24
- action_theme = models.TextField(blank=True, null=True)
21
+ action_theme = models.TextField(blank=True)
25
22
  action_theme.verbose_name = "Actions"
26
23
 
27
24
  def is_displayed_on_app(self):
@@ -106,7 +103,7 @@ class Indicator(CommonModel):
106
103
  # Indicator's DB attributes
107
104
  db_table_prefix = models.CharField(max_length=128)
108
105
  db_table_prefix.verbose_name = "Préfixe dans la DB"
109
- min_mesh = models.TextField(choices=MeshLevel.choices, default=MeshLevel.Town)
106
+ min_mesh = models.TextField(choices=MeshLevel.choices, default=MeshLevel.com)
110
107
  is_composite = models.BooleanField(default=False)
111
108
  is_composite.verbose_name = "Indicateur composite"
112
109
  show_alternative = models.BooleanField(
@@ -180,6 +177,12 @@ class Indicator(CommonModel):
180
177
 
181
178
  get_theme_title.short_description = "Thème"
182
179
 
180
+ def table_name(indicator, mesh: MeshLevel, *, flows: bool = False):
181
+ table_prefix = (
182
+ indicator.flows_db_table_prefix if flows else indicator.db_table_prefix
183
+ )
184
+ return f"{table_prefix}_{mesh}"
185
+
183
186
  class Meta:
184
187
  ordering = ("sub_theme", "index_in_theme")
185
188
  verbose_name = "3 - Indicateur"
@@ -1,8 +1,9 @@
1
1
  from typing import Annotated, Optional
2
2
 
3
- from pydantic import BaseModel, BeforeValidator, Field
3
+ from pydantic import BaseModel, BeforeValidator, Field, model_validator
4
4
 
5
5
  from territories_dashboard_lib.commons.types import MutualisedTerritoryCode, SQlName
6
+ from territories_dashboard_lib.indicators_lib.query.utils import format_sql_codes
6
7
 
7
8
  from .enums import MeshLevel
8
9
 
@@ -11,6 +12,10 @@ class Territory(BaseModel):
11
12
  id: MutualisedTerritoryCode
12
13
  mesh: MeshLevel
13
14
 
15
+ @property
16
+ def sql_codes(territory) -> str:
17
+ return format_sql_codes(territory.id.split(","))
18
+
14
19
 
15
20
  def validate_territory(value):
16
21
  if value and "-" in value:
@@ -25,6 +30,14 @@ class SubMeshPayload(BasePayload):
25
30
  submesh: MeshLevel
26
31
 
27
32
 
33
+ class SubMeshOnlyPayload(SubMeshPayload):
34
+ @model_validator(mode="after")
35
+ def check_not_same_mesh(self):
36
+ if self.submesh == self.territory.mesh:
37
+ raise ValueError("submesh and territory mesh should not be equal")
38
+ return self
39
+
40
+
28
41
  class FlowsPayload(SubMeshPayload):
29
42
  prefix: SQlName
30
43
  dimension: SQlName | None = None