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.
- territories_dashboard_lib/geo_lib/admin.py +3 -1
- territories_dashboard_lib/geo_lib/migrations/0002_geoelement_linked_to_indicator.py +18 -0
- territories_dashboard_lib/geo_lib/migrations/0003_geofeature_color_column_geofeature_size_column.py +23 -0
- territories_dashboard_lib/geo_lib/models.py +17 -0
- territories_dashboard_lib/geo_lib/payloads.py +7 -21
- territories_dashboard_lib/geo_lib/views.py +58 -53
- territories_dashboard_lib/indicators_lib/enums.py +61 -37
- territories_dashboard_lib/indicators_lib/methodo_pdf.py +6 -1
- territories_dashboard_lib/indicators_lib/migrations/0004_alter_indicator_min_mesh.py +18 -0
- territories_dashboard_lib/indicators_lib/migrations/0005_auto_20251203_1621.py +124 -0
- territories_dashboard_lib/indicators_lib/models.py +7 -4
- territories_dashboard_lib/indicators_lib/payloads.py +14 -1
- territories_dashboard_lib/indicators_lib/query/commons.py +90 -104
- territories_dashboard_lib/indicators_lib/query/comparison.py +8 -3
- territories_dashboard_lib/indicators_lib/query/details.py +8 -13
- territories_dashboard_lib/indicators_lib/query/histogram.py +6 -1
- territories_dashboard_lib/indicators_lib/query/indicator_card.py +12 -7
- territories_dashboard_lib/indicators_lib/query/top_10.py +13 -12
- territories_dashboard_lib/indicators_lib/query/utils.py +9 -0
- territories_dashboard_lib/indicators_lib/table.py +15 -12
- territories_dashboard_lib/indicators_lib/views.py +49 -59
- territories_dashboard_lib/superset_lib/logic.py +24 -25
- territories_dashboard_lib/superset_lib/migrations/0002_alter_filter_mesh.py +18 -0
- territories_dashboard_lib/tracking_lib/enums.py +1 -0
- territories_dashboard_lib/tracking_lib/migrations/0005_alter_page_cmp_territory_mesh_alter_page_submesh_and_more.py +28 -0
- territories_dashboard_lib/tracking_lib/migrations/0006_alter_event_name.py +18 -0
- territories_dashboard_lib/tracking_lib/payloads.py +4 -2
- territories_dashboard_lib/tracking_lib/views.py +7 -6
- territories_dashboard_lib/website_lib/conf.py +28 -0
- territories_dashboard_lib/website_lib/context_processors.py +5 -6
- territories_dashboard_lib/website_lib/migrations/0005_mainconf_meshes.py +20 -0
- territories_dashboard_lib/website_lib/models.py +12 -0
- territories_dashboard_lib/website_lib/params.py +34 -22
- territories_dashboard_lib/website_lib/serializers.py +1 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/anchor.mjs +45 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/page.mjs +5 -9
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/histogram.mjs +4 -1
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/page.mjs +2 -7
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/proportions.mjs +3 -2
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/top10.mjs +4 -2
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/utils.mjs +14 -5
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/dom.mjs +0 -15
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/enums.mjs +13 -10
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/side_panel.mjs +1 -15
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/page.mjs +5 -9
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/indicatorMap.bundle.js +1 -1
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/sankeyGraph.bundle.js +1 -1
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/vendors.bundle.js +1 -1
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/base.css +12 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/header.html +1 -1
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/page.html +4 -3
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/anchor.html +14 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/geo_params.html +3 -3
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/indicator-card.html +14 -8
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/select_territory.html +32 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_geo.html +8 -35
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/histogram.html +1 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/no-data.html +1 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/proportions.html +1 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/top10.html +1 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/map.css +3 -2
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.css +8 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.html +9 -8
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/methodo/methodo.js +28 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/methodo/page.html +40 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/page.html +4 -3
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/sitemap/page.html +9 -0
- territories_dashboard_lib/website_lib/templatetags/other_filters.py +6 -3
- territories_dashboard_lib/website_lib/views.py +100 -0
- {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/METADATA +3 -3
- {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/RECORD +74 -62
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.css +0 -29
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.html +0 -45
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.js +0 -19
- {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/WHEEL +0 -0
- {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/licenses/licence.md +0 -0
- {territories_dashboard_lib-0.1.33b1.dist-info → territories_dashboard_lib-1.1.1.dev7.dist-info}/top_level.txt +0 -0
|
@@ -8,7 +8,7 @@ from territories_dashboard_lib.geo_lib.models import GeoElement, GeoFeature
|
|
|
8
8
|
|
|
9
9
|
class GeoColumnInLine(admin.TabularInline):
|
|
10
10
|
model = GeoElement
|
|
11
|
-
fields = ["name", "label", "filterable"]
|
|
11
|
+
fields = ["name", "label", "filterable", "linked_to_indicator"]
|
|
12
12
|
extra = 0
|
|
13
13
|
formfield_overrides = {
|
|
14
14
|
models.TextField: {"widget": forms.TextInput()},
|
|
@@ -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,18 @@
|
|
|
1
|
+
# Generated by Django 5.2.7 on 2025-10-28 12:30
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('geo_lib', '0001_initial'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name='geoelement',
|
|
15
|
+
name='linked_to_indicator',
|
|
16
|
+
field=models.BooleanField(default=False, help_text="Si coché, le filtre de l'indicateur avec le même nom sere relié au filtre de la carte.", verbose_name="Relié aux filtres de l'indicateur"),
|
|
17
|
+
),
|
|
18
|
+
]
|
territories_dashboard_lib/geo_lib/migrations/0003_geofeature_color_column_geofeature_size_column.py
ADDED
|
@@ -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
|
|
@@ -56,3 +68,8 @@ class GeoElement(CommonModel):
|
|
|
56
68
|
name = models.TextField(verbose_name="Nom de la colonne en DB")
|
|
57
69
|
label = models.TextField()
|
|
58
70
|
filterable = models.BooleanField(default=True, verbose_name="Filtrable")
|
|
71
|
+
linked_to_indicator = models.BooleanField(
|
|
72
|
+
default=False,
|
|
73
|
+
verbose_name="Relié aux filtres de l'indicateur",
|
|
74
|
+
help_text="Si coché, le filtre de l'indicateur avec le même nom sere relié au filtre de la carte.",
|
|
75
|
+
)
|
|
@@ -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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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("
|
|
56
|
-
def
|
|
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
|
|
10
|
+
from psycopg2.sql import SQL
|
|
11
11
|
|
|
12
12
|
from territories_dashboard_lib.geo_lib.payloads import (
|
|
13
|
-
|
|
13
|
+
GeoFeaturesPayload,
|
|
14
14
|
MainTerritoryParams,
|
|
15
15
|
SearchTerritoriesParams,
|
|
16
16
|
TerritoriesParams,
|
|
17
|
-
|
|
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
|
|
25
|
-
|
|
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
|
-
|
|
49
|
+
payload = GeoFeaturesPayload(**request.GET.dict())
|
|
46
50
|
|
|
47
|
-
geo_feature = get_object_or_404(GeoFeature, id=
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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_{
|
|
77
|
-
f"WHERE contours.code IN
|
|
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
|
-
|
|
95
|
-
|
|
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":
|
|
113
|
+
"properties": properties,
|
|
100
114
|
"geometry": json.loads(r.get("geojson")),
|
|
101
115
|
}
|
|
102
|
-
|
|
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
|
|
163
|
+
SELECT code AS id, ST_asgeojson(geometry) AS polygon
|
|
153
164
|
FROM contours_geo_ign_{mesh_level}
|
|
154
|
-
WHERE
|
|
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
|
-
|
|
168
|
-
geo_level = params.geo_level
|
|
169
|
-
mesh = params.mesh
|
|
178
|
+
payload = TerritoryFeaturePayload(**request.GET.dict())
|
|
170
179
|
|
|
171
|
-
if
|
|
172
|
-
territories_ids =
|
|
180
|
+
if payload.codes:
|
|
181
|
+
territories_ids = payload.codes
|
|
173
182
|
else:
|
|
174
|
-
|
|
175
|
-
|
|
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_{
|
|
180
|
-
WHERE code IN
|
|
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.
|
|
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.
|
|
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
|
-
|
|
221
|
+
table_name = f"arbo_{mesh}"
|
|
211
222
|
pagination = 20
|
|
212
|
-
query = SQL("""
|
|
213
|
-
SELECT DISTINCT
|
|
214
|
-
WHERE unaccent(
|
|
215
|
-
AND
|
|
216
|
-
ORDER BY
|
|
217
|
-
LIMIT {
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
|
76
|
+
DEFAULT_MESH = MeshLevel.reg
|
|
46
77
|
|
|
47
|
-
|
|
48
|
-
MeshLevel.
|
|
49
|
-
MeshLevel.
|
|
50
|
-
MeshLevel.
|
|
51
|
-
MeshLevel.
|
|
52
|
-
MeshLevel.
|
|
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
|
-
|
|
56
|
-
MeshLevel.
|
|
57
|
-
MeshLevel.
|
|
58
|
-
MeshLevel.
|
|
59
|
-
MeshLevel.
|
|
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.
|
|
100
|
+
return MeshLevel.epci if town_mesh_is_disabled else MeshLevel.com
|
|
67
101
|
except AttributeError:
|
|
68
|
-
return MeshLevel.
|
|
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
|
|
@@ -6,6 +6,8 @@ from tempfile import NamedTemporaryFile
|
|
|
6
6
|
from django.conf import settings
|
|
7
7
|
from markdown import markdown
|
|
8
8
|
|
|
9
|
+
from territories_dashboard_lib.website_lib.conf import get_main_conf
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
def _html_to_pdf(html_content, output_pdf_path):
|
|
11
13
|
try:
|
|
@@ -36,6 +38,7 @@ def _generate_pdf_from_methodo(indicator, output_path):
|
|
|
36
38
|
logo_path = os.path.join(settings.BASE_DIR, relative_logo_path)
|
|
37
39
|
with open(logo_path, "rb") as fd:
|
|
38
40
|
encoded_logo = base64.b64encode(fd.read()).decode("utf-8")
|
|
41
|
+
main_conf = get_main_conf()
|
|
39
42
|
html = (
|
|
40
43
|
"""
|
|
41
44
|
<!DOCTYPE html>
|
|
@@ -59,7 +62,9 @@ def _generate_pdf_from_methodo(indicator, output_path):
|
|
|
59
62
|
+ """
|
|
60
63
|
</td>
|
|
61
64
|
<td style="padding-left: 64px;">
|
|
62
|
-
|
|
65
|
+
"""
|
|
66
|
+
+ f'<div style="font-size: 30px; font-weight: 600; margin-bottom: 8px;">{main_conf.title}</div>'
|
|
67
|
+
+ """
|
|
63
68
|
<div>Fiche méthodologique</div>
|
|
64
69
|
</td>
|
|
65
70
|
</tr>
|
|
@@ -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
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Generated by Django 5.2.9 on 2025-12-03 15:21
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
from tempfile import NamedTemporaryFile
|
|
7
|
+
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.db import migrations
|
|
10
|
+
from markdown import markdown
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _html_to_pdf(html_content, output_pdf_path):
|
|
14
|
+
try:
|
|
15
|
+
process = subprocess.Popen(
|
|
16
|
+
[
|
|
17
|
+
"wkhtmltopdf",
|
|
18
|
+
"-",
|
|
19
|
+
output_pdf_path,
|
|
20
|
+
], # '-' tells wkhtmltopdf to read from stdin
|
|
21
|
+
stdin=subprocess.PIPE,
|
|
22
|
+
stdout=subprocess.PIPE,
|
|
23
|
+
stderr=subprocess.PIPE,
|
|
24
|
+
)
|
|
25
|
+
stdout, stderr = process.communicate(input=html_content.encode("utf-8"))
|
|
26
|
+
if process.returncode != 0:
|
|
27
|
+
print("Error:", stderr.decode("utf-8"))
|
|
28
|
+
else:
|
|
29
|
+
print(f"PDF successfully created at: {output_pdf_path}")
|
|
30
|
+
except FileNotFoundError:
|
|
31
|
+
print("Error: wkhtmltopdf not found. Ensure it is installed and in your PATH.")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(f"An error occurred: {e}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _generate_pdf_from_methodo(main_conf, indicator, output_path):
|
|
37
|
+
html = markdown(indicator.methodo)
|
|
38
|
+
relative_logo_path = "whitenoise_root/ministere_logo.png"
|
|
39
|
+
logo_path = os.path.join(settings.BASE_DIR, relative_logo_path)
|
|
40
|
+
with open(logo_path, "rb") as fd:
|
|
41
|
+
encoded_logo = base64.b64encode(fd.read()).decode("utf-8")
|
|
42
|
+
html = (
|
|
43
|
+
"""
|
|
44
|
+
<!DOCTYPE html>
|
|
45
|
+
<html lang="fr">
|
|
46
|
+
<head>
|
|
47
|
+
<meta charset="UTF-8">
|
|
48
|
+
<title>Accents Test</title>
|
|
49
|
+
<style>
|
|
50
|
+
body {
|
|
51
|
+
font-family: Tahoma, Arial, sans-serif;
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
54
|
+
</head>
|
|
55
|
+
<body>
|
|
56
|
+
<header>
|
|
57
|
+
<table>
|
|
58
|
+
<tr>
|
|
59
|
+
<td>
|
|
60
|
+
"""
|
|
61
|
+
+ f'<img width="150px" src="data:image/png;base64,{encoded_logo}"/>'
|
|
62
|
+
+ """
|
|
63
|
+
</td>
|
|
64
|
+
<td style="padding-left: 64px;">
|
|
65
|
+
"""
|
|
66
|
+
+ f'<div style="font-size: 30px; font-weight: 600; margin-bottom: 8px;">{main_conf.title}</div>'
|
|
67
|
+
+ """
|
|
68
|
+
<div>Fiche méthodologique</div>
|
|
69
|
+
</td>
|
|
70
|
+
</tr>
|
|
71
|
+
</table>
|
|
72
|
+
</header>
|
|
73
|
+
<main>
|
|
74
|
+
"""
|
|
75
|
+
+ f"<h1>{indicator.title}</h1>"
|
|
76
|
+
+ f"<p>Thématique : {indicator.sub_theme.theme.title} / {indicator.sub_theme.title}</p>"
|
|
77
|
+
+ html
|
|
78
|
+
+ """
|
|
79
|
+
</main>
|
|
80
|
+
</body>
|
|
81
|
+
</html>
|
|
82
|
+
"""
|
|
83
|
+
)
|
|
84
|
+
_html_to_pdf(html, output_path)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def reset_methodo_file(main_conf, indicator):
|
|
88
|
+
# Create a temporary file for the PDF
|
|
89
|
+
with NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
|
|
90
|
+
pdf_path = temp_file.name
|
|
91
|
+
|
|
92
|
+
# Generate the PDF
|
|
93
|
+
_generate_pdf_from_methodo(main_conf, indicator, pdf_path)
|
|
94
|
+
|
|
95
|
+
# Read the binary content of the generated PDF file
|
|
96
|
+
with open(pdf_path, "rb") as pdf_file:
|
|
97
|
+
pdf_content = pdf_file.read()
|
|
98
|
+
|
|
99
|
+
# Save the binary content to the BinaryField
|
|
100
|
+
indicator.methodo_file = pdf_content
|
|
101
|
+
indicator.save()
|
|
102
|
+
|
|
103
|
+
# Clean up temporary file
|
|
104
|
+
os.remove(pdf_path)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def reset_indicators_methodo_pdf(apps, schema_editor):
|
|
108
|
+
MainConf = apps.get_model("website_lib", "MainConf")
|
|
109
|
+
Indicator = apps.get_model("indicators_lib", "Indicator")
|
|
110
|
+
main_conf = MainConf.objects.first()
|
|
111
|
+
if main_conf:
|
|
112
|
+
for indicator in Indicator.objects.all():
|
|
113
|
+
reset_methodo_file(main_conf, indicator)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Migration(migrations.Migration):
|
|
117
|
+
dependencies = [
|
|
118
|
+
("indicators_lib", "0004_alter_indicator_min_mesh"),
|
|
119
|
+
("website_lib", "0005_mainconf_meshes"),
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
operations = [
|
|
123
|
+
migrations.RunPython(reset_indicators_methodo_pdf, migrations.RunPython.noop),
|
|
124
|
+
]
|
|
@@ -10,9 +10,6 @@ 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)
|
|
@@ -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.
|
|
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"
|