territories-dashboard-lib 0.1.3__py3-none-any.whl → 0.1.5__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 (45) hide show
  1. territories_dashboard_lib/indicators_lib/enums.py +26 -0
  2. territories_dashboard_lib/indicators_lib/export.py +3 -3
  3. territories_dashboard_lib/indicators_lib/migrations/0002_filter_color_indicator_min_mesh_and_more.py +28 -0
  4. territories_dashboard_lib/indicators_lib/models.py +12 -2
  5. territories_dashboard_lib/indicators_lib/query/commons.py +8 -2
  6. territories_dashboard_lib/indicators_lib/query/details.py +11 -5
  7. territories_dashboard_lib/indicators_lib/query/top_10.py +6 -2
  8. territories_dashboard_lib/indicators_lib/refresh_filters.py +1 -1
  9. territories_dashboard_lib/indicators_lib/views.py +10 -8
  10. territories_dashboard_lib/tracking_lib/enums.py +10 -0
  11. territories_dashboard_lib/tracking_lib/payloads.py +10 -0
  12. territories_dashboard_lib/tracking_lib/urls.py +9 -0
  13. territories_dashboard_lib/tracking_lib/views.py +43 -0
  14. territories_dashboard_lib/website_lib/migrations/0002_mainconf_contact_email_mainconf_newsletter_link_and_more.py +28 -0
  15. territories_dashboard_lib/website_lib/migrations/0003_alter_mainconf_footer_navigation_and_more.py +23 -0
  16. territories_dashboard_lib/website_lib/models.py +16 -2
  17. territories_dashboard_lib/website_lib/params.py +26 -10
  18. territories_dashboard_lib/website_lib/serializers.py +2 -0
  19. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/css/website.css +24 -3
  20. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/page.mjs +3 -1
  21. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/page.mjs +3 -1
  22. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/export-graph.mjs +10 -1
  23. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/page.mjs +3 -1
  24. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/utils.mjs +3 -6
  25. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/indicatorMap.bundle.js +1 -1
  26. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/sankeyGraph.bundle.js +1 -1
  27. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/footer.html +1 -1
  28. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/components/histogram.html +28 -0
  29. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/components/indicateur-card.html +2 -23
  30. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/chart-buttons.html +1 -0
  31. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/histogram.html +21 -0
  32. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/map.html +10 -0
  33. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/proportions.html +21 -0
  34. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/sankey.html +17 -0
  35. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/table-flows.html +8 -0
  36. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/table-values.html +17 -0
  37. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/top10.html +19 -0
  38. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.html +7 -64
  39. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/components/indicateur-card.html +22 -25
  40. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/components/stats.html +12 -0
  41. territories_dashboard_lib/website_lib/templatetags/other_filters.py +8 -0
  42. {territories_dashboard_lib-0.1.3.dist-info → territories_dashboard_lib-0.1.5.dist-info}/METADATA +113 -11
  43. {territories_dashboard_lib-0.1.3.dist-info → territories_dashboard_lib-0.1.5.dist-info}/RECORD +45 -30
  44. {territories_dashboard_lib-0.1.3.dist-info → territories_dashboard_lib-0.1.5.dist-info}/WHEEL +0 -0
  45. {territories_dashboard_lib-0.1.3.dist-info → territories_dashboard_lib-0.1.5.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ from django.conf import settings
1
2
  from django.db import models
2
3
 
3
4
 
@@ -57,3 +58,28 @@ MESH_DB = {
57
58
  MeshLevel.Epci: "EPCI",
58
59
  MeshLevel.Town: "DEPCOM",
59
60
  }
61
+
62
+
63
+ def get_miminum_mesh():
64
+ try:
65
+ town_mesh_is_disabled = settings.DISABLE_TOWN_MESH
66
+ return MeshLevel.Epci if town_mesh_is_disabled else MeshLevel.Town
67
+ except AttributeError:
68
+ return MeshLevel.Town
69
+
70
+
71
+ def get_allow_same_mesh():
72
+ try:
73
+ return bool(settings.ALLOW_SAME_MESH)
74
+ except AttributeError:
75
+ 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
@@ -2,14 +2,14 @@ import csv
2
2
 
3
3
  from django.http import HttpRequest, HttpResponse
4
4
 
5
- from territories_dashboard_lib.tracking_lib.enums import EventType
5
+ from territories_dashboard_lib.tracking_lib.enums import EventType, GraphType
6
6
  from territories_dashboard_lib.tracking_lib.logic import track_event
7
7
 
8
8
  from .models import Indicator
9
9
 
10
10
 
11
11
  def export_to_csv(
12
- request: HttpRequest, indicator: Indicator, graph_name: str, data: list[dict]
12
+ request: HttpRequest, indicator: Indicator, graph_name: GraphType, data: list[dict]
13
13
  ):
14
14
  response = HttpResponse(content_type="text/csv")
15
15
  response["Content-Disposition"] = (
@@ -24,6 +24,6 @@ def export_to_csv(
24
24
  request=request,
25
25
  response=response,
26
26
  event_name=EventType.download,
27
- data={"indicator": indicator.name, "objet": graph_name},
27
+ data={"indicator": indicator.name, "objet": graph_name, "type": "csv"},
28
28
  )
29
29
  return response
@@ -0,0 +1,28 @@
1
+ # Generated by Django 5.2.3 on 2025-06-19 12:59
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('indicators_lib', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='filter',
15
+ name='color',
16
+ field=models.TextField(blank=True, help_text='format hexadécimale : #FF11DD', null=True, verbose_name='Couleur'),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='indicator',
20
+ name='min_mesh',
21
+ field=models.TextField(choices=[('fr', 'National'), ('reg', 'Region'), ('dep', 'Department'), ('epci', 'Epci'), ('com', 'Town')], default='com'),
22
+ ),
23
+ migrations.AlterField(
24
+ model_name='indicator',
25
+ name='show_evolution',
26
+ field=models.BooleanField(default=True),
27
+ ),
28
+ ]
@@ -2,7 +2,10 @@ from django.db import models
2
2
  from martor.models import MartorField
3
3
 
4
4
  from territories_dashboard_lib.commons.models import CommonModel
5
- from territories_dashboard_lib.indicators_lib.enums import AggregationFunctions
5
+ from territories_dashboard_lib.indicators_lib.enums import (
6
+ AggregationFunctions,
7
+ MeshLevel,
8
+ )
6
9
  from territories_dashboard_lib.indicators_lib.refresh_filters import refresh_filters
7
10
 
8
11
 
@@ -101,6 +104,7 @@ class Indicator(CommonModel):
101
104
  # Indicator's DB attributes
102
105
  db_table_prefix = models.CharField(max_length=128)
103
106
  db_table_prefix.verbose_name = "Préfixe dans la DB"
107
+ min_mesh = models.TextField(choices=MeshLevel.choices, default=MeshLevel.Town)
104
108
  is_composite = models.BooleanField(default=False)
105
109
  is_composite.verbose_name = "Indicateur composite"
106
110
  show_alternative = models.BooleanField(
@@ -158,7 +162,7 @@ class Indicator(CommonModel):
158
162
  help_text="Nom de la colonne des dimensions de la table des flux",
159
163
  )
160
164
  # Descriptive attributes
161
- show_evolution = models.BooleanField(default=False)
165
+ show_evolution = models.BooleanField(default=True)
162
166
  show_evolution.verbose_name = "Activer l'historique"
163
167
  source = models.TextField(default="", blank=True)
164
168
  description = models.TextField(default="", blank=True)
@@ -216,6 +220,12 @@ class Filter(CommonModel):
216
220
  order.verbose_name = "Ordre"
217
221
  default = models.BooleanField(default=True)
218
222
  default.verbose_name = "Sélectionné par défaut ?"
223
+ color = models.TextField(
224
+ null=True,
225
+ blank=True,
226
+ verbose_name="Couleur",
227
+ help_text="format hexadécimale : #FF11DD",
228
+ )
219
229
 
220
230
  def __str__(self):
221
231
  return self.db_name
@@ -1,4 +1,10 @@
1
- from ..enums import DEFAULT_MESH, FRANCE_DB_VALUES, GeoLevel, MeshLevel
1
+ from ..enums import (
2
+ DEFAULT_MESH,
3
+ FRANCE_DB_VALUES,
4
+ GeoLevel,
5
+ MeshLevel,
6
+ get_miminum_mesh,
7
+ )
2
8
  from ..models import AggregationFunctions, Indicator
3
9
  from .utils import get_breakdown_dimension, run_custom_query
4
10
 
@@ -156,7 +162,7 @@ def get_mesh_level_for_geo_level(mesh, submesh):
156
162
  else submesh
157
163
  )
