Replaced Grid.clickable with .viewable.

Clickable grid rows seemed to be more irritating than useful.  Now a view icon
is shown instead.
This commit is contained in:
Lance Edgar 2013-07-22 21:32:54 -07:00
parent 134613aadd
commit b7fdd1f797
32 changed files with 627 additions and 74 deletions

View file

@ -1,2 +1,8 @@
include *.txt *.ini *.cfg *.rst
recursive-include rattail/pyramid *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml
include *.txt *.py
recursive-include rattail/pyramid/static *.css
recursive-include rattail/pyramid/static *.png
recursive-include rattail/pyramid/templates *.mako
recursive-include rattail/pyramid/reports *.mako

View file

@ -32,5 +32,6 @@ from edbob.pyramid import Session
def includeme(config):
config.include('rattail.pyramid.static')
config.include('rattail.pyramid.subscribers')
config.include('rattail.pyramid.views')

View file

@ -32,13 +32,12 @@ from webhelpers.html import tags
import formalchemy
from edbob.pyramid.forms import pretty_datetime
from edbob.pyramid.forms.formalchemy.renderers import (
AutocompleteFieldRenderer, YesNoFieldRenderer)
from edbob.pyramid.forms.formalchemy.renderers import YesNoFieldRenderer
import rattail
from rattail.gpc import GPC
from .common import EnumFieldRenderer
from .common import AutocompleteFieldRenderer, EnumFieldRenderer
from .products import ProductFieldRenderer
from .users import UserFieldRenderer

View file

@ -29,7 +29,33 @@
from formalchemy.fields import SelectFieldRenderer
__all__ = ['EnumFieldRenderer']
__all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer']
def AutocompleteFieldRenderer(service_url, field_value=None, field_display=None, width='300px'):
"""
Returns a custom renderer class for an autocomplete field.
"""
class AutocompleteFieldRenderer(formalchemy.fields.FieldRenderer):
@property
def focus_name(self):
return self.name + '-textbox'
@property
def needs_focus(self):
return not bool(self.value or field_value)
def render(self, **kwargs):
kwargs.setdefault('field_name', self.name)
kwargs.setdefault('field_value', self.value or field_value)
kwargs.setdefault('field_display', self.raw_value or field_display)
kwargs.setdefault('service_url', service_url)
kwargs.setdefault('width', width)
return render('/forms/field_autocomplete.mako', kwargs)
return AutocompleteFieldRenderer
def EnumFieldRenderer(enum):

View file

@ -53,7 +53,6 @@ class AlchemyGrid(Grid):
self._formalchemy_grid = formalchemy.Grid(
cls, instances, session=Session(), request=request)
self._formalchemy_grid.prettify = prettify
self.noclick_fields = []
def __delattr__(self, attr):
delattr(self._formalchemy_grid, attr)
@ -63,16 +62,11 @@ class AlchemyGrid(Grid):
def cell_class(self, field):
classes = [field.name]
if field.name in self.noclick_fields:
classes.append('noclick')
return ' '.join(classes)
def checkbox(self, row):
return tags.checkbox('check-'+row.uuid)
def click_route_kwargs(self, row):
return {'uuid': row.uuid}
def column_header(self, field):
class_ = None
label = field.label()
@ -84,6 +78,9 @@ class AlchemyGrid(Grid):
return HTML.tag('th', class_=class_, field=field.key,
title=self.column_titles.get(field.key), c=label)
def view_route_kwargs(self, row):
return {'uuid': row.uuid}
def edit_route_kwargs(self, row):
return {'uuid': row.uuid}

View file

