rer.linkmap 1.0.1__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.
Files changed (52) hide show
  1. rer/linkmap/__init__.py +13 -0
  2. rer/linkmap/browser/__init__.py +0 -0
  3. rer/linkmap/browser/configure.zcml +21 -0
  4. rer/linkmap/browser/views.py +109 -0
  5. rer/linkmap/configure.zcml +28 -0
  6. rer/linkmap/content/__init__.py +0 -0
  7. rer/linkmap/controlpanels/__init__.py +0 -0
  8. rer/linkmap/controlpanels/configure.zcml +14 -0
  9. rer/linkmap/controlpanels/settings.py +189 -0
  10. rer/linkmap/dependencies.zcml +7 -0
  11. rer/linkmap/indexers/__init__.py +0 -0
  12. rer/linkmap/indexers/configure.zcml +7 -0
  13. rer/linkmap/interfaces.py +7 -0
  14. rer/linkmap/linkmap.py +143 -0
  15. rer/linkmap/locales/__init__.py +0 -0
  16. rer/linkmap/locales/__main__.py +69 -0
  17. rer/linkmap/locales/en/LC_MESSAGES/redturtle.linkmap.po +45 -0
  18. rer/linkmap/locales/it/LC_MESSAGES/redturtle.linkmap.po +468 -0
  19. rer/linkmap/locales/redturtle.linkmap.pot +471 -0
  20. rer/linkmap/permissions.zcml +5 -0
  21. rer/linkmap/profiles/default/browserlayer.xml +6 -0
  22. rer/linkmap/profiles/default/catalog.xml +13 -0
  23. rer/linkmap/profiles/default/controlpanel.xml +20 -0
  24. rer/linkmap/profiles/default/diff_tool.xml +6 -0
  25. rer/linkmap/profiles/default/metadata.xml +7 -0
  26. rer/linkmap/profiles/default/registry/main.xml +8 -0
  27. rer/linkmap/profiles/default/repositorytool.xml +6 -0
  28. rer/linkmap/profiles/default/rolemap.xml +6 -0
  29. rer/linkmap/profiles/default/types/.gitkeep +0 -0
  30. rer/linkmap/profiles/default/types.xml +10 -0
  31. rer/linkmap/profiles/uninstall/browserlayer.xml +6 -0
  32. rer/linkmap/profiles.zcml +32 -0
  33. rer/linkmap/restapi/__init__.py +0 -0
  34. rer/linkmap/restapi/configure.zcml +8 -0
  35. rer/linkmap/restapi/services/__init__.py +0 -0
  36. rer/linkmap/restapi/services/configure.zcml +13 -0
  37. rer/linkmap/restapi/services/controlpanel.py +19 -0
  38. rer/linkmap/serializers/__init__.py +0 -0
  39. rer/linkmap/serializers/configure.zcml +10 -0
  40. rer/linkmap/serializers/summary.py +10 -0
  41. rer/linkmap/setuphandlers/__init__.py +17 -0
  42. rer/linkmap/testing.py +49 -0
  43. rer/linkmap/upgrades/__init__.py +0 -0
  44. rer/linkmap/upgrades/configure.zcml +21 -0
  45. rer/linkmap/vocabularies/__init__.py +0 -0
  46. rer/linkmap/vocabularies/configure.zcml +5 -0
  47. rer_linkmap-1.0.1.dist-info/METADATA +61 -0
  48. rer_linkmap-1.0.1.dist-info/RECORD +52 -0
  49. rer_linkmap-1.0.1.dist-info/WHEEL +4 -0
  50. rer_linkmap-1.0.1.dist-info/entry_points.txt +2 -0
  51. rer_linkmap-1.0.1.dist-info/licenses/LICENSE.GPL +339 -0
  52. rer_linkmap-1.0.1.dist-info/licenses/LICENSE.md +15 -0