158
164
  elif mesh in [GeoLevel.Epci, GeoLevel.Town]:
159
- return MeshLevel.Town
165
+ return get_miminum_mesh()
160
166
  else:
161
167
  return submesh or DEFAULT_MESH
162
168
 
@@ -14,21 +14,27 @@ from .utils import get_breakdown_dimension, run_custom_query
14
14
  def get_proportions_chart(indicator, territory, filters):
15
15
  where_territory = get_where_territory(territory)
16
16
  last_year = get_last_year(indicator, territory.mesh)
17
- breakdown_dimension = get_breakdown_dimension(indicator).db_name
17
+ breakdown_dimension = get_breakdown_dimension(indicator)
18
18
 
19
19
  query = f"""
20
- SELECT {calculate_aggregate_values(indicator, with_alternative=False)}, "{breakdown_dimension}" as dimension
20
+ SELECT {calculate_aggregate_values(indicator, with_alternative=False)}, "{breakdown_dimension.db_name}" as dimension
21
21
  FROM "{indicator.db_table_prefix}_{territory.mesh}" as indic
22
22
  WHERE {where_territory} AND annee = {last_year}
23
23
  {add_optional_filters(indicator, filters)}
24
- GROUP BY "{breakdown_dimension}"
24
+ GROUP BY "{breakdown_dimension.db_name}"
25
25
  """
