Add new "v3" grids, refactor all views to use them

or at least that's the idea..hopefully we caught them all
This commit is contained in:
Lance Edgar 2017-07-07 09:13:53 -05:00
parent f244c2934b
commit 5b1ae27a10
71 changed files with 2679 additions and 2030 deletions

View file

@ -28,6 +28,7 @@ from __future__ import unicode_literals, absolute_import
from .core import View
from .master import MasterView
from .master2 import MasterView2
# TODO: deprecate / remove some of this
from .autocomplete import AutocompleteView

View file

@ -27,3 +27,4 @@ Views for batches
from __future__ import unicode_literals, absolute_import
from .core import BatchMasterView, FileBatchMasterView
from .core2 import BatchMasterView2, FileBatchMasterView2

View file

@ -0,0 +1,126 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2017 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 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Base views for maintaining batches
"""
from __future__ import unicode_literals, absolute_import
import six
from rattail.db import model
from tailbone import grids3 as grids
from tailbone.views import MasterView2
from tailbone.views.batch import BatchMasterView, FileBatchMasterView
from tailbone.views.batch.core import MobileBatchStatusFilter
class BatchMasterView2(MasterView2, BatchMasterView):
"""
Base class for all "batch master" views
"""
grid_columns = [
'id',
'created',
'created_by',
'rowcount',
'status_code',
'complete',
'executed',
'executed_by',
]
def configure_grid(self, g):
super(BatchMasterView2, self).configure_grid(g)
g.joiners['created_by'] = lambda q: q.join(model.User, model.User.uuid == self.model_class.created_by_uuid)
g.joiners['executed_by'] = lambda q: q.outerjoin(model.User, model.User.uuid == self.model_class.executed_by_uuid)
g.filters['executed'].default_active = True
g.filters['executed'].default_verb = 'is_null'
# TODO: not sure this todo is still relevant?
# TODO: in some cases grid has no sorters yet..e.g. when building query for bulk-delete
# if hasattr(g, 'sorters'):
g.sorters['created_by'] = g.make_sorter(model.User.username)
g.sorters['executed_by'] = g.make_sorter(model.User.username)
g.default_sortkey = 'id'
g.default_sortdir = 'desc'
g.set_enum('status_code', self.model_class.STATUS)
g.set_type('created', 'datetime')
g.set_type('executed', 'datetime')
g.set_renderer('id', self.render_batch_id)
g.set_link('id')
g.set_label('id', "Batch ID")
g.set_label('created_by', "Created by")
g.set_label('rowcount', "Rows")
g.set_label('status_code', "Status")
g.set_label('executed_by', "Executed by")
def render_batch_id(self, batch, column):
return batch.id_str
def configure_row_grid(self, g):
super(BatchMasterView2, self).configure_row_grid(g)
g.filters['status_code'].set_value_renderer(grids.filters.EnumValueRenderer(self.model_row_class.STATUS))
g.default_sortkey = 'sequence'
g.set_enum('status_code', self.model_row_class.STATUS)
g.set_renderer('status_code', self.render_row_status)
g.set_label('sequence', "Seq.")
g.set_label('status_code', "Status")
def render_row_status(self, row, column):
code = row.status_code
if code is None:
return ""
text = self.model_row_class.STATUS.get(code, six.text_type(code))
if row.status_text:
return HTML.tag('span', title=row.status_text, c=text)
return text
def make_mobile_filters(self):
"""
Returns a set of filters for the mobile grid.
"""
filters = grids.filters.GridFilterSet()
filters['status'] = MobileBatchStatusFilter(self.model_class, 'status', default_value='pending')
return filters
class FileBatchMasterView2(BatchMasterView2, FileBatchMasterView):
"""
Base class for all file-based "batch master" views
"""

View file

@ -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.batch import BatchMasterView
from tailbone.views.batch import BatchMasterView2 as BatchMasterView
class PricingBatchView(BatchMasterView):
@ -46,6 +46,31 @@ class PricingBatchView(BatchMasterView):
rows_editable = True
bulk_deletable = True
grid_columns = [
'id',
'created',
'created_by',
'rowcount',
# 'status_code',
# 'complete',
'executed',
'executed_by',
]
row_grid_columns = [
'sequence',
'upc',
'brand_name',
'description',
'size',
'discounted_unit_cost',
'old_price',
'new_price',
'price_margin',
'price_diff',
'status_code',
]
def configure_fieldset(self, fs):
fs.configure(
include=[
@ -57,43 +82,25 @@ class PricingBatchView(BatchMasterView):
fs.executed_by,
])
def _preconfigure_row_grid(self, g):
super(PricingBatchView, self)._preconfigure_row_grid(g)
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
g.regular_unit_cost.set(label="Reg. Cost")
g.discounted_unit_cost.set(label="Disc. Cost")
g.old_price.set(renderer=forms.renderers.CurrencyFieldRenderer)
g.new_price.set(renderer=forms.renderers.CurrencyFieldRenderer)
g.price_margin.set(label="Margin")
g.price_markup.set(label="Markup")
g.price_diff.set(label="Diff", renderer=forms.renderers.CurrencyFieldRenderer)
def configure_row_grid(self, g):
g.configure(
include=[
g.sequence,
g.upc,
g.brand_name,
g.description,
g.size,
g.discounted_unit_cost,
g.old_price,
g.new_price,
g.price_margin,
g.price_diff,
g.status_code,
],
readonly=True)
super(PricingBatchView, self).configure_row_grid(g)
def row_grid_row_attrs(self, row, i):
attrs = {}
if row.status_code in (row.STATUS_PRICE_INCREASE,
row.STATUS_PRICE_DECREASE):
attrs['class_'] = 'notice'
elif row.status_code == row.STATUS_CANNOT_CALCULATE_PRICE:
attrs['class_'] = 'warning'
return attrs
g.set_type('old_price', 'currency')
g.set_type('new_price', 'currency')
g.set_type('price_diff', 'currency')
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('regular_unit_cost', "Reg. Cost")
g.set_label('price_margin', "Margin")
g.set_label('price_markup', "Markup")
g.set_label('price_diff', "Diff")
def row_grid_extra_class(self, row, i):
if row.status_code == row.STATUS_CANNOT_CALCULATE_PRICE:
return 'warning'
if row.status_code in (row.STATUS_PRICE_INCREASE, row.STATUS_PRICE_DECREASE):
return 'notice'
def _preconfigure_row_fieldset(self, fs):
super(PricingBatchView, self)._preconfigure_row_fieldset(fs)

View file

@ -37,9 +37,9 @@ import formalchemy
from pyramid.response import FileResponse
from webhelpers2.html import literal
from tailbone import newgrids as grids
from tailbone import grids3 as grids
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
from tailbone.forms.renderers.bouncer import BounceMessageFieldRenderer
@ -53,36 +53,39 @@ class EmailBouncesView(MasterView):
creatable = False
editable = False
grid_columns = [
'config_key',
'bounced',
'bounce_recipient_address',
'intended_recipient_address',
'processed_by',
]
def __init__(self, request):
super(EmailBouncesView, self).__init__(request)
self.handler_options = [('', '(any)')] + sorted(get_profile_keys(self.rattail_config))
self.handler_options = sorted(get_profile_keys(self.rattail_config))
def get_handler(self, bounce):
return get_handler(self.rattail_config, bounce.config_key)
def configure_grid(self, g):
super(EmailBouncesView, self).configure_grid(g)
g.joiners['processed_by'] = lambda q: q.outerjoin(model.User)
g.filters['config_key'].default_active = True
g.filters['config_key'].default_verb = 'equal'
g.filters['config_key'].label = "Source"
g.filters['config_key'].set_value_renderer(grids.filters.ChoiceValueRenderer(self.handler_options))
g.filters['bounce_recipient_address'].label = "Bounced To"
g.filters['intended_recipient_address'].label = "Intended For"
g.filters['processed'].default_active = True
g.filters['processed'].default_verb = 'is_null'
g.filters['processed_by'] = g.make_filter('processed_by', model.User.username)
g.sorters['processed_by'] = g.make_sorter(model.User.username)
g.default_sortkey = 'bounced'
g.default_sortdir = 'desc'
g.configure(
include=[
g.config_key.label("Source"),
g.bounced,
g.bounce_recipient_address.label("Bounced To"),
g.intended_recipient_address.label("Intended For"),
g.processed_by,
],
readonly=True)
g.set_label('config_key', "Source")
g.set_label('bounce_recipient_address', "Bounced To")
g.set_label('intended_recipient_address', "Intended For")
def configure_fieldset(self, fs):
bounce = fs.model

View file

@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
from tailbone.views import MasterView, AutocompleteView
from tailbone.views import MasterView2 as MasterView, AutocompleteView
class BrandsView(MasterView):
@ -36,16 +36,14 @@ class BrandsView(MasterView):
Master view for the Brand class.
"""
model_class = model.Brand
grid_columns = [
'name',
]
def configure_grid(self, g):
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'name'
g.configure(
include=[
g.name,
],
readonly=True)
def configure_fieldset(self, fs):
fs.configure(

View file

@ -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.db import Session
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class CustomerGroupsView(MasterView):
@ -38,17 +38,16 @@ class CustomerGroupsView(MasterView):
"""
model_class = model.CustomerGroup
model_title = "Customer Group"
grid_columns = [
'id',
'name',
]
def configure_grid(self, g):
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'name'
g.configure(
include=[
g.id.label("ID"),
g.name,
],
readonly=True)
g.set_label('id', "ID")
def configure_fieldset(self, fs):
fs.configure(

View file

@ -35,7 +35,7 @@ from pyramid.httpexceptions import HTTPNotFound
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView, AutocompleteView
from tailbone.views import MasterView2 as MasterView, AutocompleteView
from rattail.db import model
@ -47,8 +47,17 @@ class CustomersView(MasterView):
model_class = model.Customer
has_versions = True
supports_mobile = True
grid_columns = [
'id',
'number',
'name',
'phone',
'email',
]
def configure_grid(self, g):
super(CustomersView, self).configure_grid(g)
def _preconfigure_grid(self, g):
g.joiners['email'] = lambda q: q.outerjoin(model.CustomerEmailAddress, sa.and_(
model.CustomerEmailAddress.parent_uuid == model.Customer.uuid,
model.CustomerEmailAddress.preference == 1))
@ -66,23 +75,15 @@ class CustomersView(MasterView):
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.filters['id'].label = "ID"
g.sorters['email'] = lambda q, d: q.order_by(getattr(model.CustomerEmailAddress.address, d)())
g.sorters['phone'] = lambda q, d: q.order_by(getattr(model.CustomerPhoneNumber.number, d)())
g.default_sortkey = 'name'
def configure_grid(self, g):
g.configure(
include=[
g.id.label("ID"),
g.number,
g.name,
g.phone.label("Phone Number"),
g.email.label("Email Address"),
],
readonly=True)
g.set_label('id', "ID")
g.set_label('phone', "Phone Number")
g.set_label('email', "Email Address")
def get_mobile_data(self, session=None):
# TODO: hacky!

View file

@ -31,7 +31,7 @@ import logging
from rattail.db import model
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
log = logging.getLogger(__name__)
@ -48,18 +48,18 @@ class DataSyncChangesView(MasterView):
creatable = False
editable = False
grid_columns = [
'source',
'payload_type',
'payload_key',
'deletion',
'obtained',
'consumer',
]
def configure_grid(self, g):
super(DataSyncChangesView, self).configure_grid(g)
g.default_sortkey = 'obtained'
g.configure(
include=[
g.source,
g.payload_type,
g.payload_key,
g.deletion,
g.obtained,
g.consumer,
],
readonly=True)
def restart(self):
# TODO: Add better validation (e.g. CSRF) here?

View file

@ -28,8 +28,9 @@ 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 import grids3 as grids
from tailbone.newgrids import AlchemyGrid
from tailbone.views import MasterView2 as MasterView, AutocompleteView
class DepartmentsView(MasterView):
@ -38,16 +39,16 @@ class DepartmentsView(MasterView):
"""
model_class = model.Department
grid_columns = [
'number',
'name',
]
def configure_grid(self, g):
super(DepartmentsView, self).configure_grid(g)
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'number'
g.configure(
include=[
g.number,
g.name,
],
readonly=True)
def configure_fieldset(self, fs):
fs.configure(
@ -67,11 +68,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 = 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 = 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

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -26,36 +26,18 @@ Email Views
from __future__ import unicode_literals, absolute_import
import formalchemy
from formalchemy.helpers import text_area
from pyramid.httpexceptions import HTTPFound
from rattail import mail
from rattail.db import api
from rattail.config import parse_list
from tailbone import forms, newgrids as grids
import formalchemy
from formalchemy.helpers import text_area
from pyramid.httpexceptions import HTTPFound
from webhelpers2.html import HTML
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView, View
class BoolGridColumn(grids.GridColumn):
def render(self, value):
if value is None:
return ''
return 'Yes' if value else 'No'
class EmailListGridColumn(grids.GridColumn):
def render(self, value):
if not value:
return ''
recips = parse_list(value)
if len(recips) < 3:
return value
return "{}, ...".format(', '.join(recips[:2]))
from tailbone.views import View, MasterView2 as MasterView
class EmailListFieldRenderer(formalchemy.TextAreaFieldRenderer):
@ -75,13 +57,17 @@ class ProfilesView(MasterView):
model_title = "Email Profile"
model_key = 'key'
url_prefix = '/email/profiles'
grid_factory = grids.Grid
filterable = False
pageable = False
creatable = False
deletable = False
grid_columns = [
'key',
'prefix',
'subject',
'to',
'enabled',
]
def get_data(self, session=None):
data = []
@ -91,6 +77,23 @@ class ProfilesView(MasterView):
data.append(self.normalize(email))
return data
def configure_grid(self, g):
g.sorters['key'] = g.make_simple_sorter('key', foldcase=True)
g.sorters['prefix'] = g.make_simple_sorter('prefix', foldcase=True)
g.sorters['subject'] = g.make_simple_sorter('subject', foldcase=True)
g.sorters['to'] = g.make_simple_sorter('to', foldcase=True)
g.sorters['enabled'] = g.make_simple_sorter('enabled')
g.default_sortkey = 'key'
g.set_type('enabled', 'boolean')
g.set_link('key')
g.set_link('subject')
# Make edit link visible by default, no "More" actions.
if g.more_actions:
g.main_actions.append(g.more_actions.pop())
def normalize(self, email):
def get_recips(type_):
recips = email.get_recips(type_)
@ -112,26 +115,6 @@ class ProfilesView(MasterView):
'enabled': email.get_enabled(),
}
def configure_grid(self, g):
g.columns = [
grids.GridColumn('key'),
grids.GridColumn('prefix'),
grids.GridColumn('subject'),
EmailListGridColumn('to'),
BoolGridColumn('enabled'),
]
g.sorters['key'] = g.make_sorter('key', foldcase=True)
g.sorters['prefix'] = g.make_sorter('prefix', foldcase=True)
g.sorters['subject'] = g.make_sorter('subject', foldcase=True)
g.sorters['to'] = g.make_sorter('to', foldcase=True)
g.sorters['enabled'] = g.make_sorter('enabled')
g.default_sortkey = 'key'
# Make edit link visible by default, no "More" actions.
if g.more_actions:
g.main_actions.append(g.more_actions.pop())
def get_instance(self):
key = self.request.matchdict['key']
return self.normalize(mail.get_email(self.rattail_config, key))

View file

@ -32,9 +32,9 @@ from rattail.db import model
import formalchemy as fa
from tailbone import forms, newgrids as grids
from tailbone import forms, grids3 as grids
from tailbone.db import Session
from tailbone.views import MasterView, AutocompleteView
from tailbone.views import MasterView2 as MasterView, AutocompleteView
class EmployeesView(MasterView):
@ -44,7 +44,18 @@ class EmployeesView(MasterView):
model_class = model.Employee
has_versions = True
def _preconfigure_grid(self, g):
grid_columns = [
'id',
'first_name',
'last_name',
'phone',
'email',
'status',
]
def configure_grid(self, g):
super(EmployeesView, self).configure_grid(g)
g.joiners['phone'] = lambda q: q.outerjoin(model.EmployeePhoneNumber, sa.and_(
model.EmployeePhoneNumber.parent_uuid == model.Employee.uuid,
model.EmployeePhoneNumber.preference == 1))
@ -61,7 +72,6 @@ class EmployeesView(MasterView):
label="Phone Number")
if self.request.has_perm('employees.edit'):
g.filters['id'].label = "ID"
g.filters['status'].default_active = True
g.filters['status'].default_verb = 'equal'
g.filters['status'].default_value = self.enum.EMPLOYEE_STATUS_CURRENT
@ -84,25 +94,15 @@ class EmployeesView(MasterView):
g.default_sortkey = 'first_name'
g.append(forms.AssociationProxyField('first_name'))
g.append(forms.AssociationProxyField('last_name'))
g.set_enum('status', self.enum.EMPLOYEE_STATUS)
def configure_grid(self, g):
g.configure(
include=[
g.id.label("ID"),
g.first_name,
g.last_name,
g.phone.label("Phone Number"),
g.email.label("Email Address"),
g.status.with_renderer(forms.renderers.EnumFieldRenderer(self.enum.EMPLOYEE_STATUS)),
],
readonly=True)
g.set_label('id', "ID")
g.set_label('phone', "Phone Number")
g.set_label('email', "Email Address")
if not self.request.has_perm('employees.edit'):
del g.id
del g.status
g.hide_column('id')
g.hide_column('status')
def query(self, session):
q = session.query(model.Employee).join(model.Person)

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -34,7 +34,7 @@ import formalchemy as fa
from pyramid.response import FileResponse
from tailbone import forms
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class ExportMasterView(MasterView):
@ -45,6 +45,13 @@ class ExportMasterView(MasterView):
editable = False
export_has_file = False
grid_columns = [
'id',
'created',
'created_by',
'record_count',
]
def get_export_key(self):
if hasattr(self, 'export_key'):
return self.export_key
@ -56,26 +63,22 @@ class ExportMasterView(MasterView):
export.filename,
makedirs=makedirs)
def _preconfigure_grid(self, g):
g.filters['id'].label = "ID"
g.id.set(label="ID", renderer=forms.renderers.BatchIDFieldRenderer)
def configure_grid(self, g):
super(ExportMasterView, self).configure_grid(g)
g.joiners['created_by'] = lambda q: q.join(model.User)
g.sorters['created_by'] = g.make_sorter(model.User.username)
g.filters['created_by'] = g.make_filter('created_by', model.User.username,
label="Created by")
g.created_by.set(label="Created by", renderer=forms.renderers.UserFieldRenderer)
g.filters['created_by'] = g.make_filter('created_by', model.User.username)
g.default_sortkey = 'created'
g.default_sortdir = 'desc'
def configure_grid(self, g):
g.configure(
include=[
g.id,
g.created,
g.created_by,
g.record_count,
],
readonly=True)
g.set_renderer('id', self.render_id)
g.set_label('id', "ID")
g.set_label('created_by', "Created by")
def render_id(self, export, column):
return export.id_str
def _preconfigure_fieldset(self, fs):
fs.id.set(label="ID", renderer=forms.renderers.BatchIDFieldRenderer)

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -26,10 +26,10 @@ Family Views
from __future__ import unicode_literals, absolute_import
from tailbone.views import MasterView
from rattail.db import model
from tailbone.views import MasterView2 as MasterView
class FamiliesView(MasterView):
"""
@ -39,17 +39,15 @@ class FamiliesView(MasterView):
model_title_plural = "Families"
route_prefix = 'families'
grid_key = 'families'
grid_columns = [
'code',
'name',
]
def configure_grid(self, g):
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'code'
g.configure(
include=[
g.code,
g.name,
],
readonly=True)
def configure_fieldset(self, fs):
fs.configure(

View file

@ -38,7 +38,7 @@ from webhelpers2.html import tags
from tailbone import forms
from tailbone.db import Session
from tailbone.views.batch import FileBatchMasterView
from tailbone.views.batch import FileBatchMasterView2 as FileBatchMasterView
ACTION_OPTIONS = OrderedDict([
@ -84,27 +84,37 @@ class HandheldBatchView(FileBatchMasterView):
rows_creatable = False
rows_editable = True
grid_columns = [
'id',
'device_type',
'device_name',
'created',
'created_by',
'rowcount',
'status_code',
'executed',
]
row_grid_columns = [
'sequence',
'upc',
'brand_name',
'description',
'size',
'cases',
'units',
'status_code',
]
def configure_grid(self, g):
super(HandheldBatchView, self).configure_grid(g)
device_types = OrderedDict(sorted(self.enum.HANDHELD_DEVICE_TYPE.items(),
key=lambda item: item[1]))
g.configure(
include=[
g.id,
g.device_type.with_renderer(forms.renderers.EnumFieldRenderer(device_types)),
g.device_name,
g.created,
g.created_by,
g.rowcount,
g.status_code,
g.executed,
],
readonly=True)
g.set_enum('device_type', device_types)
def row_attrs(self, batch, i):
attrs = {}
def grid_extra_class(self, batch, i):
if batch.status_code is not None and batch.status_code != batch.STATUS_OK:
attrs['class_'] = 'notice'
return attrs
return 'notice'
def _preconfigure_fieldset(self, fs):
super(HandheldBatchView, self)._preconfigure_fieldset(fs)
@ -145,32 +155,16 @@ class HandheldBatchView(FileBatchMasterView):
kwargs['device_name'] = batch.device_name
return kwargs
def _preconfigure_row_grid(self, g):
super(HandheldBatchView, self)._preconfigure_row_grid(g)
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
g.cases.set(renderer=forms.renderers.QuantityFieldRenderer)
g.units.set(renderer=forms.renderers.QuantityFieldRenderer)
def configure_row_grid(self, g):
g.configure(
include=[
g.sequence,
g.upc,
g.brand_name,
g.description,
g.size,
g.cases,
g.units,
g.status_code,
],
readonly=True)
super(HandheldBatchView, self).configure_row_grid(g)
g.set_type('cases', 'quantity')
g.set_type('units', 'quantity')
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
def row_grid_row_attrs(self, row, i):
attrs = {}
def row_grid_extra_class(self, row, i):
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
attrs['class_'] = 'warning'
return attrs
return 'warning'
def _preconfigure_row_fieldset(self, fs):
super(HandheldBatchView, self)._preconfigure_row_fieldset(fs)

View file

@ -28,6 +28,8 @@ from __future__ import unicode_literals, absolute_import
import re
import six
from rattail import pod
from rattail.db import model, api
from rattail.time import localtime
@ -36,10 +38,9 @@ from rattail.util import pretty_quantity
import formalchemy as fa
import formencode as fe
from webhelpers2.html import tags
from tailbone import forms
from tailbone.views.batch import BatchMasterView
from tailbone.views.batch import BatchMasterView2 as BatchMasterView
class InventoryBatchView(BatchMasterView):
@ -54,28 +55,42 @@ class InventoryBatchView(BatchMasterView):
creatable = False
mobile_creatable = True
grid_columns = [
'id',
'created',
'created_by',
'rowcount',
'executed',
'executed_by',
'mode',
]
model_row_class = model.InventoryBatchRow
rows_editable = True
def _preconfigure_grid(self, g):
super(InventoryBatchView, self)._preconfigure_grid(g)
g.mode.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.INVENTORY_MODE),
label="Count Mode")
row_grid_columns = [
'sequence',
'upc',
'brand_name',
'description',
'size',
'cases',
'units',
'unit_cost',
'status_code',
]
def configure_grid(self, g):
g.configure(include=[
g.id,
g.created,
g.created_by,
g.rowcount,
g.executed,
g.executed_by,
g.mode,
], readonly=True)
super(InventoryBatchView, self).configure_grid(g)
g.set_enum('mode', self.enum.INVENTORY_MODE)
g.set_label('mode', "Count Mode")
def configure_mobile_grid(self, g):
super(InventoryBatchView, self).configure_mobile_grid(g)
g.listitem.set(renderer=InventoryBatchRenderer)
def render_mobile_listitem(self, batch, i):
return "({}) {} rows - {}, {}".format(
batch.id_str,
"?" if batch.rowcount is None else batch.rowcount,
batch.created_by,
localtime(self.request.rattail_config, batch.created, from_utc=True).strftime('%Y-%m-%d'))
def _preconfigure_fieldset(self, fs):
super(InventoryBatchView, self)._preconfigure_fieldset(fs)
@ -202,45 +217,26 @@ class InventoryBatchView(BatchMasterView):
return self.render_to_response('view_row', context, mobile=True)
def _preconfigure_row_grid(self, g):
super(InventoryBatchView, self)._preconfigure_row_grid(g)
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
g.cases.set(renderer=forms.renderers.QuantityFieldRenderer)
g.units.set(renderer=forms.renderers.QuantityFieldRenderer)
g.status_code.set(label="Status")
g.unit_cost.set(renderer=forms.renderers.CurrencyFieldRenderer)
def configure_row_grid(self, g):
g.configure(
include=[
g.sequence,
g.upc,
g.brand_name,
g.description,
g.size,
g.cases,
g.units,
g.unit_cost,
g.status_code,
],
readonly=True)
super(InventoryBatchView, self).configure_row_grid(g)
def row_grid_row_attrs(self, row, i):
attrs = {}
g.set_renderer('cases', 'quantity')
g.set_renderer('units', 'quantity')
g.set_renderer('unit_cost', 'currency')
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('status_code', "Status")
def row_grid_extra_class(self, row, i):
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
attrs['class_'] = 'warning'
return attrs
return 'warning'
def render_mobile_row_listitem(self, row, **kwargs):
if row is None:
return ''
def render_mobile_row_listitem(self, row, i):
description = row.product.full_description if row.product else row.description
unit_uom = 'LB' if row.product and row.product.weighed else 'EA'
qty = "{} {}".format(pretty_quantity(row.cases or row.units), 'CS' if row.cases else unit_uom)
title = "({}) {} - {}".format(row.upc.pretty(), description, qty)
url = self.request.route_url('mobile.batch.inventory.rows.view', uuid=row.uuid)
return tags.link_to(title, url)
return "({}) {} - {}".format(row.upc.pretty(), description, qty)
def _preconfigure_row_fieldset(self, fs):
super(InventoryBatchView, self)._preconfigure_row_fieldset(fs)
@ -281,19 +277,6 @@ class InventoryBatchView(BatchMasterView):
permission='{}.create'.format(row_permission_prefix))
class InventoryBatchRenderer(fa.FieldRenderer):
def render_readonly(self, **kwargs):
batch = self.raw_value
title = "({}) {} rows - {}, {}".format(
batch.id_str,
"?" if batch.rowcount is None else batch.rowcount,
batch.created_by,
localtime(self.request.rattail_config, batch.created, from_utc=True).strftime('%Y-%m-%d'))
url = self.request.route_url('mobile.batch.inventory.view', uuid=batch.uuid)
return tags.link_to(title, url)
class ValidBatchRow(forms.validators.ModelValidator):
model_class = model.InventoryBatchRow

View file

@ -31,7 +31,7 @@ from rattail.db import model
import formalchemy as fa
from tailbone import forms
from tailbone.views.batch import BatchMasterView
from tailbone.views.batch import BatchMasterView2 as BatchMasterView
class LabelBatchView(BatchMasterView):
@ -49,6 +49,19 @@ class LabelBatchView(BatchMasterView):
rows_editable = True
cloneable = True
row_grid_columns = [
'sequence',
'upc',
'brand_name',
'description',
'size',
'regular_price',
'sale_price',
'label_profile',
'label_quantity',
'status_code',
]
def _preconfigure_fieldset(self, fs):
super(LabelBatchView, self)._preconfigure_fieldset(fs)
fs.append(fa.Field('handheld_batches', renderer=forms.renderers.HandheldBatchesFieldRenderer, readonly=True,
@ -69,35 +82,17 @@ class LabelBatchView(BatchMasterView):
if self.viewing and not batch._handhelds:
del fs.handheld_batches
def _preconfigure_row_grid(self, g):
super(LabelBatchView, self)._preconfigure_row_grid(g)
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
g.regular_price.set(label="Reg Price")
g.label_profile.set(label="Label Type")
g.label_quantity.set(label="Qty")
def configure_row_grid(self, g):
g.configure(
include=[
g.sequence,
g.upc,
g.brand_name,
g.description,
g.size,
g.regular_price,
g.sale_price,
g.label_profile,
g.label_quantity,
g.status_code,
],
readonly=True)
super(LabelBatchView, self).configure_row_grid(g)
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('regular_price', "Reg Price")
g.set_label('label_profile', "Label Type")
g.set_label('label_quantity', "Qty")
def row_grid_row_attrs(self, row, i):
attrs = {}
def row_grid_extra_class(self, row, i):
if row.status_code != row.STATUS_OK:
attrs['class_'] = 'warning'
return attrs
return 'warning'
def _preconfigure_row_fieldset(self, fs):
fs.sequence.set(readonly=True)

View file

@ -32,7 +32,7 @@ from pyramid.httpexceptions import HTTPFound
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class ProfilesView(MasterView):
@ -42,17 +42,17 @@ class ProfilesView(MasterView):
model_class = model.LabelProfile
model_title = "Label Profile"
url_prefix = '/labels/profiles'
grid_columns = [
'ordinal',
'code',
'description',
'visible',
]
def configure_grid(self, g):
super(ProfilesView, self).configure_grid(g)
g.default_sortkey = 'ordinal'
g.configure(
include=[
g.ordinal,
g.code,
g.description,
g.visible,
],
readonly=True)
g.set_type('visible', 'boolean')
def configure_fieldset(self, fs):
fs.printer_spec.set(renderer=forms.renderers.StrippedTextFieldRenderer)

View file

@ -129,7 +129,7 @@ class MasterView(View):
return self.redirect(self.request.current_route_url(_query=None))
# Stash some grid stats, for possible use when generating URLs.
if grid.pageable and grid.pager:
if grid.pageable and hasattr(grid, 'pager'):
self.first_visible_grid_index = grid.pager.first_item
# Return grid only, if partial page was requested.
@ -170,6 +170,15 @@ class MasterView(View):
"""
return getattr(cls, 'mobile_grid_factory', MobileGrid)
@classmethod
def get_mobile_row_grid_factory(cls):
"""
Must return a callable to be used when creating new mobile grid
instances. Instead of overriding this, you can set
:attr:`mobile_grid_factory`. Default factory is :class:`MobileGrid`.
"""
return getattr(cls, 'mobile_row_grid_factory', MobileGrid)
@classmethod
def get_mobile_grid_key(cls):
"""
@ -643,7 +652,7 @@ class MasterView(View):
kwargs.setdefault('request', self.request)
kwargs.setdefault('model_class', self.model_row_class)
kwargs = self.make_mobile_row_grid_kwargs(**kwargs)
factory = self.get_mobile_grid_factory()
factory = self.get_mobile_row_grid_factory()
grid = factory(**kwargs)
self.configure_mobile_row_grid(grid)
grid.load_settings()
@ -703,7 +712,7 @@ class MasterView(View):
kwargs['instance'] = parent
kwargs = self.make_row_grid_kwargs(**kwargs)
key = '{}.{}'.format(self.get_grid_key(), self.request.matchdict[self.get_model_key()])
factory = self.get_grid_factory()
factory = self.get_row_grid_factory()
grid = factory(key, self.request, data=data, model_class=self.model_row_class, **kwargs)
self._preconfigure_row_grid(grid)
self.configure_row_grid(grid)
@ -1253,6 +1262,15 @@ class MasterView(View):
"""
return getattr(cls, 'grid_factory', AlchemyGrid)
@classmethod
def get_row_grid_factory(cls):
"""
Must return a callable to be used when creating new row grid instances.
Instead of overriding this, you can set :attr:`row_grid_factory`.
Default factory is :class:`AlchemyGrid`.
"""
return getattr(cls, 'row_grid_factory', AlchemyGrid)
@classmethod
def get_grid_key(cls):
"""
@ -1787,12 +1805,14 @@ class MasterView(View):
def configure_row_fieldset(self, fs):
fs.configure()
def get_row_action_url(self, action, row):
def get_row_action_url(self, action, row, mobile=False):
"""
Generate a URL for the given action on the given row.
"""
return self.request.route_url('{}.{}'.format(self.get_row_route_prefix(), action),
**self.get_row_action_route_kwargs(row))
route_name = '{}.{}'.format(self.get_row_route_prefix(), action)
if mobile:
route_name = 'mobile.{}'.format(route_name)
return self.request.route_url(route_name, **self.get_row_action_route_kwargs(row))
def get_row_action_route_kwargs(self, row):
"""

