diff --git a/CHANGES.rst b/CHANGES.rst index 03d7960a62..ccf06fcb28 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,8 @@ Changelog **Added** +- #472 Dashboard panels visibility by roles +- #467 All/Mine filters in Dashboard panels - #423 Instrument import interface for Abbott's m2000 Real Time **Removed** diff --git a/bika/lims/browser/dashboard/configure.zcml b/bika/lims/browser/dashboard/configure.zcml index a22add64b1..f979c67c3f 100644 --- a/bika/lims/browser/dashboard/configure.zcml +++ b/bika/lims/browser/dashboard/configure.zcml @@ -11,4 +11,12 @@ layer="bika.lims.interfaces.IBikaLIMS" /> + + diff --git a/bika/lims/browser/dashboard/dashboard.py b/bika/lims/browser/dashboard/dashboard.py index 4cb2341646..c002cf6352 100644 --- a/bika/lims/browser/dashboard/dashboard.py +++ b/bika/lims/browser/dashboard/dashboard.py @@ -11,18 +11,139 @@ from Products.Archetypes.public import DisplayList from Products.CMFCore.utils import getToolByName from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile +from plone import api +from plone import protect from bika.lims import bikaMessageFactory as _ from bika.lims import logger +from bika.lims.api import get_tool from bika.lims.browser import BrowserView from bika.lims.catalog import CATALOG_ANALYSIS_LISTING from bika.lims.catalog import CATALOG_ANALYSIS_REQUEST_LISTING from bika.lims.catalog import CATALOG_WORKSHEET_LISTING from bika.lims.utils import get_strings +from bika.lims.utils import get_unicode +from plone.api.exc import InvalidParameterError DASHBOARD_FILTER_COOKIE = 'dashboard_filter_cookie' +def get_dashboard_registry_record(): + """ + Return the 'bika.lims.dashboard_panels_visibility' values. + :return: A dictionary or None + """ + try: + registry = api.portal.get_registry_record( + 'bika.lims.dashboard_panels_visibility') + return registry + except InvalidParameterError: + # No entry in the registry for dashboard panels roles. + # Maybe upgradestep 1.1.8 was not run? + logger.warn("Cannot find a record with name " + "'bika.lims.dashboard_panels_visibility' in " + "registry_record. Missed upgrade 1.1.8?") + return dict() + + +def set_dashboard_registry_record(registry_info): + """ + Sets the 'bika.lims.dashboard_panels_visibility' values. + + :param registry_info: A dictionary type object with all its values as + *unicode* objects. + :return: A dictionary or None + """ + try: + api.portal.set_registry_record( + 'bika.lims.dashboard_panels_visibility', registry_info) + except InvalidParameterError: + # No entry in the registry for dashboard panels roles. + # Maybe upgradestep 1.1.8 was not run? + logger.warn("Cannot find a record with name " + "'bika.lims.dashboard_panels_visibility' in " + "registry_record. Missed upgrade 1.1.8?") + + +def setup_dashboard_panels_visibility_registry(section_name): + """ + Initializes the values for panels visibility in registry_records. By + default, only users with LabManager or Manager roles can see the panels. + :param section_name: + :return: An string like: "role1,yes,role2,no,rol3,no" + """ + registry_info = get_dashboard_registry_record() + role_permissions_list = [] + # Getting roles defined in the system + roles = [] + acl_users = get_tool("acl_users") + roles_tree = acl_users.portal_role_manager.listRoleIds() + for role in roles_tree: + roles.append(role) + # Set view permissions to each role as 'yes': + # "role1,yes,role2,no,rol3,no" + for role in roles: + role_permissions_list.append(role) + visible = 'no' + if role in ['LabManager', 'Manager']: + visible = 'yes' + role_permissions_list.append(visible) + role_permissions = ','.join(role_permissions_list) + + # Set permissions string into dict + registry_info[section_name] = get_unicode(role_permissions) + # Set new values to registry record + set_dashboard_registry_record(registry_info) + return registry_info + + +def get_dashboard_panels_visibility_by_section(section_name): + """ + Return a list of pairs as values that represents the role-permission + view relation for the panel section passed in. + :param section_name: the panels section id. + :return: a list of tuples. + """ + result = [] + registry_info = get_dashboard_registry_record() + if section_name not in registry_info: + # Registry hasn't been set, do it at least for this section + registry_info = \ + setup_dashboard_panels_visibility_registry(section_name) + + pairs = registry_info.get(section_name) + pairs = get_strings(pairs) + if pairs is None: + # In the registry, but with None value? + setup_dashboard_panels_visibility_registry(section_name) + return get_dashboard_panels_visibility_by_section(section_name) + + pairs = pairs.split(',') + if len(pairs) == 0 or len(pairs) % 2 != 0: + # Non-valid or malformed value + setup_dashboard_panels_visibility_registry(section_name) + return get_dashboard_panels_visibility_by_section(section_name) + + result = [ + (pairs[i], pairs[i + 1]) for i in range(len(pairs)) if i % 2 == 0] + return result + + +def is_panel_visible_for_user(panel, user): + """ + Checks if the user is allowed to see the panel + :param panel: panel ID as string + :param user: a MemberData object + :return: Boolean + """ + roles = user.getRoles() + visibility = get_dashboard_panels_visibility_by_section(panel) + for pair in visibility: + if pair[0] in roles and pair[1] == 'yes': + return True + return False + + class DashboardView(BrowserView): template = ViewPageTemplateFile("templates/dashboard.pt") @@ -32,22 +153,22 @@ def __init__(self, context, request): self.member = None def __call__(self): - tofrontpage = True - mtool=getToolByName(self.context, 'portal_membership') - if not mtool.isAnonymousUser() and self.context.bika_setup.getDashboardByDefault(): - # If authenticated user with labman role, - # display the Main Dashboard view - pm = getToolByName(self.context, "portal_membership") - self.member = pm.getAuthenticatedMember() - roles = self.member.getRoles() - tofrontpage = 'Manager' not in roles and 'LabManager' not in roles - - if tofrontpage: - self.request.response.redirect(self.portal_url + "/bika-frontpage") - else: - self._init_date_range() - self.dashboard_cookie = self.check_dashboard_cookie() - return self.template() + frontpage_url = self.portal_url + "/bika-frontpage" + if not self.context.bika_setup.getDashboardByDefault(): + # Do not render dashboard, render frontpage instead + self.request.response.redirect(frontpage_url) + return + + mtool = getToolByName(self.context, 'portal_membership') + if mtool.isAnonymousUser(): + # Anonymous user, redirect to frontpage + self.request.response.redirect(frontpage_url) + return + + self.member = mtool.getAuthenticatedMember() + self._init_date_range() + self.dashboard_cookie = self.check_dashboard_cookie() + return self.template() def check_dashboard_cookie(self): """ @@ -87,6 +208,15 @@ def is_filter_selected(self, selection_id, value): selected = self.dashboard_cookie.get(selection_id) return selected == value + def is_admin_user(self): + """ + Checks if the user is the admin or a SiteAdmin user. + :return: Boolean + """ + user = api.user.get_current() + roles = user.getRoles() + return "LabManager" in roles or "Manager" in roles + def _create_raw_data(self): """ Gathers the different sections ids and creates a string as first @@ -187,9 +317,14 @@ def get_sections(self): 'title': , 'panels': } """ - sections = [self.get_analyses_section(), - self.get_analysisrequests_section(), - self.get_worksheets_section()] + sections = [] + user = api.user.get_current() + if is_panel_visible_for_user('analyses', user): + sections.append(self.get_analyses_section()) + if is_panel_visible_for_user('analysisrequests', user): + sections.append(self.get_analysisrequests_section()) + if is_panel_visible_for_user('worksheets', user): + sections.append(self.get_worksheets_section()) return sections def get_filter_options(self): @@ -622,3 +757,50 @@ def _update_criteria_with_filters(self, query, section_name): if cookie_criteria == 'mine': query['Creator'] = self.member.getId() return query + + def get_dashboard_panels_visibility(self, section_name): + """ + Return a list of pairs as values that represents the role-permission + view relation for the panel section. + :param section_name: the panels section id. + :return: a list of tuples. + """ + return get_dashboard_panels_visibility_by_section(section_name) + + +class DashboardViewPermissionUpdate(BrowserView): + """ + Updates the values in 'bika.lims.dashboard_panels_visibility' registry. + """ + + def __call__(self): + protect.CheckAuthenticator(self.request) + # Getting values from post + section_name = self.request.get('section_name', None) + if section_name is None: + return None + role_id = self.request.get('role_id', None) + if role_id is None: + return None + check_state = self.request.get('check_state', None) + if check_state is None: + return None + elif check_state == 'false': + check_state = 'no' + else: + check_state = 'yes' + # Update registry + registry_info = get_dashboard_registry_record() + pairs = get_dashboard_panels_visibility_by_section(section_name) + role_permissions = list() + for pair in pairs: + visibility = pair[1] + if pair[0] == role_id: + visibility = check_state + value = '{0},{1}'.format(pair[0], visibility) + role_permissions.append(value) + role_permissions = ','.join(role_permissions) + # Set permissions string into dict + registry_info[section_name] = get_unicode(role_permissions) + set_dashboard_registry_record(registry_info) + return True diff --git a/bika/lims/browser/dashboard/templates/dashboard.pt b/bika/lims/browser/dashboard/templates/dashboard.pt index 365dee6bc8..7b3657151d 100644 --- a/bika/lims/browser/dashboard/templates/dashboard.pt +++ b/bika/lims/browser/dashboard/templates/dashboard.pt @@ -26,14 +26,13 @@ padding: 20px 0 0; } .dashboard-section-head { - background-color: #ccc; + background-color: #e3e3e3; border-radius: 5px 5px 0 0; padding: 3px 15px; } .dashboard-section-head div.actions { float:right; text-align:right; - width:30%; } .dashboard-section-head div.actions select { padding: 0 5px; @@ -58,7 +57,7 @@ padding: 0 0 10px 2px; } .dashboard-panels { - border: 1px solid #ccc; + border: 1px solid #dcdcdc; border-radius: 0px 0px 5px 5px; padding: 5px 0; } @@ -201,13 +200,32 @@ float:right; } .toggle-barchart a { - padding: 10px; + padding: 0 10px 0; } .bar-chart-no-data { color: #b22222; font-size: 0.9em; padding: 20px; } + div.roles-visibility { + align-self: right; + background-color: #e3e3e3; + border: 1px solid #cdcdcd; + border-radius: 0 0 5px 5px; + padding: 5px; + position: absolute; + text-align: left; + z-index: 10; + } + div.roles-visibility div.checkbox label { + font-weight:normal; + } + a.dashboard-visibility-link { + margin-right:10px; + } + a.dashboard-visibility-link:after { + content: " ▼"; + } -
+

+ + + + Visibility + + + +