26
26
 
27
27
  data = run_custom_query(query)
28
+ filters_color = {f.db_name: f.color for f in breakdown_dimension.filters.all()}
28
29
  data_dict = {
29
- d["dimension"]: {"label": d["dimension"], "data": [d["valeur"]]} for d in data
30
+ d["dimension"]: {
31
+ "label": d["dimension"],
32
+ "data": [d["valeur"]],
33
+ "color": filters_color.get(d["dimension"]),
34
+ }
35
+ for d in data
30
36
  }
31
- breakdown_filters = filters[breakdown_dimension]
37
+ breakdown_filters = filters[breakdown_dimension.db_name]
32
38
  sorted_data = (
33
39
  [data_dict[filter] for filter in breakdown_filters if filter in data_dict]
34
40
  if filters
@@ -47,8 +47,10 @@ def get_indicator_top_10_data(indicator, territory, submesh, filters):
47
47
  indicator_details = {}
48
48
 
49
49
  breakdown_dimension = get_breakdown_dimension(indicator)
50
- breakdown_dimension = breakdown_dimension.db_name if breakdown_dimension else None
51
- breakdown_filters = filters.get(breakdown_dimension, [])
50
+ breakdown_dimension_name = (
51
+ breakdown_dimension.db_name if breakdown_dimension else None
52
+ )
53
+ breakdown_filters = filters.get(breakdown_dimension_name, [])
52
54
 
53
55
  last_year = get_last_year(indicator, submesh)
54
56
 
@@ -63,6 +65,7 @@ def get_indicator_top_10_data(indicator, territory, submesh, filters):
63
65
  breakdown_by_geocode = defaultdict(dict)
64
66
  for row in breakdown:
65
67
  breakdown_by_geocode[row["code_geo"]][row["dimension"]] = row["valeur"]
68
+ filters_color = {f.db_name: f.color for f in breakdown_dimension.filters.all()}
66
69
  datasets_top_bar_chart = [
67
70
  {
68
71
  "label": f,
@@ -70,6 +73,7 @@ def get_indicator_top_10_data(indicator, territory, submesh, filters):
70
73
  breakdown_by_geocode[territory["code_geo"]][f]
71
74
  for territory in territories
72
75
  ],
76
+ "color": filters_color.get(f),
73
77
  }
74
78
  for f in breakdown_filters
75
79
  ]
