territories-dashboard-lib 0.1.0__py3-none-any.whl → 0.1.2__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 (138) hide show
  1. territories_dashboard_lib/commons/__init__.py +0 -0
  2. territories_dashboard_lib/commons/decorators.py +36 -0
  3. territories_dashboard_lib/commons/models.py +9 -0
  4. territories_dashboard_lib/geo_lib/__init__.py +0 -0
  5. territories_dashboard_lib/geo_lib/admin.py +64 -0
  6. territories_dashboard_lib/geo_lib/enums.py +7 -0
  7. territories_dashboard_lib/geo_lib/migrations/0001_initial.py +51 -0
  8. territories_dashboard_lib/geo_lib/migrations/__init__.py +0 -0
  9. territories_dashboard_lib/geo_lib/models.py +58 -0
  10. territories_dashboard_lib/geo_lib/urls.py +27 -0
  11. territories_dashboard_lib/geo_lib/views.py +239 -0
  12. territories_dashboard_lib/indicators_lib/__init__.py +0 -0
  13. territories_dashboard_lib/indicators_lib/admin.py +140 -0
  14. territories_dashboard_lib/indicators_lib/enums.py +59 -0
  15. territories_dashboard_lib/indicators_lib/export.py +29 -0
  16. territories_dashboard_lib/indicators_lib/format.py +34 -0
  17. territories_dashboard_lib/indicators_lib/methodo_pdf.py +99 -0
  18. territories_dashboard_lib/indicators_lib/migrations/0001_initial.py +138 -0
  19. territories_dashboard_lib/indicators_lib/migrations/__init__.py +0 -0
  20. territories_dashboard_lib/indicators_lib/models.py +230 -0
  21. territories_dashboard_lib/indicators_lib/payloads.py +54 -0
  22. territories_dashboard_lib/indicators_lib/query/commons.py +223 -0
  23. territories_dashboard_lib/indicators_lib/query/comparison.py +70 -0
  24. territories_dashboard_lib/indicators_lib/query/details.py +64 -0
  25. territories_dashboard_lib/indicators_lib/query/histogram.py +82 -0
  26. territories_dashboard_lib/indicators_lib/query/indicator_card.py +102 -0
  27. territories_dashboard_lib/indicators_lib/query/top_10.py +100 -0
  28. territories_dashboard_lib/indicators_lib/query/utils.py +20 -0
  29. territories_dashboard_lib/indicators_lib/refresh_filters.py +17 -0
  30. territories_dashboard_lib/indicators_lib/table.py +154 -0
  31. territories_dashboard_lib/indicators_lib/urls.py +97 -0
  32. territories_dashboard_lib/indicators_lib/views.py +490 -0
  33. territories_dashboard_lib/superset_lib/__init__.py +0 -0
  34. territories_dashboard_lib/superset_lib/admin.py +22 -0
  35. territories_dashboard_lib/superset_lib/guest_token.py +64 -0
  36. territories_dashboard_lib/superset_lib/logic.py +67 -0
  37. territories_dashboard_lib/superset_lib/migrations/0001_initial.py +45 -0
  38. territories_dashboard_lib/superset_lib/migrations/__init__.py +0 -0
  39. territories_dashboard_lib/superset_lib/models.py +52 -0
  40. territories_dashboard_lib/superset_lib/serializers.py +10 -0
  41. territories_dashboard_lib/superset_lib/urls.py +10 -0
  42. territories_dashboard_lib/superset_lib/views.py +19 -0
  43. territories_dashboard_lib/tracking_lib/__init__.py +0 -0
  44. territories_dashboard_lib/tracking_lib/enums.py +7 -0
  45. territories_dashboard_lib/tracking_lib/logic.py +78 -0
  46. territories_dashboard_lib/tracking_lib/migrations/0001_initial.py +45 -0
  47. territories_dashboard_lib/tracking_lib/migrations/__init__.py +0 -0
  48. territories_dashboard_lib/tracking_lib/models.py +79 -0
  49. territories_dashboard_lib/website_lib/__init__.py +0 -0
  50. territories_dashboard_lib/website_lib/admin.py +40 -0
  51. territories_dashboard_lib/website_lib/context_processors.py +27 -0
  52. territories_dashboard_lib/website_lib/forms.py +47 -0
  53. territories_dashboard_lib/website_lib/migrations/0001_initial.py +91 -0
  54. territories_dashboard_lib/website_lib/migrations/__init__.py +0 -0
  55. territories_dashboard_lib/website_lib/models.py +148 -0
  56. territories_dashboard_lib/website_lib/navigation.py +124 -0
  57. territories_dashboard_lib/website_lib/params.py +268 -0
  58. territories_dashboard_lib/website_lib/serializers.py +105 -0
  59. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/css/website.css +956 -0
  60. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/chart.js +13 -0
  61. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/chartjs-plugin-datalabels.js +1 -0
  62. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/html2canvas.js +20 -0
  63. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/data.mjs +62 -0
  64. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/history.mjs +98 -0
  65. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/main-values.mjs +30 -0
  66. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/page.mjs +105 -0
  67. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/territory-chart.mjs +141 -0
  68. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/data.mjs +59 -0
  69. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/histogram.mjs +130 -0
  70. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/map.mjs +25 -0
  71. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/page.mjs +96 -0
  72. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/proportions.mjs +77 -0
  73. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/sankey.mjs +27 -0
  74. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/table.mjs +229 -0
  75. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/top10.mjs +76 -0
  76. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/utils.mjs +8 -0
  77. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/dom.mjs +92 -0
  78. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/enums.mjs +104 -0
  79. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/export-graph.mjs +15 -0
  80. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/export.mjs +20 -0
  81. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/filters.mjs +159 -0
  82. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/format.mjs +54 -0
  83. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/side_panel.mjs +103 -0
  84. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/history.mjs +89 -0
  85. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/main-value.mjs +22 -0
  86. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/page.mjs +162 -0
  87. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/statistics.mjs +42 -0
  88. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/utils.mjs +93 -0
  89. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/static/page.mjs +35 -0
  90. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/patternomaly.js +1452 -0
  91. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/react18.js +31 -0
  92. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/reactdom18.js +267 -0
  93. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/supersetEmbed.js +355 -0
  94. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/indicatorMap.bundle.js +2 -0
  95. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/sankeyGraph.bundle.js +2 -0
  96. territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/vendors-node_modules_mapbox-gl_dist_mapbox-gl_js.bundle.js +2 -0
  97. territories_dashboard_lib/website_lib/static_content.py +20 -0
  98. territories_dashboard_lib/website_lib/templates/admin/indicators_lib/indicator/change_form.html +8 -0
  99. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/404.html +10 -0
  100. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/500.html +10 -0
  101. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/base.html +42 -0
  102. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/base.js +47 -0
  103. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/footer.html +96 -0
  104. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/header.html +101 -0
  105. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/header.js +102 -0
  106. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/components/indicateur-card.html +48 -0
  107. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/page.html +52 -0
  108. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/chart-buttons.html +29 -0
  109. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/geo_params.html +71 -0
  110. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/indicator-card.html +52 -0
  111. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/loader.html +28 -0
  112. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel.html +27 -0
  113. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_geo.html +80 -0
  114. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_geo.js +85 -0
  115. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.html +45 -0
  116. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.js +19 -0
  117. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/themes-list.html +9 -0
  118. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/themes-nav.html +33 -0
  119. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/title.html +28 -0
  120. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/filters-reminder.html +19 -0
  121. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/table.html +123 -0
  122. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.html +166 -0
  123. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/components/extremum.html +15 -0
  124. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/components/indicateur-card.html +76 -0
  125. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/page.html +66 -0
  126. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/lexique/page.html +17 -0
  127. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/page.html +14 -0
  128. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/sitemap/page.html +71 -0
  129. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/static/page.html +16 -0
  130. territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/superset/page.html +55 -0
  131. territories_dashboard_lib/website_lib/templatetags/htmlparams.py +75 -0
  132. territories_dashboard_lib/website_lib/templatetags/other_filters.py +30 -0
  133. territories_dashboard_lib/website_lib/views.py +212 -0
  134. {territories_dashboard_lib-0.1.0.dist-info → territories_dashboard_lib-0.1.2.dist-info}/METADATA +1 -1
  135. territories_dashboard_lib-0.1.2.dist-info/RECORD +138 -0
  136. territories_dashboard_lib-0.1.0.dist-info/RECORD +0 -5
  137. {territories_dashboard_lib-0.1.0.dist-info → territories_dashboard_lib-0.1.2.dist-info}/WHEEL +0 -0
  138. {territories_dashboard_lib-0.1.0.dist-info → territories_dashboard_lib-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,148 @@
1
+ from django.db import models
2
+ from martor.models import MartorField
3
+
4
+ from territories_dashboard_lib.commons.models import CommonModel
5
+ from territories_dashboard_lib.website_lib.navigation import (
6
+ parse_footer_navigation,
7
+ parse_header_navigation,
8
+ parse_markdown_link,
9
+ )
10
+ from territories_dashboard_lib.website_lib.static_content import (
11
+ markdown_content_to_html,
12
+ )
13
+
14
+
15
+ class MainConf(CommonModel):
16
+ title = models.TextField(
17
+ verbose_name="Titre principal du site", default="Tableau de bord"
18
+ )
19
+ entity = models.TextField(
20
+ verbose_name="Entité qui possède le site",
21
+ default="",
22
+ help_text="Renseigner le nom de l'entité au même format que le logo officiel en respectant les mises à la ligne.",
23
+ )
24
+ header_navigation = models.TextField(
25
+ verbose_name="Navigation",
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
+
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/>
29
+ <br/><br/>
30
+ [Accueil](/accueil/)
31
+ <br/><br/>
32
+ [Indicateurs territoriaux](/indicateurs/)
33
+ <br/><br/>
34
+ À propos
35
+ [Présentation](/presentation/)
36
+ [Journal des versions](/journal/)
37
+ """,
38
+ default="",
39
+ )
40
+ footer_navigation = models.TextField(
41
+ verbose_name="Navigation",
42
+ help_text="""Navigation du footer, mettre les liens au format markdown, un par ligne. <br/><br/>
43
+ Si le lien est interne, commencer et terminer le lien par un slash comme "/accueil/"<br/><br/>
44
+ Par exemple : <br/><br/>
45
+ [Plan du site](/plan-site/)<br/>
46
+ [Accessibilité](/accessibilite/)<br/>
47
+ [Mentions légales](/mentions-legales/) <br/>
48
+ """,
49
+ default="",
50
+ )
51
+
52
+ @property
53
+ def entity_breaklines(self):
54
+ return self.entity.replace("\n", "<br/>")
55
+
56
+ @property
57
+ def parsed_header_navigation(self):
58
+ return parse_header_navigation(self.header_navigation)
59
+
60
+ @property
61
+ def parsed_footer_navigation(self):
62
+ return parse_footer_navigation(self.footer_navigation)
63
+
64
+ def __str__(self):
65
+ return "Configuration principale"
66
+
67
+ class Meta:
68
+ verbose_name = "Configuration principale"
69
+ verbose_name_plural = "Configurations principales"
70
+
71
+
72
+ class LandingPage(CommonModel):
73
+ title = models.TextField(verbose_name="Titre")
74
+ body = MartorField(verbose_name="Contenu")
75
+ button_link = models.TextField(
76
+ verbose_name="Lien du bouton",
77
+ help_text="Au format markdown, par exemple : <br/><br/>[découvrir les indicateurs](/indicateurs/)",
78
+ )
79
+
80
+ @property
81
+ def body_html(self):
82
+ return markdown_content_to_html(self.body)
83
+
84
+ @property
85
+ def button(self):
86
+ return parse_markdown_link(self.button_link)
87
+
88
+ class Meta:
89
+ verbose_name = "Page d'accueil"
90
+ verbose_name_plural = "Page d'accueil"
91
+
92
+ def __str__(self):
93
+ return "Page d'accueil"
94
+
95
+
96
+ class StaticPage(CommonModel):
97
+ name = models.TextField(verbose_name="Nom de la page")
98
+ url = models.TextField(
99
+ verbose_name="Url de la page",
100
+ help_text="En minuscule avec des espaces remplacés par des traits '-'.<br/>Par exemple : <br/><br/> indicateurs <br/> mentions-legales",
101
+ )
102
+ body = MartorField(verbose_name="Contenu")
103
+
104
+ @property
105
+ def body_html(self):
106
+ return markdown_content_to_html(self.body)
107
+
108
+ class Meta:
109
+ verbose_name = "Page Statique"
110
+ verbose_name_plural = "Pages Statiques"
111
+
112
+ def __str__(self):
113
+ return self.name
114
+
115
+
116
+ class GlossaryItem(CommonModel):
117
+ word = models.TextField(verbose_name="Mot")
118
+ definition = models.TextField(verbose_name="Définition")
119
+
120
+ class Meta:
121
+ verbose_name = "Définition"
122
+ verbose_name_plural = "Page Lexique"
123
+ ordering = ["word"]
124
+
125
+ def __str__(self):
126
+ return self.word
127
+
128
+
129
+ class NoticeBanner(CommonModel):
130
+ title = models.CharField(max_length=64, verbose_name="Titre")
131
+ description = models.CharField(max_length=255, verbose_name="Description")
132
+ link_href = models.CharField(
133
+ null=True,
134
+ blank=True,
135
+ max_length=255,
136
+ verbose_name="Lien",
137
+ help_text="Le lien doit être un lien interne et commencer et terminer par '/', par exemple '/journal/",
138
+ )
139
+ link_text = models.CharField(
140
+ null=True,
141
+ blank=True,
142
+ max_length=64,
143
+ verbose_name="Intitulé du lien",
144
+ help_text="à remplir seulement si un lien est spécifié",
145
+ )
146
+
147
+ class Meta:
148
+ ordering = ["-created_at"]
@@ -0,0 +1,124 @@
1
+ import re
2
+
3
+ from django.apps import apps
4
+ from django.urls import reverse
5
+
6
+
7
+ def parse_header_navigation(header_navigation):
8
+ """
9
+ Retourne une liste de sections. Chaque section est soit :
10
+ - {'title': 'Accueil', 'link': '/accueil/'}
11
+ - {'title': 'À propos', 'links': [{'title': 'Présentation', 'link': '/presentation/'}]}
12
+ """
13
+ menu = []
14
+
15
+ # Découpage robuste en blocs (rubriques), même s'il y a des \r, espaces, etc.
16
+ blocks = re.split(r"\n\s*\n", header_navigation.strip())
17
+
18
+ for block in blocks:
19
+ lines = [line.strip() for line in block.strip().splitlines() if line.strip()]
20
+ if not lines:
21
+ continue
22
+
23
+ title = None
24
+ links = []
25
+
26
+ # Si la première ligne n’est pas un lien markdown, c’est le titre
27
+ first_line = lines[0]
28
+ match = re.match(r"\[(.+?)\]\((/.+?/)\)", first_line)
29
+ if match:
30
+ # Bloc sans titre
31
+ links.append(
32
+ {
33
+ "title": match.group(1).strip(),
34
+ "link": match.group(2).strip(),
35
+ }
36
+ )
37
+ other_lines = lines[1:]
38
+ else:
39
+ # cas spécial superset
40
+ if first_line == "superset":
41
+ Dashboard = apps.get_model("superset_lib", "Dashboard")
42
+ links = [
43
+ {
44
+ "title": d.label,
45
+ "link": reverse(
46
+ "website:superset", kwargs={"dashboard_name": d.short_name}
47
+ )
48
+ + "#header-navigation",
49
+ }
50
+ for d in Dashboard.objects.all().order_by("order")
51
+ ]
52
+ menu.append({"title": "Portraits de territoires", "links": links})
53
+ continue
54
+ # La première ligne est le titre
55
+ title = first_line
56
+ other_lines = lines[1:]
57
+
58
+ # Parse les lignes suivantes comme liens
59
+ for line in other_lines:
60
+ match = re.match(r"\[(.+?)\]\((/.+?/)\)", line)
61
+ if not match:
62
+ raise ValueError(
63
+ f"La ligne « {line} » n’est pas un lien valide Markdown."
64
+ )
65
+ links.append(
66
+ {
67
+ "title": match.group(1).strip(),
68
+ "link": match.group(2).strip(),
69
+ }
70
+ )
71
+
72
+ # Construction finale
73
+ if title:
74
+ if not links:
75
+ raise ValueError(f"La rubrique « {title} » ne contient aucun lien.")
76
+ menu.append({"title": title, "links": links})
77
+ elif len(links) == 1:
78
+ menu.append(links[0])
79
+ else:
80
+ raise ValueError("Un groupe de plusieurs liens doit avoir un titre.")
81
+ return menu
82
+
83
+
84
+ def parse_footer_navigation(footer_navigation):
85
+ """
86
+ Retourne une liste de liens : chacun est un dict avec :
87
+ - title : texte à afficher
88
+ - href : URL du lien
89
+ - is_external : True/False
90
+ """
91
+ links = []
92
+ for line in footer_navigation.strip().splitlines():
93
+ line = line.strip()
94
+ if not line:
95
+ continue
96
+
97
+ match = re.match(r"\[(.+?)\]\((.+?)\)", line)
98
+ if not match:
99
+ raise ValueError(f"Ligne invalide dans le footer : « {line} »")
100
+
101
+ title, href = match.group(1).strip(), match.group(2).strip()
102
+
103
+ is_external = href.startswith("http")
104
+ links.append(
105
+ {
106
+ "title": title,
107
+ "href": href,
108
+ "is_external": is_external,
109
+ }
110
+ )
111
+ return links
112
+
113
+
114
+ def parse_markdown_link(text):
115
+ text = text.strip()
116
+ if not text:
117
+ raise ValueError("Le lien est invalide (vide)")
118
+
119
+ match = re.match(r"\[(.+?)\]\((.+?)\)", text)
120
+ if not match:
121
+ raise ValueError(f"Ligne invalide dans le footer : « {text} »")
122
+
123
+ title, href = match.group(1).strip(), match.group(2).strip()
124
+ return {"title": title, "href": href}
@@ -0,0 +1,268 @@
1
+ from functools import wraps
2
+
3
+ from territories_dashboard_lib.indicators_lib.enums import (
4
+ FRANCE_GEOLEVEL_TITLES,
5
+ MESH_TITLES,
6
+ FranceGeoLevel,
7
+ MeshLevel,
8
+ )
9
+ from territories_dashboard_lib.indicators_lib.query.utils import run_custom_query
10
+
11
+ TERRITORY_DEFAULT = {
12
+ "id": FranceGeoLevel.METRO,
13
+ "mesh": MeshLevel.National,
14
+ "name": FRANCE_GEOLEVEL_TITLES[FranceGeoLevel.METRO],
15
+ }
16
+
17
+ CMP_TERRITORY_DEFAULT = {
18
+ "id": FranceGeoLevel.All,
19
+ "mesh": MeshLevel.National,
20
+ "name": FRANCE_GEOLEVEL_TITLES[FranceGeoLevel.All],
21
+ }
22
+
23
+ TERRITORY_PARAM_NAME = "territory"
24
+ CMP_TERRITORY_PARAM_NAME = "cmp-territory"
25
+ MESH_PARAM_NAME = "mesh"
26
+ ONE_WEEK_IN_SECONDS = 604800
27
+
28
+
29
+ class BadParam(Exception):
30
+ pass
31
+
32
+
33
+ class ParamsHandler:
34
+ def __init__(self, request):
35
+ self.request = request
36
+ self.territory_id = None
37
+ self.territory_mesh = None
38
+ self.territory_name = None
39
+ self.cmp_territory_id = None
40
+ self.cmp_territory_mesh = None
41
+ self.cmp_territory_name = None
42
+ self.mesh = None
43
+ self.meshes = []
44
+ self.comparison = request.resolver_match.view_name == "website:comparison"
45
+
46
+ ######################## Territory
47
+
48
+ def parse_territory(self, territory):
49
+ if not territory:
50
+ raise BadParam
51
+ parts = territory.split("-")
52
+ allowed_chars = set(
53
+ "0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz, "
54
+ )
55
+ if (
56
+ len(parts) != 2
57
+ or not all(char in allowed_chars for char in parts[0])
58
+ or parts[1] not in MeshLevel
59
+ ):
60
+ raise BadParam
61
+ return parts[0], parts[1]
62
+
63
+ def get_territory_name(self, territory_id, territory_mesh):
64
+ if territory_mesh == MeshLevel.National:
65
+ return FRANCE_GEOLEVEL_TITLES[territory_id]
66
+ mesh = "DEPCOM" if territory_mesh == "com" else territory_mesh.upper()
67
+ query = f"""
68
+ SELECT DISTINCT "NOM_{mesh}" AS name FROM arborescence_geo
69
+ WHERE "{mesh}" = '{territory_id}'
70
+ """
71
+ try:
72
+ results = run_custom_query(query)
73
+ except Exception:
74
+ raise BadParam
75
+ if not results:
76
+ raise BadParam
77
+ return results[0]["name"]
78
+
79
+ def get_territory_default(self, cookie_territory):
80
+ try:
81
+ territory_id, territory_mesh = self.parse_territory(cookie_territory)
82
+ territory_name = self.get_territory_name(territory_id, territory_mesh)
83
+ except BadParam:
84
+ return (
85
+ TERRITORY_DEFAULT["id"],
86
+ TERRITORY_DEFAULT["mesh"],
87
+ TERRITORY_DEFAULT["name"],
88
+ )
89
+ return territory_id, territory_mesh, territory_name
90
+
91
+ def choose_territory(self):
92
+ url_territory = self.request.GET.get(TERRITORY_PARAM_NAME)
93
+ try:
94
+ territory_id, territory_mesh = self.parse_territory(url_territory)
95
+ self.territory_name = self.get_territory_name(territory_id, territory_mesh)
96
+ self.territory_id = territory_id
97
+ self.territory_mesh = territory_mesh
98
+ except BadParam:
99
+ cookie_territory = self.request.COOKIES.get(TERRITORY_PARAM_NAME)
100
+ self.territory_id, self.territory_mesh, self.territory_name = (
101
+ self.get_territory_default(cookie_territory)
102
+ )
103
+
104
+ def construct_territory_value(self):
105
+ return f"{self.territory_id}-{self.territory_mesh}"
106
+
107
+ def territory_is_not_default(self):
108
+ return (
109
+ self.territory_id != TERRITORY_DEFAULT["id"]
110
+ or self.territory_mesh != TERRITORY_DEFAULT["mesh"]
111
+ )
112
+
113
+ ######################## Compared Territory
114
+
115
+ def choose_cmp_territory(self):
116
+ url_territory = self.request.GET.get(CMP_TERRITORY_PARAM_NAME)
117
+ try:
118
+ territory_id, territory_mesh = self.parse_territory(url_territory)
119
+ self.cmp_territory_name = self.get_territory_name(
120
+ territory_id, territory_mesh
121
+ )
122
+ self.cmp_territory_id = territory_id
123
+ self.cmp_territory_mesh = territory_mesh
124
+ except BadParam:
125
+ cookie_territory = self.request.COOKIES.get(CMP_TERRITORY_PARAM_NAME)
126
+ self.cmp_territory_id, self.cmp_territory_mesh, self.cmp_territory_name = (
127
+ self.get_territory_default(cookie_territory)
128
+ )
129
+
130
+ def construct_cmp_territory_value(self):
131
+ return f"{self.cmp_territory_id}-{self.cmp_territory_mesh}"
132
+
133
+ def cmp_territory_is_not_default(self):
134
+ return (
135
+ self.cmp_territory_id != CMP_TERRITORY_DEFAULT["id"]
136
+ or self.cmp_territory_mesh != CMP_TERRITORY_DEFAULT["mesh"]
137
+ )
138
+
139
+ ######################## Mesh
140
+
141
+ def get_max_territory_mesh(self):
142
+ meshes = [m for m in MeshLevel]
143
+ if self.comparison is False:
144
+ return self.territory_mesh
145
+ if meshes.index(self.cmp_territory_mesh) > meshes.index(self.territory_mesh):
146
+ return self.cmp_territory_mesh
147
+ return self.territory_mesh
148
+
149
+ def is_not_valid_mesh(self, mesh):
150
+ if mesh is None:
151
+ return True
152
+ meshes = [m for m in MeshLevel]
153
+ max_territory_mesh = self.get_max_territory_mesh()
154
+ is_not_valid = meshes.index(max_territory_mesh) >= meshes.index(mesh)
155
+ return is_not_valid
156
+
157
+ def get_default_mesh(self):
158
+ max_territory_mesh = self.get_max_territory_mesh()
159
+ meshes = [m for m in MeshLevel]
160
+ if meshes.index(max_territory_mesh) == len(meshes) - 1:
161
+ default_mesh = meshes[-1]
162
+ else:
163
+ default_mesh = meshes[meshes.index(max_territory_mesh) + 1]
164
+ return default_mesh
165
+
166
+ def get_cookie_mesh(self):
167
+ value = self.request.COOKIES.get(MESH_PARAM_NAME)
168
+ if not value:
169
+ return None
170
+ parts = value.split("-")
171
+ if len(parts) != 2 or parts[0] not in MeshLevel or parts[1] not in MeshLevel:
172
+ return None
173
+ if self.territory_mesh != parts[0]:
174
+ return None
175
+ return parts[1]
176
+
177
+ def choose_mesh(self):
178
+ url_mesh = self.request.GET.get(MESH_PARAM_NAME)
179
+ url_mesh = url_mesh if url_mesh in MeshLevel else None
180
+ if self.is_not_valid_mesh(url_mesh):
181
+ cookie_mesh = self.get_cookie_mesh()
182
+ if self.is_not_valid_mesh(cookie_mesh):
183
+ self.mesh = self.get_default_mesh()
184
+ else:
185
+ self.mesh = cookie_mesh
186
+ else:
187
+ self.mesh = url_mesh
188
+ max_territory_mesh = self.get_max_territory_mesh()
189
+ meshes = [
190
+ m
191
+ for m in MeshLevel
192
+ if not (m == MeshLevel.Town and max_territory_mesh == MeshLevel.National)
193
+ ]
194
+ self.meshes = meshes[
195
+ min(meshes.index(max_territory_mesh) + 1, len(meshes) - 1) :
196
+ ]
197
+
198
+ ######################## Commons
199
+
200
+ def set_cookie(self, response):
201
+ if self.territory_is_not_default():
202
+ response.set_cookie(
203
+ key=TERRITORY_PARAM_NAME,
204
+ value=self.construct_territory_value(),
205
+ max_age=ONE_WEEK_IN_SECONDS,
206
+ )
207
+ else:
208
+ response.delete_cookie(TERRITORY_PARAM_NAME)
209
+ if self.cmp_territory_is_not_default():
210
+ response.set_cookie(
211
+ key=CMP_TERRITORY_PARAM_NAME,
212
+ value=self.construct_cmp_territory_value(),
213
+ max_age=ONE_WEEK_IN_SECONDS,
214
+ )
215
+ else:
216
+ response.delete_cookie(CMP_TERRITORY_PARAM_NAME)
217
+ if self.mesh != self.get_default_mesh():
218
+ response.set_cookie(
219
+ key=MESH_PARAM_NAME,
220
+ value=f"{self.territory_mesh}-{self.mesh}",
221
+ max_age=ONE_WEEK_IN_SECONDS,
222
+ )
223
+ else:
224
+ response.delete_cookie(MESH_PARAM_NAME)
225
+
226
+ def add_to_context(self, context):
227
+ url_params = []
228
+ if self.territory_is_not_default():
229
+ url_params.append(
230
+ f"{TERRITORY_PARAM_NAME}={self.construct_territory_value()}"
231
+ )
232
+ if self.comparison and self.cmp_territory_is_not_default():
233
+ url_params.append(
234
+ f"{CMP_TERRITORY_PARAM_NAME}={self.construct_cmp_territory_value()}"
235
+ )
236
+ if self.mesh != self.get_default_mesh():
237
+ url_params.append(f"{MESH_PARAM_NAME}={self.mesh}")
238
+ context["params"] = {
239
+ "territory_id": self.territory_id,
240
+ "territory_mesh": self.territory_mesh,
241
+ "territory_name": self.territory_name,
242
+ "cmp_territory_id": self.cmp_territory_id,
243
+ "cmp_territory_mesh": self.cmp_territory_mesh,
244
+ "cmp_territory_name": self.cmp_territory_name,
245
+ "mesh": self.mesh,
246
+ "meshes": self.meshes,
247
+ "meshes_titles": MESH_TITLES,
248
+ "all_meshes": [m for m in MeshLevel],
249
+ "url_params": "&".join(url_params),
250
+ }
251
+
252
+
253
+ def with_params(view_func):
254
+ @wraps(view_func)
255
+ def wrapper(request, *args, **kwargs):
256
+ handler = ParamsHandler(request)
257
+ handler.choose_territory()
258
+ handler.choose_cmp_territory()
259
+ handler.choose_mesh()
260
+
261
+ context = kwargs.get("context", {})
262
+ handler.add_to_context(context)
263
+
264
+ response = view_func(request, *args, context=context, **kwargs)
265
+ handler.set_cookie(response)
266
+ return response
267
+
268
+ return wrapper
@@ -0,0 +1,105 @@
1
+ from django.db.models import Count, Q
2
+ from martor.utils import markdownify
3
+
4
+
5
+ def serialize_indicator(indicator):
6
+ return {
7
+ "id": indicator.id,
8
+ "name": indicator.name,
9
+ "title": indicator.title,
10
+ "description": indicator.description,
11
+ "source": indicator.source,
12
+ "unite": indicator.unite,
13
+ "unite_nom_accessible": indicator.unite_nom_accessible,
14
+ "unite_alternative": indicator.unite_alternative,
15
+ "unite_alternative_nom_accessible": indicator.unite_alternative_nom_accessible,
16
+ "show_alternative": indicator.show_alternative,
17
+ "methodo_html": markdownify(indicator.methodo),
18
+ "db_table_prefix": indicator.db_table_prefix,
19
+ "is_composite": indicator.is_composite,
20
+ "aggregation_constant": indicator.aggregation_constant,
21
+ "flows_db_table_prefix": indicator.flows_db_table_prefix,
22
+ "flows_dimension": indicator.flows_dimension,
23
+ "theme": indicator.sub_theme.theme.title,
24
+ "filters": serializer_filters(indicator),
25
+ "secondary_indicator": {
26
+ "name": indicator.secondary_indicator.name,
27
+ "title": indicator.secondary_indicator.title,
28
+ "aggregation_constant": indicator.secondary_indicator.aggregation_constant,
29
+ "db_table_prefix": indicator.secondary_indicator.db_table_prefix,
30
+ "unite": indicator.secondary_indicator.unite,
31
+ "unite_alternative": indicator.secondary_indicator.unite_alternative,
32
+ "is_composite": indicator.secondary_indicator.is_composite,
33
+ "filters": serializer_filters(indicator.secondary_indicator),
34
+ }
35
+ if indicator.secondary_indicator
36
+ else None,
37
+ "geo_features": [
38
+ {
39
+ "id": gf.id,
40
+ "name": gf.name,
41
+ "title": gf.title,
42
+ "unite": gf.unite,
43
+ "geo_type": gf.geo_type,
44
+ "point_icon_svg": gf.point_icon_svg,
45
+ "color": gf.color,
46
+ "show_on_fr_level": gf.show_on_fr_level,
47
+ "items": [
48
+ {
49
+ "name": item.name,
50
+ "label": item.label,
51
+ "filterable": item.filterable,
52
+ }
53
+ for item in gf.items.all()
54
+ ],
55
+ }
56
+ for gf in indicator.geo_features.all()
57
+ ],
58
+ }
59
+
60
+
61
+ def serializer_filters(indicator):
62
+ return [
63
+ (
64
+ {
65
+ "db_name": dimension.db_name,
66
+ "title": dimension.title,
67
+ "is_breakdown": dimension.is_breakdown,
68
+ },
69
+ [
70
+ {
71
+ "db_name": filtr.db_name,
72
+ "default": filtr.default,
73
+ }
74
+ for filtr in dimension.filters.all()
75
+ ],
76
+ )
77
+ for dimension in indicator.dimensions.all()
78
+ ]
79
+
80
+
81
+ def serialize_sub_themes(theme):
82
+ sub_themes = (
83
+ theme.sub_themes.all()
84
+ .annotate(
85
+ active_indicators_count=Count(
86
+ "indicators", filter=Q(indicators__is_active=True)
87
+ )
88
+ )
89
+ .filter(active_indicators_count__gt=0)
90
+ .order_by("index_in_theme")
91
+ )
92
+ sub_themes = [
93
+ {
94
+ "name": sub_theme.name,
95
+ "title": sub_theme.title,
96
+ "indicators": [
97
+ serialize_indicator(indicator)
98
+ for indicator in sub_theme.indicators.filter(is_active=True).order_by(
99
+ "index_in_theme"
100
+ )
101
+ ],
102
+ }
103
+ for sub_theme in sub_themes
104
+ ]
105
+ return sub_themes