diff --git a/tailbone/templates/roles/index.mako b/tailbone/templates/roles/index.mako
new file mode 100644
index 00000000..857e5f6a
--- /dev/null
+++ b/tailbone/templates/roles/index.mako
@@ -0,0 +1,12 @@
+## -*- coding: utf-8; -*-
+<%inherit file="/principal/index.mako" />
+
+<%def name="context_menu_items()">
+ ${parent.context_menu_items()}
+ % if master.has_perm('download_permissions_matrix'):
+
${h.link_to("Download Permissions Matrix", url('roles.download_permissions_matrix'))}
+ % endif
+%def>
+
+
+${parent.body()}
diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py
index d0dd8967..613576e6 100644
--- a/tailbone/views/roles.py
+++ b/tailbone/views/roles.py
@@ -26,12 +26,16 @@ Role Views
from __future__ import unicode_literals, absolute_import
+import os
+
import six
from sqlalchemy import orm
+from openpyxl.styles import Font, PatternFill
from rattail.db import model
from rattail.db.auth import (has_permission, grant_permission, revoke_permission,
administrator_role, guest_role, authenticated_role)
+from rattail.excel import ExcelWriter
import colander
from deform import widget as dfwidget
@@ -278,6 +282,69 @@ class RolesView(PrincipalMasterView):
roles.append(role)
return roles
+ def download_permissions_matrix(self):
+ """
+ View which renders the complete role / permissions matrix data into an
+ Excel spreadsheet, and returns that file.
+ """
+ roles = self.Session.query(model.Role)\
+ .order_by(model.Role.name)\
+ .all()
+
+ permissions = self.get_available_permissions()
+
+ # prep the excel writer
+ path = os.path.join(self.rattail_config.workdir(),
+ 'permissions-matrix.xlsx')
+ writer = ExcelWriter(path, None)
+ sheet = writer.sheet
+
+ # write header
+ sheet.cell(row=1, column=1, value="")
+ for i, role in enumerate(roles, 2):
+ sheet.cell(row=1, column=i, value=role.name)
+
+ # font and fill pattern for permission group rows
+ bold = Font(bold=True)
+ group_fill = PatternFill(patternType='solid',
+ fgColor='d9d9d9',
+ bgColor='d9d9d9')
+
+ # now we'll write the rows
+ writing_row = 2
+ for groupkey in sorted(permissions, key=lambda k: permissions[k]['label'].lower()):
+ group = permissions[groupkey]
+
+ # group label is bold, with fill pattern
+ cell = sheet.cell(row=writing_row, column=1, value=group['label'])
+ cell.font = bold
+ cell.fill = group_fill
+
+ # continue fill pattern for rest of group row
+ for col, role in enumerate(roles, 2):
+ cell = sheet.cell(row=writing_row, column=col)
+ cell.fill = group_fill
+
+ # okay, that row is done
+ writing_row += 1
+
+ # now we list each perm in the group
+ perms = group['perms']
+ for key in sorted(perms, key=lambda p: perms[p]['label'].lower()):
+ sheet.cell(row=writing_row, column=1, value=perms[key]['label'])
+
+ # and show an 'X' for any role which has this perm
+ for col, role in enumerate(roles, 2):
+ if has_permission(self.Session(), role, key, include_guest=False):
+ sheet.cell(row=writing_row, column=col, value="X")
+
+ writing_row += 1
+
+ writer.auto_resize()
+ writer.auto_freeze()
+ writer.save()
+ return self.file_response(path)
+
@classmethod
def defaults(cls, config):
cls._principal_defaults(config)
@@ -286,6 +353,8 @@ class RolesView(PrincipalMasterView):
@classmethod
def _role_defaults(cls, config):
+ route_prefix = cls.get_route_prefix()
+ url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
# extra permissions for editing built-in roles etc.
@@ -296,6 +365,14 @@ class RolesView(PrincipalMasterView):
config.add_tailbone_permission(permission_prefix, '{}.edit_my'.format(permission_prefix),
"Edit Role(s) to which current user belongs")
+ # download permissions matrix
+ config.add_tailbone_permission(permission_prefix, '{}.download_permissions_matrix'.format(permission_prefix),
+ "Download complete Role/Permissions matrix")
+ config.add_route('{}.download_permissions_matrix'.format(route_prefix), '{}/permissions-matrix'.format(url_prefix),
+ request_method='GET')
+ config.add_view(cls, attr='download_permissions_matrix', route_name='{}.download_permissions_matrix'.format(route_prefix),
+ permission='{}.download_permissions_matrix'.format(permission_prefix))
+
class PermissionsWidget(dfwidget.Widget):
template = 'permissions'