diff --git a/tailbone/grids3/core.py b/tailbone/grids3/core.py index 8d866b33..35615edc 100644 --- a/tailbone/grids3/core.py +++ b/tailbone/grids3/core.py @@ -51,7 +51,7 @@ class Grid(object): Core grid class. In sore need of documentation. """ - def __init__(self, key, data, columns, width='auto', request=None, mobile=False, model_class=None, + def __init__(self, key, data, columns=None, width='auto', request=None, mobile=False, model_class=None, enums={}, labels={}, renderers={}, extra_row_class=None, linked_columns=[], url='#', joiners={}, filterable=False, filters={}, sortable=False, sorters={}, default_sortkey=None, default_sortdir='asc', @@ -66,6 +66,8 @@ class Grid(object): self.request = request self.mobile = mobile self.model_class = model_class + if self.model_class and self.columns is None: + self.columns = self.make_columns() self.enums = enums or {} self.labels = labels or {} @@ -93,6 +95,16 @@ class Grid(object): self._whgrid_kwargs = kwargs + def make_columns(self): + """ + Return a default list of columns, based on :attr:`model_class`. + """ + if not self.model_class: + raise ValueError("Must define model_class to use make_columns()") + + mapper = orm.class_mapper(self.model_class) + return [prop.key for prop in mapper.iterate_properties] + def hide_column(self, key): if key in self.columns: self.columns.remove(key) diff --git a/tailbone/templates/roles/view.mako b/tailbone/templates/roles/view.mako index e1f77f3f..001c51ac 100644 --- a/tailbone/templates/roles/view.mako +++ b/tailbone/templates/roles/view.mako @@ -1,8 +1,8 @@ -## -*- coding: utf-8 -*- +## -*- coding: utf-8; -*- <%inherit file="/master/view.mako" /> -<%def name="head_tags()"> - ${parent.head_tags()} +<%def name="extra_styles()"> + ${parent.extra_styles()} ${h.stylesheet_link(request.static_url('tailbone:static/css/perms.css'))} diff --git a/tailbone/views/categories.py b/tailbone/views/categories.py index 06263c34..a79c7549 100644 --- a/tailbone/views/categories.py +++ b/tailbone/views/categories.py @@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import from rattail.db import model -from tailbone.views import MasterView +from tailbone.views import MasterView2 as MasterView class CategoriesView(MasterView): @@ -39,18 +39,18 @@ class CategoriesView(MasterView): model_title_plural = "Categories" route_prefix = 'categories' + grid_columns = [ + 'code', + 'number', + 'name', + 'department', + ] + def configure_grid(self, g): + super(CategoriesView, self).configure_grid(g) g.filters['name'].default_active = True g.filters['name'].default_verb = 'contains' g.default_sortkey = 'code' - g.configure( - include=[ - g.code, - g.number, - g.name, - g.department, - ], - readonly=True) def configure_fieldset(self, fs): fs.configure( diff --git a/tailbone/views/custorders/items.py b/tailbone/views/custorders/items.py index 9ff39d02..2bf27b27 100644 --- a/tailbone/views/custorders/items.py +++ b/tailbone/views/custorders/items.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework @@ -36,7 +36,8 @@ from rattail.time import localtime import formalchemy as fa from tailbone import forms -from tailbone.views import MasterView +from tailbone.views import MasterView2 as MasterView +from tailbone.util import raw_datetime class CustomerOrderItemsView(MasterView): @@ -50,6 +51,18 @@ class CustomerOrderItemsView(MasterView): editable = False deletable = False + grid_columns = [ + 'person', + 'product_brand', + 'product_description', + 'product_size', + 'case_quantity', + 'cases_ordered', + 'units_ordered', + 'order_created', + 'status_code', + ] + has_rows = True model_row_class = model.CustomerOrderItemEvent rows_title = "Event History" @@ -58,61 +71,53 @@ class CustomerOrderItemsView(MasterView): rows_pageable = False rows_viewable = False + row_grid_columns = [ + 'occurred', + 'type_code', + 'user', + 'note', + ] + def query(self, session): return session.query(model.CustomerOrderItem)\ .join(model.CustomerOrder)\ .options(orm.joinedload(model.CustomerOrderItem.order)\ .joinedload(model.CustomerOrder.person)) - def _preconfigure_grid(self, g): + def configure_grid(self, g): + super(CustomerOrderItemsView, self).configure_grid(g) - g.joiners['person'] = lambda q: q.outerjoin(model.Person) - g.filters['person'] = g.make_filter('person', model.Person.display_name, label="Person Name", + g.set_joiner('person', lambda q: q.outerjoin(model.Person)) + + g.filters['person'] = g.make_filter('person', model.Person.display_name, default_active=True, default_verb='contains') - g.sorters['person'] = g.make_sorter(model.Person.display_name) - g.filters['product_brand'].label = "Brand" - g.product_brand.set(label="Brand") - - g.filters['product_description'].label = "Description" - g.product_description.set(label="Description") - - g.filters['product_size'].label = "Size" - g.product_size.set(label="Size") - - g.case_quantity.set(renderer=forms.renderers.QuantityFieldRenderer) - g.cases_ordered.set(renderer=forms.renderers.QuantityFieldRenderer) - g.units_ordered.set(renderer=forms.renderers.QuantityFieldRenderer) - - g.total_price.set(renderer=forms.renderers.CurrencyFieldRenderer) - - g.filters['status_code'].label = "Status" - g.status_code.set(label="Status") - - g.append(fa.Field('person', value=lambda i: i.order.person)) - - g.sorters['order_created'] = g.make_sorter(model.CustomerOrder.created) - g.append(fa.Field('order_created', - value=lambda i: localtime(self.rattail_config, i.order.created, from_utc=True), - renderer=forms.renderers.DateTimeFieldRenderer)) + g.set_sorter('person', model.Person.display_name) + g.set_sorter('order_created', model.CustomerOrder.created) g.default_sortkey = 'order_created' g.default_sortdir = 'desc' - def configure_grid(self, g): - g.configure( - include=[ - g.person, - g.product_brand, - g.product_description, - g.product_size, - g.case_quantity, - g.cases_ordered, - g.units_ordered, - g.order_created, - g.status_code, - ], - readonly=True) + g.set_type('case_quantity', 'quantity') + g.set_type('cases_ordered', 'quantity') + g.set_type('units_ordered', 'quantity') + g.set_type('total_price', 'currency') + + g.set_renderer('person', self.render_person) + g.set_renderer('order_created', self.render_order_created) + + g.set_label('person', "Person Name") + g.set_label('product_brand', "Brand") + g.set_label('product_description', "Description") + g.set_label('product_size', "Size") + g.set_label('status_code', "Status") + + def render_person(self, item, column): + return item.order.person + + def render_order_created(self, item, column): + value = localtime(self.rattail_config, item.order.created, from_utc=True) + return raw_datetime(self.rattail_config, value) def _preconfigure_fieldset(self, fs): fs.order.set(renderer=forms.renderers.CustomerOrderFieldRenderer) @@ -151,21 +156,12 @@ class CustomerOrderItemsView(MasterView): .order_by(model.CustomerOrderItemEvent.occurred, model.CustomerOrderItemEvent.type_code) - def _preconfigure_row_grid(self, g): - g.occurred.set(label="When") - g.type_code.set(label="What") # TODO: enum renderer - g.user.set(label="Who") - g.note.set(label="Notes") - def configure_row_grid(self, g): - g.configure( - include=[ - g.occurred, - g.type_code, - g.user, - g.note, - ], - readonly=True) + super(CustomerOrderItemsView, self).configure_row_grid(g) + g.set_label('occurred', "When") + g.set_label('type_code', "What") # TODO: enum renderer + g.set_label('user', "Who") + g.set_label('note', "Notes") def includeme(config): diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py index 7289e8b1..cc57c7b2 100644 --- a/tailbone/views/custorders/orders.py +++ b/tailbone/views/custorders/orders.py @@ -32,7 +32,7 @@ from rattail.db import model from tailbone import forms from tailbone.db import Session -from tailbone.views import MasterView +from tailbone.views import MasterView2 as MasterView class CustomerOrdersView(MasterView): @@ -45,43 +45,42 @@ class CustomerOrdersView(MasterView): editable = False deletable = False + grid_columns = [ + 'id', + 'customer', + 'person', + 'created', + 'status_code', + ] + def query(self, session): return session.query(model.CustomerOrder)\ .options(orm.joinedload(model.CustomerOrder.customer)) - def _preconfigure_grid(self, g): - g.joiners['customer'] = lambda q: q.outerjoin(model.Customer) - g.sorters['customer'] = g.make_sorter(model.Customer.name) + def configure_grid(self, g): + super(CustomerOrdersView, self).configure_grid(g) + + g.set_joiner('customer', lambda q: q.outerjoin(model.Customer)) + g.set_joiner('person', lambda q: q.outerjoin(model.Person)) + g.filters['customer'] = g.make_filter('customer', model.Customer.name, label="Customer Name", default_active=True, default_verb='contains') - - g.joiners['person'] = lambda q: q.outerjoin(model.Person) - g.sorters['person'] = g.make_sorter(model.Person.display_name) g.filters['person'] = g.make_filter('person', model.Person.display_name, label="Person Name", default_active=True, default_verb='contains') - # TODO: enum choices renderer - g.filters['status_code'].label = "Status" - g.status_code.set(label="Status") + g.set_sorter('customer', model.Customer.name) + g.set_sorter('person', model.Person.display_name) - g.id.set(label="ID") g.default_sortkey = 'created' g.default_sortdir = 'desc' - def configure_grid(self, g): - g.configure( - include=[ - g.id, - g.customer, - g.person, - g.created, - g.status_code, - ], - readonly=True) + # TODO: enum choices renderer + g.set_label('status_code', "Status") + g.set_label('id', "ID") def _preconfigure_fieldset(self, fs): fs.customer.set(options=[]) diff --git a/tailbone/views/departments.py b/tailbone/views/departments.py index a943ec26..e48ed8b9 100644 --- a/tailbone/views/departments.py +++ b/tailbone/views/departments.py @@ -26,10 +26,11 @@ Department Views from __future__ import unicode_literals, absolute_import +import six + from rattail.db import model from tailbone import grids3 as grids -from tailbone.newgrids import AlchemyGrid from tailbone.views import MasterView2 as MasterView, AutocompleteView @@ -61,21 +62,13 @@ class DepartmentsView(MasterView): def template_kwargs_view(self, **kwargs): department = kwargs['instance'] if department.employees: - - # TODO: This is the second attempt (after role.users) at using a - # new grid outside of the context of a primary master grid. The - # API here is really much hairier than I'd like... Looks like we - # 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=[ - 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 - + employees = sorted(department.employees, key=six.text_type) + actions = [ + grids.GridAction('view', icon='zoomin', + url=lambda r, i: self.request.route_url('employees.view', uuid=r.uuid)) + ] + kwargs['employees'] = grids.Grid(None, employees, ['display_name'], request=self.request, + model_class=model.Employee, main_actions=actions) else: kwargs['employees'] = None return kwargs diff --git a/tailbone/views/depositlinks.py b/tailbone/views/depositlinks.py index 457765e7..8633ec94 100644 --- a/tailbone/views/depositlinks.py +++ b/tailbone/views/depositlinks.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework @@ -29,7 +29,7 @@ from __future__ import unicode_literals, absolute_import from rattail.db import model from tailbone import forms -from tailbone.views import MasterView +from tailbone.views import MasterView2 as MasterView class DepositLinksView(MasterView): @@ -39,20 +39,18 @@ class DepositLinksView(MasterView): model_class = model.DepositLink url_prefix = '/deposit-links' - def _preconfigure_grid(self, g): + grid_columns = [ + 'code', + 'description', + 'amount', + ] + + def configure_grid(self, g): + super(DepositLinksView, self).configure_grid(g) g.filters['description'].default_active = True g.filters['description'].default_verb = 'contains' g.default_sortkey = 'code' - g.amount.set(renderer=forms.renderers.CurrencyFieldRenderer) - - def configure_grid(self, g): - g.configure( - include=[ - g.code, - g.description, - g.amount, - ], - readonly=True) + g.set_type('amount', 'currency') def configure_fieldset(self, fs): fs.configure( diff --git a/tailbone/views/roles.py b/tailbone/views/roles.py index 80eaa37b..e7778250 100644 --- a/tailbone/views/roles.py +++ b/tailbone/views/roles.py @@ -36,7 +36,6 @@ from formalchemy.fields import IntegerFieldRenderer from tailbone import forms, grids3 as grids from tailbone.db import Session -from tailbone.newgrids import AlchemyGrid from tailbone.views.principal import PrincipalMasterView @@ -76,21 +75,13 @@ class RolesView(PrincipalMasterView): def template_kwargs_view(self, **kwargs): role = kwargs['instance'] if role.users: - - # TODO: This is the first attempt at using a new grid outside of - # the context of a primary master grid. The API here is really - # much hairier than I'd like... Looks like we shouldn't need a key - # 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=[ - 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 - + actions = [ + grids.GridAction('view', icon='zoomin', + url=lambda r, i: self.request.route_url('users.view', uuid=r.uuid)) + ] + kwargs['users'] = grids.Grid(None, users, ['username'], request=self.request, model_class=model.User, + main_actions=actions) else: kwargs['users'] = None kwargs['guest_role'] = guest_role(Session()) diff --git a/tailbone/views/taxes.py b/tailbone/views/taxes.py index e774fb89..7d89a8de 100644 --- a/tailbone/views/taxes.py +++ b/tailbone/views/taxes.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8; -*- ################################################################################ # # Rattail -- Retail Software Framework @@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import from rattail.db import model -from tailbone.views import MasterView +from tailbone.views import MasterView2 as MasterView class TaxesView(MasterView): @@ -39,17 +39,17 @@ class TaxesView(MasterView): model_title_plural = "Taxes" route_prefix = 'taxes' + grid_columns = [ + 'code', + 'description', + 'rate', + ] + def configure_grid(self, g): + super(TaxesView, self).configure_grid(g) g.filters['description'].default_active = True g.filters['description'].default_verb = 'contains' g.default_sortkey = 'code' - g.configure( - include=[ - g.code, - g.description, - g.rate, - ], - readonly=True) def configure_fieldset(self, fs): fs.configure(