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/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)