Add basic paging grid/index support for mobile

still lots to do for this yet..but readonly basics are here..
This commit is contained in:
Lance Edgar 2017-03-30 20:11:17 -05:00
parent e313e1bc8c
commit 0ad29c5283
7 changed files with 132 additions and 57 deletions

View file

@ -26,46 +26,16 @@ Mobile Grids
from __future__ import unicode_literals, absolute_import
from webhelpers.html import tags
from webhelpers.html import HTML
from tailbone.newgrids import AlchemyGrid
class Grid(object):
"""
Base class for all grids
"""
configured = False
def __init__(self, key, data=None, **kwargs):
"""
Grid constructor
"""
self.key = key
self.data = data
for k, v in kwargs.items():
setattr(self, k, v)
def __iter__(self):
"""
This grid supports iteration, over its data
"""
return iter(self.data)
def configure(self, include=None):
"""
Configure the grid. This must define which columns to display and in
which order, etc.
"""
self.configured = True
def view_url(self, obj, mobile=False):
route = '{}{}.view'.format('mobile.' if mobile else '', self.route_prefix)
return self.request.route_url(route, uuid=obj.uuid)
class MobileGrid(Grid):
class MobileGrid(AlchemyGrid):
"""
Base class for all mobile grids
"""
def render_object(self, obj):
return tags.link_to(obj, self.view_url(obj, mobile=True))
def column_header(self, column):
kwargs = {'c': column.label}
return HTML.tag('th', **kwargs)

View file

@ -108,7 +108,12 @@
% endfor
% endif
% if capture(self.page_title):
% if master is not Undefined and master.listing:
<h1>${model_title_plural}</h1>
% elif index_title is not Undefined and index_url is not Undefined:
<h1>${h.link_to(index_title, index_url)}</h1>
<h2>${self.page_title()}</h2>
% elif capture(self.page_title):
<h2>${self.page_title()}</h2>
% endif

View file

@ -10,7 +10,38 @@
<%def name="title()">${model_title_plural}</%def>
<ul data-role="listview">
% for obj in grid:
<li>${grid.render_object(obj)}</li>
% for obj in grid.iter_rows():
<li>${grid.listitem.render_readonly()}</li>
% endfor
</ul>
## <table data-role="table" class="ui-responsive table-stroke">
## <thead>
## <tr>
## % for column in grid.iter_visible_columns():
## ${grid.column_header(column)}
## % endfor
## </tr>
## </thead>
## <tbody>
## % for i, row in enumerate(grid.iter_rows(), 1):
## <tr>
## % for column in grid.iter_visible_columns():
## <td>${grid.render_cell(row, column)}</td>
## % endfor
## </tr>
## % endfor
## </tbody>
## </table>
% if grid.pageable and grid.pager:
<br />
<div data-role="controlgroup" data-type="horizontal">
${grid.pager.pager('$link_first $link_previous $link_next $link_last',
symbol_first='<< first', symbol_last='last >>',
symbol_previous='< prev', symbol_next='next >',
link_attr={'class': 'ui-btn ui-corner-all'},
curpage_attr={'class': 'ui-btn ui-corner-all'},
dotdot_attr={'class': 'ui-btn ui-corner-all'})}
</div>
% endif

View file

@ -7,6 +7,6 @@
## ##############################################################################
<%inherit file="/mobile/base.mako" />
<%def name="title()">${model_title}: ${instance_title}</%def>
<%def name="title()">${instance_title}</%def>
<p>TODO: display fieldset for object</p>
${form.render()|n}

View file

