From 62fa0f9fcb9fc988cb49785fce7bbd517875401e Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 6 Jul 2017 00:28:01 -0500 Subject: [PATCH] Remove all "old-style" (aka. version 1) grids --- docs/api/views/batch.rst | 12 - tailbone/grids/__init__.py | 32 --- tailbone/grids/alchemy.py | 123 -------- tailbone/grids/core.py | 155 ---------- tailbone/grids/search.py | 382 ------------------------- tailbone/grids/util.py | 138 --------- tailbone/views/__init__.py | 3 - tailbone/views/batch/__init__.py | 8 +- tailbone/views/batch/core.py | 428 +--------------------------- tailbone/views/custorders/orders.py | 3 +- tailbone/views/departments.py | 12 +- tailbone/views/email.py | 15 +- tailbone/views/employees.py | 6 +- tailbone/views/grids/__init__.py | 32 --- tailbone/views/grids/alchemy.py | 192 ------------- tailbone/views/grids/core.py | 72 ----- tailbone/views/purchasing/batch.py | 2 +- tailbone/views/roles.py | 13 +- tailbone/views/tables.py | 8 +- 19 files changed, 32 insertions(+), 1604 deletions(-) delete mode 100644 tailbone/grids/__init__.py delete mode 100644 tailbone/grids/alchemy.py delete mode 100644 tailbone/grids/core.py delete mode 100644 tailbone/grids/search.py delete mode 100644 tailbone/grids/util.py delete mode 100644 tailbone/views/grids/__init__.py delete mode 100644 tailbone/views/grids/alchemy.py delete mode 100644 tailbone/views/grids/core.py diff --git a/docs/api/views/batch.rst b/docs/api/views/batch.rst index a98fc39d..4de15486 100644 --- a/docs/api/views/batch.rst +++ b/docs/api/views/batch.rst @@ -5,24 +5,12 @@ .. automodule:: tailbone.views.batch -.. autoclass:: BatchGrid - :members: - -.. autoclass:: FileBatchGrid - :members: - .. autoclass:: BatchCrud :members: .. autoclass:: FileBatchCrud :members: -.. autoclass:: BatchRowGrid - :members: - -.. autoclass:: ProductBatchRowGrid - :members: - .. autoclass:: BatchRowCrud :members: diff --git a/tailbone/grids/__init__.py b/tailbone/grids/__init__.py deleted file mode 100644 index 030c0da7..00000000 --- a/tailbone/grids/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2016 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 . -# -################################################################################ -""" -Grids -""" - -from __future__ import unicode_literals, absolute_import - -from .core import * -from .alchemy import AlchemyGrid -from . import util -from . import search diff --git a/tailbone/grids/alchemy.py b/tailbone/grids/alchemy.py deleted file mode 100644 index c7e13bbb..00000000 --- a/tailbone/grids/alchemy.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2016 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 . -# -################################################################################ -""" -FormAlchemy Grid Classes -""" - -from __future__ import unicode_literals, absolute_import - -from sqlalchemy.orm import object_session - -try: - from sqlalchemy.inspection import inspect -except ImportError: - inspect = None - from sqlalchemy.orm import class_mapper - -from rattail.util import prettify - -import formalchemy as fa -from webhelpers.html import tags -from webhelpers.html import HTML - -from tailbone.db import Session -from tailbone.grids.core import Grid - - -class AlchemyGrid(Grid): - - sort_map = {} - - pager = None - pager_format = '$link_first $link_previous ~1~ $link_next $link_last' - - def __init__(self, request, cls, instances, **kwargs): - super(AlchemyGrid, self).__init__(request, **kwargs) - self._formalchemy_grid = fa.Grid(cls, instances, session=Session(), - request=request) - self._formalchemy_grid.prettify = prettify - - def __delattr__(self, attr): - delattr(self._formalchemy_grid, attr) - - def __getattr__(self, attr): - return getattr(self._formalchemy_grid, attr) - - def cell_class(self, field): - classes = [field.name] - return ' '.join(classes) - - def checkbox(self, row): - return tags.checkbox('check-'+row.uuid) - - def column_header(self, field): - class_ = None - label = field.label() - if field.key in self.sort_map: - class_ = 'sortable' - if field.key == self.config['sort']: - class_ += ' sorted ' + self.config['dir'] - label = tags.link_to(label, '#') - return HTML.tag('th', class_=class_, field=field.key, - title=self.column_titles.get(field.key), c=label) - - def crud_route_kwargs(self, row): - if inspect: - mapper = inspect(row.__class__) - else: - mapper = class_mapper(row.__class__) - keys = [k.key for k in mapper.primary_key] - values = [getattr(row, k) for k in keys] - return dict(zip(keys, values)) - - view_route_kwargs = crud_route_kwargs - edit_route_kwargs = crud_route_kwargs - delete_route_kwargs = crud_route_kwargs - - def iter_fields(self): - return self._formalchemy_grid.render_fields.itervalues() - - def iter_rows(self): - for row in self._formalchemy_grid.rows: - self._formalchemy_grid._set_active(row, object_session(row)) - yield row - - def page_count_options(self): - return [5, 10, 20, 50, 100] - - def page_links(self): - return self.pager.pager(self.pager_format, - symbol_next='next', - symbol_previous='prev', - onclick="grid_navigate_page(this, '$partial_url'); return false;") - - def render_field(self, field): - if self._formalchemy_grid.readonly: - return field.render_readonly() - return field.render() - - def row_attrs(self, row, i): - attrs = super(AlchemyGrid, self).row_attrs(row, i) - if hasattr(row, 'uuid'): - attrs['uuid'] = row.uuid - return attrs diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py deleted file mode 100644 index 911441ae..00000000 --- a/tailbone/grids/core.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/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 . -# -################################################################################ - -""" -Core Grid Classes -""" - -try: - from collections import OrderedDict -except ImportError: - from ordereddict import OrderedDict - -from webhelpers.html import HTML -from webhelpers.html.builder import format_attrs - -from pyramid.renderers import render - -from rattail.core import Object - - -__all__ = ['Grid'] - - -class Grid(Object): - - full = False - hoverable = True - checkboxes = False - partial_only = False - - 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 - - # Set this to a callable to allow ad-hoc row class additions. - extra_row_class = None - - def __init__(self, request, **kwargs): - kwargs.setdefault('fields', OrderedDict()) - kwargs.setdefault('column_titles', {}) - kwargs.setdefault('extra_columns', []) - super(Grid, self).__init__(**kwargs) - self.request = request - - def add_column(self, name, label, callback): - self.extra_columns.append( - Object(name=name, label=label, callback=callback)) - - def column_header(self, field): - return HTML.tag('th', field=field.name, - title=self.column_titles.get(field.name), - c=field.label) - - def div_attrs(self): - classes = ['grid'] - if self.full: - classes.append('full') - if self.hoverable: - classes.append('hoverable') - return format_attrs( - class_=' '.join(classes), - url=self.request.current_route_url(_query=None)) - - def get_view_url(self, row): - kwargs = {} - if self.view_route_kwargs: - if callable(self.view_route_kwargs): - kwargs = self.view_route_kwargs(row) - else: - kwargs = self.view_route_kwargs - return self.request.route_url(self.view_route_name, **kwargs) - - def get_edit_url(self, row): - kwargs = {} - if self.edit_route_kwargs: - if callable(self.edit_route_kwargs): - kwargs = self.edit_route_kwargs(row) - else: - 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) - return format_attrs(**attrs) - - def row_attrs(self, row, i): - return {'class_': self.get_row_class(row, i)} - - def get_row_class(self, row, i): - class_ = self.default_row_class(row, i) - if callable(self.extra_row_class): - extra = self.extra_row_class(row, i) - if extra: - class_ = '{0} {1}'.format(class_, extra) - return class_ - - def default_row_class(self, row, i): - return 'odd' if i % 2 else 'even' - - def iter_fields(self): - return self.fields.itervalues() - - def iter_rows(self): - """ - Iterate over the grid rows. The default implementation simply returns - an iterator over ``self.rows``; note however that by default there is - no such attribute. You must either populate that, or overrirde this - method. - """ - return iter(self.rows) - - def render(self, template='/grids/grid.mako', **kwargs): - kwargs.setdefault('grid', self) - return render(template, kwargs) - - def render_field(self, field): - raise NotImplementedError diff --git a/tailbone/grids/search.py b/tailbone/grids/search.py deleted file mode 100644 index 2bc2861f..00000000 --- a/tailbone/grids/search.py +++ /dev/null @@ -1,382 +0,0 @@ -# -*- coding: utf-8 -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2016 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 . -# -################################################################################ -""" -Grid Search Filters -""" - -from __future__ import unicode_literals, absolute_import - -import re - -from sqlalchemy import func, or_ - -from webhelpers.html import tags -from webhelpers.html import literal - -from pyramid.renderers import render -from pyramid_simpleform import Form -from pyramid_simpleform.renderers import FormRenderer - -from rattail.core import Object -from rattail.gpc import GPC -from rattail.util import prettify - - -class SearchFilter(Object): - """ - Base class and default implementation for search filters. - """ - - def __init__(self, name, label=None, **kwargs): - Object.__init__(self, **kwargs) - self.name = name - self.label = label or prettify(name) - - def types_select(self): - types = [ - ('is', "is"), - ('nt', "is not"), - ('lk', "contains"), - ('nl', "doesn't contain"), - (u'sx', u"sounds like"), - (u'nx', u"doesn't sound like"), - ] - options = [] - filter_map = self.search.filter_map[self.name] - for value, label in types: - if value in filter_map: - options.append((value, label)) - return tags.select('filter_type_'+self.name, - self.search.config.get('filter_type_'+self.name), - options, class_='filter-type') - - def value_control(self): - return tags.text(self.name, self.search.config.get(self.name)) - - -class BooleanSearchFilter(SearchFilter): - """ - Boolean search filter. - """ - - def value_control(self): - return tags.select(self.name, self.search.config.get(self.name), - ["True", "False"]) - - -class ChoiceSearchFilter(SearchFilter): - """ - Generic search filter where the user may only select among a specific set - of choices. - """ - - def __init__(self, choices): - self.choices = choices - - def __call__(self, name, label=None, **kwargs): - super(ChoiceSearchFilter, self).__init__(name, label=label, **kwargs) - return self - - def value_control(self): - return tags.select(self.name, self.search.config.get(self.name), self.choices) - - -def EnumSearchFilter(enum): - options = enum.items() - - class EnumSearchFilter(SearchFilter): - def value_control(self): - return tags.select(self.name, self.search.config.get(self.name), options) - - return EnumSearchFilter - - -class SearchForm(Form): - """ - Generic form class which aggregates :class:`SearchFilter` instances. - """ - - def __init__(self, request, filter_map, config, *args, **kwargs): - super(SearchForm, self).__init__(request, *args, **kwargs) - self.filter_map = filter_map - self.config = config - self.filters = {} - - def add_filter(self, filter_): - filter_.search = self - self.filters[filter_.name] = filter_ - - -class SearchFormRenderer(FormRenderer): - """ - Renderer for :class:`SearchForm` instances. - """ - - def __init__(self, form, *args, **kwargs): - super(SearchFormRenderer, self).__init__(form, *args, **kwargs) - self.request = form.request - self.filters = form.filters - self.config = form.config - - def add_filter(self, visible): - options = ['add a filter'] - for f in self.sorted_filters(): - options.append((f.name, f.label)) - return self.select('add-filter', options, - style='display: none;' if len(visible) == len(self.filters) else None) - - def checkbox(self, name, checked=None, **kwargs): - if name.startswith('include_filter_'): - if checked is None: - checked = self.config[name] - return tags.checkbox(name, checked=checked, **kwargs) - if checked is None: - checked = False - return super(SearchFormRenderer, self).checkbox(name, checked=checked, **kwargs) - - def render(self, **kwargs): - kwargs['search'] = self - return literal(render('/grids/search.mako', kwargs)) - - def sorted_filters(self): - return sorted(self.filters.values(), key=lambda x: x.label) - - def text(self, name, **kwargs): - return tags.text(name, value=self.config.get(name), **kwargs) - - -def filter_exact(field): - """ - Convenience function which returns a filter map entry, with typical logic - built in for "exact match" queries applied to ``field``. - """ - - return { - 'is': - lambda q, v: q.filter(field == v) if v else q, - 'nt': - lambda q, v: q.filter(field != v) if v else q, - } - - -def filter_ilike(field): - """ - Convenience function which returns a filter map entry, with typical logic - built in for "ILIKE" queries applied to ``field``. - """ - - def ilike(query, value): - if value: - query = query.filter(field.ilike('%%%s%%' % value)) - return query - - def not_ilike(query, value): - if value: - query = query.filter(or_( - field == None, - ~field.ilike('%%%s%%' % value), - )) - return query - - return {'lk': ilike, 'nl': not_ilike} - - -def filter_int(field): - """ - Returns a filter map entry for an integer field. This provides exact - matching but also strips out non-numeric characters to avoid type errors. - """ - - def filter_is(q, v): - v = re.sub(r'\D', '', v or '') - return q.filter(field == int(v)) if v else q - - def filter_nt(q, v): - v = re.sub(r'\D', '', v or '') - return q.filter(field != int(v)) if v else q - - return {'is': filter_is, 'nt': filter_nt} - - -def filter_soundex(field): - """ - Returns a filter map entry which leverages the `soundex()` SQL function. - """ - - def soundex(query, value): - if value: - query = query.filter(func.soundex(field) == func.soundex(value)) - return query - - def not_soundex(query, value): - if value: - query = query.filter(func.soundex(field) != func.soundex(value)) - return query - - return {u'sx': soundex, u'nx': not_soundex} - - -def filter_ilike_and_soundex(field): - """ - Returns a filter map which provides both the `ilike` and `soundex` - features. - """ - filters = filter_ilike(field) - filters.update(filter_soundex(field)) - return filters - - -def filter_gpc(field): - """ - Returns a filter suitable for a GPC field. - """ - - def filter_is(q, v): - if not v: - return q - try: - return q.filter(field.in_(( - GPC(v), GPC(v, calc_check_digit='upc')))) - except ValueError: - return q - - def filter_not(q, v): - if not v: - return q - try: - return q.filter(~field.in_(( - GPC(v), GPC(v, calc_check_digit='upc')))) - except ValueError: - return q - - return {'is': filter_is, 'nt': filter_not} - - -def get_filter_config(prefix, request, filter_map, **kwargs): - """ - Returns a configuration dictionary for a search form. - """ - - config = {} - - def update_config(dict_, prefix='', exclude_by_default=False): - """ - Updates the ``config`` dictionary based on the contents of ``dict_``. - """ - - for field in filter_map: - if prefix+'include_filter_'+field in dict_: - include = dict_[prefix+'include_filter_'+field] - include = bool(include) and include != '0' - config['include_filter_'+field] = include - elif exclude_by_default: - config['include_filter_'+field] = False - if prefix+'filter_type_'+field in dict_: - config['filter_type_'+field] = dict_[prefix+'filter_type_'+field] - if prefix+field in dict_: - config[field] = dict_[prefix+field] - - # Update config to exclude all filters by default. - for field in filter_map: - config['include_filter_'+field] = False - - # Update config to honor default settings. - config.update(kwargs) - - # Update config with data cached in session. - update_config(request.session, prefix=prefix+'.') - - # Update config with data from GET/POST request. - if request.params.get('filters') == 'true': - update_config(request.params, exclude_by_default=True) - - # Cache filter data in session. - for key in config: - if (not key.startswith('filter_factory_') - and not key.startswith('filter_label_')): - request.session[prefix+'.'+key] = config[key] - - return config - - -def get_filter_map(cls, exact=[], ilike=[], int_=[], **kwargs): - """ - Convenience function which returns a "filter map" for ``cls``. - - ``exact``, if provided, should be a list of field names for which "exact" - filtering is to be allowed. - - ``ilike``, if provided, should be a list of field names for which "ILIKE" - filtering is to be allowed. - - ``int_``, if provided, should be a list of field names for which "integer" - filtering is to be allowed. - - Any remaining ``kwargs`` are assumed to be filter map entries themselves, - and are added directly to the map. - """ - - fmap = {} - for name in exact: - fmap[name] = filter_exact(getattr(cls, name)) - for name in ilike: - fmap[name] = filter_ilike(getattr(cls, name)) - for name in int_: - fmap[name] = filter_int(getattr(cls, name)) - fmap.update(kwargs) - return fmap - - -def get_search_form(request, filter_map, config): - """ - Returns a :class:`SearchForm` instance with a :class:`SearchFilter` for - each filter in ``filter_map``, using configuration from ``config``. - """ - - search = SearchForm(request, filter_map, config) - for field in filter_map: - factory = config.get('filter_factory_%s' % field, SearchFilter) - label = config.get('filter_label_%s' % field) - search.add_filter(factory(field, label=label)) - return search - - -def filter_query(query, config, filter_map, join_map): - """ - Filters the given query according to filter and sorting hints found within - the config dictionary, using the filter and join maps as needed. The - filtered query is returned. - """ - joins = config.setdefault('joins', []) - for key in config: - if key.startswith('include_filter_') and config[key]: - field = key[15:] - value = config.get(field) - if value != '': - if field in join_map and field not in joins: - query = join_map[field](query) - joins.append(field) - fmap = filter_map[field] - filt = fmap[config['filter_type_'+field]] - query = filt(query, value) - return query diff --git a/tailbone/grids/util.py b/tailbone/grids/util.py deleted file mode 100644 index 6ecfd3f8..00000000 --- a/tailbone/grids/util.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/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 . -# -################################################################################ - -""" -Grid Utilities -""" - -from sqlalchemy.orm.attributes import InstrumentedAttribute - -from webhelpers.html import literal - -from pyramid.response import Response - -from .search import SearchFormRenderer - - -def get_sort_config(name, request, **kwargs): - """ - Returns a configuration dictionary for grid sorting. - """ - - # Initial config uses some default values. - config = { - 'dir': 'asc', - 'per_page': 20, - 'page': 1, - } - - # Override with defaults provided by caller. - config.update(kwargs) - - # Override with values from GET/POST request and/or session. - for key in config: - full_key = name+'_'+key - if request.params.get(key): - value = request.params[key] - config[key] = value - request.session[full_key] = value - elif request.session.get(full_key): - value = request.session[full_key] - config[key] = value - - return config - - -def get_sort_map(cls, names=None, **kwargs): - """ - Convenience function which returns a sort map for ``cls``. - - If ``names`` is not specified, the map will include all "standard" fields - present on the mapped class. Otherwise, the map will be limited to only - the fields which are named. - - All remaining ``kwargs`` are assumed to be sort map entries, and will be - added to the map directly. - """ - - smap = {} - if names is None: - names = [] - for attr in cls.__dict__: - obj = getattr(cls, attr) - if isinstance(obj, InstrumentedAttribute): - if obj.key != 'uuid': - names.append(obj.key) - for name in names: - smap[name] = sorter(getattr(cls, name)) - smap.update(kwargs) - return smap - - -def render_grid(grid, search_form=None, **kwargs): - """ - Convenience function to render ``grid`` (which should be a - :class:`tailbone.grids.Grid` instance). - - This "usually" will return a dictionary to be used as context for rendering - the final view template. - - However, if a partial grid is requested (or mandated), then the grid body - will be rendered and a :class:`pyramid.response.Response` object will be - returned instead. - """ - - if grid.partial_only or grid.request.params.get('partial'): - return Response(body=grid.render(), content_type='text/html') - kwargs['grid'] = literal(grid.render()) - if search_form: - kwargs['search'] = SearchFormRenderer(search_form) - return kwargs - - -def sort_query(query, config, sort_map, join_map={}): - """ - Sorts ``query`` according to ``config`` and ``sort_map``. ``join_map`` is - used, if necessary, to join additional tables to the base query. The - sorted query is returned. - """ - - field = config.get('sort') - if not field: - return query - joins = config.setdefault('joins', []) - if field in join_map and field not in joins: - query = join_map[field](query) - joins.append(field) - sort = sort_map[field] - return sort(query, config['dir']) - - -def sorter(field): - """ - Returns a function suitable for a sort map callable, with typical logic - built in for sorting applied to ``field``. - """ - - return lambda q, d: q.order_by(getattr(field, d)()) diff --git a/tailbone/views/__init__.py b/tailbone/views/__init__.py index aa07933f..5719965f 100644 --- a/tailbone/views/__init__.py +++ b/tailbone/views/__init__.py @@ -32,9 +32,6 @@ from .master import MasterView # TODO: deprecate / remove some of this from .autocomplete import AutocompleteView from .crud import CrudView -from .grids import ( - GridView, AlchemyGridView, SortableAlchemyGridView, - PagedAlchemyGridView, SearchableAlchemyGridView) def includeme(config): diff --git a/tailbone/views/batch/__init__.py b/tailbone/views/batch/__init__.py index e9d5b708..000d58d1 100644 --- a/tailbone/views/batch/__init__.py +++ b/tailbone/views/batch/__init__.py @@ -1,8 +1,8 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2016 Lance Edgar +# Copyright © 2010-2017 Lance Edgar # # This file is part of Rattail. # @@ -29,5 +29,5 @@ from __future__ import unicode_literals, absolute_import from .core import BatchMasterView, FileBatchMasterView # TODO: deprecate / remove this -from .core import (BaseGrid, BatchGrid, FileBatchGrid, BaseCrud, BatchCrud, FileBatchCrud, - StatusRenderer, BatchRowGrid, ProductBatchRowGrid, BatchRowCrud, defaults) +from .core import (BaseCrud, BatchCrud, FileBatchCrud, + StatusRenderer, BatchRowCrud, defaults) diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py index 05cc9f22..44bb387c 100644 --- a/tailbone/views/batch/core.py +++ b/tailbone/views/batch/core.py @@ -51,9 +51,8 @@ from webhelpers.html import HTML, tags from tailbone import forms, newgrids as grids from tailbone.db import Session -from tailbone.views import MasterView, SearchableAlchemyGridView, CrudView +from tailbone.views import MasterView, CrudView from tailbone.forms.renderers.batch import FileFieldRenderer -from tailbone.grids.search import BooleanSearchFilter, EnumSearchFilter from tailbone.progress import SessionProgress @@ -1137,264 +1136,6 @@ class FileBatchMasterView(BatchMasterView): "Download existing {} data file".format(model_title)) -class BaseGrid(SearchableAlchemyGridView): - """ - Base view for batch and batch row grid views. You should not derive from - this class, but :class:`BatchGrid` or :class:`BatchRowGrid` instead. - """ - - @property - def config_prefix(self): - """ - Config prefix for the grid view. This is used to keep track of current - filtering and sorting, within the user's session. Derived classes may - override this. - """ - return self.mapped_class.__name__.lower() - - @property - def permission_prefix(self): - """ - Permission prefix for the grid view. This is used to automatically - protect certain views common to all batches. Derived classes can - override this. - """ - return self.route_prefix - - def join_map_extras(self): - """ - Derived classes can override this. The value returned will be used to - supplement the default join map. - """ - return {} - - def filter_map_extras(self): - """ - Derived classes can override this. The value returned will be used to - supplement the default filter map. - """ - return {} - - def make_filter_map(self, **kwargs): - """ - Make a filter map by combining kwargs from the base class, with extras - supplied by a derived class. - """ - extras = self.filter_map_extras() - exact = extras.pop('exact', None) - if exact: - kwargs.setdefault('exact', []).extend(exact) - ilike = extras.pop('ilike', None) - if ilike: - kwargs.setdefault('ilike', []).extend(ilike) - kwargs.update(extras) - return super(BaseGrid, self).make_filter_map(**kwargs) - - def filter_config_extras(self): - """ - Derived classes can override this. The value returned will be used to - supplement the default filter config. - """ - return {} - - def sort_map_extras(self): - """ - Derived classes can override this. The value returned will be used to - supplement the default sort map. - """ - return {} - - def _configure_grid(self, grid): - """ - Internal method for configuring the grid. This is meant only for base - classes; derived classes should not need to override it. - """ - - def configure_grid(self, grid): - """ - Derived classes can override this. Customizes a grid which has already - been created with defaults by the base class. - """ - - -class BatchGrid(BaseGrid): - """ - Base grid view for batches, which can be filtered and sorted. - """ - - @property - def batch_class(self): - raise NotImplementedError - - @property - def mapped_class(self): - return self.batch_class - - @property - def batch_display(self): - """ - Singular display text for the batch type, e.g. "Vendor Invoice". - Override this as necessary. - """ - return self.batch_class.__name__ - - @property - def batch_display_plural(self): - """ - Plural display text for the batch type, e.g. "Vendor Invoices". - Override this as necessary. - """ - return "{0}s".format(self.batch_display) - - def join_map(self): - """ - Provides the default join map for batch grid views. Derived classes - should *not* override this, but :meth:`join_map_extras()` instead. - """ - map_ = { - 'created_by': - lambda q: q.join(model.User, model.User.uuid == self.batch_class.created_by_uuid), - 'executed_by': - lambda q: q.outerjoin(model.User, model.User.uuid == self.batch_class.executed_by_uuid), - } - map_.update(self.join_map_extras()) - return map_ - - def filter_map(self): - """ - Provides the default filter map for batch grid views. Derived classes - should *not* override this, but :meth:`filter_map_extras()` instead. - """ - - def executed_is(q, v): - if v == 'True': - return q.filter(self.batch_class.executed != None) - else: - return q.filter(self.batch_class.executed == None) - - def executed_nt(q, v): - if v == 'True': - return q.filter(self.batch_class.executed == None) - else: - return q.filter(self.batch_class.executed != None) - - return self.make_filter_map( - executed={'is': executed_is, 'nt': executed_nt}) - - def filter_config(self): - """ - Provides the default filter config for batch grid views. Derived - classes should *not* override this, but :meth:`filter_config_extras()` - instead. - """ - defaults = self.filter_config_extras() - config = self.make_filter_config( - filter_factory_executed=BooleanSearchFilter, - filter_type_executed='is', - executed=False, - include_filter_executed=True) - defaults.update(config) - return defaults - - def sort_map(self): - """ - Provides the default sort map for batch grid views. Derived classes - should *not* override this, but :meth:`sort_map_extras()` instead. - """ - map_ = self.make_sort_map( - created_by=self.sorter(model.User.username), - executed_by=self.sorter(model.User.username)) - map_.update(self.sort_map_extras()) - return map_ - - def sort_config(self): - """ - Provides the default sort config for batch grid views. Derived classes - may override this. - """ - return self.make_sort_config(sort='created', dir='desc') - - def grid(self): - """ - Creates the grid for the view. Derived classes should *not* override - this, but :meth:`configure_grid()` instead. - """ - g = self.make_grid() - g.created_by.set(renderer=forms.renderers.UserFieldRenderer) - g.cognized_by.set(renderer=forms.renderers.UserFieldRenderer) - g.executed_by.set(renderer=forms.renderers.UserFieldRenderer) - self._configure_grid(g) - self.configure_grid(g) - if self.request.has_perm('{0}.view'.format(self.permission_prefix)): - g.viewable = True - g.view_route_name = '{0}.view'.format(self.route_prefix) - if self.request.has_perm('{0}.edit'.format(self.permission_prefix)): - g.editable = True - g.edit_route_name = '{0}.edit'.format(self.route_prefix) - if self.request.has_perm('{0}.delete'.format(self.permission_prefix)): - g.deletable = True - g.delete_route_name = '{0}.delete'.format(self.route_prefix) - return g - - def _configure_grid(self, grid): - grid.created_by.set(label="Created by") - grid.executed_by.set(label="Executed by") - - def configure_grid(self, grid): - """ - Derived classes can override this. Customizes a grid which has already - been created with defaults by the base class. - """ - g = grid - g.configure( - include=[ - g.created, - g.created_by, - g.executed, - g.executed_by, - ], - readonly=True) - - def render_kwargs(self): - """ - Add some things to the template context: batch type display name, route - and permission prefixes. - """ - return { - 'batch_display': self.batch_display, - 'batch_display_plural': self.batch_display_plural, - 'route_prefix': self.route_prefix, - 'permission_prefix': self.permission_prefix, - } - - -class FileBatchGrid(BatchGrid): - """ - Base grid view for batches, which involve primarily a file upload. - """ - - def _configure_grid(self, g): - super(FileBatchGrid, self)._configure_grid(g) - g.created.set(label="Uploaded") - g.created_by.set(label="Uploaded by") - - def configure_grid(self, grid): - """ - Derived classes can override this. Customizes a grid which has already - been created with defaults by the base class. - """ - g = grid - g.configure( - include=[ - g.created, - g.created_by, - g.filename, - g.executed, - g.executed_by, - ], - readonly=True) - - class BaseCrud(CrudView): """ Base CRUD view for batches and batch rows. @@ -2000,173 +1741,6 @@ class StatusRenderer(forms.renderers.EnumFieldRenderer): return status_code_text -class BatchRowGrid(BaseGrid): - """ - Base grid view for batch rows, which can be filtered and sorted. Also it - can delete all rows matching the current list view query. - """ - - @property - def row_class(self): - raise NotImplementedError - - @property - def mapped_class(self): - return self.row_class - - @property - def config_prefix(self): - """ - Config prefix for the grid view. This is used to keep track of current - filtering and sorting, within the user's session. Derived classes may - override this. - """ - return '{0}.{1}'.format(self.mapped_class.__name__.lower(), - self.request.matchdict['uuid']) - - @property - def batch_class(self): - """ - Model class of the batch to which the rows belong. - """ - return self.row_class.__batch_class__ - - @property - def batch_display(self): - """ - Singular display text for the batch type, e.g. "Vendor Invoice". - Override this as necessary. - """ - return self.batch_class.__name__ - - def current_batch(self): - """ - Return the current batch, based on the UUID within the URL. - """ - return Session.query(self.batch_class).get(self.request.matchdict['uuid']) - - def modify_query(self, q): - q = super(BatchRowGrid, self).modify_query(q) - q = q.filter(self.row_class.batch == self.current_batch()) - q = q.filter(self.row_class.removed == False) - return q - - def join_map(self): - """ - Provides the default join map for batch row grid views. Derived - classes should *not* override this, but :meth:`join_map_extras()` - instead. - """ - return self.join_map_extras() - - def filter_map(self): - """ - Provides the default filter map for batch row grid views. Derived - classes should *not* override this, but :meth:`filter_map_extras()` - instead. - """ - return self.make_filter_map(exact=['status_code']) - - def filter_config(self): - """ - Provides the default filter config for batch grid views. Derived - classes should *not* override this, but :meth:`filter_config_extras()` - instead. - """ - kwargs = {'filter_label_status_code': "Status", - 'filter_factory_status_code': EnumSearchFilter(self.row_class.STATUS)} - kwargs.update(self.filter_config_extras()) - return self.make_filter_config(**kwargs) - - def sort_map(self): - """ - Provides the default sort map for batch grid views. Derived classes - should *not* override this, but :meth:`sort_map_extras()` instead. - """ - map_ = self.make_sort_map() - map_.update(self.sort_map_extras()) - return map_ - - def sort_config(self): - """ - Provides the default sort config for batch grid views. Derived classes - may override this. - """ - return self.make_sort_config(sort='sequence', dir='asc') - - def grid(self): - """ - Creates the grid for the view. Derived classes should *not* override - this, but :meth:`configure_grid()` instead. - """ - g = self.make_grid() - g.extra_row_class = self.tr_class - g.sequence.set(label="Seq.") - g.status_code.set(label="Status", renderer=StatusRenderer(self.row_class.STATUS)) - self._configure_grid(g) - self.configure_grid(g) - - batch = self.current_batch() - g.viewable = True - g.view_route_name = '{0}.row.view'.format(self.route_prefix) - # TODO: Fix this check for edit mode. - edit_mode = self.request.referrer.endswith('/edit') - if edit_mode and not batch.executed and self.request.has_perm('{0}.edit'.format(self.permission_prefix)): - # g.editable = True - # g.edit_route_name = '{0}.rows.edit'.format(self.route_prefix) - g.deletable = True - g.delete_route_name = '{0}.rows.delete'.format(self.route_prefix) - return g - - def tr_class(self, row, i): - pass - - def render_kwargs(self): - """ - Add the current batch and route prefix to the template context. - """ - return {'batch': self.current_batch(), - 'route_prefix': self.route_prefix} - - def bulk_delete(self): - """ - "Delete" all rows matching the current row grid view query. This sets - the ``removed`` flag on the rows but does not truly delete them. - """ - self.query().update({'removed': True}, synchronize_session=False) - return httpexceptions.HTTPFound(location=self.request.route_url('{}.view'.format(self.route_prefix), - uuid=self.request.matchdict['uuid'])) - - -class ProductBatchRowGrid(BatchRowGrid): - """ - Base grid view for batch rows which deal directly with products. - """ - - def filter_map(self): - """ - Provides the default filter map for batch row grid views. Derived - classes should *not* override this, but :meth:`filter_map_extras()` - instead. - """ - return self.make_filter_map(exact=['status_code'], - ilike=['brand_name', 'description', 'size'], - upc=self.filter_gpc(self.row_class.upc)) - - def filter_config(self): - """ - Provides the default filter config for batch grid views. Derived - classes should *not* override this, but :meth:`filter_config_extras()` - instead. - """ - kwargs = {'filter_label_status_code': "Status", - 'filter_factory_status_code': EnumSearchFilter(self.row_class.STATUS), - 'filter_label_upc': "UPC", - 'filter_label_brand_name': "Brand"} - kwargs.update(self.filter_config_extras()) - return self.make_filter_config(**kwargs) - - class BatchRowCrud(BaseCrud): """ Base CRUD view for batch rows. diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index 6ef4f5f8..dc9990bf 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework @@ -33,7 +33,6 @@ from rattail.db import model from tailbone import forms from tailbone.db import Session from tailbone.views import MasterView -from tailbone.newgrids.filters import ChoiceValueRenderer class CustomerOrdersView(MasterView): diff --git a/tailbone/views/departments.py b/tailbone/views/departments.py index f9aebf5b..37eed9ab 100644 --- a/tailbone/views/departments.py +++ b/tailbone/views/departments.py @@ -28,8 +28,8 @@ from __future__ import unicode_literals, absolute_import from rattail.db import model +from tailbone import newgrids as grids from tailbone.views import MasterView, AutocompleteView -from tailbone.newgrids import AlchemyGrid, GridAction class DepartmentsView(MasterView): @@ -67,11 +67,11 @@ class DepartmentsView(MasterView): # shouldn't need a key for this one, for instance (no settings # required), but there is plenty of room for improvement here. employees = sorted(department.employees, key=unicode) - employees = AlchemyGrid('departments.employees', self.request, data=employees, model_class=model.Employee, - main_actions=[ - GridAction('view', icon='zoomin', - url=lambda r, i: self.request.route_url('employees.view', uuid=r.uuid)), - ]) + employees = grids.AlchemyGrid('departments.employees', self.request, data=employees, model_class=model.Employee, + main_actions=[ + grids.GridAction('view', icon='zoomin', + url=lambda r, i: self.request.route_url('employees.view', uuid=r.uuid)), + ]) employees.configure(include=[employees.display_name], readonly=True) kwargs['employees'] = employees diff --git a/tailbone/views/email.py b/tailbone/views/email.py index 9da076e4..b6dca171 100644 --- a/tailbone/views/email.py +++ b/tailbone/views/email.py @@ -34,13 +34,12 @@ from rattail import mail from rattail.db import api from rattail.config import parse_list -from tailbone import forms +from tailbone import forms, newgrids as grids from tailbone.db import Session from tailbone.views import MasterView, View -from tailbone.newgrids import Grid, GridColumn -class BoolGridColumn(GridColumn): +class BoolGridColumn(grids.GridColumn): def render(self, value): if value is None: @@ -48,7 +47,7 @@ class BoolGridColumn(GridColumn): return 'Yes' if value else 'No' -class EmailListGridColumn(GridColumn): +class EmailListGridColumn(grids.GridColumn): def render(self, value): if not value: @@ -77,7 +76,7 @@ class ProfilesView(MasterView): model_key = 'key' url_prefix = '/email/profiles' - grid_factory = Grid + grid_factory = grids.Grid filterable = False pageable = False @@ -115,9 +114,9 @@ class ProfilesView(MasterView): def configure_grid(self, g): g.columns = [ - GridColumn('key'), - GridColumn('prefix'), - GridColumn('subject'), + grids.GridColumn('key'), + grids.GridColumn('prefix'), + grids.GridColumn('subject'), EmailListGridColumn('to'), BoolGridColumn('enabled'), ] diff --git a/tailbone/views/employees.py b/tailbone/views/employees.py index a4b86713..58962cc6 100644 --- a/tailbone/views/employees.py +++ b/tailbone/views/employees.py @@ -32,11 +32,9 @@ from rattail.db import model import formalchemy as fa -from tailbone import forms +from tailbone import forms, newgrids as grids from tailbone.db import Session from tailbone.views import MasterView, AutocompleteView -from tailbone.newgrids import AlchemyGrid, GridAction -from tailbone.newgrids.filters import EnumValueRenderer class EmployeesView(MasterView): @@ -67,7 +65,7 @@ class EmployeesView(MasterView): g.filters['status'].default_active = True g.filters['status'].default_verb = 'equal' g.filters['status'].default_value = self.enum.EMPLOYEE_STATUS_CURRENT - g.filters['status'].set_value_renderer(EnumValueRenderer(self.enum.EMPLOYEE_STATUS)) + g.filters['status'].set_value_renderer(grids.filters.EnumValueRenderer(self.enum.EMPLOYEE_STATUS)) else: del g.filters['id'] del g.filters['status'] diff --git a/tailbone/views/grids/__init__.py b/tailbone/views/grids/__init__.py deleted file mode 100644 index ba3106ed..00000000 --- a/tailbone/views/grids/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/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 . -# -################################################################################ - -""" -Grid Views -""" - -from tailbone.views.grids.core import GridView -from tailbone.views.grids.alchemy import ( - AlchemyGridView, SortableAlchemyGridView, - PagedAlchemyGridView, SearchableAlchemyGridView) diff --git a/tailbone/views/grids/alchemy.py b/tailbone/views/grids/alchemy.py deleted file mode 100644 index ee564b8d..00000000 --- a/tailbone/views/grids/alchemy.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2014 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 . -# -################################################################################ - -""" -FormAlchemy Grid Views -""" - -from webhelpers import paginate - -from .core import GridView -from ... import grids -from ...db import Session - - -__all__ = ['AlchemyGridView', 'SortableAlchemyGridView', - 'PagedAlchemyGridView', 'SearchableAlchemyGridView'] - - -class AlchemyGridView(GridView): - - def make_query(self, session=Session): - query = session.query(self.mapped_class) - return self.modify_query(query) - - def modify_query(self, query): - return query - - def query(self): - return self.make_query() - - def make_grid(self, **kwargs): - self.update_grid_kwargs(kwargs) - return grids.AlchemyGrid( - self.request, self.mapped_class, self._data, **kwargs) - - def grid(self): - return self.make_grid() - - def __call__(self): - self._data = self.query() - grid = self.grid() - return self.render_grid(grid) - - -class SortableAlchemyGridView(AlchemyGridView): - - sort = None - full = True - - @property - def config_prefix(self): - raise NotImplementedError - - def join_map(self): - return {} - - def make_sort_map(self, *args, **kwargs): - return grids.util.get_sort_map( - self.mapped_class, names=args or None, **kwargs) - - def sorter(self, field): - return grids.util.sorter(field) - - def sort_map(self): - return self.make_sort_map() - - def make_sort_config(self, **kwargs): - return grids.util.get_sort_config( - self.config_prefix, self.request, **kwargs) - - def sort_config(self): - return self.make_sort_config(sort=self.sort) - - def modify_query(self, query): - return grids.util.sort_query( - query, self._sort_config, self.sort_map(), self.join_map()) - - def make_grid(self, **kwargs): - self.update_grid_kwargs(kwargs) - return grids.AlchemyGrid( - self.request, self.mapped_class, self._data, - sort_map=self.sort_map(), config=self._sort_config, **kwargs) - - def grid(self): - return self.make_grid() - - def __call__(self): - self._sort_config = self.sort_config() - self._data = self.query() - grid = self.grid() - return self.render_grid(grid) - - -class PagedAlchemyGridView(SortableAlchemyGridView): - - def make_pager(self): - config = self._sort_config - query = self.query() - return paginate.Page( - query, item_count=query.count(), - items_per_page=int(config['per_page']), - page=int(config['page']), - url=paginate.PageURL_WebOb(self.request)) - - def __call__(self): - self._sort_config = self.sort_config() - self._data = self.make_pager() - grid = self.grid() - grid.pager = self._data - return self.render_grid(grid) - - -class SearchableAlchemyGridView(PagedAlchemyGridView): - - def filter_exact(self, field): - return grids.search.filter_exact(field) - - def filter_ilike(self, field): - return grids.search.filter_ilike(field) - - def filter_int(self, field): - return grids.search.filter_int(field) - - def filter_soundex(self, field): - return grids.search.filter_soundex(field) - - def filter_ilike_and_soundex(self, field): - return grids.search.filter_ilike_and_soundex(field) - - def filter_gpc(self, field): - return grids.search.filter_gpc(field) - - def make_filter_map(self, **kwargs): - return grids.search.get_filter_map(self.mapped_class, **kwargs) - - def filter_map(self): - return self.make_filter_map() - - def make_filter_config(self, **kwargs): - return grids.search.get_filter_config( - self.config_prefix, self.request, self.filter_map(), **kwargs) - - def filter_config(self): - return self.make_filter_config() - - def make_search_form(self): - return grids.search.get_search_form( - self.request, self.filter_map(), self._filter_config) - - def search_form(self): - return self.make_search_form() - - def modify_query(self, query): - join_map = self.join_map() - if not hasattr(self, '_filter_config'): - self._filter_config = self.filter_config() - query = grids.search.filter_query( - query, self._filter_config, self.filter_map(), join_map) - if hasattr(self, '_sort_config'): - self._sort_config['joins'] = self._filter_config['joins'] - query = grids.util.sort_query( - query, self._sort_config, self.sort_map(), join_map) - return query - - def __call__(self): - self._filter_config = self.filter_config() - search = self.search_form() - self._sort_config = self.sort_config() - self._data = self.make_pager() - grid = self.grid() - grid.pager = self._data - return self.render_grid(grid, search) diff --git a/tailbone/views/grids/core.py b/tailbone/views/grids/core.py deleted file mode 100644 index a2cfd48d..00000000 --- a/tailbone/views/grids/core.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/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 . -# -################################################################################ - -""" -Core Grid View -""" - -from .. import View -from ... import grids - - -__all__ = ['GridView'] - - -class GridView(View): - - route_name = None - route_url = None - renderer = None - permission = None - - full = False - checkboxes = False - deletable = False - - partial_only = False - - def update_grid_kwargs(self, kwargs): - kwargs.setdefault('full', self.full) - kwargs.setdefault('checkboxes', self.checkboxes) - kwargs.setdefault('deletable', self.deletable) - kwargs.setdefault('partial_only', self.partial_only) - - def make_grid(self, **kwargs): - self.update_grid_kwargs(kwargs) - return grids.Grid(self.request, **kwargs) - - def grid(self): - return self.make_grid() - - def render_kwargs(self): - return {} - - def render_grid(self, grid, search=None, **kwargs): - kwargs = self.render_kwargs() - kwargs['search_form'] = search - return grids.util.render_grid(grid, **kwargs) - - def __call__(self): - grid = self.grid() - return self.render_grid(grid) diff --git a/tailbone/views/purchasing/batch.py b/tailbone/views/purchasing/batch.py index 1316b385..f2c3c31c 100644 --- a/tailbone/views/purchasing/batch.py +++ b/tailbone/views/purchasing/batch.py @@ -35,7 +35,7 @@ from rattail.time import localtime import formalchemy as fa from pyramid import httpexceptions -from tailbone import forms, newgrids as grids +from tailbone import forms from tailbone.views.batch import BatchMasterView diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py index aed204cf..6c194296 100644 --- a/tailbone/views/roles.py +++ b/tailbone/views/roles.py @@ -35,10 +35,9 @@ import formalchemy as fa from formalchemy.fields import IntegerFieldRenderer from webhelpers.html import HTML, tags -from tailbone import forms +from tailbone import forms, newgrids as grids from tailbone.db import Session from tailbone.views.principal import PrincipalMasterView -from tailbone.newgrids import AlchemyGrid, GridAction class RolesView(PrincipalMasterView): @@ -86,11 +85,11 @@ class RolesView(PrincipalMasterView): # for this one, for instance (no settings required), but there is # plenty of room for improvement here. users = sorted(role.users, key=lambda u: u.username) - users = AlchemyGrid('roles.users', self.request, data=users, model_class=model.User, - main_actions=[ - GridAction('view', icon='zoomin', - url=lambda r, i: self.request.route_url('users.view', uuid=r.uuid)), - ]) + users = grids.AlchemyGrid('roles.users', self.request, data=users, model_class=model.User, + main_actions=[ + grids.GridAction('view', icon='zoomin', + url=lambda r, i: self.request.route_url('users.view', uuid=r.uuid)), + ]) users.configure(include=[users.username], readonly=True) kwargs['users'] = users diff --git a/tailbone/views/tables.py b/tailbone/views/tables.py index 379df0b5..c3c1dc3e 100644 --- a/tailbone/views/tables.py +++ b/tailbone/views/tables.py @@ -26,8 +26,8 @@ Views with info about the underlying Rattail tables from __future__ import unicode_literals, absolute_import +from tailbone import newgrids as grids from tailbone.views import MasterView -from tailbone.newgrids import Grid, GridColumn class TablesView(MasterView): @@ -41,7 +41,7 @@ class TablesView(MasterView): editable = False deletable = False viewable = False - grid_factory = Grid + grid_factory = grids.Grid filterable = False pageable = False @@ -59,8 +59,8 @@ class TablesView(MasterView): def configure_grid(self, g): g.columns = [ - GridColumn('name'), - GridColumn('row_count'), + grids.GridColumn('name'), + grids.GridColumn('row_count'), ] g.sorters['name'] = g.make_sorter('name', foldcase=True)