@ -46,19 +46,18 @@ class Grid(Object):
full = False
hoverable = True
clickable = False
checkboxes = False
editable = False
deletable = False
partial_only = False
click_route_name = None
click_route_kwargs = None
viewable = False
view_route_name = None
view_route_kwargs = None
editable = False
edit_route_name = None
edit_route_kwargs = None
deletable = False
delete_route_name = None
delete_route_kwargs = None
@ -82,22 +81,20 @@ class Grid(Object):
classes = ['grid']
if self.full:
classes.append('full')
if self.clickable:
classes.append('clickable')
if self.hoverable:
classes.append('hoverable')
return format_attrs(
class_=' '.join(classes),
url=self.request.current_route_url())
def get_delete_url(self, row):
def get_view_url(self, row):
kwargs = {}
if self.delete_route_kwargs:
if callable(self.delete_route_kwargs):
kwargs = self.delete_route_kwargs(row)
if self.view_route_kwargs:
if callable(self.view_route_kwargs):
kwargs = self.view_route_kwargs(row)
else:
kwargs = self.delete_route_kwargs
return self.request.route_url(self.delete_route_name, **kwargs)
kwargs = self.view_route_kwargs
return self.request.route_url(self.view_route_name, **kwargs)
def get_edit_url(self, row):
kwargs = {}
@ -108,16 +105,17 @@ class Grid(Object):
kwargs = self.edit_route_kwargs
return self.request.route_url(self.edit_route_name, **kwargs)
def get_delete_url(self, row):
kwargs = {}
if self.delete_route_kwargs:
if callable(self.delete_route_kwargs):
kwargs = self.delete_route_kwargs(row)
else:
kwargs = self.delete_route_kwargs
return self.request.route_url(self.delete_route_name, **kwargs)
def get_row_attrs(self, row, i):
attrs = self.row_attrs(row, i)
if self.clickable:
kwargs = {}
if self.click_route_kwargs:
if callable(self.click_route_kwargs):
kwargs = self.click_route_kwargs(row)
else:
kwargs = self.click_route_kwargs
attrs['url'] = self.request.route_url(self.click_route_name, **kwargs)
return format_attrs(**attrs)
def iter_fields(self):

View file