@@ -7,7 +7,7 @@ def refresh_filters(dimension):
7
7
  if dimension:
8
8
  query = f'SELECT DISTINCT({dimension.db_name}) as filter FROM "{dimension.indicator.db_table_prefix}_reg"'
9
9
  results = run_custom_query(query)
10
- Filter = apps.get_model("tdbmd_indicators", "Filter")
10
+ Filter = apps.get_model("indicators_lib", "Filter")
11
11
  Filter.objects.filter(dimension=dimension).delete()
12
12
  Filter.objects.bulk_create(
13
13
  [
@@ -6,7 +6,7 @@ from django.views.decorators.cache import cache_control
6
6
  from django.views.decorators.http import require_GET
7
7
 
8
8
  from territories_dashboard_lib.commons.decorators import use_payload
9
- from territories_dashboard_lib.tracking_lib.enums import EventType
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
12
  from .enums import MESH_TITLES
@@ -54,7 +54,7 @@ def download_indicator_methodo_view(request, name):
54
54
  request=request,
55
55
  response=response,
56
56
  event_name=EventType.download,
57
- data={"indicator": indicator.name, "objet": "methodo"},
57
+ data={"indicator": indicator.name, "objet": "methodo", "type": "pdf"},
58
58
  )
59
59
  return response
60
60
 
@@ -129,14 +129,16 @@ def indicator_values_export_view(request, name, payload):
129
129
  "Année": value["annee"],
130
130
  f"Valeur {territory_name} ({indicator.unite})": value["valeur"],
131
131
  }
132
+ tracking_objet = "historique"
132
133
  if results.get("cmp_values") is not None:
133
134
  cmp_territory_name = get_territory_name(payload.cmp_territory)
134
135
  for value in results["cmp_values"]:
135
136
  export_values[value["annee"]][
136
137
  f"Valeur {cmp_territory_name} ({indicator.unite})"
137
138
  ] = value["valeur"]
139
+ tracking_objet = "comparaison-" + tracking_objet
138
140
  return export_to_csv(
139
- request, indicator, "comparaison-historique", list(export_values.values())
141
+ request, indicator, tracking_objet, list(export_values.values())
140
142
  )
141
143
 
142
144
 
@@ -183,7 +185,7 @@ def indicator_proportions_export_view(request, name, payload):
183
185
  {"Dimension": r["label"], f"Valeur {indicator.unite}": r["data"][0]}
184
186
  for r in rows
185
187
  ]
186
- return export_to_csv(request, indicator, "repartition-dimension", rows)
188
+ return export_to_csv(request, indicator, GraphType.repartition_dimension, rows)
187
189
 
188
190
 
189
191
  @cache_control(max_age=3600)
@@ -227,7 +229,7 @@ def indicator_histogram_export_view(request, name, payload):
227
229
  index
228
230
  ].replace("\n", " | ")
229
231
  rows.append(row)
230
- return export_to_csv(request, indicator, "repartition-valeurs", rows)
232
+ return export_to_csv(request, indicator, GraphType.repartition_valeurs, rows)
231
233
 
232
234
 
233
235
  @cache_control(max_age=3600)
@@ -255,7 +257,7 @@ def indicator_top_10_export_view(request, name, payload):
255
257
  payload.submesh,
256
258
  filters,
257
259
  )
