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):
super(AlchemyGrid, self).__init__(*args, **kwargs)
self._fa_grid = formalchemy.Grid(self.model_class, session=Session(),
request=self.request)
self._fa_grid = formalchemy.Grid(self.model_class, instances=self.data,
session=Session(), request=self.request)
def __delattr__(self, attr):
delattr(self._fa_grid, attr)

View file

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

View file

@ -46,9 +46,8 @@
select.selectmenu('refresh');
if (select.find('option:enabled').length == 1) { // prompt is always enabled
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)):
<li>${h.link_to("View this {0}".format(model_title), action_url('view', instance))}</li>
% 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>
<ul id="context-menu">

View file

@ -8,6 +8,9 @@
% if request.has_perm('{0}.edit'.format(permission_prefix)):
<li>${h.link_to("Edit this {0}".format(model_title), action_url('edit', instance))}</li>
% 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>
<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.get_model_title(), instance))
return HTTPFound(location=self.get_action_url('view', instance))
return self.render_to_response('edit', {
'instance': instance, 'form': form})
return self.render_to_response('edit', {'instance': instance, 'form': form})
def delete(self):
"""
@ -117,6 +116,12 @@ class MasterView(View):
instance = Session.query(self.model_class).get(key)
if not instance:
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
# way we don't set flash message until we know we have success.
Session.delete(instance)
@ -238,6 +243,9 @@ class MasterView(View):
'index_url': self.get_index_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:
return render_to_response('{0}/{1}.mako'.format(self.get_template_prefix(), template),
data, request=self.request)
@ -245,6 +253,12 @@ class MasterView(View):
return render_to_response('/master/{0}.mako'.format(template),
data, request=self.request)
def redirect(self, url):
"""
Convenience method to return a HTTP 302 response.
"""
return HTTPFound(location=url)
##############################
# Grid Stuff
##############################
@ -408,6 +422,11 @@ class MasterView(View):
fieldset.prettify = prettify
return fieldset
def template_kwargs(self, **kwargs):
"""
Supplement the template context, for all views.
"""
return kwargs
##############################
# Config Stuff

View file

@ -1,9 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
# Copyright © 2010-2015 Lance Edgar
#
# This file is part of Rattail.
#
@ -21,171 +20,28 @@
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Role Views
"""
from __future__ import unicode_literals
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
import formalchemy
from webhelpers.html import tags
from webhelpers.html import HTML
from webhelpers.html import HTML, tags
from .continuum import VersionView, version_defaults
default_permissions = [
("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
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views.continuum import VersionView, version_defaults
from tailbone.newgrids import AlchemyGrid, GridAction
class PermissionsField(formalchemy.Field):
"""
Custom field for role permissions.
"""
def sync(self):
if not self.is_readonly():
@ -193,7 +49,7 @@ class PermissionsField(formalchemy.Field):
role.permissions = self.renderer.deserialize()
def PermissionsFieldRenderer(permissions, *args, **kwargs):
def OldPermissionsFieldRenderer(permissions, *args, **kwargs):
perms = permissions
@ -242,36 +98,60 @@ def PermissionsFieldRenderer(permissions, *args, **kwargs):
return PermissionsFieldRenderer
class RoleCrud(CrudView):
class RolesView(MasterView):
"""
Master view for the Role model.
"""
model_class = model.Role
mapped_class = Role
home_route = 'roles'
permissions = default_permissions
def configure_grid(self, g):
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'name'
g.configure(
include=[
g.name,
],
readonly=True)
def fieldset(self, role):
fs = self.make_fieldset(role)
fs.append(PermissionsField(
'permissions',
renderer=PermissionsFieldRenderer(self.permissions)))
def configure_fieldset(self, fs):
fs.append(PermissionsField('permissions'))
fs.permissions.set(renderer=OldPermissionsFieldRenderer(self.old_permissions))
fs.configure(
include=[
fs.name,
fs.permissions,
])
return fs
])
def template_kwargs(self, form):
kwargs = super(RoleCrud, self).template_kwargs(form)
kwargs['role'] = form.fieldset.model
kwargs['guest_role'] = guest_role(Session)
def template_kwargs_view(self, **kwargs):
role = kwargs['instance']
if role.users:
# 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
def pre_delete(self, model):
def before_delete(self, role):
admin = administrator_role(Session())
guest = 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())
if role in (admin, guest):
self.request.session.flash("You may not delete the {0} role.".format(role.name), 'error')
return self.redirect(self.request.get_referrer())
class RoleVersionView(VersionView):
@ -283,34 +163,11 @@ class RoleVersionView(VersionView):
def includeme(config):
config.add_route('roles', '/roles')
config.add_view(RolesGrid, route_name='roles',
renderer='/roles/index.mako',
permission='roles.list')
# TODO: This can finally be removed once all CRUD/index views have been
# converted to use the new master view etc.
settings = config.get_settings()
perms = settings.get('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.old_permissions = settings['edbob.permissions']
RolesView.defaults(config)
version_defaults(config, RoleVersionView, 'role')

View file

@ -133,7 +133,7 @@ def RolesFieldRenderer(request):
for uuid in self.value:
role = roles.get(uuid)
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('ul', c=html)
return html