Convert Roles to use master view.

Also tweak the grid API here and there.
This commit is contained in:
Lance Edgar 2015-08-11 15:01:21 -05:00
parent 585eb09bec
commit 9605d44b05
14 changed files with 144 additions and 281 deletions

View file

@ -61,8 +61,8 @@ class AlchemyGrid(Grid):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(AlchemyGrid, self).__init__(*args, **kwargs) super(AlchemyGrid, self).__init__(*args, **kwargs)
self._fa_grid = formalchemy.Grid(self.model_class, session=Session(), self._fa_grid = formalchemy.Grid(self.model_class, instances=self.data,
request=self.request) session=Session(), request=self.request)
def __delattr__(self, attr): def __delattr__(self, attr):
delattr(self._fa_grid, attr) delattr(self._fa_grid, attr)

View file

@ -71,6 +71,7 @@ body > #body-wrapper {
#context-menu { #context-menu {
float: right; float: right;
list-style-type: none; list-style-type: none;
text-align: right;
} }

View file

@ -46,9 +46,8 @@
select.selectmenu('refresh'); select.selectmenu('refresh');
if (select.find('option:enabled').length == 1) { // prompt is always enabled if (select.find('option:enabled').length == 1) { // prompt is always enabled
select.selectmenu('disable'); select.selectmenu('disable');
} else {
that.apply_filters.button('enable');
} }
that.apply_filters.button('enable');
} }
}); });

View file

@ -8,6 +8,9 @@
% if request.has_perm('{0}.view'.format(permission_prefix)): % if request.has_perm('{0}.view'.format(permission_prefix)):
<li>${h.link_to("View this {0}".format(model_title), action_url('view', instance))}</li> <li>${h.link_to("View this {0}".format(model_title), action_url('view', instance))}</li>
% endif % endif
% if request.has_perm('{0}.delete'.format(permission_prefix)):
<li>${h.link_to("Delete this {0}".format(model_title), action_url('delete', instance))}</li>
% endif
</%def> </%def>
<ul id="context-menu"> <ul id="context-menu">

View file

@ -8,6 +8,9 @@
% if request.has_perm('{0}.edit'.format(permission_prefix)): % if request.has_perm('{0}.edit'.format(permission_prefix)):
<li>${h.link_to("Edit this {0}".format(model_title), action_url('edit', instance))}</li> <li>${h.link_to("Edit this {0}".format(model_title), action_url('edit', instance))}</li>
% endif % endif
% if request.has_perm('{0}.delete'.format(permission_prefix)):
<li>${h.link_to("Delete this {0}".format(model_title), action_url('delete', instance))}</li>
% endif
</%def> </%def>
<ul id="context-menu"> <ul id="context-menu">

View file

@ -0,0 +1,9 @@
## -*- coding: utf-8 -*-
<%inherit file="/master/create.mako" />
<%def name="head_tags()">
${parent.head_tags()}
${h.stylesheet_link(request.static_url('tailbone:static/css/perms.css'))}
</%def>
${parent.body()}

View file

@ -1,22 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/crud.mako" />
<%def name="head_tags()">
${parent.head_tags()}
${h.stylesheet_link(request.static_url('tailbone:static/css/perms.css'))}
</%def>
<%def name="context_menu_items()">
<li>${h.link_to("Back to Roles", url('roles'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Role", url('role.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Role", url('role.read', uuid=form.fieldset.model.uuid))}</li>
% endif
<li>${h.link_to("Delete this Role", url('role.delete', uuid=form.fieldset.model.uuid), class_='delete')}</li>
% if version_count is not Undefined and request.has_perm('role.versions.view'):
<li>${h.link_to("View Change History ({0})".format(version_count), url('role.versions', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,16 @@
## -*- coding: utf-8 -*-
<%inherit file="/master/edit.mako" />
<%def name="head_tags()">
${parent.head_tags()}
${h.stylesheet_link(request.static_url('tailbone:static/css/perms.css'))}
</%def>
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if version_count is not Undefined and request.has_perm('role.versions.view'):
<li>${h.link_to("View Change History ({0})".format(version_count), url('role.versions', uuid=instance.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,12 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/grid.mako" />
<%def name="title()">Roles</%def>
<%def name="context_menu_items()">
% if request.has_perm('roles.create'):
<li>${h.link_to("Create a new Role", url('role.create'))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,37 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/roles/crud.mako" />
${parent.body()}
<h2>Users</h2>
% if role is guest_role:
<p>The guest role is implied for all users.</p>
% elif role.users:
<p>The following users are assigned to this role:</p>
<br />
<div class="grid clickable">
<table>
<thead>
<th>Username</th>
<th>Full Name</th>
</thead>
<tbody>
% for i, user in enumerate(role.users, 1):
<tr class="${'odd' if i % 2 else 'even'}" url="${url('user.read', uuid=user.uuid)}">
<td>${user.username}
<td>${user.display_name}</td>
</tr>
% endfor
</tbody>
</table>
</div>
% else:
<p>There are no users assigned to this role.</p>
% endif

View file

@ -0,0 +1,27 @@
## -*- coding: utf-8 -*-
<%inherit file="/master/view.mako" />
<%def name="head_tags()">
${parent.head_tags()}
${h.stylesheet_link(request.static_url('tailbone:static/css/perms.css'))}
</%def>
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if version_count is not Undefined and request.has_perm('role.versions.view'):
<li>${h.link_to("View Change History ({0})".format(version_count), url('role.versions', uuid=instance.uuid))}</li>
% endif
</%def>
${parent.body()}
<h2>Users</h2>
% if instance is guest_role:
<p>The guest role is implied for all users.</p>
% elif users:
<p>The following users are assigned to this role:</p>
${users.render_grid()|n}
% else:
<p>There are no users assigned to this role.</p>
% endif

View file

@ -106,8 +106,7 @@ class MasterView(View):
self.request.session.flash("{0} {1} has been updated.".format( self.request.session.flash("{0} {1} has been updated.".format(
self.get_model_title(), instance)) self.get_model_title(), instance))
return HTTPFound(location=self.get_action_url('view', instance)) return HTTPFound(location=self.get_action_url('view', instance))
return self.render_to_response('edit', { return self.render_to_response('edit', {'instance': instance, 'form': form})
'instance': instance, 'form': form})
def delete(self): def delete(self):
""" """
@ -117,6 +116,12 @@ class MasterView(View):
instance = Session.query(self.model_class).get(key) instance = Session.query(self.model_class).get(key)
if not instance: if not instance:
return HTTPNotFound() return HTTPNotFound()
# Let derived classes prep for (or cancel) deletion.
result = self.before_delete(instance)
if result is not None:
return result
# Flush immediately to force any pending integrity errors etc.; that # Flush immediately to force any pending integrity errors etc.; that
# way we don't set flash message until we know we have success. # way we don't set flash message until we know we have success.
Session.delete(instance) Session.delete(instance)
@ -238,6 +243,9 @@ class MasterView(View):
'index_url': self.get_index_url(), 'index_url': self.get_index_url(),
'action_url': self.get_action_url, 'action_url': self.get_action_url,
}) })
data.update(self.template_kwargs(**data))
if hasattr(self, 'template_kwargs_{0}'.format(template)):
data.update(getattr(self, 'template_kwargs_{0}'.format(template))(**data))
try: try:
return render_to_response('{0}/{1}.mako'.format(self.get_template_prefix(), template), return render_to_response('{0}/{1}.mako'.format(self.get_template_prefix(), template),
data, request=self.request) data, request=self.request)
@ -245,6 +253,12 @@ class MasterView(View):
return render_to_response('/master/{0}.mako'.format(template), return render_to_response('/master/{0}.mako'.format(template),
data, request=self.request) data, request=self.request)
def redirect(self, url):
"""
Convenience method to return a HTTP 302 response.
"""
return HTTPFound(location=url)
############################## ##############################
# Grid Stuff # Grid Stuff
############################## ##############################
@ -408,6 +422,11 @@ class MasterView(View):
fieldset.prettify = prettify fieldset.prettify = prettify
return fieldset return fieldset
def template_kwargs(self, **kwargs):
"""
Supplement the template context, for all views.
"""
return kwargs
############################## ##############################
# Config Stuff # Config Stuff

View file

@ -1,9 +1,8 @@
#!/usr/bin/env python # -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar # Copyright © 2010-2015 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -21,171 +20,28 @@
# along with Rattail. If not, see <http://www.gnu.org/licenses/>. # along with Rattail. If not, see <http://www.gnu.org/licenses/>.
# #
################################################################################ ################################################################################
""" """
Role Views Role Views
""" """
from __future__ import unicode_literals
from rattail.db import model from rattail.db import model
from . import SearchableAlchemyGridView, CrudView
from pyramid.httpexceptions import HTTPFound
from ..db import Session
from rattail.db.model import Role
from rattail.db.auth import has_permission, administrator_role, guest_role from rattail.db.auth import has_permission, administrator_role, guest_role
import formalchemy import formalchemy
from webhelpers.html import tags from webhelpers.html import HTML, tags
from webhelpers.html import HTML
from .continuum import VersionView, version_defaults from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views.continuum import VersionView, version_defaults
default_permissions = [ from tailbone.newgrids import AlchemyGrid, GridAction
("Batches", [
('batches.list', "List Batches"),
('batches.read', "View Batches"),
('batches.create', "Create Batches"),
('batches.update', "Edit Batches"),
('batches.delete', "Delete Batches"),
('batches.execute', "Execute Batches"),
('batch_rows.read', "View Batch Rows"),
('batch_rows.update', "Edit Batch Rows"),
('batch_rows.delete', "Delete Batch Rows"),
]),
("Brands", [
('brands.list', "List Brands"),
('brands.read', "View Brands"),
('brands.create', "Create Brands"),
('brands.update', "Edit Brands"),
('brands.delete', "Delete Brands"),
('brands.force_sync', "Forcibly Sync Brands"),
]),
("Customers", [
('customers.list', "List Customers"),
('customers.read', "View Customers"),
('customers.force_sync', "Forcibly Sync Customers"),
('customer_groups.list', "List Customer Groups"),
('customer_groups.read', "View Customer Groups"),
('customer_groups.force_sync', "Forcibly Sync Customer Groups"),
]),
("Departments", [
('departments.list', "List Departments"),
('departments.read', "View Departments"),
('departments.create', "Create Departments"),
('departments.update', "Edit Departments"),
('departments.delete', "Delete Departments"),
('departments.force_sync', "Forcibly Sync Departments"),
]),
("Employees", [
('employees.list', "List Employees"),
('employees.force_sync', "Forcibly Sync Employees"),
]),
("Label Profiles", [
('label_profiles.list', "List Label Profiles"),
('label_profiles.view', "View Label Profiles"),
('label_profiles.create', "Create Label Profiles"),
('label_profiles.update', "Edit Label Profiles"),
('label_profiles.delete', "Delete Label Profiles"),
]),
("People", [
('people.list', "List People"),
('people.read', "View People"),
('people.create', "Create People"),
('people.update', "Edit People"),
('people.delete', "Delete People"),
('people.force_sync', "Forcibly Sync People"),
]),
("Products", [
('products.list', "List Products"),
('products.read', "View Products"),
('products.create', "Create Products"),
('products.update', "Edit Products"),
('products.delete', "Delete Products"),
('products.print_labels', "Print Product Labels"),
('products.force_sync', "Forcibly Sync Products"),
]),
("Roles", [
('roles.list', "List Roles"),
('roles.read', "View Roles"),
('roles.create', "Create Roles"),
('roles.update', "Edit Roles"),
('roles.delete', "Delete Roles"),
]),
("Stores", [
('stores.list', "List Stores"),
('stores.read', "View Stores"),
('stores.create', "Create Stores"),
('stores.update', "Edit Stores"),
('stores.delete', "Delete Stores"),
('stores.force_sync', "Forcibly Sync Stores"),
]),
("Subdepartments", [
('subdepartments.list', "List Subdepartments"),
('subdepartments.read', "View Subdepartments"),
('subdepartments.create', "Create Subdepartments"),
('subdepartments.update', "Edit Subdepartments"),
('subdepartments.delete', "Delete Subdepartments"),
('subdepartments.force_sync', "Forcibly Sync Subdepartments"),
]),
("Users", [
('users.list', "List Users"),
('users.read', "View Users"),
('users.create', "Create Users"),
('users.update', "Edit Users"),
('users.delete', "Delete Users"),
('users.force_sync', "Forcibly Sync Users"),
]),
("Vendors", [
('vendors.list', "List Vendors"),
('vendors.read', "View Vendors"),
('vendors.create', "Create Vendors"),
('vendors.update', "Edit Vendors"),
('vendors.delete', "Delete Vendors"),
('vendors.import_catalog', "Import Vendor Catalogs"),
('vendors.force_sync', "Forcibly Sync Vendors"),
]),
]
class RolesGrid(SearchableAlchemyGridView):
mapped_class = Role
config_prefix = 'roles'
sort = 'name'
def filter_map(self):
return self.make_filter_map(ilike=['name'])
def filter_config(self):
return self.make_filter_config(
include_filter_name=True,
filter_type_name='lk')
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.viewable = True
g.view_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(formalchemy.Field): class PermissionsField(formalchemy.Field):
"""
Custom field for role permissions.
"""
def sync(self): def sync(self):
if not self.is_readonly(): if not self.is_readonly():
@ -193,7 +49,7 @@ class PermissionsField(formalchemy.Field):
role.permissions = self.renderer.deserialize() role.permissions = self.renderer.deserialize()
def PermissionsFieldRenderer(permissions, *args, **kwargs): def OldPermissionsFieldRenderer(permissions, *args, **kwargs):
perms = permissions perms = permissions
@ -242,36 +98,60 @@ def PermissionsFieldRenderer(permissions, *args, **kwargs):
return PermissionsFieldRenderer return PermissionsFieldRenderer
class RoleCrud(CrudView): class RolesView(MasterView):
"""
Master view for the Role model.
"""
model_class = model.Role
mapped_class = Role def configure_grid(self, g):
home_route = 'roles' g.filters['name'].default_active = True
permissions = default_permissions g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'name'
g.configure(
include=[
g.name,
],
readonly=True)
def fieldset(self, role): def configure_fieldset(self, fs):
fs = self.make_fieldset(role) fs.append(PermissionsField('permissions'))
fs.append(PermissionsField( fs.permissions.set(renderer=OldPermissionsFieldRenderer(self.old_permissions))
'permissions',
renderer=PermissionsFieldRenderer(self.permissions)))
fs.configure( fs.configure(
include=[ include=[
fs.name, fs.name,
fs.permissions, fs.permissions,
]) ])
return fs
def template_kwargs(self, form): def template_kwargs_view(self, **kwargs):
kwargs = super(RoleCrud, self).template_kwargs(form) role = kwargs['instance']
kwargs['role'] = form.fieldset.model if role.users:
kwargs['guest_role'] = guest_role(Session)
# TODO: This is the first attempt at using a new grid outside of
# the context of a primary master grid. The API here is really
# much hairier than I'd like... Looks like we shouldn't need a key
# for this one, for instance (no settings required), but there is
# plenty of room for improvement here.
users = sorted(role.users, key=lambda u: u.username)
users = AlchemyGrid('roles.users', self.request, data=users, model_class=model.User,
main_actions=[
GridAction('view', icon='zoomin',
url=lambda u: self.request.route_url('user.read', uuid=u.uuid)),
])
users.configure(include=[users.username], readonly=True)
kwargs['users'] = users
else:
kwargs['users'] = None
kwargs['guest_role'] = guest_role(Session())
return kwargs return kwargs
def pre_delete(self, model): def before_delete(self, role):
admin = administrator_role(Session()) admin = administrator_role(Session())
guest = guest_role(Session()) guest = guest_role(Session())
if model in (admin, guest): if role in (admin, guest):
self.request.session.flash("You may not delete the %s role." % str(model), 'error') self.request.session.flash("You may not delete the {0} role.".format(role.name), 'error')
return HTTPFound(location=self.request.get_referrer()) return self.redirect(self.request.get_referrer())
class RoleVersionView(VersionView): class RoleVersionView(VersionView):
@ -284,33 +164,10 @@ class RoleVersionView(VersionView):
def includeme(config): def includeme(config):
config.add_route('roles', '/roles') # TODO: This can finally be removed once all CRUD/index views have been
config.add_view(RolesGrid, route_name='roles', # converted to use the new master view etc.
renderer='/roles/index.mako',
permission='roles.list')
settings = config.get_settings() settings = config.get_settings()
perms = settings.get('edbob.permissions') RolesView.old_permissions = settings['edbob.permissions']
if perms:
RoleCrud.permissions = perms
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.read', '/roles/{uuid}')
config.add_view(RoleCrud, attr='read', route_name='role.read',
renderer='/roles/read.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')
RolesView.defaults(config)
version_defaults(config, RoleVersionView, 'role') version_defaults(config, RoleVersionView, 'role')

View file

@ -133,7 +133,7 @@ def RolesFieldRenderer(request):
for uuid in self.value: for uuid in self.value:
role = roles.get(uuid) role = roles.get(uuid)
link = tags.link_to( link = tags.link_to(
role.name, request.route_url('role.read', uuid=role.uuid)) role.name, request.route_url('roles.view', uuid=role.uuid))
html += HTML.tag('li', c=link) html += HTML.tag('li', c=link)
html = HTML.tag('ul', c=html) html = HTML.tag('ul', c=html)
return html return html