@@ -0,0 +1,13 @@
1
+ """Init and utils."""
2
+
3
+ from zope.i18nmessageid import MessageFactory
4
+
5
+ import logging
6
+
7
+ __version__ = "1.0.1"
8
+
9
+ PACKAGE_NAME = "rer.linkmap"
10
+
11
+ _ = MessageFactory(PACKAGE_NAME)
12
+
13
+ logger = logging.getLogger(PACKAGE_NAME)
File without changes
@@ -0,0 +1,21 @@
1
+ <configure
2
+ xmlns="http://namespaces.zope.org/zope"
3
+ xmlns:browser="http://namespaces.zope.org/browser"
4
+ i18n_domain="rer.linkmap"
5
+ >
6
+
7
+ <browser:page
8
+ name="at_map.json"
9
+ for="Products.CMFPlone.interfaces.IPloneSiteRoot"
10
+ class=".views.ATMapJSONView"
11
+ permission="zope2.View"
12
+ />
13
+
14
+ <browser:page
15
+ name="at_map.xml"
16
+ for="Products.CMFPlone.interfaces.IPloneSiteRoot"
17
+ class=".views.ATMapXMLView"
18
+ permission="zope2.View"
19
+ />
20
+
21
+ </configure>
@@ -0,0 +1,109 @@
1
+ from Acquisition import aq_base
2
+ from json import dumps
3
+ from plone import api
4
+ from Products.Five import BrowserView
5
+ from rer.linkmap.linkmap import CATEGORY_C1
6
+ from rer.linkmap.linkmap import CATEGORY_KEYS
7
+ from rer.linkmap.linkmap import ensure_required_root_url
8
+ from rer.linkmap.linkmap import is_valid_date
9
+ from rer.linkmap.linkmap import is_valid_url
10
+ from rer.linkmap.linkmap import today_date_string
11
+ from xml.sax.saxutils import escape
12
+ from zExceptions import NotFound
13
+
14
+ REGISTRY_PREFIX = "rer.linkmap.controlpanels.settings.ILinkMapSettings"
15
+ ROOT_KEY = "amministrazione_trasparente"
16
+
17
+
18
+ def get_registry_value(field_name, default=""):
19
+ return api.portal.get_registry_record(
20
+ f"{REGISTRY_PREFIX}.{field_name}",
21
+ default=default,
22
+ )
23
+
24
+
25
+ def get_expose_json():
26
+ return get_registry_value("expose_json", default=True)
27
+
28
+
29
+ def get_expose_xml():
30
+ return get_registry_value("expose_xml", default=True)
31
+
32
+
33
+ def get_data_ultima_modifica():
34
+ value = get_registry_value("data_ultima_modifica")
35
+ if is_valid_date(value):
36
+ return value
37
+ return today_date_string()
38
+
39
+
40
+ def build_category_map_from_fields():
41
+ """Build category map from individual field values."""
42
+ category_map = {}
43
+ for key in CATEGORY_KEYS:
44
+ value = get_registry_value(key)
45
+ if value and is_valid_url(value.strip()):
46
+ category_map[key] = value.strip()
47
+ return category_map
48
+
49
+
50
+ def build_payload():
51
+ data_ultima_modifica = get_data_ultima_modifica()
52
+ category_map = build_category_map_from_fields()
53
+ ensure_required_root_url(category_map, api.portal.get().absolute_url())
54
+
55
+ payload = {
56
+ "data_ultima_modifica": data_ultima_modifica,
57
+ CATEGORY_C1: category_map,
58
+ }
59
+ return payload
60
+
61
+
62
+ def build_xml(payload):
63
+ root_open = '<amministrazione_trasparente xmlns="https://guida-servizi.anticorruzione.it/trasparenza">'
64
+ lines = [
65
+ '<?xml version="1.0" encoding="utf-8"?>',
66
+ root_open,
67
+ f" <data_ultima_modifica>{escape(payload['data_ultima_modifica'])}"
68
+ "</data_ultima_modifica>",
69
+ " <map>",
70
+ ]
71
+
72
+ category = CATEGORY_C1
73
+
74
+ lines.append(f" <{category}>")
75
+ map_values = payload.get(category, {})
76
+ for map_key in CATEGORY_KEYS:
77
+ map_url = map_values.get(map_key)
78
+ if not map_url:
79
+ continue
80
+ lines.append(f" <{map_key}>{escape(map_url)}</{map_key}>")
81
+ lines.append(f" </{category}>")
82
+ lines.extend([" </map>", "</amministrazione_trasparente>"])
83
+ return "\n".join(lines)
84
+
85
+
86
+ class ATMapJSONView(BrowserView):
87
+ def __call__(self):
88
+ if aq_base(self.context) is not aq_base(api.portal.get()):
89
+ raise NotFound()
90
+ if not get_expose_json():
91
+ raise NotFound("JSON view is not enabled")
92
+ self.request.response.setHeader(
93
+ "Content-Type", "application/json; charset=utf-8"
94
+ )
95
+ payload = build_payload()
96
+ return dumps(payload, indent=2, ensure_ascii=False, sort_keys=True)
97
+
98
+
99
+ class ATMapXMLView(BrowserView):
100
+ def __call__(self):
101
+ if aq_base(self.context) is not aq_base(api.portal.get()):
102
+ raise NotFound()
103
+ if not get_expose_xml():
104
+ raise NotFound("XML view is not enabled")
105
+ self.request.response.setHeader(
106
+ "Content-Type", "application/xml; charset=utf-8"
107
+ )
108
+ payload = build_payload()
109
+ return build_xml(payload)
@@ -0,0 +1,28 @@
1
+ <configure
2
+ xmlns="http://namespaces.zope.org/zope"
3
+ xmlns:i18n="http://namespaces.zope.org/i18n"
4
+ i18n_domain="rer.linkmap"
5
+ >
6
+
7
+ <i18n:registerTranslations directory="locales" />
8
+
9
+ <include
10
+ package="Products.CMFCore"
11
+ file="permissions.zcml"
12
+ />
13
+
14
+
15
+ <include file="dependencies.zcml" />
16
+ <include file="profiles.zcml" />
17
+ <include file="permissions.zcml" />
18
+
19
+ <include package=".browser" />
20
+ <include package=".controlpanels" />
21
+ <include package=".indexers" />
22
+ <include package=".restapi" />
23
+ <include package=".serializers" />
24
+ <include package=".vocabularies" />
25
+
26
+ <!-- -*- extra stuff goes here -*- -->
27
+
28
+ </configure>
File without changes
File without changes
@@ -0,0 +1,14 @@
1
+ <configure
2
+ xmlns="http://namespaces.zope.org/zope"
3
+ xmlns:browser="http://namespaces.zope.org/browser"
4
+ i18n_domain="rer.linkmap"
5
+ >
6
+
7
+ <browser:page
8
+ name="linkmap-settings"
9
+ for="Products.CMFPlone.interfaces.IPloneSiteRoot"
10
+ class=".settings.LinkMapControlPanelView"
11
+ permission="cmf.ManagePortal"
12
+ />
13
+
14
+ </configure>
@@ -0,0 +1,189 @@
1
+ from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper
2
+ from plone.app.registry.browser.controlpanel import RegistryEditForm
3
+ from rer.linkmap import _
4
+ from rer.linkmap.linkmap import is_valid_date
5
+ from zope.interface import Interface
6
+ from zope.interface import invariant
7
+ from zope.interface import Invalid
8
+ from zope.schema import Bool
9
+ from zope.schema import TextLine
10
+ from zope.schema import URI
11
+
12
+
13
+ def _category_field(key):
14
+ return URI(
15
+ title=_(
16
+ f"{key}_title",
17
+ default=key.replace("_", " ").title(),
18
+ ),
19
+ description=_(
20
+ f"{key}_description",
21
+ default="Inserisci l'url per questo dato",
22
+ ),
23
+ required=False,
24
+ )
25
+
26
+
27
+ class ILinkMapSettings(Interface):
28
+ expose_json = Bool(
29
+ title=_("expose_json_title", default="Esponi vista JSON"),
30
+ description=_(
31
+ "expose_json_description",
32
+ default="Se selezionato, la mappa dei link sarà disponibile nel "
33
+ "formato JSON.",
34
+ ),
35
+ required=False,
36
+ default=True,
37
+ )
38
+
39
+ expose_xml = Bool(
40
+ title=_("expose_xml_title", default="Esponi vista XML"),
41
+ description=_(
42
+ "expose_xml_description",
43
+ default="Se selezionato, la mappa dei link sarà disponibile nel "
44
+ "formato XML.",
45
+ ),
46
+ required=False,
47
+ default=True,
48
+ )
49
+
50
+ data_ultima_modifica = TextLine(
51
+ title=_("data_ultima_modifica_title", default="Data ultima modifica"),
52
+ description=_(
53
+ "data_ultima_modifica_description",
54
+ default="Data in formato GG/MM/AAAA. Se non impostata verra usata "
55
+ "la data corrente.",
56
+ ),
57
+ required=False,
58
+ default="",
59
+ )
60
+
61
+ amministrazione_trasparente = _category_field("amministrazione_trasparente")
62
+ disposizioni_generali = _category_field("disposizioni_generali")
63
+ consulenti_collaboratori = _category_field("consulenti_collaboratori")
64
+ bandi_di_concorso = _category_field("bandi_di_concorso")
65
+ enti_controllati = _category_field("enti_controllati")
66
+ sovvenzioni = _category_field("sovvenzioni")
67
+ beni_immobili = _category_field("beni_immobili")
68
+ opere_pubbliche = _category_field("opere_pubbliche")
69
+ organizzazione = _category_field("organizzazione")
70
+ personale = _category_field("personale")
71
+ performance = _category_field("performance")
72
+ attivita_e_procedimenti = _category_field("attivita_e_procedimenti")
73
+ bandi_gara_e_contratti = _category_field("bandi_gara_e_contratti")
74
+ bilanci = _category_field("bilanci")
75
+ controlli_e_rilievi = _category_field("controlli_e_rilievi")
76
+ pagamenti = _category_field("pagamenti")
77
+ altri_contenuti = _category_field("altri_contenuti")
78
+ provvedimenti = _category_field("provvedimenti")
79
+ servizi_erogati = _category_field("servizi_erogati")
80
+ informazioni_ambientali = _category_field("informazioni_ambientali")
81
+ interventi_straordinari = _category_field("interventi_straordinari")
82
+ pianificazione_governo_territorio = _category_field(
83
+ "pianificazione_governo_territorio"
84
+ )
85
+ strutture_sanitarie_accreditate = _category_field("strutture_sanitarie_accreditate")
86
+ atti_generali = _category_field("atti_generali")
87
+ rischi_corruttivi_e_trasparenza = _category_field("rischi_corruttivi_e_trasparenza")
88
+ piano_integrato_attivita_organizzazione = _category_field(
89
+ "piano_integrato_attivita_organizzazione"
90
+ )
91
+ piano_triennale_prevenzione_corruzione_e_trasparenza = _category_field(
92
+ "piano_triennale_prevenzione_corruzione_e_trasparenza"
93
+ )
94
+ titolari_incarichi_collaborazione_consulenza = _category_field(
95
+ "titolari_incarichi_collaborazione_consulenza"
96
+ )
97
+ enti_diritto_privato_controllati = _category_field(
98
+ "enti_diritto_privato_controllati"
99
+ )
100
+ rappresentazione_grafica = _category_field("rappresentazione_grafica")
101
+ societa_partecipate = _category_field("societa_partecipate")
102
+ criteri_e_modalita = _category_field("criteri_e_modalita")
103
+ atti_di_concessione = _category_field("atti_di_concessione")
104
+ patrimonio_immobiliare = _category_field("patrimonio_immobiliare")
105
+ canoni_locazione_o_affitto = _category_field("canoni_locazione_o_affitto")
106
+ tempi_costi_opere_pubbliche = _category_field("tempi_costi_opere_pubbliche")
107
+ atti_programmazione_opere_pubbliche = _category_field(
108
+ "atti_programmazione_opere_pubbliche"
109
+ )
110
+ organi_di_indirizzo_politico_amministativo = _category_field(
111
+ "organi_di_indirizzo_politico_amministativo"
112
+ )
113
+ telefono_e_posta_elettronica = _category_field("telefono_e_posta_elettronica")
114
+ sanzioni_mancata_comunicazione_dati = _category_field(
115
+ "sanzioni_mancata_comunicazione_dati"
116
+ )
117
+ articolazione_uffici = _category_field("articolazione_uffici")
118
+ incarichi_amministrativi_di_vertice = _category_field(
119
+ "incarichi_amministrativi_di_vertice"
120
+ )
121
+ dirigenti_cessati = _category_field("dirigenti_cessati")
122
+ incarichi_conferiti_e_autorizzati_ai_dipendenti = _category_field(
123
+ "incarichi_conferiti_e_autorizzati_ai_dipendenti"
124
+ )
125
+ contrattazione_integrativa = _category_field("contrattazione_integrativa")
126
+ titolari_incarichi_dirigenziali_non_generali = _category_field(
127
+ "titolari_incarichi_dirigenziali_non_generali"
128
+ )
129
+ dotazione_organica = _category_field("dotazione_organica")
130
+ tassi_di_assenza = _category_field("tassi_di_assenza")
131
+ contrattazione_collettiva = _category_field("contrattazione_collettiva")
132
+ ammontare_complessivo_dei_premi = _category_field("ammontare_complessivo_dei_premi")
133
+ dati_relativi_ai_premi = _category_field("dati_relativi_ai_premi")
134
+ tipologie_di_procedimento = _category_field("tipologie_di_procedimento")
135
+ bilancio_preventivo_e_consuntivo = _category_field(
136
+ "bilancio_preventivo_e_consuntivo"
137
+ )
138
+ budget = _category_field("budget")
139
+ nuclei_di_valutazione = _category_field("nuclei_di_valutazione")
140
+ corte_dei_conti = _category_field("corte_dei_conti")
141
+ organi_revisione_amministrativa_e_contabile = _category_field(
142
+ "organi_revisione_amministrativa_e_contabile"
143
+ )
144
+ dati_sui_pagamenti = _category_field("dati_sui_pagamenti")
145
+ tempestivita_dei_pagamenti = _category_field("tempestivita_dei_pagamenti")
146
+ IBAN_e_pagamenti_informatici = _category_field("IBAN_e_pagamenti_informatici")
147
+ prevenzione_della_corruzione = _category_field("prevenzione_della_corruzione")
148
+ accessibilita_e_catalogo_dati = _category_field("accessibilita_e_catalogo_dati")
149
+ accesso_civico = _category_field("accesso_civico")
150
+ dati_ulteriori = _category_field("dati_ulteriori")
151
+ provvedimenti_organo_indirizzo_politico = _category_field(
152
+ "provvedimenti_organo_indirizzo_politico"
153
+ )
154
+ provvedimenti_dirigenti_amministrativi = _category_field(
155
+ "provvedimenti_dirigenti_amministrativi"
156
+ )
157
+ servizi_e_standard_di_qualita = _category_field("servizi_e_standard_di_qualita")
158
+ costi_contabilizzati = _category_field("costi_contabilizzati")
159
+ servizi_in_rete = _category_field("servizi_in_rete")
160
+ class_action = _category_field("class_action")
161
+ liste_di_attesa = _category_field("liste_di_attesa")
162
+
163
+ @invariant
164
+ def validate_map(data):
165
+ """Validate the control panel settings."""
166
+ value = data.data_ultima_modifica or ""
167
+ if value and not is_valid_date(value):
168
+ raise Invalid(
169
+ _(
170
+ "invalid_date",
171
+ default="La data deve essere nel formato GG/MM/AAAA.",
172
+ )
173
+ )
174
+
175
+
176
+ class LinkMapControlPanelForm(RegistryEditForm):
177
+ schema = ILinkMapSettings
178
+ id = "redturtle-linkmap-settings"
179
+ label = _(
180
+ "linkmap_controlpanel_title",
181
+ default="Configurazione at_map",
182
+ )
183
+
184
+
185
+ class LinkMapControlPanelView(ControlPanelFormWrapper):
186
+ form = LinkMapControlPanelForm
187
+
188
+
189
+ __all__ = ["ILinkMapSettings", "LinkMapControlPanelForm", "LinkMapControlPanelView"]
@@ -0,0 +1,7 @@
1
+ <configure xmlns="http://namespaces.zope.org/zope">
2
+
3
+ <!-- List all packages you depend here -->
4
+ <include package="plone.restapi" />
5
+ <include package="plone.volto" />
6
+
7
+ </configure>
File without changes
@@ -0,0 +1,7 @@
1
+ <configure xmlns="http://namespaces.zope.org/zope">
2
+
3
+ <!-- Indexers/Metadata -->
4
+
5
+ <!-- -*- extra stuff goes here -*- -->
6
+
7
+ </configure>
@@ -0,0 +1,7 @@
1
+ """Module where all interfaces, events and exceptions live."""
2
+
3
+ from zope.publisher.interfaces.browser import IDefaultBrowserLayer
4
+
5
+
6
+ class IBrowserLayer(IDefaultBrowserLayer):
7
+ """Marker interface that defines a browser layer."""
rer/linkmap/linkmap.py ADDED
@@ -0,0 +1,143 @@
1
+ from datetime import date
2
+
3
+ import re
4
+ from zope.schema import URI
5
+ from zope.schema.interfaces import InvalidURI
6
+
7
+ CATEGORY_C1 = "at_art2_bis_c1"
8
+ ROOT_KEY = "amministrazione_trasparente"
9
+
10
+ CATEGORY_KEYS = (
11
+ "amministrazione_trasparente",
12
+ "disposizioni_generali",
13
+ "consulenti_collaboratori",
14
+ "bandi_di_concorso",
15
+ "enti_controllati",
16
+ "sovvenzioni",
17
+ "beni_immobili",
18
+ "opere_pubbliche",
19
+ "organizzazione",
20
+ "personale",
21
+ "performance",
22
+ "attivita_e_procedimenti",
23
+ "bandi_gara_e_contratti",
24
+ "bilanci",
25
+ "controlli_e_rilievi",
26
+ "pagamenti",
27
+ "altri_contenuti",
28
+ "provvedimenti",
29
+ "servizi_erogati",
30
+ "informazioni_ambientali",
31
+ "interventi_straordinari",
32
+ "pianificazione_governo_territorio",
33
+ "strutture_sanitarie_accreditate",
34
+ "atti_generali",
35
+ "rischi_corruttivi_e_trasparenza",
36
+ "piano_integrato_attivita_organizzazione",
37
+ "piano_triennale_prevenzione_corruzione_e_trasparenza",
38
+ "titolari_incarichi_collaborazione_consulenza",
39
+ "enti_diritto_privato_controllati",
40
+ "rappresentazione_grafica",
41
+ "societa_partecipate",
42
+ "criteri_e_modalita",
43
+ "atti_di_concessione",
44
+ "patrimonio_immobiliare",
45
+ "canoni_locazione_o_affitto",
46
+ "tempi_costi_opere_pubbliche",
47
+ "atti_programmazione_opere_pubbliche",
48
+ "organi_di_indirizzo_politico_amministativo",
49
+ "telefono_e_posta_elettronica",
50
+ "sanzioni_mancata_comunicazione_dati",
51
+ "articolazione_uffici",
52
+ "incarichi_amministrativi_di_vertice",
53
+ "dirigenti_cessati",
54
+ "incarichi_conferiti_e_autorizzati_ai_dipendenti",
55
+ "contrattazione_integrativa",
56
+ "titolari_incarichi_dirigenziali_non_generali",
57
+ "dotazione_organica",
58
+ "tassi_di_assenza",
59
+ "contrattazione_collettiva",
60
+ "ammontare_complessivo_dei_premi",
61
+ "dati_relativi_ai_premi",
62
+ "tipologie_di_procedimento",
63
+ "bilancio_preventivo_e_consuntivo",
64
+ "budget",
65
+ "nuclei_di_valutazione",
66
+ "corte_dei_conti",
67
+ "organi_revisione_amministrativa_e_contabile",
68
+ "dati_sui_pagamenti",
69
+ "tempestivita_dei_pagamenti",
70
+ "IBAN_e_pagamenti_informatici",
71
+ "prevenzione_della_corruzione",
72
+ "accessibilita_e_catalogo_dati",
73
+ "accesso_civico",
74
+ "dati_ulteriori",
75
+ "provvedimenti_organo_indirizzo_politico",
76
+ "provvedimenti_dirigenti_amministrativi",
77
+ "servizi_e_standard_di_qualita",
78
+ "costi_contabilizzati",
79
+ "servizi_in_rete",
80
+ "class_action",
81
+ "liste_di_attesa",
82
+ )
83
+
84
+ DATE_RE = re.compile(r"^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/([2]\d{3})$")
85
+ URL_FIELD = URI(required=False)
86
+
87
+
88
+ def is_valid_date(value):
89
+ return bool(value and DATE_RE.fullmatch(value))
90
+
91
+
92
+ def today_date_string():
93
+ return date.today().strftime("%d/%m/%Y")
94
+
95
+
96
+ def is_valid_url(value):
97
+ if not value:
98
+ return False
99
+ try:
100
+ URL_FIELD.validate(value.strip())
101
+ except InvalidURI:
102
+ return False
103
+ return True
104
+
105
+
106
+ def allowed_keys():
107
+ return set(CATEGORY_KEYS)
108
+
109
+
110
+ def parse_entries(raw_value):
111
+ lines = (raw_value or "").splitlines()
112
+ pairs = []
113
+ for line in lines:
114
+ cleaned = line.strip()
115
+ if not cleaned:
116
+ continue
117
+ if "|" not in cleaned:
118
+ raise ValueError(f"Invalid line format: {cleaned}")
119
+ key, url = cleaned.split("|", 1)
120
+ key = key.strip()
121
+ url = url.strip()
122
+ if not key or not url:
123
+ raise ValueError(f"Invalid line format: {cleaned}")
124
+ pairs.append((key, url))
125
+ return pairs
126
+
127
+
128
+ def build_category_map(raw_entries):
129
+ key_values = {}
130
+ allowed = allowed_keys()
131
+ for key, url in parse_entries(raw_entries):
132
+ if key not in allowed:
133
+ continue
134
+ if not is_valid_url(url):
135
+ continue
136
+ key_values[key] = url
137
+ return key_values
138
+
139
+
140
+ def ensure_required_root_url(category_map, fallback_url):
141
+ if category_map.get(ROOT_KEY):
142
+ return
143
+ category_map[ROOT_KEY] = fallback_url
File without changes
@@ -0,0 +1,69 @@
1
+ """Update locales."""
2
+
3
+ from pathlib import Path
4
+
5
+ import logging
6
+ import re
7
+ import subprocess
8
+
9
+
10
+ logger = logging.getLogger("i18n")
11
+ logger.setLevel(logging.DEBUG)
12
+
13
+
14
+ PATTERN = r"^[a-z]{2}.*"
15
+
16
+ locale_path = Path(__file__).parent.resolve()
17
+ target_path = locale_path.parent.resolve()
18
+ domains = [path.name[:-4] for path in locale_path.glob("*.pot")]
19
+
20
+ i18ndude = "uvx i18ndude"
21
+
22
+ # ignore node_modules files resulting in errors
23
+ excludes = '"*.html *json-schema*.xml"'
24
+
25
+
26
+ def locale_folder_setup(domain: str):
27
+ languages = [path for path in locale_path.glob("*") if path.is_dir()]
28
+ for lang_folder in languages:
29
+ lc_messages_path = lang_folder / "LC_MESSAGES"
30
+ lang = lang_folder.name
31
+ if lc_messages_path.exists():
32
+ continue
33
+ elif re.match(PATTERN, lang):
34
+ lc_messages_path.mkdir()
35
+ cmd = (
36
+ f"msginit --locale={lang} "
37
+ f"--input={locale_path}/{domain}.pot "
38
+ f"--output={locale_path}/{lang}/LC_MESSAGES/{domain}.po"
39
+ )
40
+ subprocess.call(cmd, shell=True) # noQA: S602
41
+
42
+
43
+ def _rebuild(domain: str):
44
+ cmd = (
45
+ f"{i18ndude} rebuild-pot --pot {locale_path}/{domain}.pot "
46
+ f"--exclude {excludes} "
47
+ f"--create {domain} {target_path}"
48
+ )
49
+ subprocess.call(cmd, shell=True) # noQA: S602
50
+
51
+
52
+ def _sync(domain: str):
53
+ cmd = (
54
+ f"{i18ndude} sync --pot {locale_path}/{domain}.pot "
55
+ f"{locale_path}/*/LC_MESSAGES/{domain}.po"
56
+ )
57
+ subprocess.call(cmd, shell=True) # noQA: S602
58
+
59
+
60
+ def main():
61
+ for domain in domains:
62
+ logger.info(f"Updating translations for {domain}")
63
+ locale_folder_setup(domain)
64
+ _rebuild(domain)
65
+ _sync(domain)
66
+
67
+
68
+ if __name__ == "__main__":
69
+ main()
@@ -0,0 +1,45 @@
1
+ msgid ""
2
+ msgstr ""
3
+ "Project-Id-Version: PACKAGE VERSION\n"
4
+ "POT-Creation-Date: 2022-05-25 17:12+0000\n"
5
+ "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n"
6
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
7
+ "Language-Team: LANGUAGE <LL@li.org>\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=utf-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "Plural-Forms: nplurals=1; plural=0\n"
12
+ "Language-Code: en\n"
13
+ "Language-Name: English\n"
14
+ "Preferred-Encodings: utf-8 latin1\n"
15
+ "Domain: rer.linkmap\n"
16
+
17
+ msgid "data_ultima_modifica_title"
18
+ msgstr "Last update date"
19
+
20
+ msgid "data_ultima_modifica_description"
21
+ msgstr "Date in DD/MM/YYYY format. If empty, current date will be used."
22
+
23
+ msgid "map_entries_title"
24
+ msgstr "Link map"
25
+
26
+ msgid "map_entries_description"
27
+ msgstr "One line per item using key|url format. Only c.1 schema keys are allowed."
28
+
29
+ msgid "invalid_entries_line"
30
+ msgstr "Each row must be in 'key|url' format."
31
+
32
+ msgid "invalid_entries_key"
33
+ msgstr "Invalid key for selected category."
34
+
35
+ msgid "invalid_entries_url"
36
+ msgstr "Invalid URL."
37
+
38
+ msgid "missing_required_root_key"
39
+ msgstr "Please add the required root section row."
40
+
41
+ msgid "invalid_date"
42
+ msgstr "Date must be in DD/MM/YYYY format."
43
+
44
+ msgid "linkmap_controlpanel_title"
45
+ msgstr "Link Map Configuration"