@ -133,6 +133,13 @@ class CustomersView(MasterView):
fs.email_preference,
])
def configure_mobile_fieldset(self, fs):
fs.configure(
include=[
fs.email,
fs.phone,
])
class CustomerNameAutocomplete(AutocompleteView):
"""

View file

@ -26,6 +26,7 @@ Model Master View
from __future__ import unicode_literals, absolute_import
import six
import sqlalchemy as sa
from sqlalchemy import orm
@ -102,7 +103,7 @@ class MasterView(View):
View to list/filter/sort the model data.
If this view receives a non-empty 'partial' parameter in the query
string, then the view will return the renderered grid only. Otherwise
string, then the view will return the rendered grid only. Otherwise
returns the full page.
"""
self.listing = True
@ -135,16 +136,15 @@ class MasterView(View):
def make_mobile_grid(self, **kwargs):
factory = self.get_mobile_grid_factory()
key = self.get_mobile_grid_key()
data = self.get_mobile_data(session=kwargs.get('session'))
kwargs = self.make_mobile_grid_kwargs(**kwargs)
kwargs.setdefault('key', key)
kwargs.setdefault('key', self.get_mobile_grid_key())
kwargs.setdefault('request', self.request)
kwargs.setdefault('data', data)
kwargs.setdefault('data', self.get_mobile_data(session=kwargs.get('session')))
kwargs.setdefault('model_class', self.get_model_class(error=False))
grid = factory(**kwargs)
self.preconfigure_mobile_grid(grid)
self.configure_mobile_grid(grid)
grid.load_settings()
return grid
@classmethod
@ -166,7 +166,7 @@ class MasterView(View):
"""
if hasattr(cls, 'mobile_grid_key'):
return cls.mobile_grid_key
return cls.get_route_prefix()
return 'mobile.{}'.format(cls.get_route_prefix())
def get_mobile_data(self, session=None):
"""
@ -184,6 +184,8 @@ class MasterView(View):
"""
defaults = {
'route_prefix': self.get_route_prefix(),
'pageable': self.pageable,
'sortable': True,
}
defaults.update(kwargs)
return defaults
@ -200,7 +202,39 @@ class MasterView(View):
which columns to show and in which order etc. Along the way you're
free to customize any column(s) you like, as needed.
"""
grid.configure()
listitem = self.mobile_listitem_field()
if listitem:
grid.append(listitem)
grid.configure(include=[grid.listitem])
else:
grid.configure()
def mobile_listitem_field(self):
"""
Must return a FormAlchemy field to be appended to grid, or ``None`` if
none is desired.
"""
return fa.Field('listitem', value=lambda obj: obj,
renderer=self.mobile_listitem_renderer())
def mobile_listitem_renderer(self):
"""
Must return a FormAlchemy field renderer callable for the mobile grid's
list item field.
"""
master = self
class ListItemRenderer(fa.FieldRenderer):
def render_readonly(self, **kwargs):
obj = self.raw_value
if obj is None:
return ''
title = master.get_instance_title(obj)
url = master.get_action_url('view', obj, mobile=True)
return tags.link_to(title, url)
return ListItemRenderer
def create(self):
"""
@ -271,17 +305,39 @@ class MasterView(View):
"""
self.viewing = True
instance = self.get_instance()
# form = self.make_form(instance)
form = self.make_mobile_form(instance)
context = {
'instance': instance,
'instance_title': self.get_instance_title(instance),
# 'instance_editable': self.editable_instance(instance),
# 'instance_deletable': self.deletable_instance(instance),
# 'form': form,
'form': form,
}
return self.render_to_response('view', context, mobile=True)
def make_mobile_form(self, instance, **kwargs):
"""
Make a FormAlchemy-based form for use with mobile CRUD views
"""
fieldset = self.make_fieldset(instance)
self.preconfigure_mobile_fieldset(fieldset)
self.configure_mobile_fieldset(fieldset)
factory = kwargs.pop('factory', forms.AlchemyForm)
kwargs.setdefault('session', self.Session())
form = factory(self.request, fieldset, **kwargs)
form.readonly = self.viewing
return form
def preconfigure_mobile_fieldset(self, fieldset):
self._preconfigure_fieldset(fieldset)
def configure_mobile_fieldset(self, fieldset):
"""
Configure the given mobile fieldset.
"""
self.configure_fieldset(fieldset)
def make_default_row_grid_tools(self, obj):
if self.rows_creatable:
link = tags.link_to("Create a new {}".format(self.get_row_model_title()),
@ -547,7 +603,7 @@ class MasterView(View):
object_to_keep = self.Session.query(self.get_model_class()).get(uuids[1])
if object_to_remove and object_to_keep and self.request.POST.get('commit-merge') == 'yes':
msg = unicode(object_to_remove)
msg = six.text_type(object_to_remove)
try:
self.validate_merge(object_to_remove, object_to_keep)
except Exception as error:
@ -691,11 +747,14 @@ class MasterView(View):
"""
return getattr(cls, 'permission_prefix', cls.get_route_prefix())
def get_index_url(self):
def get_index_url(self, mobile=False):
"""
Returns the master view's index URL.
"""
return self.request.route_url(self.get_route_prefix())
route = self.get_route_prefix()
if mobile:
route = 'mobile.{}'.format(route)
return self.request.route_url(route)
@classmethod
def get_index_title(cls):
@ -704,13 +763,15 @@ class MasterView(View):
"""
return getattr(cls, 'index_title', cls.get_model_title_plural())
def get_action_url(self, action, instance, **kwargs):
def get_action_url(self, action, instance, mobile=False, **kwargs):
"""
Generate a URL for the given action on the given instance
"""
kw = self.get_action_route_kwargs(instance)
kw.update(kwargs)
route_prefix = self.get_route_prefix()
if mobile:
route_prefix = 'mobile.{}'.format(route_prefix)
return self.request.route_url('{}.{}'.format(route_prefix, action), **kw)
def render_to_response(self, template, data, mobile=False):
@ -728,7 +789,7 @@ class MasterView(View):
'route_prefix': self.get_route_prefix(),
'permission_prefix': self.get_permission_prefix(),
'index_title': self.get_index_title(),
'index_url': self.get_index_url(),
'index_url': self.get_index_url(mobile=mobile),
'action_url': self.get_action_url,
'grid_index': self.grid_index,
}
@ -1066,7 +1127,7 @@ class MasterView(View):
"""
Return a "pretty" title for the instance, to be used in the page title etc.
"""
return unicode(instance)
return six.text_type(instance)
def make_form(self, instance, **kwargs):
"""

View file

@ -93,6 +93,7 @@ class ProductsView(MasterView):
Master view for the Product class.
"""
model_class = model.Product
supports_mobile = True
# child_version_classes = [
# (model.ProductCode, 'product_uuid'),