337
tailbone/views/master2.py Normal file
View file

@ -0,0 +1,337 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2017 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 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 General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Master View
"""
from __future__ import unicode_literals, absolute_import
from tailbone import grids3 as grids
from tailbone.views import MasterView
class MasterView2(MasterView):
"""
Base "master" view class. All model master views should derive from this.
"""
sortable = True
rows_pageable = True
mobile_pageable = True
def get_fallback_templates(self, template, mobile=False):
if mobile:
return [
'/mobile/master/{}.mako'.format(template),
]
return [
'/master2/{}.mako'.format(template),
'/master/{}.mako'.format(template),
]
@classmethod
def get_grid_factory(cls):
"""
Returns the grid factory or class which is to be used when creating new
grid instances.
"""
return getattr(cls, 'grid_factory', grids.Grid)
@classmethod
def get_row_grid_factory(cls):
"""
Returns the grid factory or class which is to be used when creating new
row grid instances.
"""
return getattr(cls, 'row_grid_factory', grids.Grid)
@classmethod
def get_mobile_grid_factory(cls):
"""
Must return a callable to be used when creating new mobile grid
instances. Instead of overriding this, you can set
:attr:`mobile_grid_factory`. Default factory is :class:`MobileGrid`.
"""
return getattr(cls, 'mobile_grid_factory', grids.MobileGrid)
@classmethod
def get_mobile_row_grid_factory(cls):
"""
Must return a callable to be used when creating new mobile row grid
instances. Instead of overriding this, you can set
:attr:`mobile_row_grid_factory`. Default factory is :class:`MobileGrid`.
"""
return getattr(cls, 'mobile_row_grid_factory', grids.MobileGrid)
def make_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
"""
Creates a new grid instance
"""
if factory is None:
factory = self.get_grid_factory()
if key is None:
key = self.get_grid_key()
if data is None:
data = self.get_data(session=kwargs.get('session'))
if columns is None:
columns = self.get_grid_columns()
kwargs.setdefault('request', self.request)
kwargs = self.make_grid_kwargs(**kwargs)
grid = factory(key, data, columns, **kwargs)
self.configure_grid(grid)
grid.load_settings()
return grid
def make_row_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
"""
Make and return a new (configured) rows grid instance.
"""
instance = kwargs.pop('instance', None)
if not instance:
instance = self.get_instance()
if factory is None:
factory = self.get_row_grid_factory()
if key is None:
key = '{}.{}'.format(self.get_grid_key(), self.request.matchdict[self.get_model_key()])
if data is None:
data = self.get_row_data(instance)
if columns is None:
columns = self.get_row_grid_columns()
kwargs.setdefault('request', self.request)
kwargs = self.make_row_grid_kwargs(**kwargs)
grid = factory(key, data, columns, **kwargs)
self.configure_row_grid(grid)
grid.load_settings()
return grid
def make_mobile_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
"""
Creates a new mobile grid instance
"""
if factory is None:
factory = self.get_mobile_grid_factory()
if key is None:
key = self.get_mobile_grid_key()
if data is None:
data = self.get_mobile_data(session=kwargs.get('session'))
if columns is None:
columns = self.get_mobile_grid_columns()
kwargs.setdefault('request', self.request)
kwargs.setdefault('mobile', True)
kwargs = self.make_mobile_grid_kwargs(**kwargs)
grid = factory(key, data, columns, **kwargs)
self.configure_mobile_grid(grid)
grid.load_settings()
return grid
def make_mobile_row_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
"""
Make a new (configured) rows grid instance for mobile.
"""
instance = kwargs.pop('instance', self.get_instance())
if factory is None:
factory = self.get_mobile_row_grid_factory()
if key is None:
key = 'mobile.{}.{}'.format(self.get_grid_key(), self.request.matchdict[self.get_model_key()])
if data is None:
data = self.get_mobile_row_data(instance)
if columns is None:
columns = self.get_mobile_row_grid_columns()
kwargs.setdefault('request', self.request)
kwargs.setdefault('mobile', True)
kwargs = self.make_mobile_row_grid_kwargs(**kwargs)
grid = factory(key, data, columns, **kwargs)
self.configure_mobile_row_grid(grid)
grid.load_settings()
return grid
def get_grid_columns(self):
if hasattr(self, 'grid_columns'):
return self.grid_columns
# TODO
raise NotImplementedError
def get_row_grid_columns(self):
if hasattr(self, 'row_grid_columns'):
return self.row_grid_columns
# TODO
raise NotImplementedError
def get_mobile_grid_columns(self):
if hasattr(self, 'mobile_grid_columns'):
return self.mobile_grid_columns
# TODO
return ['listitem']
def get_mobile_row_grid_columns(self):
if hasattr(self, 'mobile_row_grid_columns'):
return self.mobile_row_grid_columns
# TODO
return ['listitem']
def make_grid_kwargs(self, **kwargs):
"""
Return a dictionary of kwargs to be passed to the factory when creating
new grid instances.
"""
defaults = {
'model_class': getattr(self, 'model_class', None),
# 'width': 'full',
'filterable': self.filterable,
'sortable': self.sortable,
'pageable': self.pageable,
'extra_row_class': self.grid_extra_class,
'url': lambda obj: self.get_action_url('view', obj),
'checkboxes': self.checkboxes or (
self.mergeable and self.request.has_perm('{}.merge'.format(self.get_permission_prefix()))),
}
if 'main_actions' not in kwargs and 'more_actions' not in kwargs:
main, more = self.get_grid_actions()
defaults['main_actions'] = main
defaults['more_actions'] = more
defaults.update(kwargs)
return defaults
def make_row_grid_kwargs(self, **kwargs):
"""
Return a dict of kwargs to be used when constructing a new rows grid.
"""
route_prefix = self.get_row_route_prefix()
permission_prefix = self.get_row_permission_prefix()
defaults = {
'model_class': self.model_row_class,
# 'width': 'full',
'filterable': self.rows_filterable,
'sortable': self.rows_sortable,
'pageable': self.rows_pageable,
'default_pagesize': self.rows_default_pagesize,
'extra_row_class': self.row_grid_extra_class,
}
if self.has_rows and 'main_actions' not in defaults:
actions = []
# view action
if self.rows_viewable:
view = lambda r, i: self.get_row_action_url('view', r)
actions.append(grids.GridAction('view', icon='zoomin', url=view))
# edit action
if self.rows_editable:
actions.append(grids.GridAction('edit', icon='pencil', url=self.row_edit_action_url))
# delete action
if self.rows_deletable and self.request.has_perm('{}.delete_row'.format(permission_prefix)):
actions.append(grids.GridAction('delete', icon='trash', url=self.row_delete_action_url))
defaults['delete_speedbump'] = self.rows_deletable_speedbump
defaults['main_actions'] = actions
defaults.update(kwargs)
return defaults
def make_mobile_grid_kwargs(self, **kwargs):
"""
Must return a dictionary of kwargs to be passed to the factory when
creating new mobile grid instances.
"""
defaults = {
'model_class': getattr(self, 'model_class', None),
'pageable': self.mobile_pageable,
'sortable': False,
'filterable': self.mobile_filterable,
'renderers': self.make_mobile_grid_renderers(),
'url': lambda obj: self.get_action_url('view', obj, mobile=True),
}
# TODO: this seems wrong..
if self.mobile_filterable:
defaults['filters'] = self.make_mobile_filters()
defaults.update(kwargs)
return defaults
def make_mobile_row_grid_kwargs(self, **kwargs):
"""
Must return a dictionary of kwargs to be passed to the factory when
creating new mobile *row* grid instances.
"""
defaults = {
'model_class': self.model_row_class,
# TODO
'pageable': self.pageable,
'sortable': False,
'filterable': self.mobile_rows_filterable,
'renderers': self.make_mobile_row_grid_renderers(),
'url': lambda obj: self.get_row_action_url('view', obj, mobile=True),
}
# TODO: this seems wrong..
if self.mobile_rows_filterable:
defaults['filters'] = self.make_mobile_row_filters()
defaults.update(kwargs)
return defaults
def make_mobile_grid_renderers(self):
return {
'listitem': self.render_mobile_listitem,
}
def render_mobile_listitem(self, obj, i):
return obj
def make_mobile_row_grid_renderers(self):
return {
'listitem': self.render_mobile_row_listitem,
}
def render_mobile_row_listitem(self, obj, i):
return obj
def grid_extra_class(self, obj, i):
"""
Returns string of extra class(es) for the table row corresponding to
the given object, or ``None``.
"""
def row_grid_extra_class(self, obj, i):
"""
Returns string of extra class(es) for the table row corresponding to
the given row object, or ``None``.
"""
def configure_grid(self, grid):
pass
def configure_row_grid(self, grid):
pass
def configure_mobile_grid(self, grid):
pass
def configure_mobile_row_grid(self, grid):
pass

View file

@ -28,6 +28,7 @@ from __future__ import unicode_literals, absolute_import
import json
import pytz
import six
from rattail import enum
from rattail.db import model
@ -40,16 +41,8 @@ from webhelpers2.html import tags, HTML
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView
class SubjectFieldRenderer(formalchemy.FieldRenderer):
def render_readonly(self, **kwargs):
subject = self.raw_value
if not subject:
return ''
return tags.link_to(subject, self.request.route_url('messages.view', uuid=self.field.parent.model.uuid))
from tailbone.views import MasterView2 as MasterView
from tailbone.util import raw_datetime
class SenderFieldRenderer(forms.renderers.UserFieldRenderer):
@ -106,23 +99,6 @@ class RecipientsFieldRenderer(formalchemy.FieldRenderer):
return ', '.join(recips)
class TerseRecipientsFieldRenderer(formalchemy.FieldRenderer):
def render_readonly(self, **kwargs):
recipients = self.raw_value
if not recipients:
return ''
message = self.field.parent.model
recips = [r for r in recipients if r.recipient is not self.request.user]
recips = sorted([r.recipient.display_name for r in recips])
if len(recips) < len(recipients) and (
message.sender is not self.request.user or not recips):
recips.insert(0, 'you')
if len(recips) < 5:
return ', '.join(recips)
return "{}, ...".format(', '.join(recips[:4]))
class MessagesView(MasterView):
"""
Base class for message views.
@ -133,6 +109,7 @@ class MessagesView(MasterView):
checkboxes = True
replying = False
reply_header_sent_format = '%a %d %b %Y at %I:%M %p'
grid_columns = ['subject', 'sender', 'recipients', 'sent']
def get_index_title(self):
if self.listing:
@ -176,37 +153,49 @@ class MessagesView(MasterView):
.outerjoin(model.MessageRecipient)\
.filter(model.MessageRecipient.recipient == self.request.user)
def _preconfigure_grid(self, g):
def configure_grid(self, g):
g.joiners['sender'] = lambda q: q.join(model.User, model.User.uuid == model.Message.sender_uuid).outerjoin(model.Person)
g.filters['sender'] = g.make_filter('sender', model.Person.display_name,
default_active=True, default_verb='contains')
g.sorters['sender'] = g.make_sorter(model.Person.display_name)
g.sender.set(label="From", renderer=SenderFieldRenderer)
g.filters['subject'].default_active = True
g.filters['subject'].default_verb = 'contains'
g.subject.set(renderer=SubjectFieldRenderer)
g.recipients.set(label="To", renderer=TerseRecipientsFieldRenderer)
g.default_sortkey = 'sent'
g.default_sortdir = 'desc'
def configure_grid(self, g):
g.configure(
include=[
g.subject,
g.sender,
g.recipients,
g.sent,
],
readonly=True)
g.set_renderer('sent', self.render_sent)
g.set_renderer('sender', self.render_sender)
g.set_renderer('recipients', self.render_recipients)
def row_attrs(self, row, i):
recip = self.get_recipient(row)
if recip:
return {'data-uuid': recip.uuid}
return {}
g.set_link('subject')
g.set_label('sender', "From")
g.set_label('recipients', "To")
def render_sent(self, message, column_name):
return raw_datetime(self.rattail_config, message.sent)
def render_sender(self, message, column_name):
sender = message.sender
if sender is self.request.user:
return 'you'
return six.text_type(sender)
def render_recipients(self, message, column_name):
recipients = message.recipients
if recipients:
recips = [r for r in recipients if r.recipient is not self.request.user]
recips = sorted([r.recipient.display_name for r in recips])
if len(recips) < len(recipients) and (
message.sender is not self.request.user or not recips):
recips.insert(0, "you")
if len(recips) < 5:
return ", ".join(recips)
return "{}, ...".format(', '.join(recips[:4]))
return ""
def make_form(self, instance, **kwargs):
form = super(MessagesView, self).make_form(instance, **kwargs)
@ -398,10 +387,12 @@ class MessagesView(MasterView):
if uuids:
new_status = enum.MESSAGE_STATUS_INBOX if dest == 'inbox' else enum.MESSAGE_STATUS_ARCHIVE
for uuid in uuids:
recip = Session.query(model.MessageRecipient).get(uuid) if uuid else None
if recip and recip.recipient is self.request.user:
if recip.status != new_status:
recip.status = new_status
recip = self.Session.query(model.MessageRecipient)\
.filter(model.MessageRecipient.message_uuid == uuid)\
.filter(model.MessageRecipient.recipient_uuid == self.request.user.uuid)\
.first()
if recip and recip.status != new_status:
recip.status = new_status
route = 'messages.{}'.format('archive' if dest == 'inbox' else 'inbox')
return self.redirect(self.request.route_url(route))
@ -480,8 +471,8 @@ class SentView(MessagesView):
return session.query(model.Message)\
.filter(model.Message.sender == self.request.user)
def _preconfigure_grid(self, g):
super(SentView, self)._preconfigure_grid(g)
def configure_grid(self, g):
super(SentView, self).configure_grid(g)
g.filters['sender'].default_active = False
g.joiners['recipients'] = lambda q: q.join(model.MessageRecipient)\
.join(model.User, model.User.uuid == model.MessageRecipient.recipient_uuid)\

