diff --git a/edbob/db/extensions/auth/model.py b/edbob/db/extensions/auth/model.py index 44f486b..5b5f122 100644 --- a/edbob/db/extensions/auth/model.py +++ b/edbob/db/extensions/auth/model.py @@ -89,7 +89,9 @@ class Role(Base): creator=lambda x: Permission(permission=x), getset_factory=getset_factory) - _users = relationship(UserRole, backref='role') + _users = relationship( + UserRole, backref='role', + cascade='save-update, merge, delete, delete-orphan') users = association_proxy('_users', 'user', creator=lambda x: UserRole(user=x), getset_factory=getset_factory) diff --git a/edbob/pyramid/grids/alchemy.py b/edbob/pyramid/grids/alchemy.py index 4402ac4..ca9e787 100644 --- a/edbob/pyramid/grids/alchemy.py +++ b/edbob/pyramid/grids/alchemy.py @@ -54,6 +54,9 @@ class AlchemyGrid(Grid): self._formalchemy_grid.prettify = prettify self.noclick_fields = [] + def __delattr__(self, attr): + delattr(self._formalchemy_grid, attr) + def __getattr__(self, attr): return getattr(self._formalchemy_grid, attr) diff --git a/edbob/pyramid/static/css/perms.css b/edbob/pyramid/static/css/perms.css index 764f8eb..86fcfce 100644 --- a/edbob/pyramid/static/css/perms.css +++ b/edbob/pyramid/static/css/perms.css @@ -1,17 +1,33 @@ /****************************** - * perms.css + * Permission Lists ******************************/ -div.field-couple.permissions div.field p.group { +div.field-wrapper.permissions div.field div.group { + margin-bottom: 10px; +} + +div.field-wrapper.permissions div.field div.group p { font-weight: bold; } -div.field-couple.permissions div.field label { +div.field-wrapper.permissions div.field label { float: none; font-weight: normal; } -div.field-couple.permissions div.field label input { +div.field-wrapper.permissions div.field label input { + margin-left: 15px; + margin-right: 10px; +} + +div.field-wrapper.permissions div.field div.group p.perm { + font-weight: normal; + margin-left: 15px; +} + +div.field-wrapper.permissions div.field div.group p.perm span { + font-family: monospace; + /* font-weight: bold; */ margin-right: 10px; } diff --git a/edbob/pyramid/subscribers.py b/edbob/pyramid/subscribers.py index 9331a35..0340d97 100644 --- a/edbob/pyramid/subscribers.py +++ b/edbob/pyramid/subscribers.py @@ -79,6 +79,13 @@ def context_found(event): return has_permission(request.user, perm, session=Session()) request.has_perm = has_perm + def has_any_perm(perms): + for perm in perms: + if has_permission(request.user, perm, session=Session()): + return True + return False + request.has_any_perm = has_any_perm + def get_referrer(default=None): if request.params.get('referrer'): return request.params['referrer'] diff --git a/edbob/pyramid/templates/crud.mako b/edbob/pyramid/templates/crud.mako index 0ca6e40..8844cd1 100644 --- a/edbob/pyramid/templates/crud.mako +++ b/edbob/pyramid/templates/crud.mako @@ -1,3 +1,18 @@ -<%inherit file="/edbob/crud.mako" /> +<%inherit file="/form.mako" /> + +<%def name="title()">${"New "+form.pretty_name if form.creating else form.pretty_name+' : '+h.literal(str(form.fieldset.model))} + +<%def name="head_tags()"> + ${parent.head_tags()} + + ${parent.body()} diff --git a/edbob/pyramid/templates/edbob/crud.mako b/edbob/pyramid/templates/edbob/crud.mako deleted file mode 100644 index 4fc6112..0000000 --- a/edbob/pyramid/templates/edbob/crud.mako +++ /dev/null @@ -1,5 +0,0 @@ -<%inherit file="/form.mako" /> - -<%def name="title()">${"New "+form.pretty_name if form.creating else form.pretty_name+' : '+h.literal(str(form.fieldset.model))} - -${parent.body()} diff --git a/edbob/pyramid/templates/forms/fieldset_readonly.mako b/edbob/pyramid/templates/forms/fieldset_readonly.mako index 0eea814..350a315 100644 --- a/edbob/pyramid/templates/forms/fieldset_readonly.mako +++ b/edbob/pyramid/templates/forms/fieldset_readonly.mako @@ -1,7 +1,7 @@
% for field in fieldset.render_fields.itervalues(): % if field.requires_label: -
+
${field.label_tag()|n}
${field.render_readonly()} diff --git a/edbob/pyramid/templates/roles/base.mako b/edbob/pyramid/templates/roles/base.mako deleted file mode 100644 index 27f7dd9..0000000 --- a/edbob/pyramid/templates/roles/base.mako +++ /dev/null @@ -1,2 +0,0 @@ -<%inherit file="/base.mako" /> -${parent.body()} diff --git a/edbob/pyramid/templates/roles/crud.mako b/edbob/pyramid/templates/roles/crud.mako new file mode 100644 index 0000000..863a773 --- /dev/null +++ b/edbob/pyramid/templates/roles/crud.mako @@ -0,0 +1,18 @@ +<%inherit file="edbob.pyramid:templates/crud.mako" /> + +<%def name="head_tags()"> + ${parent.head_tags()} + ${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/perms.css'))} + + +<%def name="context_menu_items()"> +
  • ${h.link_to("Back to Roles", url('roles'))}
  • + % if form.readonly: +
  • ${h.link_to("Edit this Role", url('role.update', uuid=form.fieldset.model.uuid))}
  • + % elif form.updating: +
  • ${h.link_to("View this Role", url('role.read', uuid=form.fieldset.model.uuid))}
  • + % endif +
  • ${h.link_to("Delete this Role", url('role.delete', uuid=form.fieldset.model.uuid), class_='delete')}
  • + + +${parent.body()} diff --git a/edbob/pyramid/templates/roles/index.mako b/edbob/pyramid/templates/roles/index.mako index 773153a..49deacb 100644 --- a/edbob/pyramid/templates/roles/index.mako +++ b/edbob/pyramid/templates/roles/index.mako @@ -1,10 +1,11 @@ -<%inherit file="/roles/base.mako" /> -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Roles <%def name="context_menu_items()"> -
  • ${h.link_to("Create a new Role", url('role.new'))}
  • + % if request.has_perm('roles.create'): +
  • ${h.link_to("Create a new Role", url('role.create'))}
  • + % endif ${parent.body()} diff --git a/edbob/pyramid/templates/roles/role.mako b/edbob/pyramid/templates/roles/role.mako deleted file mode 100644 index a532b06..0000000 --- a/edbob/pyramid/templates/roles/role.mako +++ /dev/null @@ -1,15 +0,0 @@ -<%inherit file="/roles/base.mako" /> -<%inherit file="/crud.mako" /> - -<%def name="crud_name()">Role - -<%def name="head_tags()"> - ${parent.head_tags()} - ${h.stylesheet_link(request.static_url('edbob.pyramid:static/css/perms.css'))} - - -<%def name="menu()"> -

    ${h.link_to("Back to Roles", url('roles.list'))}

    - - -${parent.body()} diff --git a/edbob/pyramid/views/roles.py b/edbob/pyramid/views/roles.py index 54c86ca..1281937 100644 --- a/edbob/pyramid/views/roles.py +++ b/edbob/pyramid/views/roles.py @@ -26,75 +26,83 @@ ``edbob.pyramid.views.roles`` -- Role Views """ -import transaction from pyramid.httpexceptions import HTTPFound -from formalchemy import Field, FieldRenderer -from webhelpers.html import literal -from webhelpers.html.tags import checkbox, hidden +import formalchemy +from webhelpers.html import tags +from webhelpers.html.builder import HTML import edbob -from edbob.db.auth import administrator_role, has_permission -from edbob.pyramid import filters -from edbob.pyramid import forms -from edbob.pyramid import grids +from edbob.db import auth from edbob.pyramid import Session +from edbob.pyramid.views import SearchableAlchemyGridView, CrudView -def filter_map(): - return filters.get_filter_map( - edbob.Role, - ilike=['name']) +default_permissions = [ -def search_config(request, fmap): - return filters.get_search_config( - 'roles.list', request, fmap, - include_filter_name=True, - filter_type_name='lk') + ("People", [ + ('people.list', "List People"), + ('people.read', "View Person"), + ('people.create', "Create Person"), + ('people.update', "Edit Person"), + ('people.delete', "Delete Person"), + ]), -def search_form(config): - return filters.get_search_form(config) + ("Roles", [ + ('roles.list', "List Roles"), + ('roles.read', "View Role"), + ('roles.create', "Create Role"), + ('roles.update', "Edit Role"), + ('roles.delete', "Delete Role"), + ]), -def grid_config(request, search, fmap): - return grids.get_grid_config( - 'roles.list', request, search, - filter_map=fmap, sort='name') - -def sort_map(): - return grids.get_sort_map(edbob.Role, ['name']) - -def query(config): - smap = sort_map() - q = Session.query(edbob.Role) - q = filters.filter_query(q, config) - q = grids.sort_query(q, config, smap) - return q + ("Users", [ + ('users.list', "List Users"), + ('users.read', "View User"), + ('users.create', "Create User"), + ('users.update', "Edit User"), + ('users.delete', "Delete User"), + ]), + ] -def roles(request): +class RolesGrid(SearchableAlchemyGridView): - fmap = filter_map() - config = search_config(request, fmap) - search = search_form(config) - config = grid_config(request, search, fmap) - roles = grids.get_pager(query, config) + mapped_class = edbob.Role + config_prefix = 'roles' + sort = 'name' - g = forms.AlchemyGrid( - edbob.Role, roles, config, - gridurl=request.route_url('roles.list'), - objurl='role.edit') + def filter_map(self): + return self.make_filter_map(ilike=['name']) - g.configure( - include=[ - g.name, - ], - readonly=True) + def filter_config(self): + return self.make_filter_config( + include_filter_name=True, + filter_type_name='lk') - grid = g.render(class_='clickable roles') - return grids.render_grid(request, grid, search) + def sort_map(self): + return self.make_sort_map('name') + + def grid(self): + g = self.make_grid() + g.configure( + include=[ + g.name, + ], + readonly=True) + if self.request.has_perm('roles.read'): + g.clickable = True + g.click_route_name = 'role.read' + if self.request.has_perm('roles.update'): + g.editable = True + g.edit_route_name = 'role.update' + if self.request.has_perm('roles.delete'): + g.deletable = True + g.delete_route_name = 'role.delete' + return g -class PermissionsField(Field): +class PermissionsField(formalchemy.Field): def sync(self): if not self.is_readonly(): @@ -102,154 +110,108 @@ class PermissionsField(Field): role.permissions = self.renderer.deserialize() -class PermissionsFieldRenderer(FieldRenderer): +def PermissionsFieldRenderer(permissions, *args, **kwargs): - available_permissions = [ - - ("Batches", [ - ('batches.list', "List Batches"), - ('batches.edit', "Edit Batch"), - ('batches.create', "Create Batch"), - ]), - - ("Roles", [ - ('roles.list', "List Roles"), - ('roles.edit', "Edit Role"), - ('roles.create', "Create Role"), - ]), - ] - - def deserialize(self): - perms = [] - i = len(self.name) + 1 - for key in self.params: - if key.startswith(self.name): - perms.append(key[i:]) - return perms - - def _render(self, readonly=False, **kwargs): - # result = literal('') - # for group_name, group_label, perm_list in self.field.model_value: - # rendered_group_name = literal('

    ' + group_label + '

    \n') - # if readonly: - # result += literal('') + rendered_group_name + literal('') - # else: - # result += rendered_group_name - # result += literal('
    ') - # for perm_name, perm_label, checked in perm_list: - # if readonly: - # result += literal('' - # + '' + ('[X]' if checked else '[  ]') + '' - # + '' + perm_label + '' - # + '\n') - # else: - # name = '.'.join((self.name, group_name, perm_name)) - # result += check_box(name, label=perm_label, checked=checked) - # if not readonly: - # result += literal('
    ') - # if readonly: - # return literal('') + result + literal('
    ') - # return literal('
    ') + result + literal('
    ') - - role = self.field.model - if role is administrator_role(Session()): - res = literal('

    This is the administrative role; ' - 'it has full access to the entire system.

    ') - if not readonly: - res += hidden(self.name, value='') # ugly hack..or good idea? - else: - res = '' - for group, perms in self.available_permissions: - res += literal('

    %s

    ' % group) - for perm, title in perms: - if readonly: - res += literal('

    %s

    ' % title) - else: - checked = has_permission(role, perm) - res += checkbox(self.name + '-' + perm, - checked=checked, label=title) - return res - - def render(self, **kwargs): - return self._render(**kwargs) - - def render_readonly(self, **kwargs): - return self._render(readonly=True, **kwargs) - - -def role_fieldset(role, request): - fs = forms.make_fieldset(role, url=request.route_url, - url_action=request.current_route_url(), - route_name='roles.list') + perms = permissions - fs.append(PermissionsField('permissions', - renderer=PermissionsFieldRenderer)) + class PermissionsFieldRenderer(formalchemy.FieldRenderer): - fs.configure( - include=[ - fs.name, - fs.permissions, - ]) + permissions = perms - if not fs.edit: - del fs.permissions + def deserialize(self): + perms = [] + i = len(self.name) + 1 + for key in self.params: + if key.startswith(self.name): + perms.append(key[i:]) + return perms - return fs + def _render(self, readonly=False, **kwargs): + role = self.field.model + admin = auth.administrator_role(Session()) + if role is admin: + html = HTML.tag('p', c="This is the administrative role; " + "it has full access to the entire system.") + if not readonly: + html += tags.hidden(self.name, value='') # ugly hack..or good idea? + else: + html = '' + for group, perms in self.permissions: + inner = HTML.tag('p', c=group) + for perm, title in perms: + checked = auth.has_permission(role, perm, Session()) + if readonly: + span = HTML.tag('span', c="[X]" if checked else "[ ]") + inner += HTML.tag('p', class_='perm', c=span + ' ' + title) + else: + checked = auth.has_permission(role, perm, Session()) + inner += tags.checkbox(self.name + '-' + perm, + checked=checked, label=title) + html += HTML.tag('div', class_='group', c=inner) + return html + + def render(self, **kwargs): + return self._render(**kwargs) + + def render_readonly(self, **kwargs): + return self._render(readonly=True, **kwargs) + + return PermissionsFieldRenderer -def new_role(request): +class RoleCrud(CrudView): - fs = role_fieldset(edbob.Role, request) - if request.POST: - fs.rebind(data=request.params) - if fs.validate(): + mapped_class = edbob.Role + home_route = 'roles' + permissions = default_permissions - with transaction.manager: - fs.sync() - fs.model = Session.merge(fs.model) - request.session.flash("%s \"%s\" has been %s." % ( - fs.crud_title, fs.get_display_text(), - 'updated' if fs.edit else 'created')) - home = request.route_url('roles.list') + def fieldset(self, role): + fs = self.make_fieldset(role) + fs.append(PermissionsField( + 'permissions', + renderer=PermissionsFieldRenderer(self.permissions))) + fs.configure( + include=[ + fs.name, + fs.permissions, + ]) + return fs - return HTTPFound(location=home) - - return {'fieldset': fs, 'crud': True} - - -def edit_role(request): - uuid = request.matchdict['uuid'] - role = Session.query(edbob.Role).get(uuid) if uuid else None - assert role - - fs = role_fieldset(role, request) - if request.POST: - fs.rebind(data=request.params) - if fs.validate(): - - with transaction.manager: - Session.add(fs.model) - fs.sync() - request.session.flash("%s \"%s\" has been %s." % ( - fs.crud_title, fs.get_display_text(), - 'updated' if fs.edit else 'created')) - home = request.route_url('roles.list') - - return HTTPFound(location=home) - - return {'fieldset': fs, 'crud': True} + def pre_delete(self, model): + admin = auth.administrator_role(Session()) + guest = auth.guest_role(Session()) + if model in (admin, guest): + self.request.session.flash("You may not delete the %s role." % str(model), 'error') + return HTTPFound(location=self.request.get_referrer()) def includeme(config): + + config.add_route('roles', '/roles') + config.add_view(RolesGrid, route_name='roles', + renderer='/roles/index.mako', + permission='roles.list') - config.add_route('roles.list', '/roles') - config.add_view(roles, route_name='roles.list', renderer='/roles/index.mako', - permission='roles.list', http_cache=0) + settings = config.get_settings() + perms = settings.get('edbob.permissions') + if perms: + RoleCrud.permissions = perms - config.add_route('role.new', '/roles/new') - config.add_view(new_role, route_name='role.new', renderer='/roles/role.mako', - permission='roles.create', http_cache=0) + config.add_route('role.create', '/roles/new') + config.add_view(RoleCrud, attr='create', route_name='role.create', + renderer='/roles/crud.mako', + permission='roles.create') - config.add_route('role.edit', '/roles/{uuid}/edit') - config.add_view(edit_role, route_name='role.edit', renderer='/roles/role.mako', - permission='roles.edit', http_cache=0) + config.add_route('role.read', '/roles/{uuid}') + config.add_view(RoleCrud, attr='read', route_name='role.read', + renderer='/roles/crud.mako', + permission='roles.read') + + config.add_route('role.update', '/roles/{uuid}/edit') + config.add_view(RoleCrud, attr='update', route_name='role.update', + renderer='/roles/crud.mako', + permission='roles.update') + + config.add_route('role.delete', '/roles/{uuid}/delete') + config.add_view(RoleCrud, attr='delete', route_name='role.delete', + permission='roles.delete') diff --git a/edbob/pyramid/views/users.py b/edbob/pyramid/views/users.py index 1ef9500..0ed80c5 100644 --- a/edbob/pyramid/views/users.py +++ b/edbob/pyramid/views/users.py @@ -95,7 +95,7 @@ class _RolesFieldRenderer(SelectFieldRenderer): role = roles.get(uuid) res += literal('
  • %s
  • ' % ( tags.link_to(role.name, - self.request.route_url('role.edit', uuid=role.uuid)))) + self.request.route_url('role.read', uuid=role.uuid)))) res += literal('') return res @@ -206,6 +206,10 @@ class UserCrud(CrudView): fs.roles, ]) + if self.readonly: + del fs.password + del fs.confirm_password + # if fs.edit and user.person: if isinstance(user, edbob.User) and user.person: fs.person.set(readonly=True,