From 00885c9f1ae30d4b723935340ef9bb90aae73301 Mon Sep 17 00:00:00 2001 From: Elio Schmutz Date: Fri, 22 Nov 2024 11:14:07 +0100 Subject: [PATCH 1/3] Move role assignment excel exporter to the sharing module --- opengever/base/browser/configure.zcml | 7 ------- opengever/sharing/configure.zcml | 1 + opengever/sharing/local_roles_lookup/configure.zcml | 12 ++++++++++++ .../local_roles_lookup/exporter.py} | 0 .../tests/test_role_assignment_excel_export.py} | 0 5 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 opengever/sharing/local_roles_lookup/configure.zcml rename opengever/{base/browser/role_assignment_excel_report.py => sharing/local_roles_lookup/exporter.py} (100%) rename opengever/{base/tests/test_role_assignment_excel_report.py => sharing/tests/test_role_assignment_excel_export.py} (100%) diff --git a/opengever/base/browser/configure.zcml b/opengever/base/browser/configure.zcml index 3b7b397790..e42240e02b 100644 --- a/opengever/base/browser/configure.zcml +++ b/opengever/base/browser/configure.zcml @@ -239,13 +239,6 @@ permission="cmf.ManagePortal" /> - - + diff --git a/opengever/sharing/local_roles_lookup/configure.zcml b/opengever/sharing/local_roles_lookup/configure.zcml new file mode 100644 index 0000000000..19ce7e33cd --- /dev/null +++ b/opengever/sharing/local_roles_lookup/configure.zcml @@ -0,0 +1,12 @@ + + + + + diff --git a/opengever/base/browser/role_assignment_excel_report.py b/opengever/sharing/local_roles_lookup/exporter.py similarity index 100% rename from opengever/base/browser/role_assignment_excel_report.py rename to opengever/sharing/local_roles_lookup/exporter.py diff --git a/opengever/base/tests/test_role_assignment_excel_report.py b/opengever/sharing/tests/test_role_assignment_excel_export.py similarity index 100% rename from opengever/base/tests/test_role_assignment_excel_report.py rename to opengever/sharing/tests/test_role_assignment_excel_export.py From 2c68ed2358512e965f784ffb2f84bbcab33288a6 Mon Sep 17 00:00:00 2001 From: Elio Schmutz Date: Mon, 25 Nov 2024 12:40:34 +0100 Subject: [PATCH 2/3] Implement ad-hoc role assignment excel export. --- changes/TI-1546.feature | 1 + .../sharing/local_roles_lookup/exporter.py | 121 +++++---- .../sharing/local_roles_lookup/reporter.py | 82 ++++++ .../de/LC_MESSAGES/opengever.sharing.po | 37 ++- .../en/LC_MESSAGES/opengever.sharing.po | 37 ++- .../fr/LC_MESSAGES/opengever.sharing.po | 37 ++- .../sharing/locales/opengever.sharing.pot | 37 ++- .../test_role_assignment_excel_export.py | 96 ++++--- .../tests/test_role_assignment_reporter.py | 248 ++++++++++++++++++ 9 files changed, 612 insertions(+), 84 deletions(-) create mode 100644 changes/TI-1546.feature diff --git a/changes/TI-1546.feature b/changes/TI-1546.feature new file mode 100644 index 0000000000..cecd1b047e --- /dev/null +++ b/changes/TI-1546.feature @@ -0,0 +1 @@ +Implement ad-hoc role assignment excel export. [elioschmutz] diff --git a/opengever/sharing/local_roles_lookup/exporter.py b/opengever/sharing/local_roles_lookup/exporter.py index 7e489ee851..c6e6f861b7 100644 --- a/opengever/sharing/local_roles_lookup/exporter.py +++ b/opengever/sharing/local_roles_lookup/exporter.py @@ -1,73 +1,98 @@ -from opengever.base import _ +from datetime import datetime from opengever.base.browser.reporting_view import BaseReporterView -from opengever.base.interfaces import IRoleAssignmentReportsStorage from opengever.base.reporter import XLSReporter -from opengever.sharing.browser.sharing import GEVER_ROLE_MAPPING -from opengever.sharing.browser.sharing import WORKSPACE_ROLE_MAPPING -from opengever.workspace import is_workspace_feature_enabled +from opengever.sharing import _ +from opengever.sharing.local_roles_lookup.reporter import RoleAssignmentReporter from plone.app.uuid.utils import uuidToObject -from zExceptions import BadRequest -from zExceptions import NotFound -from zope.interface import implements -from zope.publisher.interfaces import IPublishTraverse class RoleAssignmentReportExcelDownload(BaseReporterView): - implements(IPublishTraverse) - - def __init__(self, context, request): - super(RoleAssignmentReportExcelDownload, self).__init__(context, request) - self.params = [] - - def publishTraverse(self, request, name): - self.params.append(name) - return self - def __call__(self): - storage = IRoleAssignmentReportsStorage(self.context) - - # Expects report_id as path parameter - if not len(self.params) == 1: - raise NotFound() + principal_ids, include_memberships, root = self.extract_query_params() - self.report_id = self.params[0] - try: - report = storage.get(self.report_id) - except KeyError: - raise BadRequest(u"Invalid report_id '{}'".format(self.report_id)) + report = RoleAssignmentReporter().excel_report_for( + principal_ids=principal_ids, + include_memberships=include_memberships, + root=root) items = list(self.prepare_report_for_export(report)) reporter = XLSReporter(self.request, self.columns(), items) return self.return_excel(reporter) + def extract_query_params(self): + filters = self.request.get('filters', {}) + + principal_ids = filters.get("principal_ids", []) + include_memberships = filters.get("include_memberships", False) + root = filters.get("root") + + return principal_ids, include_memberships, root + @property def filename(self): - return u'{}.xlsx'.format(self.report_id) + principal_ids, include_membership, root = self.extract_query_params() - def prepare_report_for_export(self, report): - for item in report.get('items'): - obj = uuidToObject(item.get('UID')) - data = {u'title': obj.Title(), u'url': obj.absolute_url()} + parts = [datetime.now().strftime('%Y-%m-%d_%H-%M')] - for role in self.available_roles(): - data[role] = bool(role in item['roles']) + if root: + root = uuidToObject(root) + parts.append('branch_{}'.format(root.id)) - yield data + if include_membership: + parts.append('including_memberships') - def available_roles(self): - return WORKSPACE_ROLE_MAPPING.keys() if is_workspace_feature_enabled() else GEVER_ROLE_MAPPING.keys() + if principal_ids: + parts.append('-'.join(principal_ids)) - @property - def _columns(self): - columns = [{'id': 'title', - 'title': _('label_title', default=u'Title'), - 'hyperlink': lambda value, obj: obj.get('url')}] + return u'role_assignment_report_{}.xlsx'.format('_'.join(parts)) - role_mapping = WORKSPACE_ROLE_MAPPING if is_workspace_feature_enabled() else GEVER_ROLE_MAPPING + def prepare_report_for_export(self, report): + for report_item in report: + item = report_item.get('item') + principal = report_item.get('principal') + yield { + u'title': item.get('title'), + u'url': item.get('@id'), + u'portal_type': item.get('@type'), + u'principal_id': principal.get('principal_id'), + u'username': principal.get('username'), + u'groupname': principal.get('groupname'), + u'role': report_item.get('role'), + } - for role_id, role_title in role_mapping.items(): - columns.append({'id': role_id, 'title': role_title, - 'transform': lambda value: 'x' if value else ''}) + @property + def _columns(self): + columns = [ + { + 'id': 'title', + 'title': _('label_title', default=u'Title'), + }, + { + 'id': 'url', + 'title': _('label_url', default=u'URL'), + 'hyperlink': lambda value, obj: obj.get('url'), + }, + { + 'id': 'portal_type', + 'title': _('label_portal_type', default=u'Portal type'), + }, + { + 'id': 'principal_id', + 'title': _('label_principal_id', default=u'Principal id'), + }, + { + 'id': 'username', + 'title': _('label_user_name', default=u'User name'), + }, + { + 'id': 'groupname', + 'title': _('label_group_name', default=u'Group name'), + }, + { + 'id': 'role', + 'title': _('label_role', default=u'Role'), + } + ] return columns diff --git a/opengever/sharing/local_roles_lookup/reporter.py b/opengever/sharing/local_roles_lookup/reporter.py index a9d4518eaf..8515078443 100644 --- a/opengever/sharing/local_roles_lookup/reporter.py +++ b/opengever/sharing/local_roles_lookup/reporter.py @@ -3,8 +3,10 @@ from ftw.solr.query import make_filters from opengever.base.solr import batched_solr_results from opengever.base.solr import OGSolrDocument +from opengever.ogds.models.group import Group from opengever.ogds.models.user import User from opengever.sharing.local_roles_lookup.manager import LocalRolesLookupManager +from opengever.sharing.local_roles_lookup.model import LocalRoles from plone.app.uuid.utils import uuidToObject from zope.component import getUtility @@ -82,6 +84,56 @@ def report_for(self, return {'total_items': total_items, 'items': items} + def excel_report_for(self, + principal_ids=[], + include_memberships=False, + root=None): + """Generates a report of objects with custom local role assignments optimized + to export in excel. + + Caution: This function will return unbatched results. + + Example: + [ + { + "item": {"@id": "..."}, + "role": "Editor", + "principal": { + "principal_id": "principal-1" + "username": "username or empty string", + "groupname": "groupname or empty string", + } + } + ] + """ + principal_ids = self.expand_principal_ids(principal_ids, + include_memberships) + uids = self.get_distinct_uids(principal_ids) + + if not uids: + return + + filters = self.build_filters(uids, root) + local_role_entries_by_uid = self.local_role_entries_by_uid(uids, principal_ids) + + solr_items, total_items = self.solr_service.fetch_all( + filters, self.field_list, self.sort_order) + + for solr_item in solr_items: + serialized_solr_item = self.serialize_solr_doc(solr_item) + for local_role_entry in local_role_entries_by_uid.get(solr_item.get('UID'), []): + local_role_item, username, groupname = local_role_entry + for role in local_role_item.roles: + yield { + 'item': serialized_solr_item, + 'role': role, + 'principal': { + 'principal_id': local_role_item.principal_id, + 'username': username, + 'groupname': groupname, + } + } + def serialize_solr_doc(self, doc): item = OGSolrDocument(doc) serialized_item = { @@ -149,6 +201,36 @@ def role_assignments_by_uid(self, uids_filter=None, principal_ids_filter=None): uids[entry.object_uid][role].append(entry.principal_id) return uids + def local_role_entries_by_uid(self, uids_filter=None, principal_ids_filter=None): + """Returns a dict of uids containing a list of local role items and + principal details: + + { + "uid1": [ + entry_with_principal_details1, + entry_with_principal_details2, + ] + } + """ + uids = defaultdict(list) + for entry in self.get_entries_with_principal_details( + uids_filter=uids_filter, + principal_ids_filter=principal_ids_filter): + uids[entry[0].object_uid].append(entry) + + return uids + + def get_entries_with_principal_details( + self, uids_filter=None, principal_ids_filter=None): + query = self.local_roles_manager.get_entries_query( + uids_filter=uids_filter, + principal_ids_filter=principal_ids_filter) + + query = query.outerjoin(User, LocalRoles.principal_id == User.userid) + query = query.outerjoin(Group, LocalRoles.principal_id == Group.groupid) + query = query.with_entities(LocalRoles, User.username, Group.groupname) + return query.all() + class SolrFilterBuilder: """Builds Solr filters for querying.""" diff --git a/opengever/sharing/locales/de/LC_MESSAGES/opengever.sharing.po b/opengever/sharing/locales/de/LC_MESSAGES/opengever.sharing.po index d742c88d86..99008e9df7 100644 --- a/opengever/sharing/locales/de/LC_MESSAGES/opengever.sharing.po +++ b/opengever/sharing/locales/de/LC_MESSAGES/opengever.sharing.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-09-12 08:35+0000\n" +"POT-Creation-Date: 2024-11-25 11:36+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -34,6 +34,11 @@ msgstr "Automatische Berechtigung" msgid "label_blocked_local_roles" msgstr "Geschützte Objekte" +#. Default: "Group name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_group_name" +msgstr "Gruppenname" + #. Default: "Local permission" #: ./opengever/sharing/browser/sharing.py msgid "label_local_permission" @@ -44,11 +49,41 @@ msgstr "Lokale Berechtigung" msgid "label_no_blocked_local_roles" msgstr "Keine geschützte Objekte in diesem Bereich gefunden." +#. Default: "Portal type" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_portal_type" +msgstr "Inhaltstyp" + +#. Default: "Principal id" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_principal_id" +msgstr "Benutzer- oder Gruppen-ID" + +#. Default: "Role" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_role" +msgstr "Rolle" + #. Default: "Local roles successfully changed" #: ./opengever/sharing/browser/sharing.py msgid "label_roles_successfully_changed" msgstr "Lokale Rolle erfolgreich gespeichert" +#. Default: "Title" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_title" +msgstr "Titel" + +#. Default: "URL" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_url" +msgstr "URL" + +#. Default: "User name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_user_name" +msgstr "Benutzername" + #. Default: "Local roles save failed." #: ./opengever/sharing/browser/sharing.py msgid "message_save_failed" diff --git a/opengever/sharing/locales/en/LC_MESSAGES/opengever.sharing.po b/opengever/sharing/locales/en/LC_MESSAGES/opengever.sharing.po index 3e3b03ce0d..8698357c54 100644 --- a/opengever/sharing/locales/en/LC_MESSAGES/opengever.sharing.po +++ b/opengever/sharing/locales/en/LC_MESSAGES/opengever.sharing.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-09-12 08:35+0000\n" +"POT-Creation-Date: 2024-11-25 11:36+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -38,6 +38,11 @@ msgstr "Automatic permission" msgid "label_blocked_local_roles" msgstr "Protected objects" +#. Default: "Group name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_group_name" +msgstr "Group name" + #. German translation: Lokale Berechtigung #. Default: "Local permission" #: ./opengever/sharing/browser/sharing.py @@ -50,12 +55,42 @@ msgstr "Local permission" msgid "label_no_blocked_local_roles" msgstr "No protected objects were found within this scope." +#. Default: "Portal type" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_portal_type" +msgstr "Portal type" + +#. Default: "Principal id" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_principal_id" +msgstr "Principal id" + +#. Default: "Role" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_role" +msgstr "Role" + #. German translation: Lokale Rolle erfolgreich gespeichert #. Default: "Local roles successfully changed" #: ./opengever/sharing/browser/sharing.py msgid "label_roles_successfully_changed" msgstr "Local roles successfully changed" +#. Default: "Title" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_title" +msgstr "Title" + +#. Default: "URL" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_url" +msgstr "URL" + +#. Default: "User name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_user_name" +msgstr "User name" + #. German translation: Lokale Rollen konnten nicht gespeichert werden. #. Default: "Local roles save failed." #: ./opengever/sharing/browser/sharing.py diff --git a/opengever/sharing/locales/fr/LC_MESSAGES/opengever.sharing.po b/opengever/sharing/locales/fr/LC_MESSAGES/opengever.sharing.po index 268f61ffd6..8bb8799e74 100644 --- a/opengever/sharing/locales/fr/LC_MESSAGES/opengever.sharing.po +++ b/opengever/sharing/locales/fr/LC_MESSAGES/opengever.sharing.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-09-12 08:35+0000\n" +"POT-Creation-Date: 2024-11-25 11:36+0000\n" "PO-Revision-Date: 2017-12-03 11:34+0000\n" "Last-Translator: Jacqueline Sposato \n" "Language-Team: French \n" @@ -36,6 +36,11 @@ msgstr "Autorisation automatique" msgid "label_blocked_local_roles" msgstr "Objets protégés" +#. Default: "Group name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_group_name" +msgstr "Nom du groupe" + #. Default: "Local permission" #: ./opengever/sharing/browser/sharing.py msgid "label_local_permission" @@ -46,11 +51,41 @@ msgstr "Autorisations locales" msgid "label_no_blocked_local_roles" msgstr "Aucun objet protégé n'a été trouvé dans ce domaine." +#. Default: "Portal type" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_portal_type" +msgstr "Titre de l'acteur" + +#. Default: "Principal id" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_principal_id" +msgstr "ID d'utilisateur ou de groupe" + +#. Default: "Role" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_role" +msgstr "Rôle" + #. Default: "Local roles successfully changed" #: ./opengever/sharing/browser/sharing.py msgid "label_roles_successfully_changed" msgstr "Role local enregistré avec succès" +#. Default: "Title" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_title" +msgstr "Titre" + +#. Default: "URL" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_url" +msgstr "URL" + +#. Default: "User name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_user_name" +msgstr "Nom d'utilisateur" + #. Default: "Local roles save failed." #: ./opengever/sharing/browser/sharing.py msgid "message_save_failed" diff --git a/opengever/sharing/locales/opengever.sharing.pot b/opengever/sharing/locales/opengever.sharing.pot index e7cadefade..3fba0f529d 100644 --- a/opengever/sharing/locales/opengever.sharing.pot +++ b/opengever/sharing/locales/opengever.sharing.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-09-12 08:35+0000\n" +"POT-Creation-Date: 2024-11-25 11:36+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI +ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -37,6 +37,11 @@ msgstr "" msgid "label_blocked_local_roles" msgstr "" +#. Default: "Group name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_group_name" +msgstr "" + #. Default: "Local permission" #: ./opengever/sharing/browser/sharing.py msgid "label_local_permission" @@ -47,11 +52,41 @@ msgstr "" msgid "label_no_blocked_local_roles" msgstr "" +#. Default: "Portal type" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_portal_type" +msgstr "" + +#. Default: "Principal id" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_principal_id" +msgstr "" + +#. Default: "Role" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_role" +msgstr "" + #. Default: "Local roles successfully changed" #: ./opengever/sharing/browser/sharing.py msgid "label_roles_successfully_changed" msgstr "" +#. Default: "Title" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_title" +msgstr "" + +#. Default: "URL" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_url" +msgstr "" + +#. Default: "User name" +#: ./opengever/sharing/local_roles_lookup/exporter.py +msgid "label_user_name" +msgstr "" + #. Default: "Local roles save failed." #: ./opengever/sharing/browser/sharing.py msgid "message_save_failed" diff --git a/opengever/sharing/tests/test_role_assignment_excel_export.py b/opengever/sharing/tests/test_role_assignment_excel_export.py index 77b41be4b0..f06739ca48 100644 --- a/opengever/sharing/tests/test_role_assignment_excel_export.py +++ b/opengever/sharing/tests/test_role_assignment_excel_export.py @@ -1,45 +1,28 @@ +from datetime import datetime from ftw.testbrowser import browsing -from opengever.testing import IntegrationTestCase +from ftw.testing import freeze +from opengever.testing import SolrIntegrationTestCase from openpyxl import load_workbook from tempfile import NamedTemporaryFile import os +import pytz -class TestExcelRoleAssignmentReport(IntegrationTestCase): +class TestExcelRoleAssignmentReport(SolrIntegrationTestCase): @browsing - def test_raises_notfound_if_no_report_id_is_given(self, browser): + def test_role_assignment_report(self, browser): self.login(self.administrator, browser=browser) url = u'{}/download-role-assignment-report'.format( self.portal.absolute_url()) - with browser.expect_http_error(code=404): - browser.open(url) - - @browsing - def test_raises_badrequest_if_report_does_not_exists(self, browser): - self.login(self.administrator, browser=browser) - - with browser.expect_http_error(code=400): - url = u'{}/download-role-assignment-report/not-existing'.format( - self.portal.absolute_url()) - browser.open(url) - - @browsing - def test_role_assignment_report(self, browser): - self.login(self.administrator, browser=browser) - - url = u'{}/download-role-assignment-report/report_0'.format( - self.portal.absolute_url()) - browser.open(url) + with freeze(datetime(2017, 10, 16, 0, 0, tzinfo=pytz.utc)): + browser.open(url, view="?filters.principal_ids:record:list=jurgen.konig") self.assertEquals( 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', browser.headers['content-type']) - self.assertEquals( - 'attachment; filename="report_0.xlsx"', - browser.headers['content-disposition']) data = browser.contents with NamedTemporaryFile(delete=False, suffix='.xlsx') as tmpfile: @@ -51,11 +34,60 @@ def test_role_assignment_report(self, browser): rows = list(workbook.active.rows) self.assertSequenceEqual( - [[u'Title', u'Read', u'Add dossiers', u'Edit dossiers', - u'Resolve dossiers', u'Reactivate dossiers', u'Manage dossiers', - u'Task responsible', u'Role manager'], - [u'Ordnungssystem', None, u'x', None, None, None, None, None, None], - [u'Subsubdossier', u'x', None, u'x', u'x', None, None, None, None], - [u'2. Rechnungspr\xfcfungskommission', - None, u'x', None, None, u'x', None, None, None]], + [ + ["Title", "URL", "Portal type", "Principal id", "User name", "Group name", "Role"], + [ + "Ordnungssystem", + "http://nohost/plone/ordnungssystem", + "opengever.repository.repositoryroot", + "jurgen.konig", + "jurgen.konig", + None, + "Reviewer", + ], + [ + "Ordnungssystem", + "http://nohost/plone/ordnungssystem", + "opengever.repository.repositoryroot", + "jurgen.konig", + "jurgen.konig", + None, + "Publisher", + ], + ], [[cell.value for cell in row] for row in rows]) + + @browsing + def test_filename_includes_date_time(self, browser): + self.login(self.administrator, browser=browser) + + url = u'{}/download-role-assignment-report'.format( + self.portal.absolute_url()) + + with freeze(datetime(2017, 10, 16, 0, 0, tzinfo=pytz.utc)): + browser.open(url) + + self.assertEquals( + 'attachment; filename="role_assignment_report_2017-10-16_00-00.xlsx"', + browser.headers['content-disposition']) + + @browsing + def test_filename_includes_filter_information(self, browser): + self.login(self.administrator, browser=browser) + + url = u'{}/download-role-assignment-report'.format( + self.portal.absolute_url()) + + with freeze(datetime(2017, 10, 16, 0, 0, tzinfo=pytz.utc)): + browser.open( + url, + view="?filters.principal_ids:record:list=jurgen.konig" + "&filters.include_memberships:record:boolean=true&" + "&filters.root:record={}".format( + self.dossier.UID())) + + self.assertEquals( + 'attachment; filename="role_assignment_report_2017-10-16_00-00' + '_branch_dossier-1' + '_including_memberships_jurgen.konig.xlsx"', + browser.headers['content-disposition']) diff --git a/opengever/sharing/tests/test_role_assignment_reporter.py b/opengever/sharing/tests/test_role_assignment_reporter.py index 78c7d58456..f1e75b3467 100644 --- a/opengever/sharing/tests/test_role_assignment_reporter.py +++ b/opengever/sharing/tests/test_role_assignment_reporter.py @@ -14,6 +14,16 @@ def strip_item_metadata(self, report): return formatted_report + def strip_excel_item_metadata(self, report): + """Formats the report by removing item metadata for easier testing + """ + formatted_report = [] + for report_item in report: + report_item['item'] = {'@id': report_item['item'].get('@id')} + formatted_report.append(report_item) + + return formatted_report + def test_can_creates_a_report_for_all_principals_and_objects(self): self.login(self.administrator) reporter = RoleAssignmentReporter() @@ -257,3 +267,241 @@ def test_report_for_is_batched(self): report = reporter(rows=3) self.assertEqual(3, len(report.get('items'))) self.assertEqual(10, report.get('total_items')) + + def test_can_creates_an_excel_report_for_all_principals_and_objects(self): + self.login(self.administrator) + reporter = RoleAssignmentReporter() + self.assertEqual( + [ + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": None, + "principal_id": "archivist", + "username": "jurgen.fischer", + }, + "role": "Contributor", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": "fa_users", + "principal_id": "fa_users", + "username": None, + }, + "role": "Contributor", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": "fa_users", + "principal_id": "fa_users", + "username": None, + }, + "role": "Editor", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": "fa_users", + "principal_id": "fa_users", + "username": None, + }, + "role": "Reader", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": None, + "principal_id": "jurgen.konig", + "username": "jurgen.konig", + }, + "role": "Reviewer", + }, + ], self.strip_excel_item_metadata(reporter.excel_report_for())[:5]) + + def test_can_creates_an_excel_report_for_a_single_principal(self): + self.login(self.administrator) + reporter = RoleAssignmentReporter() + self.assertEqual( + [ + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": None, + "principal_id": "jurgen.konig", + "username": "jurgen.konig", + }, + "role": "Reviewer", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": None, + "principal_id": "jurgen.konig", + "username": "jurgen.konig", + }, + "role": "Publisher", + }, + ], self.strip_excel_item_metadata(reporter.excel_report_for(['jurgen.konig']))) + + def test_can_creates_an_excel_report_for_a_principal_including_all_group_memberships(self): + self.login(self.administrator) + reporter = RoleAssignmentReporter() + self.assertEqual( + [ + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": "fa_users", + "principal_id": "fa_users", + "username": None, + }, + "role": "Contributor", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": "fa_users", + "principal_id": "fa_users", + "username": None, + }, + "role": "Editor", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": "fa_users", + "principal_id": "fa_users", + "username": None, + }, + "role": "Reader", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": None, + "principal_id": "jurgen.konig", + "username": "jurgen.konig", + }, + "role": "Reviewer", + }, + { + "item": {"@id": "http://nohost/plone/ordnungssystem"}, + "principal": { + "groupname": None, + "principal_id": "jurgen.konig", + "username": "jurgen.konig", + }, + "role": "Publisher", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1" + }, + "principal": { + "groupname": "fa_inbox_users", + "principal_id": "fa_inbox_users", + "username": None, + }, + "role": "TaskResponsible", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-17" + }, + "principal": { + "groupname": "fa_inbox_users", + "principal_id": "fa_inbox_users", + "username": None, + }, + "role": "TaskResponsible", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-5" + }, + "principal": { + "groupname": "fa_inbox_users", + "principal_id": "fa_inbox_users", + "username": None, + }, + "role": "TaskResponsible", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-6" + }, + "principal": { + "groupname": "fa_inbox_users", + "principal_id": "fa_inbox_users", + "username": None, + }, + "role": "TaskResponsible", + }, + ], + self.strip_excel_item_metadata(reporter.excel_report_for( + ['jurgen.konig'], include_memberships=True))) + + def test_can_creates_an_excel_report_restricted_to_a_specific_branch(self): + self.login(self.administrator) + reporter = RoleAssignmentReporter() + self.assertEqual( + [ + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1" + }, + "principal": { + "groupname": "fa_inbox_users", + "principal_id": "fa_inbox_users", + "username": None, + }, + "role": "TaskResponsible", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1" + }, + "principal": { + "groupname": None, + "principal_id": "regular_user", + "username": "kathi.barfuss", + }, + "role": "TaskResponsible", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1/dossier-2/dossier-4" + }, + "principal": { + "groupname": None, + "principal_id": "archivist", + "username": "jurgen.fischer", + }, + "role": "Reviewer", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1/dossier-2/dossier-4" + }, + "principal": { + "groupname": None, + "principal_id": "archivist", + "username": "jurgen.fischer", + }, + "role": "Editor", + }, + { + "item": { + "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1/dossier-2/dossier-4" + }, + "principal": { + "groupname": None, + "principal_id": "archivist", + "username": "jurgen.fischer", + }, + "role": "Reader", + }, + ], + self.strip_excel_item_metadata(reporter.excel_report_for(root=self.dossier.UID()))) From f72a6fd3fab182fdca5eebfb4137e14f862e85d0 Mon Sep 17 00:00:00 2001 From: Elio Schmutz Date: Wed, 27 Nov 2024 15:58:57 +0100 Subject: [PATCH 3/3] Restructure query string format of @role-assignment-report api --- docs/public/dev-manual/api/role_assignment_reports.rst | 2 +- opengever/api/role_assignment_reports.py | 8 ++++---- opengever/api/tests/test_role_assignment_reports.py | 7 +++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/public/dev-manual/api/role_assignment_reports.rst b/docs/public/dev-manual/api/role_assignment_reports.rst index 1568549d00..58daf98e1b 100644 --- a/docs/public/dev-manual/api/role_assignment_reports.rst +++ b/docs/public/dev-manual/api/role_assignment_reports.rst @@ -202,7 +202,7 @@ Mittels eines GET-Requests können die Daten für einen spezifischen Berechtigun Content-Type: application/json { - "@id": "http://localhost:8081/fd/@role-assignment-report?b_size=25&b_start=0&principal_id:list=hugo.boss&root=abca20b04af54d2cbb2816545333e555&include_memberships=true", + "@id": "http://localhost:8081/fd/@role-assignment-report?b_size=25&b_start=0&filters.principal_id:record:list=hugo.boss&filters.root:record=abca20b04af54d2cbb2816545333e555&filters.include_memberships:record:boolean=true", "items": [ { "@id": "http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1", diff --git a/opengever/api/role_assignment_reports.py b/opengever/api/role_assignment_reports.py index ed50aa23c1..a732e4fb86 100644 --- a/opengever/api/role_assignment_reports.py +++ b/opengever/api/role_assignment_reports.py @@ -159,11 +159,11 @@ def reply(self): class RoleAssignmentReportGet(Service): def reply(self): - principal_ids = self.resolve_principals( - self.request.form.get("principal_ids", [])) + filters = self.request.get('filters', {}) - include_memberships = self.request.form.get("include_memberships", False) - root = self.request.form.get("root") + principal_ids = self.resolve_principals(filters.get("principal_ids", [])) + include_memberships = filters.get("include_memberships", False) + root = filters.get("root") b_start = safe_int(self.request.form.get("b_start", 0)) b_size = safe_int(self.request.form.get("b_size", 25)) diff --git a/opengever/api/tests/test_role_assignment_reports.py b/opengever/api/tests/test_role_assignment_reports.py index 178dfffa57..4aa71c1c1a 100644 --- a/opengever/api/tests/test_role_assignment_reports.py +++ b/opengever/api/tests/test_role_assignment_reports.py @@ -1,7 +1,6 @@ from datetime import datetime from ftw.testbrowser import browsing from ftw.testing import freeze -from opengever.sharing.local_roles_lookup.reporter import RoleAssignmentReporter from opengever.testing import IntegrationTestCase from opengever.testing import SolrIntegrationTestCase import json @@ -290,13 +289,13 @@ class TestRoleAssignmentReportGet(SolrIntegrationTestCase): def test_role_assignment_report(self, browser): self.login(self.administrator, browser=browser) - url = "{absolute_url}/@role-assignment-report?principal_ids:list={regular_user}".format( + url = "{absolute_url}/@role-assignment-report?filters.principal_ids:record:list={regular_user}".format( absolute_url=self.portal.absolute_url(), regular_user=self.regular_user.getId() ) browser.open(url, method='GET', headers=self.api_headers) expected_data = { - u'@id': u'http://nohost/plone/@role-assignment-report?principal_ids%3Alist=regular_user', + u'@id': u'http://nohost/plone/@role-assignment-report?filters.principal_ids%3Arecord%3Alist=regular_user', u'items': [ { u'@id': u'http://nohost/plone/ordnungssystem/fuhrung/vertrage-und-vereinbarungen/dossier-1', @@ -384,4 +383,4 @@ def test_role_assignment_report(self, browser): ] } self.assertEqual(200, browser.status_code) - self.assertSequenceEqual(expected_data, browser.json) + self.assertEqual(expected_data, browser.json)