View file

@ -33,7 +33,7 @@ from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from webhelpers2.html import HTML, tags
from tailbone import forms
from tailbone.views import MasterView, AutocompleteView
from tailbone.views import MasterView2 as MasterView, AutocompleteView
from rattail.db import model
@ -63,7 +63,17 @@ class PeopleView(MasterView):
route_prefix = 'people'
has_versions = True
def _preconfigure_grid(self, g):
grid_columns = [
'display_name',
'first_name',
'last_name',
'phone',
'email',
]
def configure_grid(self, g):
super(PeopleView, self).configure_grid(g)
g.joiners['email'] = lambda q: q.outerjoin(model.PersonEmailAddress, sa.and_(
model.PersonEmailAddress.parent_uuid == model.Person.uuid,
model.PersonEmailAddress.preference == 1))
@ -71,13 +81,11 @@ class PeopleView(MasterView):
model.PersonPhoneNumber.parent_uuid == model.Person.uuid,
model.PersonPhoneNumber.preference == 1))
g.filters['email'] = g.make_filter('email', model.PersonEmailAddress.address,
label="Email Address")
g.filters['phone'] = g.make_filter('phone', model.PersonPhoneNumber.number,
label="Phone Number")
g.filters['email'] = g.make_filter('email', model.PersonEmailAddress.address)
g.filters['phone'] = g.make_filter('phone', model.PersonPhoneNumber.number)
g.joiners['customer_id'] = lambda q: q.outerjoin(model.CustomerPerson).outerjoin(model.Customer)
g.filters['customer_id'] = g.make_filter('customer_id', model.Customer.id, label="Customer ID")
g.filters['customer_id'] = g.make_filter('customer_id', model.Customer.id)
g.filters['first_name'].default_active = True
g.filters['first_name'].default_verb = 'contains'
@ -90,16 +98,10 @@ class PeopleView(MasterView):
g.default_sortkey = 'display_name'
def configure_grid(self, g):
g.configure(
include=[
g.display_name.label("Full Name"),
g.first_name,
g.last_name,
g.phone.label("Phone Number"),
g.email.label("Email Address"),
],
readonly=True)
g.set_label('display_name', "Full Name")
g.set_label('phone', "Phone Number")
g.set_label('email', "Email Address")
g.set_label('customer_id', "Customer ID")
def get_instance(self):
# TODO: I don't recall why this fallback check for a vendor contact

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -30,7 +30,7 @@ import copy
import wtforms
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class PrincipalMasterView(MasterView):

