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:
parent
134613aadd
commit
b7fdd1f797
10
MANIFEST.in
10
MANIFEST.in
|
@ -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
|
||||||
|
|
|
@ -32,5 +32,6 @@ from edbob.pyramid import Session
|
||||||
|
|
||||||
|
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
config.include('rattail.pyramid.static')
|
||||||
config.include('rattail.pyramid.subscribers')
|
config.include('rattail.pyramid.subscribers')
|
||||||
config.include('rattail.pyramid.views')
|
config.include('rattail.pyramid.views')
|
||||||
|
|
|
@ -32,13 +32,12 @@ from webhelpers.html import tags
|
||||||
import formalchemy
|
import formalchemy
|
||||||
|
|
||||||
from edbob.pyramid.forms import pretty_datetime
|
from edbob.pyramid.forms import pretty_datetime
|
||||||
from edbob.pyramid.forms.formalchemy.renderers import (
|
from edbob.pyramid.forms.formalchemy.renderers import YesNoFieldRenderer
|
||||||
AutocompleteFieldRenderer, YesNoFieldRenderer)
|
|
||||||
|
|
||||||
import rattail
|
import rattail
|
||||||
from rattail.gpc import GPC
|
from rattail.gpc import GPC
|
||||||
|
|
||||||
from .common import EnumFieldRenderer
|
from .common import AutocompleteFieldRenderer, EnumFieldRenderer
|
||||||
from .products import ProductFieldRenderer
|
from .products import ProductFieldRenderer
|
||||||
from .users import UserFieldRenderer
|
from .users import UserFieldRenderer
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,33 @@
|
||||||
from formalchemy.fields import SelectFieldRenderer
|
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):
|
def EnumFieldRenderer(enum):
|
||||||
|
|
|
@ -53,7 +53,6 @@ class AlchemyGrid(Grid):
|
||||||
self._formalchemy_grid = formalchemy.Grid(
|
self._formalchemy_grid = formalchemy.Grid(
|
||||||
cls, instances, session=Session(), request=request)
|
cls, instances, session=Session(), request=request)
|
||||||
self._formalchemy_grid.prettify = prettify
|
self._formalchemy_grid.prettify = prettify
|
||||||
self.noclick_fields = []
|
|
||||||
|
|
||||||
def __delattr__(self, attr):
|
def __delattr__(self, attr):
|
||||||
delattr(self._formalchemy_grid, attr)
|
delattr(self._formalchemy_grid, attr)
|
||||||
|
@ -63,16 +62,11 @@ class AlchemyGrid(Grid):
|
||||||
|
|
||||||
def cell_class(self, field):
|
def cell_class(self, field):
|
||||||
classes = [field.name]
|
classes = [field.name]
|
||||||
if field.name in self.noclick_fields:
|
|
||||||
classes.append('noclick')
|
|
||||||
return ' '.join(classes)
|
return ' '.join(classes)
|
||||||
|
|
||||||
def checkbox(self, row):
|
def checkbox(self, row):
|
||||||
return tags.checkbox('check-'+row.uuid)
|
return tags.checkbox('check-'+row.uuid)
|
||||||
|
|
||||||
def click_route_kwargs(self, row):
|
|
||||||
return {'uuid': row.uuid}
|
|
||||||
|
|
||||||
def column_header(self, field):
|
def column_header(self, field):
|
||||||
class_ = None
|
class_ = None
|
||||||
label = field.label()
|
label = field.label()
|
||||||
|
@ -84,6 +78,9 @@ class AlchemyGrid(Grid):
|
||||||
return HTML.tag('th', class_=class_, field=field.key,
|
return HTML.tag('th', class_=class_, field=field.key,
|
||||||
title=self.column_titles.get(field.key), c=label)
|
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):
|
def edit_route_kwargs(self, row):
|
||||||
return {'uuid': row.uuid}
|
return {'uuid': row.uuid}
|
||||||
|
|
||||||
|
|
|
@ -46,19 +46,18 @@ class Grid(Object):
|
||||||
|
|
||||||
full = False
|
full = False
|
||||||
hoverable = True
|
hoverable = True
|
||||||
clickable = False
|
|
||||||
checkboxes = False
|
checkboxes = False
|
||||||
editable = False
|
|
||||||
deletable = False
|
|
||||||
|
|
||||||
partial_only = False
|
partial_only = False
|
||||||
|
|
||||||
click_route_name = None
|
viewable = False
|
||||||
click_route_kwargs = None
|
view_route_name = None
|
||||||
|
view_route_kwargs = None
|
||||||
|
|
||||||
|
editable = False
|
||||||
edit_route_name = None
|
edit_route_name = None
|
||||||
edit_route_kwargs = None
|
edit_route_kwargs = None
|
||||||
|
|
||||||
|
deletable = False
|
||||||
delete_route_name = None
|
delete_route_name = None
|
||||||
delete_route_kwargs = None
|
delete_route_kwargs = None
|
||||||
|
|
||||||
|
@ -82,22 +81,20 @@ class Grid(Object):
|
||||||
classes = ['grid']
|
classes = ['grid']
|
||||||
if self.full:
|
if self.full:
|
||||||
classes.append('full')
|
classes.append('full')
|
||||||
if self.clickable:
|
|
||||||
classes.append('clickable')
|
|
||||||
if self.hoverable:
|
if self.hoverable:
|
||||||
classes.append('hoverable')
|
classes.append('hoverable')
|
||||||
return format_attrs(
|
return format_attrs(
|
||||||
class_=' '.join(classes),
|
class_=' '.join(classes),
|
||||||
url=self.request.current_route_url())
|
url=self.request.current_route_url())
|
||||||
|
|
||||||
def get_delete_url(self, row):
|
def get_view_url(self, row):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if self.delete_route_kwargs:
|
if self.view_route_kwargs:
|
||||||
if callable(self.delete_route_kwargs):
|
if callable(self.view_route_kwargs):
|
||||||
kwargs = self.delete_route_kwargs(row)
|
kwargs = self.view_route_kwargs(row)
|
||||||
else:
|
else:
|
||||||
kwargs = self.delete_route_kwargs
|
kwargs = self.view_route_kwargs
|
||||||
return self.request.route_url(self.delete_route_name, **kwargs)
|
return self.request.route_url(self.view_route_name, **kwargs)
|
||||||
|
|
||||||
def get_edit_url(self, row):
|
def get_edit_url(self, row):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
@ -108,16 +105,17 @@ class Grid(Object):
|
||||||
kwargs = self.edit_route_kwargs
|
kwargs = self.edit_route_kwargs
|
||||||
return self.request.route_url(self.edit_route_name, **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):
|
def get_row_attrs(self, row, i):
|
||||||
attrs = self.row_attrs(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)
|
return format_attrs(**attrs)
|
||||||
|
|
||||||
def iter_fields(self):
|
def iter_fields(self):
|
||||||
|
|
31
rattail/pyramid/static/__init__.py
Normal file
31
rattail/pyramid/static/__init__.py
Normal 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')
|
164
rattail/pyramid/static/css/grids.css
Normal file
164
rattail/pyramid/static/css/grids.css
Normal 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;
|
||||||
|
}
|
BIN
rattail/pyramid/static/img/delete.png
Normal file
BIN
rattail/pyramid/static/img/delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 641 B |
BIN
rattail/pyramid/static/img/edit.png
Normal file
BIN
rattail/pyramid/static/img/edit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 533 B |
BIN
rattail/pyramid/static/img/view.png
Normal file
BIN
rattail/pyramid/static/img/view.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 616 B |
3
rattail/pyramid/templates/forms/field_autocomplete.mako
Normal file
3
rattail/pyramid/templates/forms/field_autocomplete.mako
Normal 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)}
|
63
rattail/pyramid/templates/grids/grid.mako
Normal file
63
rattail/pyramid/templates/grids/grid.mako
Normal 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> </th>
|
||||||
|
% endif
|
||||||
|
% if grid.editable:
|
||||||
|
<th> </th>
|
||||||
|
% endif
|
||||||
|
% if grid.deletable:
|
||||||
|
<th> </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)}"> </td>
|
||||||
|
% endif
|
||||||
|
% if grid.editable:
|
||||||
|
<td class="edit" url="${grid.get_edit_url(row)}"> </td>
|
||||||
|
% endif
|
||||||
|
% if grid.deletable:
|
||||||
|
<td class="delete" url="${grid.get_delete_url(row)}"> </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
|
||||||
|
${grid.page_links()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
</div>
|
|
@ -41,6 +41,7 @@ def includeme(config):
|
||||||
config.include('rattail.pyramid.views.employees')
|
config.include('rattail.pyramid.views.employees')
|
||||||
config.include('rattail.pyramid.views.labels')
|
config.include('rattail.pyramid.views.labels')
|
||||||
config.include('rattail.pyramid.views.products')
|
config.include('rattail.pyramid.views.products')
|
||||||
|
config.include('rattail.pyramid.views.roles')
|
||||||
config.include('rattail.pyramid.views.stores')
|
config.include('rattail.pyramid.views.stores')
|
||||||
config.include('rattail.pyramid.views.subdepartments')
|
config.include('rattail.pyramid.views.subdepartments')
|
||||||
config.include('rattail.pyramid.views.vendors')
|
config.include('rattail.pyramid.views.vendors')
|
||||||
|
|
|
@ -101,8 +101,8 @@ class BatchesGrid(SearchableAlchemyGridView):
|
||||||
return tags.link_to("View Rows", self.request.route_url(
|
return tags.link_to("View Rows", self.request.route_url(
|
||||||
'batch.rows', uuid=row.uuid))
|
'batch.rows', uuid=row.uuid))
|
||||||
g.add_column('rows', "", rows)
|
g.add_column('rows', "", rows)
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'batch.read'
|
g.view_route_name = 'batch.read'
|
||||||
if self.request.has_perm('batches.update'):
|
if self.request.has_perm('batches.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'batch.update'
|
g.edit_route_name = 'batch.update'
|
||||||
|
|
|
@ -97,9 +97,9 @@ def BatchRowsGrid(request):
|
||||||
route_kwargs = lambda x: {'batch_uuid': x.batch.uuid, 'uuid': x.uuid}
|
route_kwargs = lambda x: {'batch_uuid': x.batch.uuid, 'uuid': x.uuid}
|
||||||
|
|
||||||
if self.request.has_perm('batch_rows.read'):
|
if self.request.has_perm('batch_rows.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'batch_row.read'
|
g.view_route_name = 'batch_row.read'
|
||||||
g.click_route_kwargs = route_kwargs
|
g.view_route_kwargs = route_kwargs
|
||||||
|
|
||||||
if self.request.has_perm('batch_rows.update'):
|
if self.request.has_perm('batch_rows.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
|
|
|
@ -57,8 +57,8 @@ class BrandsGrid(SearchableAlchemyGridView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
if self.request.has_perm('brands.read'):
|
if self.request.has_perm('brands.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'brand.read'
|
g.view_route_name = 'brand.read'
|
||||||
if self.request.has_perm('brands.update'):
|
if self.request.has_perm('brands.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'brand.update'
|
g.edit_route_name = 'brand.update'
|
||||||
|
|
|
@ -57,8 +57,8 @@ class CategoriesGrid(SearchableAlchemyGridView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
if self.request.has_perm('categories.read'):
|
if self.request.has_perm('categories.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'category.read'
|
g.view_route_name = 'category.read'
|
||||||
if self.request.has_perm('categories.update'):
|
if self.request.has_perm('categories.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'category.update'
|
g.edit_route_name = 'category.update'
|
||||||
|
|
|
@ -58,8 +58,8 @@ class CustomerGroupsGrid(SearchableAlchemyGridView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
if self.request.has_perm('customer_groups.read'):
|
if self.request.has_perm('customer_groups.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'customer_group.read'
|
g.view_route_name = 'customer_group.read'
|
||||||
if self.request.has_perm('customer_groups.update'):
|
if self.request.has_perm('customer_groups.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'customer_group.update'
|
g.edit_route_name = 'customer_group.update'
|
||||||
|
|
|
@ -46,7 +46,6 @@ class CustomersGrid(SearchableAlchemyGridView):
|
||||||
mapped_class = Customer
|
mapped_class = Customer
|
||||||
config_prefix = 'customers'
|
config_prefix = 'customers'
|
||||||
sort = 'name'
|
sort = 'name'
|
||||||
clickable = True
|
|
||||||
|
|
||||||
def join_map(self):
|
def join_map(self):
|
||||||
return {
|
return {
|
||||||
|
@ -93,8 +92,8 @@ class CustomersGrid(SearchableAlchemyGridView):
|
||||||
readonly=True)
|
readonly=True)
|
||||||
|
|
||||||
if self.request.has_perm('customers.read'):
|
if self.request.has_perm('customers.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'customer.read'
|
g.view_route_name = 'customer.read'
|
||||||
if self.request.has_perm('customers.update'):
|
if self.request.has_perm('customers.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'customer.update'
|
g.edit_route_name = 'customer.update'
|
||||||
|
|
|
@ -59,8 +59,8 @@ class DepartmentsGrid(SearchableAlchemyGridView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
if self.request.has_perm('departments.read'):
|
if self.request.has_perm('departments.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'department.read'
|
g.view_route_name = 'department.read'
|
||||||
if self.request.has_perm('departments.update'):
|
if self.request.has_perm('departments.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'department.update'
|
g.edit_route_name = 'department.update'
|
||||||
|
|
|
@ -117,8 +117,8 @@ class EmployeesGrid(SearchableAlchemyGridView):
|
||||||
del g.status
|
del g.status
|
||||||
|
|
||||||
if self.request.has_perm('employees.read'):
|
if self.request.has_perm('employees.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'employee.read'
|
g.view_route_name = 'employee.read'
|
||||||
if self.request.has_perm('employees.update'):
|
if self.request.has_perm('employees.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'employee.update'
|
g.edit_route_name = 'employee.update'
|
||||||
|
|
|
@ -42,7 +42,6 @@ class GridView(View):
|
||||||
|
|
||||||
full = False
|
full = False
|
||||||
checkboxes = False
|
checkboxes = False
|
||||||
clickable = False
|
|
||||||
deletable = False
|
deletable = False
|
||||||
|
|
||||||
partial_only = False
|
partial_only = False
|
||||||
|
@ -50,7 +49,6 @@ class GridView(View):
|
||||||
def update_grid_kwargs(self, kwargs):
|
def update_grid_kwargs(self, kwargs):
|
||||||
kwargs.setdefault('full', self.full)
|
kwargs.setdefault('full', self.full)
|
||||||
kwargs.setdefault('checkboxes', self.checkboxes)
|
kwargs.setdefault('checkboxes', self.checkboxes)
|
||||||
kwargs.setdefault('clickable', self.clickable)
|
|
||||||
kwargs.setdefault('deletable', self.deletable)
|
kwargs.setdefault('deletable', self.deletable)
|
||||||
kwargs.setdefault('partial_only', self.partial_only)
|
kwargs.setdefault('partial_only', self.partial_only)
|
||||||
|
|
||||||
|
|
|
@ -69,8 +69,8 @@ class ProfilesGrid(SearchableAlchemyGridView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
if self.request.has_perm('label_profiles.read'):
|
if self.request.has_perm('label_profiles.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'label_profile.read'
|
g.view_route_name = 'label_profile.read'
|
||||||
if self.request.has_perm('label_profiles.update'):
|
if self.request.has_perm('label_profiles.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'label_profile.update'
|
g.edit_route_name = 'label_profile.update'
|
||||||
|
|
|
@ -88,8 +88,8 @@ class PeopleGrid(SearchableAlchemyGridView):
|
||||||
readonly=True)
|
readonly=True)
|
||||||
|
|
||||||
if self.request.has_perm('people.read'):
|
if self.request.has_perm('people.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'person.read'
|
g.view_route_name = 'person.read'
|
||||||
if self.request.has_perm('people.update'):
|
if self.request.has_perm('people.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'person.update'
|
g.edit_route_name = 'person.update'
|
||||||
|
|
|
@ -176,8 +176,8 @@ class ProductsGrid(SearchableAlchemyGridView):
|
||||||
readonly=True)
|
readonly=True)
|
||||||
|
|
||||||
if self.request.has_perm('products.read'):
|
if self.request.has_perm('products.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'product.read'
|
g.view_route_name = 'product.read'
|
||||||
if self.request.has_perm('products.update'):
|
if self.request.has_perm('products.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'product.update'
|
g.edit_route_name = 'product.update'
|
||||||
|
|
218
rattail/pyramid/views/roles.py
Normal file
218
rattail/pyramid/views/roles.py
Normal 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')
|
|
@ -79,8 +79,8 @@ class StoresGrid(SearchableAlchemyGridView):
|
||||||
g.email.label("Email Address"),
|
g.email.label("Email Address"),
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'store.read'
|
g.view_route_name = 'store.read'
|
||||||
if self.request.has_perm('stores.update'):
|
if self.request.has_perm('stores.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'store.update'
|
g.edit_route_name = 'store.update'
|
||||||
|
|
|
@ -58,8 +58,8 @@ class SubdepartmentsGrid(SearchableAlchemyGridView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
if self.request.has_perm('subdepartments.read'):
|
if self.request.has_perm('subdepartments.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'subdepartment.read'
|
g.view_route_name = 'subdepartment.read'
|
||||||
if self.request.has_perm('subdepartments.update'):
|
if self.request.has_perm('subdepartments.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'subdepartment.update'
|
g.edit_route_name = 'subdepartment.update'
|
||||||
|
|
|
@ -30,9 +30,58 @@ import formalchemy
|
||||||
|
|
||||||
from edbob.pyramid.views import users
|
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.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):
|
class UserCrud(CrudView):
|
||||||
|
@ -73,7 +122,7 @@ class UserCrud(CrudView):
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
|
|
||||||
config.add_route('users', '/users')
|
config.add_route('users', '/users')
|
||||||
config.add_view(users.UsersGrid, route_name='users',
|
config.add_view(UsersGrid, route_name='users',
|
||||||
renderer='/users/index.mako',
|
renderer='/users/index.mako',
|
||||||
permission='users.list')
|
permission='users.list')
|
||||||
|
|
||||||
|
|
|
@ -63,8 +63,8 @@ class VendorsGrid(SearchableAlchemyGridView):
|
||||||
],
|
],
|
||||||
readonly=True)
|
readonly=True)
|
||||||
if self.request.has_perm('vendors.read'):
|
if self.request.has_perm('vendors.read'):
|
||||||
g.clickable = True
|
g.viewable = True
|
||||||
g.click_route_name = 'vendor.read'
|
g.view_route_name = 'vendor.read'
|
||||||
if self.request.has_perm('vendors.update'):
|
if self.request.has_perm('vendors.update'):
|
||||||
g.editable = True
|
g.editable = True
|
||||||
g.edit_route_name = 'vendor.update'
|
g.edit_route_name = 'vendor.update'
|
||||||
|
|
|
@ -36,8 +36,8 @@ class DepartmentsGridTests(TestCase):
|
||||||
view.request.has_perm = Mock(return_value=True)
|
view.request.has_perm = Mock(return_value=True)
|
||||||
view.make_grid = Mock()
|
view.make_grid = Mock()
|
||||||
g = view.grid()
|
g = view.grid()
|
||||||
self.assertTrue(g.clickable)
|
self.assertTrue(g.viewable)
|
||||||
self.assertEqual(g.click_route_name, 'department.read')
|
self.assertEqual(g.view_route_name, 'department.read')
|
||||||
self.assertTrue(g.editable)
|
self.assertTrue(g.editable)
|
||||||
self.assertEqual(g.edit_route_name, 'department.update')
|
self.assertEqual(g.edit_route_name, 'department.update')
|
||||||
self.assertTrue(g.deletable)
|
self.assertTrue(g.deletable)
|
||||||
|
@ -47,13 +47,13 @@ class DepartmentsGridTests(TestCase):
|
||||||
view = self.view()
|
view = self.view()
|
||||||
view.request.has_perm = Mock(return_value=False)
|
view.request.has_perm = Mock(return_value=False)
|
||||||
grid = Mock(
|
grid = Mock(
|
||||||
clickable=False, click_route_name=None,
|
viewable=False, view_route_name=None,
|
||||||
editable=False, edit_route_name=None,
|
editable=False, edit_route_name=None,
|
||||||
deletable=False, delete_route_name=None)
|
deletable=False, delete_route_name=None)
|
||||||
view.make_grid = Mock(return_value=grid)
|
view.make_grid = Mock(return_value=grid)
|
||||||
g = view.grid()
|
g = view.grid()
|
||||||
self.assertFalse(g.clickable)
|
self.assertFalse(g.viewable)
|
||||||
self.assertEqual(g.click_route_name, None)
|
self.assertEqual(g.view_route_name, None)
|
||||||
self.assertFalse(g.editable)
|
self.assertFalse(g.editable)
|
||||||
self.assertEqual(g.edit_route_name, None)
|
self.assertEqual(g.edit_route_name, None)
|
||||||
self.assertFalse(g.deletable)
|
self.assertFalse(g.deletable)
|
||||||
|
|
Loading…
Reference in a new issue