258
- return export_to_csv(request, indicator, "top_10", csv_data)
260
+ return export_to_csv(request, indicator, GraphType.top_10, csv_data)
259
261
 
260
262
 
261
263
  @require_GET
@@ -404,7 +406,7 @@ def comparison_histogram_export_view(request, name, payload):
404
406
  cmp_values[index + 1][:10]
405
407
  )
406
408
  rows.append(row)
407
- return export_to_csv(request, indicator, "comparison-histogram", rows)
409
+ return export_to_csv(request, indicator, GraphType.comparison_histogram, rows)
408
410
 
409
411
 
410
412
  def get_label(props, indicator, key):
@@ -487,4 +489,4 @@ def indicator_details_table_export_view(request, name, payload):
487
489
  indicator = get_object_or_404(Indicator, name=name)
488
490
  filters = get_filters(request, indicator)
489
491
  table_values = get_export_indicator_table_values(indicator, payload, filters)
490
- return export_to_csv(request, indicator, "table", table_values)
492
+ return export_to_csv(request, indicator, GraphType.table, table_values)
@@ -5,3 +5,13 @@ TRACKING_COOKIE_NAME = "omnibus"
5
5
 
6
6
  class EventType(models.TextChoices):
7
7
  download = "download"
8
+
9
+
10
+ class GraphType(models.TextChoices):
11
+ comparaison_historique = "comparaison-historique"
12
+ repartition_dimension = "repartition-dimension"
13
+ repartition_valeurs = "repartition-valeurs"
14
+ top_10 = "top_10"
15
+ historique = "historique"
16
+ comparison_histogram = "comparison-histogram"
17
+ table = "table"
@@ -0,0 +1,10 @@
1
+ from pydantic import BaseModel
2
+
3
+ from territories_dashboard_lib.tracking_lib.enums import EventType
4
+
5
+
6
+ class EventPayload(BaseModel):
7
+ indicator: str
8
+ event: EventType
9
+ objet: str
10
+ type: str
@@ -0,0 +1,9 @@
1
+ from django.urls import path
2
+
3
+ from territories_dashboard_lib.tracking_lib.views import track_event_view
4
+
5
+ app_name = "tracking-api"
6
+
7
+ urlpatterns = [
8
+ path("event/", track_event_view, name="event"),
9
+ ]
@@ -0,0 +1,43 @@
1
+ import json
2
+ from datetime import timedelta
3
+
4
+ from django.http import HttpResponse, JsonResponse
5
+ from django.utils import timezone
6
+ from django.views.decorators.csrf import csrf_exempt
7
+ from django.views.decorators.http import require_POST
8
+ from pydantic import ValidationError
9
+
10
+ from territories_dashboard_lib.tracking_lib.logic import (
11
+ track_event,
12
+ )
13
+ from territories_dashboard_lib.tracking_lib.models import Event
14
+ from territories_dashboard_lib.tracking_lib.payloads import EventPayload
15
+
16
+
17
+ @require_POST
18
+ @csrf_exempt
19
+ def track_event_view(request):
20
+ try:
21
+ data = json.loads(request.body)
22
+ payload = EventPayload(**data)
23
+ except json.JSONDecodeError:
24
+ return JsonResponse({"error": "Invalid JSON"}, status=400)
25
+ except ValidationError as e:
26
+ return JsonResponse({"error": e.errors()}, status=422)
27
+ if (
28
+ Event.objects.filter(created_at__gte=timezone.now() - timedelta(days=1)).count()
29
+ > 1000
30
+ ):
31
+ return HttpResponse(status=429)
32
+ response = HttpResponse()
33
+ track_event(
34
+ request=request,
35
+ response=response,
36
+ event_name=payload.event,
37
+ data={
38
+ "indicator": payload.indicator,
39
+ "objet": payload.objet,
40
+ "type": payload.type,
41
+ },
42
+ )
43
+ return HttpResponse()
@@ -0,0 +1,28 @@
1
+ # Generated by Django 5.2.3 on 2025-06-19 14:03
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('website_lib', '0001_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='mainconf',
15
+ name='contact_email',
16
+ field=models.TextField(blank=True, default=None, null=True, verbose_name='Email de contact'),
17
+ ),
18
+ migrations.AddField(
19
+ model_name='mainconf',
20
+ name='newsletter_link',
21
+ field=models.TextField(blank=True, default=None, null=True, verbose_name="Lien d'inscription à la newsletter"),
22
+ ),
23
+ migrations.AddField(
24
+ model_name='mainconf',
25
+ name='show_footer_contact_banner',
26
+ field=models.BooleanField(default=False, help_text="Pour afficher la bannière, l'email de contact et le lien d'inscription à la newsletter doivent être renseignés.", verbose_name='Afficher une bannière de contact au-dessus du footer'),
27
+ ),
28
+ ]
@@ -0,0 +1,23 @@
1
+ # Generated by Django 5.1.6 on 2025-06-23 12:31
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('website_lib', '0002_mainconf_contact_email_mainconf_newsletter_link_and_more'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='mainconf',
15
+ name='footer_navigation',
16
+ field=models.TextField(default='', help_text='Navigation du footer, mettre les liens au format markdown, un par ligne. <br/><br/>\n Si le lien est interne, commencer et terminer le lien par un slash comme "/accueil/"<br/><br/>\n Par exemple : <br/><br/>\n [Plan du site](/plan-site/)<br/>\n [Accessibilité](/accessibilite/)<br/>\n [Mentions légales](/mentions-legales/) <br/>\n ', verbose_name='Navigation du footer'),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='mainconf',
20
+ name='header_navigation',
21
+ field=models.TextField(default='', help_text='Navigation du header, mettre les liens au format markdown avec un slash à la fin du lien, mettre une ligne vide entre chaque rubrique. <br/><br/>\n \n Une rubrique peut contenir un seul lien, ou bien plusiers, dans ce cas la rubrique sera sous forme de dropdown et il faut mettre un titre en premier. <br/><br/>Par exemple : <br/>\n <br/><br/>\n [Accueil](/accueil/)\n <br/><br/>\n [Indicateurs territoriaux](/indicateurs/)\n <br/><br/>\n À propos\n [Présentation](/presentation/)\n [Journal des versions](/journal/)\n ', verbose_name='Navigation du header'),
22
+ ),
23
+ ]
@@ -22,7 +22,7 @@ class MainConf(CommonModel):
22
22
  help_text="Renseigner le nom de l'entité au même format que le logo officiel en respectant les mises à la ligne.",