@ -0,0 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``rattail.pyramid.static`` -- Static Assets
"""
def includeme(config):
config.add_static_view('rattail', 'rattail.pyramid:static')

View file

@ -0,0 +1,164 @@
/******************************
* Grid Header
******************************/
table.grid-header {
padding-bottom: 5px;
width: 100%;
}
/******************************
* Form (Filters etc.)
******************************/
table.grid-header td.form {
vertical-align: bottom;
}
/******************************
* Context Menu
******************************/
table.grid-header td.context-menu {
vertical-align: top;
}
table.grid-header td.context-menu ul {
list-style-type: none;
margin: 0px;
text-align: right;
}
table.grid-header td.context-menu ul li {
line-height: 2em;
}
/******************************
* Tools
******************************/
table.grid-header td.tools {
padding-bottom: 10px;
text-align: right;
vertical-align: bottom;
}
table.grid-header td.tools div.buttons button {
margin-left: 5px;
}
/******************************
* Grid
******************************/
div.grid {
clear: both;
}
div.grid table {
border-top: 1px solid black;
border-left: 1px solid black;
border-collapse: collapse;
font-size: 9pt;
line-height: normal;
white-space: nowrap;
}
div.grid.full table {
width: 100%;
}
div.grid table th,
div.grid table td {
border-right: 1px solid black;
border-bottom: 1px solid black;
padding: 2px 3px;
}
div.grid table th.sortable a {
display: block;
padding-right: 18px;
}
div.grid table th.sorted {
background-position: right center;
background-repeat: no-repeat;
}
div.grid table th.sorted.asc {
background-image: url(../img/sort_arrow_up.png);
}
div.grid table th.sorted.desc {
background-image: url(../img/sort_arrow_down.png);
}
div.grid table tbody td {
text-align: left;
}
div.grid table tbody td.center {
text-align: center;
}
div.grid table tbody td.right {
float: none;
text-align: right;
}
div.grid table tr.odd {
background-color: #e0e0e0;
}
div.grid table tbody tr.hovering {
background-color: #bbbbbb;
}
div.grid table tbody tr td.view,
div.grid table tbody tr td.edit,
div.grid table tbody tr td.delete {
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
min-width: 18px;
text-align: center;
width: 18px;
}
div.grid table tbody tr td.view {
background-image: url(../img/view.png);
}
div.grid table tbody tr td.edit {
background-image: url(../img/edit.png);
}
div.grid table tbody tr td.delete {
background-image: url(../img/delete.png);
}
div.pager {
margin-bottom: 20px;
margin-top: 5px;
}
div.pager p {
font-size: 10pt;
margin: 0px;
}
div.pager p.showing {
float: left;
}
div.pager #grid-page-count {
font-size: 8pt;
}
div.pager p.page-links {
float: right;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 B

View file

@ -0,0 +1,3 @@
<%namespace file="/autocomplete.mako" import="autocomplete" />
${autocomplete(field_name, service_url, field_value, field_display, width=width, selected=selected, cleared=cleared)}

View file

@ -0,0 +1,63 @@
<div ${grid.div_attrs()}>
<table>
<thead>
<tr>
% if grid.checkboxes:
<th class="checkbox">${h.checkbox('check-all')}</th>
% endif
% for field in grid.iter_fields():
${grid.column_header(field)}
% endfor
% for col in grid.extra_columns:
<th>${col.label}</td>
% endfor
% if grid.viewable:
<th>&nbsp;</th>
% endif
% if grid.editable:
<th>&nbsp;</th>
% endif
% if grid.deletable:
<th>&nbsp;</th>
% endif
</tr>
</thead>
<tbody>
% for i, row in enumerate(grid.iter_rows(), 1):
<tr ${grid.get_row_attrs(row, i)}>
% if grid.checkboxes:
<td class="checkbox">${grid.checkbox(row)}</td>
% endif
% for field in grid.iter_fields():
<td class="${grid.cell_class(field)}">${grid.render_field(field)}</td>
% endfor
% for col in grid.extra_columns:
<td class="${col.name}">${col.callback(row)}</td>
% endfor
% if grid.viewable:
<td class="view" url="${grid.get_view_url(row)}">&nbsp;</td>
% endif
% if grid.editable:
<td class="edit" url="${grid.get_edit_url(row)}">&nbsp;</td>
% endif
% if grid.deletable:
<td class="delete" url="${grid.get_delete_url(row)}">&nbsp;</td>
% endif
</tr>
% endfor
</tbody>
</table>
% if grid.pager:
<div class="pager">
<p class="showing">
showing ${grid.pager.first_item} thru ${grid.pager.last_item} of ${grid.pager.item_count}
(page ${grid.pager.page} of ${grid.pager.page_count})
</p>
<p class="page-links">
${h.select('grid-page-count', grid.pager.items_per_page, grid.page_count_options())}
per page&nbsp;
${grid.page_links()}
</p>
</div>
% endif
</div>

View file

@ -41,6 +41,7 @@ def includeme(config):
config.include('rattail.pyramid.views.employees')
config.include('rattail.pyramid.views.labels')
config.include('rattail.pyramid.views.products')
config.include('rattail.pyramid.views.roles')
config.include('rattail.pyramid.views.stores')
config.include('rattail.pyramid.views.subdepartments')
config.include('rattail.pyramid.views.vendors')

View file

@ -101,8 +101,8 @@ class BatchesGrid(SearchableAlchemyGridView):
return tags.link_to("View Rows", self.request.route_url(
'batch.rows', uuid=row.uuid))
g.add_column('rows', "", rows)
g.clickable = True
g.click_route_name = 'batch.read'
g.viewable = True
g.view_route_name = 'batch.read'
if self.request.has_perm('batches.update'):
g.editable = True
g.edit_route_name = 'batch.update'

View file

@ -97,9 +97,9 @@ def BatchRowsGrid(request):
route_kwargs = lambda x: {'batch_uuid': x.batch.uuid, 'uuid': x.uuid}
if self.request.has_perm('batch_rows.read'):
g.clickable = True
g.click_route_name = 'batch_row.read'
g.click_route_kwargs = route_kwargs
g.viewable = True
g.view_route_name = 'batch_row.read'
g.view_route_kwargs = route_kwargs
if self.request.has_perm('batch_rows.update'):
g.editable = True

View file

@ -57,8 +57,8 @@ class BrandsGrid(SearchableAlchemyGridView):
],
readonly=True)
if self.request.has_perm('brands.read'):
g.clickable = True
g.click_route_name = 'brand.read'
g.viewable = True
g.view_route_name = 'brand.read'
if self.request.has_perm('brands.update'):
g.editable = True
g.edit_route_name = 'brand.update'

View file

@ -57,8 +57,8 @@ class CategoriesGrid(SearchableAlchemyGridView):
],
readonly=True)
if self.request.has_perm('categories.read'):
g.clickable = True
g.click_route_name = 'category.read'
g.viewable = True
g.view_route_name = 'category.read'
if self.request.has_perm('categories.update'):
g.editable = True
g.edit_route_name = 'category.update'

View file

@ -58,8 +58,8 @@ class CustomerGroupsGrid(SearchableAlchemyGridView):
],
readonly=True)
if self.request.has_perm('customer_groups.read'):
g.clickable = True
g.click_route_name = 'customer_group.read'
g.viewable = True
g.view_route_name = 'customer_group.read'
if self.request.has_perm('customer_groups.update'):
g.editable = True
g.edit_route_name = 'customer_group.update'

View file

@ -46,7 +46,6 @@ class CustomersGrid(SearchableAlchemyGridView):
mapped_class = Customer
config_prefix = 'customers'
sort = 'name'
clickable = True
def join_map(self):
return {
@ -93,8 +92,8 @@ class CustomersGrid(SearchableAlchemyGridView):
readonly=True)
if self.request.has_perm('customers.read'):
g.clickable = True
g.click_route_name = 'customer.read'
g.viewable = True
g.view_route_name = 'customer.read'
if self.request.has_perm('customers.update'):
g.editable = True
g.edit_route_name = 'customer.update'

View file

@ -59,8 +59,8 @@ class DepartmentsGrid(SearchableAlchemyGridView):
],
readonly=True)
if self.request.has_perm('departments.read'):
g.clickable = True
g.click_route_name = 'department.read'
g.viewable = True
g.view_route_name = 'department.read'
if self.request.has_perm('departments.update'):
g.editable = True
g.edit_route_name = 'department.update'

View file

@ -117,8 +117,8 @@ class EmployeesGrid(SearchableAlchemyGridView):
del g.status
if self.request.has_perm('employees.read'):
g.clickable = True
g.click_route_name = 'employee.read'
g.viewable = True
g.view_route_name = 'employee.read'
if self.request.has_perm('employees.update'):
g.editable = True
g.edit_route_name = 'employee.update'

View file

@ -42,7 +42,6 @@ class GridView(View):
full = False
checkboxes = False
clickable = False
deletable = False
partial_only = False
@ -50,7 +49,6 @@ class GridView(View):
def update_grid_kwargs(self, kwargs):
kwargs.setdefault('full', self.full)
kwargs.setdefault('checkboxes', self.checkboxes)
kwargs.setdefault('clickable', self.clickable)
kwargs.setdefault('deletable', self.deletable)
kwargs.setdefault('partial_only', self.partial_only)

View file

@ -69,8 +69,8 @@ class ProfilesGrid(SearchableAlchemyGridView):
],
readonly=True)
if self.request.has_perm('label_profiles.read'):
g.clickable = True
g.click_route_name = 'label_profile.read'
g.viewable = True
g.view_route_name = 'label_profile.read'
if self.request.has_perm('label_profiles.update'):
g.editable = True
g.edit_route_name = 'label_profile.update'

View file

@ -88,8 +88,8 @@ class PeopleGrid(SearchableAlchemyGridView):
readonly=True)
if self.request.has_perm('people.read'):
g.clickable = True
g.click_route_name = 'person.read'
g.viewable = True
g.view_route_name = 'person.read'
if self.request.has_perm('people.update'):
g.editable = True
g.edit_route_name = 'person.update'

View file

@ -176,8 +176,8 @@ class ProductsGrid(SearchableAlchemyGridView):
readonly=True)
if self.request.has_perm('products.read'):
g.clickable = True
g.click_route_name = 'product.read'
g.viewable = True
g.view_route_name = 'product.read'
if self.request.has_perm('products.update'):
g.editable = True
g.edit_route_name = 'product.update'

View file

@ -0,0 +1,218 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
``rattail.pyramid.views.roles`` -- Role Views
"""
from pyramid.httpexceptions import HTTPFound
import formalchemy
from webhelpers.html import tags
from webhelpers.html.builder import HTML
from edbob.db import auth
from rattail.pyramid import Session
from rattail.pyramid.views import SearchableAlchemyGridView, CrudView
from rattail.db.model import Role
default_permissions = [
("People", [
('people.list', "List People"),
('people.read', "View Person"),
('people.create', "Create Person"),
('people.update', "Edit Person"),
('people.delete', "Delete Person"),
]),
("Roles", [
('roles.list', "List Roles"),
('roles.read', "View Role"),
('roles.create', "Create Role"),
('roles.update', "Edit Role"),
('roles.delete', "Delete Role"),
]),
("Users", [
('users.list', "List Users"),
('users.read', "View User"),
('users.create', "Create User"),
('users.update', "Edit User"),
('users.delete', "Delete User"),
]),
]
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):
def sync(self):
if not self.is_readonly():
role = self.model
role.permissions = self.renderer.deserialize()
def PermissionsFieldRenderer(permissions, *args, **kwargs):
perms = permissions
class PermissionsFieldRenderer(formalchemy.FieldRenderer):
permissions = perms
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):
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, include_guest=False, session=Session())
if readonly:
span = HTML.tag('span', c="[X]" if checked else "[ ]")
inner += HTML.tag('p', class_='perm', c=span + ' ' + title)
else:
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
class RoleCrud(CrudView):
mapped_class = Role
home_route = 'roles'
permissions = default_permissions
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
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')
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/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')

