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.
- territories_dashboard_lib/commons/__init__.py +0 -0
- territories_dashboard_lib/commons/decorators.py +36 -0
- territories_dashboard_lib/commons/models.py +9 -0
- territories_dashboard_lib/geo_lib/__init__.py +0 -0
- territories_dashboard_lib/geo_lib/admin.py +64 -0
- territories_dashboard_lib/geo_lib/enums.py +7 -0
- territories_dashboard_lib/geo_lib/migrations/0001_initial.py +51 -0
- territories_dashboard_lib/geo_lib/migrations/__init__.py +0 -0
- territories_dashboard_lib/geo_lib/models.py +58 -0
- territories_dashboard_lib/geo_lib/urls.py +27 -0
- territories_dashboard_lib/geo_lib/views.py +239 -0
- territories_dashboard_lib/indicators_lib/__init__.py +0 -0
- territories_dashboard_lib/indicators_lib/admin.py +140 -0
- territories_dashboard_lib/indicators_lib/enums.py +59 -0
- territories_dashboard_lib/indicators_lib/export.py +29 -0
- territories_dashboard_lib/indicators_lib/format.py +34 -0
- territories_dashboard_lib/indicators_lib/methodo_pdf.py +99 -0
- territories_dashboard_lib/indicators_lib/migrations/0001_initial.py +138 -0
- territories_dashboard_lib/indicators_lib/migrations/__init__.py +0 -0
- territories_dashboard_lib/indicators_lib/models.py +230 -0
- territories_dashboard_lib/indicators_lib/payloads.py +54 -0
- territories_dashboard_lib/indicators_lib/query/commons.py +223 -0
- territories_dashboard_lib/indicators_lib/query/comparison.py +70 -0
- territories_dashboard_lib/indicators_lib/query/details.py +64 -0
- territories_dashboard_lib/indicators_lib/query/histogram.py +82 -0
- territories_dashboard_lib/indicators_lib/query/indicator_card.py +102 -0
- territories_dashboard_lib/indicators_lib/query/top_10.py +100 -0
- territories_dashboard_lib/indicators_lib/query/utils.py +20 -0
- territories_dashboard_lib/indicators_lib/refresh_filters.py +17 -0
- territories_dashboard_lib/indicators_lib/table.py +154 -0
- territories_dashboard_lib/indicators_lib/urls.py +97 -0
- territories_dashboard_lib/indicators_lib/views.py +490 -0
- territories_dashboard_lib/superset_lib/__init__.py +0 -0
- territories_dashboard_lib/superset_lib/admin.py +22 -0
- territories_dashboard_lib/superset_lib/guest_token.py +64 -0
- territories_dashboard_lib/superset_lib/logic.py +67 -0
- territories_dashboard_lib/superset_lib/migrations/0001_initial.py +45 -0
- territories_dashboard_lib/superset_lib/migrations/__init__.py +0 -0
- territories_dashboard_lib/superset_lib/models.py +52 -0
- territories_dashboard_lib/superset_lib/serializers.py +10 -0
- territories_dashboard_lib/superset_lib/urls.py +10 -0
- territories_dashboard_lib/superset_lib/views.py +19 -0
- territories_dashboard_lib/tracking_lib/__init__.py +0 -0
- territories_dashboard_lib/tracking_lib/enums.py +7 -0
- territories_dashboard_lib/tracking_lib/logic.py +78 -0
- territories_dashboard_lib/tracking_lib/migrations/0001_initial.py +45 -0
- territories_dashboard_lib/tracking_lib/migrations/__init__.py +0 -0
- territories_dashboard_lib/tracking_lib/models.py +79 -0
- territories_dashboard_lib/website_lib/__init__.py +0 -0
- territories_dashboard_lib/website_lib/admin.py +40 -0
- territories_dashboard_lib/website_lib/context_processors.py +27 -0
- territories_dashboard_lib/website_lib/forms.py +47 -0
- territories_dashboard_lib/website_lib/migrations/0001_initial.py +91 -0
- territories_dashboard_lib/website_lib/migrations/__init__.py +0 -0
- territories_dashboard_lib/website_lib/models.py +148 -0
- territories_dashboard_lib/website_lib/navigation.py +124 -0
- territories_dashboard_lib/website_lib/params.py +268 -0
- territories_dashboard_lib/website_lib/serializers.py +105 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/css/website.css +956 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/chart.js +13 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/chartjs-plugin-datalabels.js +1 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/html2canvas.js +20 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/data.mjs +62 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/history.mjs +98 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/main-values.mjs +30 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/page.mjs +105 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/comparaison/territory-chart.mjs +141 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/data.mjs +59 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/histogram.mjs +130 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/map.mjs +25 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/page.mjs +96 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/proportions.mjs +77 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/sankey.mjs +27 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/table.mjs +229 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/top10.mjs +76 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/details/utils.mjs +8 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/dom.mjs +92 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/enums.mjs +104 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/export-graph.mjs +15 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/export.mjs +20 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/filters.mjs +159 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/format.mjs +54 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/side_panel.mjs +103 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/history.mjs +89 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/main-value.mjs +22 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/page.mjs +162 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/theme/statistics.mjs +42 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/indicators/utils.mjs +93 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/pages/static/page.mjs +35 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/patternomaly.js +1452 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/react18.js +31 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/reactdom18.js +267 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/js/supersetEmbed.js +355 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/indicatorMap.bundle.js +2 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/sankeyGraph.bundle.js +2 -0
- territories_dashboard_lib/website_lib/static/territories_dashboard_lib/website/react/vendors-node_modules_mapbox-gl_dist_mapbox-gl_js.bundle.js +2 -0
- territories_dashboard_lib/website_lib/static_content.py +20 -0
- territories_dashboard_lib/website_lib/templates/admin/indicators_lib/indicator/change_form.html +8 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/404.html +10 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/500.html +10 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/base.html +42 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/base.js +47 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/footer.html +96 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/header.html +101 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/layout/header.js +102 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/components/indicateur-card.html +48 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/comparaison/[theme]/page.html +52 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/chart-buttons.html +29 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/geo_params.html +71 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/indicator-card.html +52 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/loader.html +28 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel.html +27 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_geo.html +80 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_geo.js +85 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.html +45 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/side_panel_methodo.js +19 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/themes-list.html +9 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/themes-nav.html +33 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/components/title.html +28 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/filters-reminder.html +19 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/components/table.html +123 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/details/page.html +166 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/components/extremum.html +15 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/components/indicateur-card.html +76 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/indicators/themes/page.html +66 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/lexique/page.html +17 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/page.html +14 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/sitemap/page.html +71 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/static/page.html +16 -0
- territories_dashboard_lib/website_lib/templates/territories_dashboard_lib/website/pages/superset/page.html +55 -0
- territories_dashboard_lib/website_lib/templatetags/htmlparams.py +75 -0
- territories_dashboard_lib/website_lib/templatetags/other_filters.py +30 -0
- territories_dashboard_lib/website_lib/views.py +212 -0
- {territories_dashboard_lib-0.1.0.dist-info → territories_dashboard_lib-0.1.2.dist-info}/METADATA +1 -1
- territories_dashboard_lib-0.1.2.dist-info/RECORD +138 -0
- territories_dashboard_lib-0.1.0.dist-info/RECORD +0 -5
- {territories_dashboard_lib-0.1.0.dist-info → territories_dashboard_lib-0.1.2.dist-info}/WHEEL +0 -0
- {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
|