View file

@ -44,11 +44,11 @@ import wtforms
import formalchemy as fa
from pyramid import httpexceptions
from pyramid.renderers import render_to_response
from webhelpers2.html import tags
from webhelpers2.html import tags, HTML
from tailbone import forms, newgrids as grids
from tailbone import forms, grids3 as grids
from tailbone.db import Session
from tailbone.views import MasterView, AutocompleteView
from tailbone.views import MasterView2 as MasterView, AutocompleteView
from tailbone.progress import SessionProgress
@ -71,21 +71,6 @@ from tailbone.progress import SessionProgress
# return query
class DescriptionFieldRenderer(fa.TextFieldRenderer):
"""
Renderer for product descriptions within the grid; adds hyperlink.
"""
def render_readonly(self, **kwargs):
description = self.raw_value
if description is None:
return ''
if kwargs.get('link') and description:
product = self.field.parent.model
description = tags.link_to(description, kwargs['link'](product))
return description
class ProductsView(MasterView):
"""
Master view for the Product class.
@ -93,6 +78,17 @@ class ProductsView(MasterView):
model_class = model.Product
supports_mobile = True
grid_columns = [
'upc',
'brand',
'description',
'size',
'subdepartment',
'vendor',
'regular_price',
'current_price',
]
# child_version_classes = [
# (model.ProductCode, 'product_uuid'),
# (model.ProductCost, 'product_uuid'),
@ -133,7 +129,9 @@ class ProductsView(MasterView):
return query
def _preconfigure_grid(self, g):
def configure_grid(self, g):
super(ProductsView, self).configure_grid(g)
def join_vendor(q):
return q.outerjoin(model.ProductCost,
sa.and_(
@ -185,7 +183,6 @@ class ProductsView(MasterView):
g.filters['upc'].default_active = True
g.filters['upc'].default_verb = 'equal'
g.filters['upc'].label = "UPC"
g.filters['description'].default_active = True
g.filters['description'].default_verb = 'contains'
g.filters['brand'] = g.make_filter('brand', model.Brand.name,
@ -196,51 +193,64 @@ class ProductsView(MasterView):
g.filters['subdepartment'] = g.make_filter('subdepartment', model.Subdepartment.name)
g.filters['report_code'] = g.make_filter('report_code', model.ReportCode.name)
g.filters['code'] = g.make_filter('code', model.ProductCode.code)
g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name, label="Vendor (preferred)")
g.filters['vendor_any'] = g.make_filter('vendor_any', self.VendorAny.name, label="Vendor (any)")
g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name)
g.filters['vendor_any'] = g.make_filter('vendor_any', self.VendorAny.name)
# factory=VendorAnyFilter, joiner=join_vendor_any)
g.filters['vendor_code'] = g.make_filter('vendor_code', ProductCostCode.code)
g.filters['vendor_code_any'] = g.make_filter('vendor_code_any', ProductCostCodeAny.code)
product_link = lambda p: self.get_action_url('view', p)
g.upc.set(label="UPC", renderer=forms.renderers.GPCFieldRenderer)
g.upc.attrs(link=product_link)
g.description.set(renderer=DescriptionFieldRenderer)
g.description.attrs(link=product_link)
g.regular_price.set(label="Reg. Price", renderer=forms.renderers.PriceFieldRenderer)
g.current_price.set(label="Cur. Price", renderer=forms.renderers.PriceFieldRenderer)
g.vendor.set(label="Pref. Vendor")
g.joiners['cost'] = lambda q: q.outerjoin(model.ProductCost,
sa.and_(
model.ProductCost.product_uuid == model.Product.uuid,
model.ProductCost.preference == 1))
g.sorters['cost'] = g.make_sorter(model.ProductCost.unit_cost)
g.filters['cost'] = g.make_filter('cost', model.ProductCost.unit_cost)
g.cost.set(renderer=forms.renderers.CostFieldRenderer)
g.default_sortkey = 'upc'
if self.print_labels and self.request.has_perm('products.print_labels'):
g.more_actions.append(grids.GridAction('print_label', icon='print'))
def configure_grid(self, g):
g.configure(
include=[
g.upc,
g.brand,
g.description,
g.size,
g.subdepartment,
g.vendor,
g.regular_price,
g.current_price,
],
readonly=True)
g.set_type('upc', 'gpc')
g.set_renderer('regular_price', self.render_price)
g.set_renderer('current_price', self.render_price)
g.set_renderer('cost', self.render_cost)
g.set_link('upc')
g.set_link('description')
g.set_label('upc', "UPC")
g.set_label('vendor', "Vendor (preferred)")
g.set_label('vendor_any', "Vendor (any)")
g.set_label('regular_price', "Reg. Price")
g.set_label('current_price', "Cur. Price")
g.set_label('vendor', "Pref. Vendor")
def render_price(self, product, column):
price = product[column]
if price:
if not product.not_for_sale:
if price.price is not None and price.pack_price is not None:
if price.multiple > 1:
return HTML("$ {:0.2f} / {}&nbsp; ($ {:0.2f} / {})".format(
price.price, price.multiple,
price.pack_price, price.pack_multiple))
return HTML("$ {:0.2f}&nbsp; ($ {:0.2f} / {})".format(
price.price, price.pack_price, price.pack_multiple))
if price.price is not None:
if price.multiple > 1:
return "$ {:0.2f} / {}".format(price.price, price.multiple)
return "$ {:0.2f}".format(price.price)
if price.pack_price is not None:
return "$ {:0.2f} / {}".format(price.pack_price, price.pack_multiple)
return ""
def render_cost(self, product, column):
cost = product.cost
if not cost:
return ""
return "'${:0.2f}".format(cost.unit_cost)
def template_kwargs_index(self, **kwargs):
if self.print_labels:
@ -250,19 +260,15 @@ class ProductsView(MasterView):
.all()
return kwargs
def row_attrs(self, row, i):
attrs = {'uuid': row.uuid}
def grid_extra_class(self, product, i):
classes = []
if row.not_for_sale:
if product.not_for_sale:
classes.append('not-for-sale')
if row.deleted:
if product.deleted:
classes.append('deleted')
if classes:
attrs['class_'] = ' '.join(classes)
return attrs
return ' '.join(classes)
def get_instance(self):
key = self.request.matchdict['uuid']

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -29,4 +29,4 @@ from __future__ import unicode_literals, absolute_import
def includeme(config):
config.include('tailbone.views.purchases.core')
config.include('tailbone.views.purchases.batch')
config.include('tailbone.views.purchases.credits')

File diff suppressed because it is too large Load diff

View file

@ -33,7 +33,7 @@ from webhelpers2.html import HTML, tags
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class BatchesFieldRenderer(fa.FieldRenderer):
@ -73,6 +73,32 @@ class PurchaseView(MasterView):
model_row_class = model.PurchaseItem
row_model_title = 'Purchase Item'
grid_columns = [
'store',
'vendor',
'department',
'buyer',
'date_ordered',
'date_received',
'invoice_number',
'status',
]
row_grid_columns = [
'sequence',
'upc',
'item_id',
'brand_name',
'description',
'size',
'cases_ordered',
'units_ordered',
'cases_received',
'units_received',
'po_total',
'invoice_total',
]
def get_instance_title(self, purchase):
if purchase.status >= self.enum.PURCHASE_STATUS_COSTED:
if purchase.invoice_date:
@ -90,7 +116,9 @@ class PurchaseView(MasterView):
return "{} (ordered)".format(purchase.vendor)
return unicode(purchase)
def _preconfigure_grid(self, g):
def configure_grid(self, g):
super(PurchaseView, self).configure_grid(g)
g.joiners['store'] = lambda q: q.join(model.Store)
g.filters['store'] = g.make_filter('store', model.Store.name)
g.sorters['store'] = g.make_sorter(model.Store.name)
@ -116,24 +144,11 @@ class PurchaseView(MasterView):
g.default_sortkey = 'date_ordered'
g.default_sortdir = 'desc'
g.date_ordered.set(label="Ordered")
g.date_received.set(label="Received")
g.invoice_number.set(label="Invoice No.")
g.status.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.PURCHASE_STATUS))
g.set_enum('status', self.enum.PURCHASE_STATUS)
def configure_grid(self, g):
g.configure(
include=[
g.store,
g.vendor,
g.department,
g.buyer,
g.date_ordered,
g.date_received,
g.invoice_number,
g.status,
],
readonly=True)
g.set_label('date_ordered', "Ordered")
g.set_label('date_received', "Received")
g.set_label('invoice_number', "Invoice No.")
def _preconfigure_fieldset(self, fs):
fs.store.set(renderer=forms.renderers.StoreFieldRenderer)
@ -189,43 +204,36 @@ class PurchaseView(MasterView):
return Session.query(model.PurchaseItem)\
.filter(model.PurchaseItem.purchase == purchase)
def _preconfigure_row_grid(self, g):
g.default_sortkey = 'sequence'
g.sequence.set(label="Seq")
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
g.cases_ordered.set(label="Cases Ord.", renderer=forms.renderers.QuantityFieldRenderer)
g.units_ordered.set(label="Units Ord.", renderer=forms.renderers.QuantityFieldRenderer)
g.cases_received.set(label="Cases Rec.", renderer=forms.renderers.QuantityFieldRenderer)
g.units_received.set(label="Units Rec.", renderer=forms.renderers.QuantityFieldRenderer)
g.po_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
g.invoice_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
def configure_row_grid(self, g):
super(PurchaseView, self).configure_row_grid(g)
g.default_sortkey = 'sequence'
g.set_type('cases_ordered', 'quantity')
g.set_type('units_ordered', 'quantity')
g.set_type('cases_received', 'quantity')
g.set_type('units_received', 'quantity')
g.set_type('po_total', 'currency')
g.set_type('invoice_total', 'currency')
g.set_label('sequence', "Seq")
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('cases_ordered', "Cases Ord.")
g.set_label('units_ordered', "Units Ord.")
g.set_label('cases_received', "Cases Rec.")
g.set_label('units_received', "Units Rec.")
g.set_label('po_total', "Total")
g.set_label('invoice_total', "Total")
purchase = self.get_instance()
g.configure(
include=[
g.sequence,
g.upc,
g.item_id,
g.brand_name,
g.description,
g.size,
g.cases_ordered,
g.units_ordered,
g.cases_received,
g.units_received,
g.po_total,
g.invoice_total,
],
readonly=True)
if purchase.status == self.enum.PURCHASE_STATUS_ORDERED:
del g.cases_received
del g.units_received
del g.invoice_total
g.hide_column('cases_received')
g.hide_column('units_received')
g.hide_column('invoice_total')
elif purchase.status in (self.enum.PURCHASE_STATUS_RECEIVED,
self.enum.PURCHASE_STATUS_COSTED):
del g.po_total
g.hide_column('po_total')
def _preconfigure_row_fieldset(self, fs):
fs.vendor_code.set(label="Vendor Item Code")

View file

@ -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 PurchaseCreditView(MasterView):
@ -42,7 +42,21 @@ class PurchaseCreditView(MasterView):
creatable = False
editable = False
def _preconfigure_grid(self, g):
grid_columns = [
'vendor',
'upc',
'brand_name',
'description',
'size',
'cases_shorted',
'units_shorted',
'credit_type',
'date_received',
'status',
]
def configure_grid(self, g):
super(PurchaseCreditView, self).configure_grid(g)
g.joiners['vendor'] = lambda q: q.outerjoin(model.Vendor)
g.sorters['vendor'] = g.make_sorter(model.Vendor.name)
@ -50,28 +64,15 @@ class PurchaseCreditView(MasterView):
g.default_sortkey = 'date_received'
g.default_sortdir = 'desc'
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
g.cases_shorted.set(label="Cases", renderer=forms.renderers.QuantityFieldRenderer)
g.units_shorted.set(label="Units", renderer=forms.renderers.QuantityFieldRenderer)
g.credit_type.set(label="Type")
g.date_received.set(label="Date")
g.set_type('cases_shorted', 'quantity')
g.set_type('units_shorted', 'quantity')
def configure_grid(self, g):
g.configure(
include=[
g.vendor,
g.upc,
g.brand_name,
g.description,
g.size,
g.cases_shorted,
g.units_shorted,
g.credit_type,
g.date_received,
g.status,
],
readonly=True)
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('cases_shorted', "Cases")
g.set_label('units_shorted', "Units")
g.set_label('credit_type', "Type")
g.set_label('date_received', "Date")
def includeme(config):

View file

@ -36,7 +36,7 @@ import formalchemy as fa
from pyramid import httpexceptions
from tailbone import forms
from tailbone.views.batch import BatchMasterView
from tailbone.views.batch import BatchMasterView2 as BatchMasterView
class PurchasingBatchView(BatchMasterView):
@ -47,6 +47,34 @@ class PurchasingBatchView(BatchMasterView):
model_row_class = model.PurchaseBatchRow
default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler'
grid_columns = [
'id',
'vendor',
'department',
'buyer',
'date_ordered',
'created',
'created_by',
'executed',
]
# row_grid_columns = [
# 'sequence',
# 'upc',
# # 'item_id',
# 'brand_name',
# 'description',
# 'size',
# 'cases_ordered',
# 'units_ordered',
# 'cases_received',
# 'units_received',
# 'po_total',
# 'invoice_total',
# 'credits',
# 'status_code',
# ]
@property
def batch_mode(self):
raise NotImplementedError("Please define `batch_mode` for your purchasing batch view")
@ -55,9 +83,8 @@ class PurchasingBatchView(BatchMasterView):
return session.query(model.PurchaseBatch)\
.filter(model.PurchaseBatch.mode == self.batch_mode)
def _preconfigure_grid(self, g):
super(PurchasingBatchView, self)._preconfigure_grid(g)
del g.filters['mode']
def configure_grid(self, g):
super(PurchasingBatchView, self).configure_grid(g)
g.joiners['vendor'] = lambda q: q.join(model.Vendor)
g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name,
@ -77,22 +104,8 @@ class PurchasingBatchView(BatchMasterView):
g.filters['complete'].default_active = True
g.filters['complete'].default_verb = 'is_true'
g.date_ordered.set(label="Ordered")
g.date_received.set(label="Received")
def configure_grid(self, g):
g.configure(
include=[
g.id,
g.vendor,
g.department,
g.buyer,
g.date_ordered,
g.created,
g.created_by,
g.executed,
],
readonly=True)
g.set_label('date_ordered', "Ordered")
g.set_label('date_received', "Received")
# def make_form(self, batch, **kwargs):
# if self.creating:
@ -299,65 +312,36 @@ class PurchasingBatchView(BatchMasterView):
# query = super(PurchasingBatchView, self).get_row_data(batch)
# return query.options(orm.joinedload(model.PurchaseBatchRow.credits))
def _preconfigure_row_grid(self, g):
super(PurchasingBatchView, self)._preconfigure_row_grid(g)
g.filters['upc'].label = "UPC"
g.filters['brand_name'].label = "Brand"
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
g.cases_ordered.set(label="Cases Ord.", renderer=forms.renderers.QuantityFieldRenderer)
g.units_ordered.set(label="Units Ord.", renderer=forms.renderers.QuantityFieldRenderer)
g.cases_received.set(label="Cases Rec.", renderer=forms.renderers.QuantityFieldRenderer)
g.units_received.set(label="Units Rec.", renderer=forms.renderers.QuantityFieldRenderer)
g.po_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
g.invoice_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
g.append(fa.Field('has_credits', type=fa.types.Boolean, label="Credits?",
value=lambda row: bool(row.credits)))
def configure_row_grid(self, g):
batch = self.get_instance()
super(PurchasingBatchView, self).configure_row_grid(g)
g.configure(
include=[
g.sequence,
g.upc,
# g.item_id,
g.brand_name,
g.description,
g.size,
g.cases_ordered,
g.units_ordered,
g.cases_received,
g.units_received,
g.po_total,
g.invoice_total,
g.has_credits,
g.status_code,
],
readonly=True)
g.set_type('upc', 'gpc')
g.set_type('cases_ordered', 'quantity')
g.set_type('units_ordered', 'quantity')
g.set_type('cases_received', 'quantity')
g.set_type('units_received', 'quantity')
g.set_type('po_total', 'currency')
g.set_type('invoice_total', 'currency')
g.set_type('credits', 'boolean')
if batch.mode == self.enum.PURCHASE_BATCH_MODE_ORDERING:
del g.cases_received
del g.units_received
del g.has_credits
del g.invoice_total
elif batch.mode in (self.enum.PURCHASE_BATCH_MODE_RECEIVING,
self.enum.PURCHASE_BATCH_MODE_COSTING):
del g.po_total
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('cases_ordered', "Cases Ord.")
g.set_label('units_ordered', "Units Ord.")
g.set_label('cases_received', "Cases Rec.")
g.set_label('units_received', "Units Rec.")
g.set_label('po_total', "Total")
g.set_label('invoice_total', "Total")
g.set_label('credits', "Credits?")
def make_row_grid_tools(self, batch):
return self.make_default_row_grid_tools(batch)
def row_grid_row_attrs(self, row, i):
attrs = {}
def row_grid_extra_class(self, row, i):
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
attrs['class_'] = 'warning'
elif row.status_code in (row.STATUS_INCOMPLETE,
row.STATUS_ORDERED_RECEIVED_DIFFER):
attrs['class_'] = 'notice'
return attrs
return 'warning'
if row.status_code in (row.STATUS_INCOMPLETE, row.STATUS_ORDERED_RECEIVED_DIFFER):
return 'notice'
def _preconfigure_row_fieldset(self, fs):
super(PurchasingBatchView, self)._preconfigure_row_fieldset(fs)

View file

@ -51,6 +51,23 @@ class OrderingBatchView(PurchasingBatchView):
model_title = "Ordering Batch"
model_title_plural = "Ordering Batches"
row_grid_columns = [
'sequence',
'upc',
# 'item_id',
'brand_name',
'description',
'size',
'cases_ordered',
'units_ordered',
# 'cases_received',
# 'units_received',
'po_total',
# 'invoice_total',
# 'credits',
'status_code',
]
order_form_header_columns = [
"UPC",
"Brand",

View file

@ -39,7 +39,7 @@ import formalchemy as fa
import formencode as fe
from webhelpers2.html import tags
from tailbone import forms, newgrids as grids
from tailbone import forms, grids3 as grids
from tailbone.views.purchasing import PurchasingBatchView
@ -93,10 +93,36 @@ class ReceivingBatchView(PurchasingBatchView):
mobile_creatable = True
mobile_rows_filterable = True
row_grid_columns = [
'sequence',
'upc',
# 'item_id',
'brand_name',
'description',
'size',
'cases_ordered',
'units_ordered',
'cases_received',
'units_received',
# 'po_total',
'invoice_total',
'credits',
'status_code',
]
@property
def batch_mode(self):
return self.enum.PURCHASE_BATCH_MODE_RECEIVING
def render_mobile_listitem(self, batch, i):
title = "({}) {} for ${:0,.2f} - {}, {}".format(
batch.id_str,
batch.vendor,
batch.po_total or 0,
batch.department,
batch.created_by)
return title
def make_mobile_row_filters(self):
"""
Returns a set of filters for the mobile row grid.
@ -158,10 +184,6 @@ class ReceivingBatchView(PurchasingBatchView):
kwargs['sms_transaction_number'] = batch.sms_transaction_number
return kwargs
def configure_mobile_grid(self, g):
super(ReceivingBatchView, self).configure_mobile_grid(g)
g.listitem.set(renderer=ReceivingBatchRenderer)
def configure_mobile_fieldset(self, fs):
fs.configure(include=[
fs.vendor.with_renderer(fa.TextFieldRenderer),
@ -178,13 +200,9 @@ class ReceivingBatchView(PurchasingBatchView):
else:
del fs.complete
def render_mobile_row_listitem(self, row, **kwargs):
if row is None:
return ''
def render_mobile_row_listitem(self, row, i):
description = row.product.full_description if row.product else row.description
title = "({}) {}".format(row.upc.pretty(), description)
url = self.request.route_url('mobile.receiving.rows.view', uuid=row.uuid)
return tags.link_to(title, url)
return "({}) {}".format(row.upc.pretty(), description)
# TODO: this view can create new rows, with only a GET query. that should
# probably be changed to require POST; for now we just require the "create
@ -344,20 +362,6 @@ class ReceivingBatchView(PurchasingBatchView):
cls._defaults(config)
class ReceivingBatchRenderer(fa.FieldRenderer):
def render_readonly(self, **kwargs):
batch = self.raw_value
title = "({}) {} for ${:0,.2f} - {}, {}".format(
batch.id_str,
batch.vendor,
batch.po_total or 0,
batch.department,
batch.created_by)
url = self.request.route_url('mobile.receiving.view', uuid=batch.uuid)
return tags.link_to(title, url)
class ValidBatchRow(forms.validators.ModelValidator):
model_class = model.PurchaseBatchRow

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -26,10 +26,10 @@ Report Code Views
from __future__ import unicode_literals, absolute_import
from tailbone.views import MasterView
from rattail.db import model
from tailbone.views import MasterView2 as MasterView
class ReportCodesView(MasterView):
"""
@ -38,16 +38,16 @@ class ReportCodesView(MasterView):
model_class = model.ReportCode
model_title = "Report Code"
grid_columns = [
'code',
'name',
]
def configure_grid(self, g):
super(ReportCodesView, 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.name,
],
readonly=True)
def configure_fieldset(self, fs):
fs.configure(

View file

@ -204,16 +204,13 @@ class ReportOutputView(ExportMasterView):
url_prefix = '/reports/generated'
downloadable = True
def configure_grid(self, g):
g.configure(
include=[
g.id,
g.report_name,
g.filename,
g.created,
g.created_by,
],
readonly=True)
grid_columns = [
'id',
'report_name',
'filename',
'created',
'created_by',
]
def _preconfigure_fieldset(self, fs):
super(ReportOutputView, self)._preconfigure_fieldset(fs)

View file

@ -34,8 +34,9 @@ from rattail.db.auth import has_permission, administrator_role, guest_role, auth
import formalchemy as fa
from formalchemy.fields import IntegerFieldRenderer
from tailbone import forms, newgrids as grids
from tailbone import forms, grids3 as grids
from tailbone.db import Session
from tailbone.newgrids import AlchemyGrid
from tailbone.views.principal import PrincipalMasterView
@ -45,19 +46,17 @@ class RolesView(PrincipalMasterView):
"""
model_class = model.Role
def _preconfigure_grid(self, g):
grid_columns = [
'name',
'session_timeout',
]
def configure_grid(self, g):
super(RolesView, self).configure_grid(g)
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'name'
def configure_grid(self, g):
g.configure(
include=[
g.name,
g.session_timeout,
],
readonly=True)
def _preconfigure_fieldset(self, fs):
fs.append(PermissionsField('permissions'))
permissions = self.request.registry.settings.get('tailbone_permissions', {})
@ -84,11 +83,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 = 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 = 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

View file

@ -33,7 +33,7 @@ from rattail.db import model
import formalchemy as fa
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
def unique_name(value, field):
@ -48,17 +48,16 @@ class SettingsView(MasterView):
"""
model_class = model.Setting
feedback = re.compile(r'^rattail\.mail\.user_feedback\..*')
grid_columns = [
'name',
'value',
]
def configure_grid(self, g):
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'name'
g.configure(
include=[
g.name,
g.value,
],
readonly=True)
g.set_link('name')
def _preconfigure_fieldset(self, fs):
fs.name.set(validate=unique_name)

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -36,7 +36,7 @@ from rattail.time import localtime
import formalchemy
from tailbone import forms
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class ShiftLengthField(formalchemy.Field):
@ -53,6 +53,14 @@ class ShiftLengthField(formalchemy.Field):
return humanize.naturaldelta(shift.end_time - shift.start_time)
def render_shift_length(shift, column):
if not shift.start_time or not shift.end_time:
return ""
if shift.end_time < shift.start_time:
return "??"
return humanize.naturaldelta(shift.end_time - shift.start_time)
class ScheduledShiftsView(MasterView):
"""
Master view for employee scheduled shifts.
@ -60,24 +68,25 @@ class ScheduledShiftsView(MasterView):
model_class = model.ScheduledShift
url_prefix = '/shifts/scheduled'
grid_columns = [
'employee',
'store',
'start_time',
'end_time',
'length',
]
def configure_grid(self, g):
g.joiners['employee'] = lambda q: q.join(model.Employee).join(model.Person)
g.filters['employee'] = g.make_filter('employee', model.Person.display_name,
default_active=True, default_verb='contains',
label="Employee Name")
default_active=True, default_verb='contains')
g.default_sortkey = 'start_time'
g.default_sortdir = 'desc'
g.append(ShiftLengthField('length'))
g.configure(
include=[
g.employee,
g.store,
g.start_time,
g.end_time,
g.length,
],
readonly=True)
g.set_renderer('length', render_shift_length)
g.set_label('employee', "Employee Name")
def configure_fieldset(self, fs):
fs.append(ShiftLengthField('length'))
@ -98,37 +107,37 @@ class WorkedShiftsView(MasterView):
model_class = model.WorkedShift
url_prefix = '/shifts/worked'
def _preconfigure_grid(self, g):
grid_columns = [
'employee',
'store',
'start_time',
'end_time',
'length',
]
def configure_grid(self, g):
super(WorkedShiftsView, self).configure_grid(g)
g.joiners['employee'] = lambda q: q.join(model.Employee).join(model.Person)
g.filters['employee'] = g.make_filter('employee', model.Person.display_name,
label="Employee Name")
g.filters['employee'] = g.make_filter('employee', model.Person.display_name)
g.sorters['employee'] = g.make_sorter(model.Person.display_name)
g.joiners['store'] = lambda q: q.join(model.Store)
g.filters['store'] = g.make_filter('store', model.Store.name, label="Store Name")
g.filters['store'] = g.make_filter('store', model.Store.name)
g.sorters['store'] = g.make_sorter(model.Store.name)
g.filters['punch_in'].label = "Start Time"
g.filters['punch_out'].label = "End Time"
# TODO: these sorters should be automatic once we fix the schema
g.sorters['start_time'] = g.make_sorter(model.WorkedShift.punch_in)
g.sorters['end_time'] = g.make_sorter(model.WorkedShift.punch_out)
g.default_sortkey = 'start_time'
g.default_sortdir = 'desc'
g.append(ShiftLengthField('length'))
g.set_renderer('length', render_shift_length)
def configure_grid(self, g):
g.configure(
include=[
g.employee,
g.store,
g.start_time,
g.end_time,
g.length,
],
readonly=True)
g.set_label('employee', "Employee Name")
g.set_label('store', "Store Name")
g.set_label('punch_in', "Start Time")
g.set_label('punch_out', "End Time")
def get_instance_title(self, shift):
time = shift.start_time or shift.end_time

View file

@ -30,7 +30,7 @@ import sqlalchemy as sa
from rattail.db import model
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class StoresView(MasterView):
@ -39,6 +39,12 @@ class StoresView(MasterView):
"""
model_class = model.Store
has_versions = True
grid_columns = [
'id',
'name',
'phone',
'email',
]
def configure_grid(self, g):
@ -49,28 +55,21 @@ class StoresView(MasterView):
model.StorePhoneNumber.parent_uuid == model.Store.uuid,
model.StorePhoneNumber.preference == 1))
g.filters['email'] = g.make_filter('email', model.StoreEmailAddress.address,
label="Email Address")
g.filters['phone'] = g.make_filter('phone', model.StorePhoneNumber.number,
label="Phone Number")
g.filters['phone'] = g.make_filter('phone', model.StorePhoneNumber.number)
g.filters['email'] = g.make_filter('email', model.StoreEmailAddress.address)
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.filters['id'].label = "ID"
g.sorters['email'] = lambda q, d: q.order_by(getattr(model.StoreEmailAddress.address, d)())
g.sorters['phone'] = lambda q, d: q.order_by(getattr(model.StorePhoneNumber.number, d)())
g.sorters['phone'] = g.make_sorter(model.StorePhoneNumber.number)
g.sorters['email'] = g.make_sorter(model.StoreEmailAddress.address)
g.default_sortkey = 'id'
g.configure(
include=[
g.id.label("ID"),
g.name,
g.phone.label("Phone Number"),
g.email.label("Email Address"),
],
readonly=True)
g.set_link('id')
g.set_link('name')
g.set_label('id', "ID")
g.set_label('phone', "Phone Number")
g.set_label('email', "Email Address")
def configure_fieldset(self, fs):
fs.configure(

View file

@ -29,7 +29,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class SubdepartmentsView(MasterView):
@ -37,6 +37,13 @@ class SubdepartmentsView(MasterView):
Master view for the Subdepartment class.
"""
model_class = model.Subdepartment
grid_columns = [
'number',
'name',
'department',
]
mergeable = True
merge_additive_fields = [
'product_count',
@ -49,16 +56,10 @@ class SubdepartmentsView(MasterView):
]
def configure_grid(self, g):
super(SubdepartmentsView, self).configure_grid(g)
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.default_sortkey = 'name'
g.configure(
include=[
g.number,
g.name,
g.department,
],
readonly=True)
def configure_fieldset(self, fs):
fs.configure(

View file

@ -26,8 +26,7 @@ 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.views import MasterView2 as MasterView
class TablesView(MasterView):
@ -41,10 +40,14 @@ class TablesView(MasterView):
editable = False
deletable = False
viewable = False
grid_factory = grids.Grid
filterable = False
pageable = False
grid_columns = [
'name',
'row_count',
]
def get_data(self, **kwargs):
"""
Fetch existing table names and estimate row counts via PG SQL
@ -58,13 +61,8 @@ class TablesView(MasterView):
return [dict(name=row[1], row_count=row[2]) for row in result]
def configure_grid(self, g):
g.columns = [
grids.GridColumn('name'),
grids.GridColumn('row_count'),
]
g.sorters['name'] = g.make_sorter('name', foldcase=True)
g.sorters['row_count'] = g.make_sorter('row_count')
g.sorters['name'] = g.make_simple_sorter('name', foldcase=True)
g.sorters['row_count'] = g.make_simple_sorter('row_count')
g.default_sortkey = 'name'

View file

@ -69,25 +69,27 @@ class TempmonClientView(MasterView):
route_prefix = 'tempmon.clients'
url_prefix = '/tempmon/clients'
def _preconfigure_grid(self, g):
grid_columns = [
'config_key',
'hostname',
'location',
'delay',
'enabled',
'online',
]
def configure_grid(self, g):
super(TempmonClientView, self).configure_grid(g)
g.filters['hostname'].default_active = True
g.filters['hostname'].default_verb = 'contains'
g.filters['location'].default_active = True
g.filters['location'].default_verb = 'contains'
g.default_sortkey = 'config_key'
g.config_key.set(label="Key")
def configure_grid(self, g):
g.configure(
include=[
g.config_key,
g.hostname,
g.location,
g.delay,
g.enabled,
g.online,
],
readonly=True)
g.set_type('enabled', 'boolean')
g.set_type('online', 'boolean')
g.set_label('config_key', "Key")
def _preconfigure_fieldset(self, fs):
fs.config_key.set(validate=unique_config_key)

View file

@ -33,7 +33,7 @@ from tailbone import views
from tailbone.db import TempmonSession
class MasterView(views.MasterView):
class MasterView(views.MasterView2):
"""
Base class for tempmon views.
"""

View file

@ -55,26 +55,29 @@ class TempmonProbeView(MasterView):
route_prefix = 'tempmon.probes'
url_prefix = '/tempmon/probes'
def _preconfigure_grid(self, g):
grid_columns = [
'client',
'config_key',
'appliance_type',
'description',
'device_path',
'enabled',
'status',
]
def configure_grid(self, g):
super(TempmonProbeView, self).configure_grid(g)
g.joiners['client'] = lambda q: q.join(tempmon.Client)
g.sorters['client'] = g.make_sorter(tempmon.Client.config_key)
g.default_sortkey = 'client'
g.config_key.set(label="Key")
g.appliance_type.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.TEMPMON_APPLIANCE_TYPE))
g.status.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.TEMPMON_PROBE_STATUS))
def configure_grid(self, g):
g.configure(
include=[
g.client,
g.config_key,
g.appliance_type,
g.description,
g.device_path,
g.enabled,
g.status,
],
readonly=True)
g.set_enum('appliance_type', self.enum.TEMPMON_APPLIANCE_TYPE)
g.set_enum('status', self.enum.TEMPMON_PROBE_STATUS)
g.set_type('enabled', 'boolean')
g.set_label('config_key', "Key")
def _preconfigure_fieldset(self, fs):
fs.config_key.set(validate=unique_config_key)

View file

@ -47,39 +47,45 @@ class TempmonReadingView(MasterView):
creatable = False
editable = False
grid_columns = [
'client_key',
'client_host',
'probe',
'taken',
'degrees_f',
]
def query(self, session):
return session.query(tempmon.Reading)\
.join(tempmon.Client)\
.options(orm.joinedload(tempmon.Reading.client))
def _preconfigure_grid(self, g):
def configure_grid(self, g):
super(TempmonReadingView, self).configure_grid(g)
g.append(fa.Field('client_key', value=lambda r: r.client.config_key))
g.sorters['client_key'] = g.make_sorter(tempmon.Client.config_key)
g.filters['client_key'] = g.make_filter('client_key', tempmon.Client.config_key)
g.append(fa.Field('client_host', value=lambda r: r.client.hostname))
g.sorters['client_host'] = g.make_sorter(tempmon.Client.hostname)
g.filters['client_host'] = g.make_filter('client_host', tempmon.Client.hostname)
g.joiners['probe'] = lambda q: q.join(tempmon.Probe,
tempmon.Probe.uuid == tempmon.Reading.probe_uuid)
g.joiners['probe'] = lambda q: q.join(tempmon.Probe, tempmon.Probe.uuid == tempmon.Reading.probe_uuid)
g.sorters['probe'] = g.make_sorter(tempmon.Probe.description)
g.filters['probe'] = g.make_filter('probe', tempmon.Probe.description)
g.default_sortkey = 'taken'
g.default_sortdir = 'desc'
def configure_grid(self, g):
g.configure(
include=[
g.client_key,
g.client_host,
g.probe,
g.taken,
g.degrees_f,
],
readonly=True)
g.set_type('taken', 'datetime')
g.set_renderer('client_key', self.render_client_key)
g.set_renderer('client_host', self.render_client_host)
def render_client_key(self, reading, column):
return reading.client.config_key
def render_client_host(self, reading, column):
return reading.client.hostname
def _preconfigure_fieldset(self, fs):
fs.client.set(label="TempMon Client", renderer=ClientFieldRenderer)

View file

@ -32,7 +32,7 @@ from rattail.time import localtime
from tailbone import forms
from tailbone.db import TrainwreckSession
from tailbone.views import MasterView
from tailbone.views import MasterView2 as MasterView
class TransactionView(MasterView):
@ -49,11 +49,35 @@ class TransactionView(MasterView):
editable = False
deletable = False
grid_columns = [
'start_time',
'system',
'terminal_id',
'receipt_number',
'customer_id',
'customer_name',
'total',
]
has_rows = True
# model_row_class = trainwreck.TransactionItem
rows_default_pagesize = 100
def _preconfigure_grid(self, g):
row_grid_columns = [
'sequence',
'item_type',
'item_id',
'department_number',
'description',
'unit_quantity',
'subtotal',
'tax',
'total',
'void',
]
def configure_grid(self, g):
super(TransactionView, self).configure_grid(g)
g.filters['receipt_number'].default_active = True
g.filters['receipt_number'].default_verb = 'equal'
g.filters['start_time'].default_active = True
@ -62,24 +86,11 @@ class TransactionView(MasterView):
g.default_sortkey = 'start_time'
g.default_sortdir = 'desc'
g.system.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.TRAINWRECK_SYSTEM))
g.terminal_id.set(label="Terminal")
g.receipt_number.set(label="Receipt No.")
g.customer_id.set(label="Customer ID")
g.total.set(renderer=forms.renderers.CurrencyFieldRenderer)
def configure_grid(self, g):
g.configure(
include=[
g.start_time,
g.system,
g.terminal_id,
g.receipt_number,
g.customer_id,
g.customer_name,
g.total,
],
readonly=True)
g.set_enum('system', self.enum.TRAINWRECK_SYSTEM)
g.set_type('total', 'currency')
g.set_label('terminal_id', "Terminal")
g.set_label('receipt_number', "Receipt No.")
g.set_label('customer_id', "Customer ID")
def _preconfigure_fieldset(self, fs):
fs.system.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.TRAINWRECK_SYSTEM))
@ -117,32 +128,18 @@ class TransactionView(MasterView):
def get_parent(self, item):
return item.transaction
def _preconfigure_row_grid(self, g):
def configure_row_grid(self, g):
super(TransactionView, self).configure_row_grid(g)
g.default_sortkey = 'sequence'
g.item_id.set(label="Item ID")
g.department_number.set(label="Dept. No.")
g.unit_quantity.set(renderer=forms.renderers.QuantityFieldRenderer)
g.subtotal.set(renderer=forms.renderers.CurrencyFieldRenderer)
g.discounted_subtotal.set(renderer=forms.renderers.CurrencyFieldRenderer)
g.tax.set(renderer=forms.renderers.CurrencyFieldRenderer)
g.total.set(renderer=forms.renderers.CurrencyFieldRenderer)
g.set_type('unit_quantity', 'quantity')
g.set_type('subtotal', 'currency')
g.set_type('discounted_subtotal', 'currency')
g.set_type('tax', 'currency')
g.set_type('total', 'currency')
def configure_row_grid(self, g):
g.configure(
include=[
g.sequence,
g.item_type,
g.item_id,
g.department_number,
g.description,
g.unit_quantity,
g.subtotal,
g.tax,
g.total,
g.void,
],
readonly=True)
g.set_label('item_id', "Item ID")
g.set_label('department_number', "Dept. No.")
def _preconfigure_row_fieldset(self, fs):
fs.item_id.set(label="Item ID")

View file

@ -142,11 +142,18 @@ class UsersView(PrincipalMasterView):
'active',
]
grid_columns = [
'username',
'person',
]
def query(self, session):
return session.query(model.User)\
.options(orm.joinedload(model.User.person))
def configure_grid(self, g):
super(UsersView, self).configure_grid(g)
g.joiners['person'] = lambda q: q.outerjoin(model.Person)
del g.filters['password']
@ -155,7 +162,7 @@ class UsersView(PrincipalMasterView):
g.filters['username'].default_verb = 'contains'
g.filters['active'].default_active = True
g.filters['active'].default_verb = 'is_true'
g.filters['person'] = g.make_filter('person', model.Person.display_name, label="Person's Name",
g.filters['person'] = g.make_filter('person', model.Person.display_name,
default_active=True, default_verb='contains')
g.filters['password'] = g.make_filter('password', model.User.password,
verbs=['is_null', 'is_not_null'])
@ -163,13 +170,7 @@ class UsersView(PrincipalMasterView):
g.sorters['person'] = lambda q, d: q.order_by(getattr(model.Person.display_name, d)())
g.default_sortkey = 'username'
g.person.set(label="Person's Name")
g.configure(
include=[
g.username,
g.person,
],
readonly=True)
g.set_label('person', "Person's Name")
def _preconfigure_fieldset(self, fs):
fs.username.set(renderer=forms.renderers.StrippedTextFieldRenderer, validate=unique_username)

View file

@ -35,7 +35,7 @@ import formalchemy
from tailbone import forms
from tailbone.db import Session
from tailbone.views.batch import FileBatchMasterView
from tailbone.views.batch import FileBatchMasterView2 as FileBatchMasterView
log = logging.getLogger(__name__)
@ -52,28 +52,40 @@ class VendorCatalogsView(FileBatchMasterView):
editable = False
rows_bulk_deletable = True
grid_columns = [
'created',
'created_by',
'vendor',
'effective',
'filename',
'executed',
]
row_grid_columns = [
'sequence',
'upc',
'brand_name',
'description',
'size',
'vendor_code',
'old_unit_cost',
'unit_cost',
'unit_cost_diff',
'status_code',
]
def get_parsers(self):
if not hasattr(self, 'parsers'):
self.parsers = sorted(iter_catalog_parsers(), key=lambda p: p.display)
return self.parsers
def configure_grid(self, g):
super(VendorCatalogsView, self).configure_grid(g)
g.joiners['vendor'] = lambda q: q.join(model.Vendor)
g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name,
default_active=True, default_verb='contains')
g.sorters['vendor'] = g.make_sorter(model.Vendor.name)
g.configure(
include=[
g.created,
g.created_by,
g.vendor,
g.effective,
g.filename,
g.executed,
],
readonly=True)
def get_instance_title(self, batch):
return unicode(batch.vendor)
@ -115,28 +127,18 @@ class VendorCatalogsView(FileBatchMasterView):
return kwargs
def configure_row_grid(self, g):
g.configure(
include=[
g.sequence,
g.upc.label("UPC"),
g.brand_name.label("Brand"),
g.description,
g.size,
g.vendor_code,
g.old_unit_cost.label("Old Cost"),
g.unit_cost.label("New Cost"),
g.unit_cost_diff.label("Diff."),
g.status_code,
],
readonly=True)
super(VendorCatalogsView, self).configure_row_grid(g)
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('old_unit_cost', "Old Cost")
g.set_label('unit_cost', "New Cost")
g.set_label('unit_cost_diff', "Diff.")
def row_grid_row_attrs(self, row, i):
attrs = {}
if row.status_code in (row.STATUS_NEW_COST, row.STATUS_UPDATE_COST):
attrs['class_'] = 'notice'
def row_grid_extra_class(self, row, i):
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
attrs['class_'] = 'warning'
return attrs
return 'warning'
if row.status_code in (row.STATUS_NEW_COST, row.STATUS_UPDATE_COST):
return 'notice'
def template_kwargs_create(self, **kwargs):
parsers = self.get_parsers()

View file

@ -30,7 +30,7 @@ from rattail.db import model
from tailbone import forms
from tailbone.db import Session
from tailbone.views import MasterView, AutocompleteView
from tailbone.views import MasterView2 as MasterView, AutocompleteView
class VendorsView(MasterView):
@ -40,22 +40,24 @@ class VendorsView(MasterView):
model_class = model.Vendor
has_versions = True
grid_columns = [
'id',
'name',
'phone',
'email',
'contact',
]
def configure_grid(self, g):
super(VendorsView, self).configure_grid(g)
g.filters['name'].default_active = True
g.filters['name'].default_verb = 'contains'
g.filters['id'].label = "ID"
g.default_sortkey = 'name'
g.append(forms.AssociationProxyField('contact'))
g.configure(
include=[
g.id.label("ID"),
g.name,
g.phone.label("Phone Number"),
g.email.label("Email Address"),
g.contact,
],
readonly=True)
g.set_label('id', "ID")
g.set_label('phone', "Phone Number")
g.set_label('email', "Email Address")
def configure_fieldset(self, fs):
fs.append(forms.AssociationProxyField('contact'))

View file

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8; -*-
################################################################################
#
# Rattail -- Retail Software Framework
@ -32,7 +32,7 @@ from rattail.vendors.invoices import iter_invoice_parsers, require_invoice_parse
import formalchemy
from tailbone.db import Session
from tailbone.views.batch import FileBatchMasterView
from tailbone.views.batch import FileBatchMasterView2 as FileBatchMasterView
class VendorInvoicesView(FileBatchMasterView):
@ -44,23 +44,36 @@ class VendorInvoicesView(FileBatchMasterView):
default_handler_spec = 'rattail.batch.vendorinvoice:VendorInvoiceHandler'
url_prefix = '/vendors/invoices'
grid_columns = [
'created',
'created_by',
'vendor',
'filename',
'executed',
]
row_grid_columns = [
'sequence',
'upc',
'brand_name',
'description',
'size',
'vendor_code',
'shipped_cases',
'shipped_units',
'unit_cost',
'status_code',
]
def get_instance_title(self, batch):
return unicode(batch.vendor)
def configure_grid(self, g):
super(VendorInvoicesView, self).configure_grid(g)
g.joiners['vendor'] = lambda q: q.join(model.Vendor)
g.filters['vendor'] = g.make_filter('vendor', model.Vendor.name,
default_active=True, default_verb='contains')
g.sorters['vendor'] = g.make_sorter(model.Vendor.name)
g.configure(
include=[
g.created,
g.created_by,
g.vendor,
g.filename,
g.executed,
],
readonly=True)
def configure_fieldset(self, fs):
fs.purchase_order_number.set(label=self.handler.po_number_title)
@ -123,34 +136,21 @@ class VendorInvoicesView(FileBatchMasterView):
return True
def configure_row_grid(self, g):
g.filters['upc'].label = "UPC"
g.filters['brand_name'].label = "Brand"
g.configure(
include=[
g.sequence,
g.upc.label("UPC"),
g.brand_name.label("Brand"),
g.description,
g.size,
g.vendor_code,
g.shipped_cases.label("Cases"),
g.shipped_units.label("Units"),
g.unit_cost,
g.status_code,
],
readonly=True)
super(VendorInvoicesView, self).configure_row_grid(g)
g.set_label('upc', "UPC")
g.set_label('brand_name', "Brand")
g.set_label('shipped_cases', "Cases")
g.set_label('shipped_units', "Units")
def row_grid_row_attrs(self, row, i):
attrs = {}
if row.status_code in (row.STATUS_NOT_IN_PURCHASE,
row.STATUS_NOT_IN_INVOICE,
row.STATUS_DIFFERS_FROM_PURCHASE):
attrs['class_'] = 'notice'
def row_grid_extra_class(self, row, i):
if row.status_code in (row.STATUS_NOT_IN_DB,
row.STATUS_COST_NOT_IN_DB,
row.STATUS_NO_CASE_QUANTITY):
attrs['class_'] = 'warning'
return attrs
return 'warning'
if row.status_code in (row.STATUS_NOT_IN_PURCHASE,
row.STATUS_NOT_IN_INVOICE,
row.STATUS_DIFFERS_FROM_PURCHASE):
return 'notice'
def includeme(config):