23
23
  )
24
24
  header_navigation = models.TextField(
25
- verbose_name="Navigation",
25
+ verbose_name="Navigation du header",
26
26
  help_text="""Navigation du header, mettre les liens au format markdown avec un slash à la fin du lien, mettre une ligne vide entre chaque rubrique. <br/><br/>
27
27
 
28
28
  Une rubrique peut contenir un seul lien, ou bien plusiers, dans ce cas la rubrique sera sous forme de dropdown et il faut mettre un titre en premier. <br/><br/>Par exemple : <br/>
@@ -38,7 +38,7 @@ class MainConf(CommonModel):
38
38
  default="",
39
39
  )
40
40
  footer_navigation = models.TextField(
41
- verbose_name="Navigation",
41
+ verbose_name="Navigation du footer",
42
42
  help_text="""Navigation du footer, mettre les liens au format markdown, un par ligne. <br/><br/>
43
43
  Si le lien est interne, commencer et terminer le lien par un slash comme "/accueil/"<br/><br/>
44
44
  Par exemple : <br/><br/>
@@ -48,6 +48,20 @@ class MainConf(CommonModel):
48
48
  """,
49
49
  default="",
50
50
  )
51
+ contact_email = models.TextField(
52
+ default=None, null=True, blank=True, verbose_name="Email de contact"
53
+ )
54
+ newsletter_link = models.TextField(
55
+ default=None,
56
+ null=True,
57
+ blank=True,
58
+ verbose_name="Lien d'inscription à la newsletter",
59
+ )
60
+ show_footer_contact_banner = models.BooleanField(
61
+ default=False,
62
+ verbose_name="Afficher une bannière de contact au-dessus du footer",
63
+ help_text="Pour afficher la bannière, l'email de contact et le lien d'inscription à la newsletter doivent être renseignés.",
64
+ )
51
65
 
52
66
  @property
53
67
  def entity_breaklines(self):
@@ -5,6 +5,8 @@ from territories_dashboard_lib.indicators_lib.enums import (
5
5
  MESH_TITLES,
6
6
  FranceGeoLevel,
7
7
  MeshLevel,
8
+ get_all_meshes,
9
+ get_allow_same_mesh,
8
10
  )
9
11
  from territories_dashboard_lib.indicators_lib.query.utils import run_custom_query
10
12
 
@@ -139,7 +141,7 @@ class ParamsHandler:
139
141
  ######################## Mesh
140
142
 
141
143
  def get_max_territory_mesh(self):
142
- meshes = [m for m in MeshLevel]
144
+ meshes = get_all_meshes()
143
145
  if self.comparison is False:
144
146
  return self.territory_mesh
145
147
  if meshes.index(self.cmp_territory_mesh) > meshes.index(self.territory_mesh):
@@ -149,18 +151,26 @@ class ParamsHandler:
149
151
  def is_not_valid_mesh(self, mesh):
150
152
  if mesh is None:
151
153
  return True
152
- meshes = [m for m in MeshLevel]
154
+ meshes = get_all_meshes()
153
155
  max_territory_mesh = self.get_max_territory_mesh()
154
- is_not_valid = meshes.index(max_territory_mesh) >= meshes.index(mesh)
156
+ allow_same_mesh = get_allow_same_mesh()
157
+ is_not_valid = (
158
+ meshes.index(max_territory_mesh) > meshes.index(mesh)
159
+ if allow_same_mesh
160
+ else meshes.index(max_territory_mesh) >= meshes.index(mesh)
161
+ )
155
162
  return is_not_valid
156
163
 
157
164
  def get_default_mesh(self):
158
165
  max_territory_mesh = self.get_max_territory_mesh()
159
- meshes = [m for m in MeshLevel]
166
+ meshes = get_all_meshes()
160
167
  if meshes.index(max_territory_mesh) == len(meshes) - 1:
161
168
  default_mesh = meshes[-1]
162
169
  else:
163
- default_mesh = meshes[meshes.index(max_territory_mesh) + 1]
170
+ mesh_index = meshes.index(max_territory_mesh)
171
+ if get_allow_same_mesh() is False:
172
+ mesh_index += 1
173
+ default_mesh = meshes[mesh_index]
164
174
  return default_mesh
165
175
 
166
176
  def get_cookie_mesh(self):
@@ -188,12 +198,17 @@ class ParamsHandler:
188
198
  max_territory_mesh = self.get_max_territory_mesh()
189
199
  meshes = [
190
200
  m
191
- for m in MeshLevel
201
+ for m in get_all_meshes()
192
202
  if not (m == MeshLevel.Town and max_territory_mesh == MeshLevel.National)
193
203
  ]
194
- self.meshes = meshes[
195
- min(meshes.index(max_territory_mesh) + 1, len(meshes) - 1) :
196
- ]
204
+ print("MESHES", meshes)
205
+ min_mesh_index = meshes.index(max_territory_mesh)
206
+ print("index", min_mesh_index)
207
+ if get_allow_same_mesh() is False:
208
+ min_mesh_index += 1
209
+ print("AGAIN", min_mesh_index)
210
+ print("Allow same mesh", get_allow_same_mesh())
211
+ self.meshes = meshes[min(min_mesh_index, len(meshes) - 1) :]
197
212
 
198
213
  ######################## Commons
199
214
 
@@ -245,7 +260,7 @@ class ParamsHandler:
245
260
  "mesh": self.mesh,
246
261
  "meshes": self.meshes,
247
262
  "meshes_titles": MESH_TITLES,
248
- "all_meshes": [m for m in MeshLevel],
263
+ "all_meshes": get_all_meshes(),
249
264
  "url_params": "&".join(url_params),
250
265
  }
251
266
 
@@ -263,6 +278,7 @@ def with_params(view_func):
263
278
 
264
279
  response = view_func(request, *args, context=context, **kwargs)
265
280
  handler.set_cookie(response)
281
+ print(context["params"])
266
282
  return response
267
283
 
268
284
  return wrapper
@@ -22,6 +22,8 @@ def serialize_indicator(indicator):
22
22
  "flows_dimension": indicator.flows_dimension,
23
23
  "theme": indicator.sub_theme.theme.title,
24
24
  "filters": serializer_filters(indicator),
25
+ "show_evolution": indicator.show_evolution,
26
+ "min_mesh": indicator.min_mesh,
25
27
  "secondary_indicator": {
26
28
  "name": indicator.secondary_indicator.name,
27
29
  "title": indicator.secondary_indicator.title,
@@ -875,13 +875,17 @@ footer .fr-follow {
875
875
  display: flex;
876
876
  flex-direction: column;
877
877
  gap: 8px;
878
- padding-bottom: 32px;
878
+ padding-bottom: 64px;
879
879
  }
880
880
 
881
881
  .table-geo-filters summary {
882
882
  font-size: 0.9rem;
883
883
  }
884
884
 
885
+ .table-geo-filters details:last-child:open {
886
+ padding-bottom: 500px;
887
+ }
888
+
885
889
  .table-geo-filters input,
886
890
  .table-geo-filters select {
887
891
  padding: 4px 8px;
@@ -890,10 +894,11 @@ footer .fr-follow {
890
894
  }
891
895
 
892
896
  .table-geo-filters input[type="number"] {
893
- max-width: 150px;
897
+ max-width: 70px;
894
898
  }
895
899
 
896
- .table-geo-filters label {
900
+ .table-geo-filters label,
901
+ .table-geo-filters i {
897
902
  font-size: 0.8rem;
898
903
  }
899
904
 
@@ -908,6 +913,12 @@ footer .fr-follow {
908
913
  gap: 8px;
909
914
  }
910
915
 
916
+ .geo-filters-numbers-row {
917
+ display: flex;
918
+ flex-direction: row;
919
+ gap: 32px;
920
+ }
921
+
911
922
  .staticPage .code {
912
923
  background-color: #ececfe;
913
924
  font-family: inherit;
@@ -954,3 +965,13 @@ footer .fr-follow {
954
965
  .glossary .definitions dd {
955
966
  width: 100%;
956
967
  }
968
+
969
+ @media (min-width: 48em) {
970
+ .fr-footer__bottom-item::before {
971
+ margin-right: 0.5rem;
972
+ }
973
+ }
974
+
975
+ .fr-footer__bottom-link {
976
+ white-space: nowrap;
977
+ }
@@ -74,7 +74,9 @@ document
74
74
  await new Promise((resolve) => setTimeout(resolve, 50));
75
75
  await exportImageAsync(
76
76
  button.parentElement.previousElementSibling,
77
- `${indicator.name} - ${button.dataset.title}`
77
+ `${indicator.name} - ${button.dataset.title}`,
78
+ indicator,
79
+ button.dataset["trackingobjet"]
78
80
  );
79
81
  button.removeAttribute("disabled");
80
82
  });
@@ -59,7 +59,9 @@ document
59
59
  await new Promise((resolve) => setTimeout(resolve, 50));
60
60
  await exportImageAsync(
61
61
  button.parentElement.previousElementSibling,
62
- `${indicator.name} - ${button.dataset.title}`
62
+ `${indicator.name} - ${button.dataset.title}`,
63
+ indicator,
64
+ button.dataset["trackingobjet"]
63
65
  );
64
66
  button.removeAttribute("disabled");
65
67
  });