View file

@ -79,8 +79,8 @@ class StoresGrid(SearchableAlchemyGridView):
g.email.label("Email Address"),
],
readonly=True)
g.clickable = True
g.click_route_name = 'store.read'
g.viewable = True
g.view_route_name = 'store.read'
if self.request.has_perm('stores.update'):
g.editable = True
g.edit_route_name = 'store.update'

View file

@ -58,8 +58,8 @@ class SubdepartmentsGrid(SearchableAlchemyGridView):
],
readonly=True)
if self.request.has_perm('subdepartments.read'):
g.clickable = True
g.click_route_name = 'subdepartment.read'
g.viewable = True
g.view_route_name = 'subdepartment.read'
if self.request.has_perm('subdepartments.update'):
g.editable = True
g.edit_route_name = 'subdepartment.update'

View file

@ -30,9 +30,58 @@ import formalchemy
from edbob.pyramid.views import users
from rattail.pyramid.views import CrudView
from rattail.pyramid.views import SearchableAlchemyGridView, CrudView
from rattail.pyramid.forms import PersonFieldRenderer
from rattail.db.model import User
from rattail.db.model import User, Person
class UsersGrid(SearchableAlchemyGridView):
mapped_class = User
config_prefix = 'users'
sort = 'username'
def join_map(self):
return {
'person':
lambda q: q.outerjoin(Person),
}
def filter_map(self):
return self.make_filter_map(
ilike=['username'],
person=self.filter_ilike(Person.display_name))
def filter_config(self):
return self.make_filter_config(
include_filter_username=True,
filter_type_username='lk',
include_filter_person=True,
filter_type_person='lk')
def sort_map(self):
return self.make_sort_map(
'username',
person=self.sorter(Person.display_name))
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.username,
g.person,
],
readonly=True)
if self.request.has_perm('users.read'):
g.viewable = True
g.view_route_name = 'user.read'
if self.request.has_perm('users.update'):
g.editable = True
g.edit_route_name = 'user.update'
if self.request.has_perm('users.delete'):
g.deletable = True
g.delete_route_name = 'user.delete'
return g
class UserCrud(CrudView):
@ -73,7 +122,7 @@ class UserCrud(CrudView):
def includeme(config):
config.add_route('users', '/users')
config.add_view(users.UsersGrid, route_name='users',
config.add_view(UsersGrid, route_name='users',
renderer='/users/index.mako',
permission='users.list')

View file

@ -63,8 +63,8 @@ class VendorsGrid(SearchableAlchemyGridView):
],
readonly=True)
if self.request.has_perm('vendors.read'):
g.clickable = True
g.click_route_name = 'vendor.read'
g.viewable = True
g.view_route_name = 'vendor.read'
if self.request.has_perm('vendors.update'):
g.editable = True
g.edit_route_name = 'vendor.update'

View file

@ -36,8 +36,8 @@ class DepartmentsGridTests(TestCase):
view.request.has_perm = Mock(return_value=True)
view.make_grid = Mock()
g = view.grid()
self.assertTrue(g.clickable)
self.assertEqual(g.click_route_name, 'department.read')
self.assertTrue(g.viewable)
self.assertEqual(g.view_route_name, 'department.read')
self.assertTrue(g.editable)
self.assertEqual(g.edit_route_name, 'department.update')
self.assertTrue(g.deletable)
@ -47,13 +47,13 @@ class DepartmentsGridTests(TestCase):
view = self.view()
view.request.has_perm = Mock(return_value=False)
grid = Mock(
clickable=False, click_route_name=None,
viewable=False, view_route_name=None,
editable=False, edit_route_name=None,
deletable=False, delete_route_name=None)
view.make_grid = Mock(return_value=grid)
g = view.grid()
self.assertFalse(g.clickable)
self.assertEqual(g.click_route_name, None)
self.assertFalse(g.viewable)
self.assertEqual(g.view_route_name, None)
self.assertFalse(g.editable)
self.assertEqual(g.edit_route_name, None)
self.assertFalse(g.deletable)