Convert Roles to use master view.
Also tweak the grid API here and there.
This commit is contained in:
parent
585eb09bec
commit
9605d44b05
|
@ -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)
|
||||
|
|
|
@ -71,6 +71,7 @@ body > #body-wrapper {
|
|||
#context-menu {
|
||||
float: right;
|
||||
list-style-type: none;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
|
|
3
tailbone/static/js/jquery.ui.tailbone.js
vendored
3
tailbone/static/js/jquery.ui.tailbone.js
vendored
|
@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
9
tailbone/templates/roles/create.mako
Normal file
9
tailbone/templates/roles/create.mako
Normal 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()}
|
|
@ -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()}
|
16
tailbone/templates/roles/edit.mako
Normal file
16
tailbone/templates/roles/edit.mako
Normal 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()}
|
|
@ -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()}
|
|
@ -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
|
27
tailbone/templates/roles/view.mako
Normal file
27
tailbone/templates/roles/view.mako
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
@ -284,33 +164,10 @@ 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')
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue