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
					
				
					 7 changed files with 132 additions and 57 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue