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:
parent
e313e1bc8c
commit
0ad29c5283
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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,8 +202,40 @@ 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.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
View for creating a new model record.
|
||||
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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'),
|
||||
|
|
Loading…
Reference in a new issue