diff --git a/src/design/plone/contenttypes/browser/manage_content/check_persone.py b/src/design/plone/contenttypes/browser/manage_content/check_persone.py new file mode 100644 index 00000000..a4dac1e5 --- /dev/null +++ b/src/design/plone/contenttypes/browser/manage_content/check_persone.py @@ -0,0 +1,211 @@ +from DateTime import DateTime +from openpyxl import Workbook +from openpyxl.styles import Alignment +from openpyxl.styles import Font +from openpyxl.styles import PatternFill +from openpyxl.utils import get_column_letter +from plone import api +from Products.Five import BrowserView +from zope.globalrequest import getRequest +from zope.component import getMultiAdapter +from plone.restapi.interfaces import ISerializeToJsonSummary + +import io + + +class CheckPersone(BrowserView): + cds = None + + def is_anonymous(self): + return api.user.is_anonymous() + + def get_relations(self, obj, field): + return api.relation.get(source=obj, relationship=field, unrestricted=False) + + def get_related_objects(self, obj, field): + """ """ + items = [] + relations = self.get_relations(obj, field) + + for rel in relations: + rel_obj = rel.to_object + if rel_obj is not None: + summary = getMultiAdapter( + (rel_obj, getRequest()), ISerializeToJsonSummary + )() + items.append(summary) + return sorted(items, key=lambda k: k["title"]) + + def information_dict(self, persona): + relations = self.get_related_objects(persona, "organizzazione_riferimento") + return { + "title": getattr(persona, "title"), + "has_related_uo": bool(relations), + "organizzazione_riferimento": relations, + } + + def plone2volto(self, url): + portal_url = api.portal.get().absolute_url() + frontend_domain = api.portal.get_registry_record( + "volto.frontend_domain", default="" + ) + if frontend_domain and url.startswith(portal_url): + return url.replace(portal_url, frontend_domain, 1) + return url + + def get_persone(self): + if self.is_anonymous(): + return [] + pc = api.portal.get_tool("portal_catalog") + + # show_inactive ha sempre avuto una gestione... particolare! aggiungo ai + # kw effectiveRange = DateTime() che è quello che fa Products.CMFPlone + # nel CatalogTool.py + query = { + "portal_type": "Persona", + # "review_state": "published", + } + brains = pc(query, **{"effectiveRange": DateTime()}) + results = {} + for brain in brains: + persona = brain.getObject() + + information_dict = self.information_dict(persona) + + if not information_dict.get("has_related_uo"): + continue + + parent = persona.aq_inner.aq_parent + if parent.title not in results: + results[parent.title] = { + "url": self.plone2volto(parent.absolute_url()), + "children": [], + } + + results[parent.title]["children"].append( + { + "title": persona.title, + "url": self.plone2volto(persona.absolute_url()), + "data": { + "title": information_dict.get("title"), + "has_related_uo": information_dict.get("has_related_uo", "V"), + "organizzazione_riferimento": information_dict.get( + "organizzazione_riferimento" + ), + }, + } + ) + + results = dict(sorted(results.items())) + for key in results: + results[key]["children"].sort(key=lambda x: x["title"]) + return results + + +class DownloadCheckPersone(CheckPersone): + CT = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + + def __call__(self): + HEADER = [ + "Titolo", + "Ha organizzazioni di riferimento", + "Unità org. di riferimento", + ] + + EMPTY_ROW = [""] * 3 + + persone = self.get_persone() + + workbook = Workbook() + sheet = workbook.active + sheet.title = "Check Persone" + header_font = Font(bold=True) + section_link_font = Font(underline="single", color="0563C1", size=14) + link_fill = PatternFill(fill_type="solid", fgColor="DDDDDD") + link_font = Font(underline="single", color="0563C1") + section_fill = PatternFill(fill_type="solid", fgColor="E9E9E9") + alignment = Alignment(horizontal="center", vertical="top", wrapText=True) + + section_row_height = int(14 * 1.5) + + for category, category_data in persone.items(): + section_url = category_data["url"] + section_title = category + section_row = [section_title, "", ""] + sheet.append(section_row) + section_cell = sheet.cell(row=sheet.max_row, column=1) + + section_cell.alignment = alignment + section_cell.hyperlink = section_url + sheet.merge_cells( + start_row=sheet.max_row, + start_column=1, + end_row=sheet.max_row, + end_column=3, + ) # noqa + for row in sheet.iter_rows( + min_row=sheet.max_row, max_row=sheet.max_row, min_col=1, max_col=3 + ): # noqa + sheet.row_dimensions[row[0].row].height = section_row_height + for cell in row: + cell.fill = section_fill + cell.font = section_link_font + + sheet.append(HEADER) + for col in range(1, len(HEADER) + 1): + header_cell = sheet.cell(row=sheet.max_row, column=col) + header_cell.fill = link_fill + header_cell.font = header_font + + for col in sheet.columns: + column_letter = get_column_letter(col[0].column) + sheet.column_dimensions[column_letter].width = 35 + + for persona in category_data["children"]: + organizzazioni = "\n".join( + [ + f"{x.get('title')} - {self.plone2volto(x.get('@id'))}" + for x in persona["data"]["organizzazione_riferimento"] + ] + ) # noqa + title_url = persona["url"] + dati_persona = [ + persona["title"], + "X" if persona["data"]["has_related_uo"] else "", + organizzazioni, + ] + row = dati_persona + sheet.append(row) + + title_cell = sheet.cell(row=sheet.max_row, column=1) + check_cell = sheet.cell(row=sheet.max_row, column=2) + check_cell.alignment = check_cell.alignment.copy(horizontal="center") + title_cell.hyperlink = title_url + title_cell.font = link_font + column_letter_unit = get_column_letter(title_cell.column) + sheet.column_dimensions[column_letter_unit].width = 60 + max_index = sheet.max_row + for org in organizzazioni.split("\n"): + org_title, org_url = org.split(" - ") + org_link = f'=HYPERLINK("{org_url}", "{org_title}")' + org_cell = sheet.cell(row=max_index, column=3) + org_cell.value = org_link + org_cell.font = link_font + org_cell.alignment = org_cell.alignment.copy(horizontal="left") + column_letter_unit = get_column_letter(org_cell.column) + sheet.column_dimensions[column_letter_unit].width = 80 + max_index += 1 + + sheet.append(EMPTY_ROW) + sheet.append(EMPTY_ROW) + + bytes_io = io.BytesIO() + workbook.save(bytes_io) + data = bytes_io.getvalue() + self.request.response.setHeader("Content-Length", len(data)) + self.request.RESPONSE.setHeader("Content-Type", self.CT) + self.request.response.setHeader( + "Content-Disposition", + "attachment; filename=check_persone.xlsx", + ) + return data diff --git a/src/design/plone/contenttypes/browser/manage_content/configure.zcml b/src/design/plone/contenttypes/browser/manage_content/configure.zcml index 700dbbe4..4c015ef1 100644 --- a/src/design/plone/contenttypes/browser/manage_content/configure.zcml +++ b/src/design/plone/contenttypes/browser/manage_content/configure.zcml @@ -34,5 +34,11 @@ class=".check_servizi.DownloadCheckServizi" permission="zope2.Public" /> + diff --git a/src/design/plone/contenttypes/interfaces/persona.py b/src/design/plone/contenttypes/interfaces/persona.py index ff91609e..021d96e3 100644 --- a/src/design/plone/contenttypes/interfaces/persona.py +++ b/src/design/plone/contenttypes/interfaces/persona.py @@ -33,6 +33,10 @@ class IPersona(model.Schema, IDesignPloneContentType): "La dimensione suggerita è 100x180px.", ), ) + # Questo campo per direttive e richieste viene nascosto nella form + # Lo si tiene perche si vuole evitare di perder dati tra le migrazioni + # e magari non poter piu' usare la feature collegata, ossia + # la check persone, in quanto relazioni potrebbero rompersi o perdersi organizzazione_riferimento = RelationList( title=_( "organizzazione_riferimento_label", @@ -50,6 +54,7 @@ class IPersona(model.Schema, IDesignPloneContentType): default=[], required=False, ) + form.omitted("organizzazione_riferimento") incarichi_persona = RelationList( title=_( "incarichi_label", diff --git a/src/design/plone/contenttypes/restapi/serializers/persona.py b/src/design/plone/contenttypes/restapi/serializers/persona.py index 830606e1..ed16ad89 100644 --- a/src/design/plone/contenttypes/restapi/serializers/persona.py +++ b/src/design/plone/contenttypes/restapi/serializers/persona.py @@ -49,10 +49,12 @@ def __call__(self, version=None, include_items=True): responsabile_di = self.related_contents(field="responsabile") assessore_di = self.related_contents(field="assessore_riferimento") - if strutture_correlate: - result["strutture_correlate"] = strutture_correlate - if responsabile_di: - result["responsabile_di"] = responsabile_di + organizzazioni = [] if assessore_di: - result["assessore_di"] = assessore_di + organizzazioni = organizzazioni + assessore_di + if responsabile_di: + organizzazioni = organizzazioni + responsabile_di + if strutture_correlate: + organizzazioni = organizzazioni + strutture_correlate + result["organizzazione_riferimento"] = organizzazioni return result diff --git a/src/design/plone/contenttypes/tests/test_ct_persona.py b/src/design/plone/contenttypes/tests/test_ct_persona.py index 6cfd5535..7a8c7ba6 100644 --- a/src/design/plone/contenttypes/tests/test_ct_persona.py +++ b/src/design/plone/contenttypes/tests/test_ct_persona.py @@ -85,51 +85,6 @@ def setUp(self): def tearDown(self): self.api_session.close() - def test_persona_strutture_correlate(self): - uo = api.content.create( - container=self.portal, - type="UnitaOrganizzativa", - title="UO 1", - persone_struttura=[self.persona_ref], - ) - commit() - response = self.api_session.get(self.persona.absolute_url()) - res = response.json() - - self.assertIn("strutture_correlate", list(res.keys())) - self.assertEqual(len(res["strutture_correlate"]), 1) - self.assertEqual(res["strutture_correlate"][0]["title"], uo.title) - - def test_persona_responsabile_di(self): - uo = api.content.create( - container=self.portal, - type="UnitaOrganizzativa", - title="UO 1", - responsabile=[self.persona_ref], - ) - commit() - response = self.api_session.get(self.persona.absolute_url()) - res = response.json() - - self.assertIn("responsabile_di", list(res.keys())) - self.assertEqual(len(res["responsabile_di"]), 1) - self.assertEqual(res["responsabile_di"][0]["title"], uo.title) - - def test_persona_assessore_di(self): - uo = api.content.create( - container=self.portal, - type="UnitaOrganizzativa", - title="UO 1", - assessore_riferimento=[self.persona_ref], - ) - commit() - response = self.api_session.get(self.persona.absolute_url()) - res = response.json() - - self.assertIn("assessore_di", list(res.keys())) - self.assertEqual(len(res["assessore_di"]), 1) - self.assertEqual(res["assessore_di"][0]["title"], uo.title) - def test_atto_di_nomina_incarico(self): incarico = api.content.create( container=self.persona.incarichi, type="Incarico", title="Sindaco" diff --git a/src/design/plone/contenttypes/upgrades/to_7001.py b/src/design/plone/contenttypes/upgrades/to_7001.py index 17acdb3b..698aa7df 100644 --- a/src/design/plone/contenttypes/upgrades/to_7001.py +++ b/src/design/plone/contenttypes/upgrades/to_7001.py @@ -126,6 +126,7 @@ def create_incarico_for_persona(context): ) # incarico.persona = [RelationValue(intids.getId(persona))] api.relation.create(source=incarico, target=persona, relationship="persona") + if safe_hasattr(persona, "organizzazione_riferimento"): incarico.unita_organizzativa = persona.organizzazione_riferimento