Refactored AutocompleteFieldRenderer.

Also improved some organization of renderers.
This commit is contained in:
Lance Edgar 2013-09-21 15:02:55 -07:00
parent c1d726d48c
commit 9f8a3d3a5c
8 changed files with 211 additions and 152 deletions

View file

@ -26,74 +26,6 @@
FormAlchemy Field Renderers FormAlchemy Field Renderers
""" """
from webhelpers.html import literal from .common import *
from webhelpers.html import tags from .people import *
from .products import *
import formalchemy
from edbob.pyramid.forms import pretty_datetime
from edbob.pyramid.forms.formalchemy.renderers import YesNoFieldRenderer
from .common import AutocompleteFieldRenderer, EnumFieldRenderer
from .products import GPCFieldRenderer, ProductFieldRenderer
from .users import UserFieldRenderer
__all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer', 'YesNoFieldRenderer',
'GPCFieldRenderer', 'PersonFieldRenderer', 'PriceFieldRenderer',
'PriceWithExpirationFieldRenderer', 'ProductFieldRenderer', 'UserFieldRenderer']
def PersonFieldRenderer(url):
BaseRenderer = AutocompleteFieldRenderer(url)
class PersonFieldRenderer(BaseRenderer):
def render_readonly(self, **kwargs):
person = self.raw_value
if not person:
return ''
return tags.link_to(
str(person),
self.request.route_url('person.read', uuid=person.uuid))
return PersonFieldRenderer
class PriceFieldRenderer(formalchemy.TextFieldRenderer):
"""
Renderer for fields which reference a :class:`ProductPrice` instance.
"""
def render_readonly(self, **kwargs):
price = self.field.raw_value
if price:
if price.price is not None and price.pack_price is not None:
if price.multiple > 1:
return literal('$ %0.2f / %u  ($ %0.2f / %u)' % (
price.price, price.multiple,
price.pack_price, price.pack_multiple))
return literal('$ %0.2f  ($ %0.2f / %u)' % (
price.price, price.pack_price, price.pack_multiple))
if price.price is not None:
if price.multiple > 1:
return '$ %0.2f / %u' % (price.price, price.multiple)
return '$ %0.2f' % price.price
if price.pack_price is not None:
return '$ %0.2f / %u' % (price.pack_price, price.pack_multiple)
return ''
class PriceWithExpirationFieldRenderer(PriceFieldRenderer):
"""
Price field renderer which also displays the expiration date, if present.
"""
def render_readonly(self, **kwargs):
res = super(PriceWithExpirationFieldRenderer, self).render_readonly(**kwargs)
if res:
price = self.field.raw_value
if price.ends:
res += '  (%s)' % pretty_datetime(price.ends, from_='utc')
return res

View file

@ -26,37 +26,54 @@
Common Field Renderers Common Field Renderers
""" """
from formalchemy.fields import FieldRenderer, SelectFieldRenderer from formalchemy.fields import FieldRenderer, SelectFieldRenderer, CheckBoxFieldRenderer
from pyramid.renderers import render from pyramid.renderers import render
__all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer'] __all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer', 'YesNoFieldRenderer']
def AutocompleteFieldRenderer(service_url, field_value=None, field_display=None, width='300px'): class AutocompleteFieldRenderer(FieldRenderer):
""" """
Returns a custom renderer class for an autocomplete field. Custom renderer for an autocomplete field.
""" """
class AutocompleteFieldRenderer(FieldRenderer): service_route = None
width = '300px'
@property @property
def focus_name(self): def focus_name(self):
return self.name + '-textbox' return self.name + '-textbox'
@property @property
def needs_focus(self): def needs_focus(self):
return not bool(self.value or field_value) return not bool(self.value or self.field_value)
def render(self, **kwargs): @property
kwargs.setdefault('field_name', self.name) def field_display(self):
kwargs.setdefault('field_value', self.value or field_value) return self.raw_value
kwargs.setdefault('field_display', self.raw_value or field_display)
kwargs.setdefault('service_url', service_url)
kwargs.setdefault('width', width)
return render('/forms/field_autocomplete.mako', kwargs)
return AutocompleteFieldRenderer @property
def field_value(self):
return self.value
@property
def service_url(self):
return self.request.route_url(self.service_route)
def render(self, **kwargs):
kwargs.setdefault('field_name', self.name)
kwargs.setdefault('field_value', self.field_value)
kwargs.setdefault('field_display', self.field_display)
kwargs.setdefault('service_url', self.service_url)
kwargs.setdefault('width', self.width)
return render('/forms/field_autocomplete.mako', kwargs)
def render_readonly(self, **kwargs):
value = self.field_display
if value is None:
return u''
return unicode(value)
def EnumFieldRenderer(enum): def EnumFieldRenderer(enum):
@ -79,3 +96,12 @@ def EnumFieldRenderer(enum):
return SelectFieldRenderer.render(self, opts, **kwargs) return SelectFieldRenderer.render(self, opts, **kwargs)
return Renderer return Renderer
class YesNoFieldRenderer(CheckBoxFieldRenderer):
def render_readonly(self, **kwargs):
value = self.raw_value
if value is None:
return u''
return u'Yes' if value else u'No'

View file

@ -0,0 +1,88 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
People Field Renderers
"""
from formalchemy.fields import TextFieldRenderer
from .common import AutocompleteFieldRenderer
from webhelpers.html import tags
__all__ = ['PersonFieldRenderer', 'PersonFieldLinkRenderer',
'CustomerFieldRenderer', 'CustomerFieldLinkRenderer',
'UserFieldRenderer']
class PersonFieldRenderer(AutocompleteFieldRenderer):
"""
Renderer for :class:`rattail.db.model.Person` instance fields.
"""
service_route = 'people.autocomplete'
class PersonFieldLinkRenderer(PersonFieldRenderer):
"""
Renderer for :class:`rattail.db.model.Person` instance fields (with hyperlink).
"""
def render_readonly(self, **kwargs):
person = self.raw_value
if person:
return tags.link_to(
unicode(person),
self.request.route_url('person.read', uuid=person.uuid))
return u''
class CustomerFieldRenderer(AutocompleteFieldRenderer):
"""
Renderer for :class:`rattail.db.model.Customer` instance fields.
"""
service_route = 'customers.autocomplete'
class CustomerFieldLinkRenderer(CustomerFieldRenderer):
"""
Renderer for :class:`rattail.db.model.Customer` instance fields (with hyperlink).
"""
def render_readonly(self, **kwargs):
customer = self.raw_value
if customer:
return tags.link_to(
unicode(customer),
self.request.route_url('customer.read', uuid=customer.uuid))
return u''
class UserFieldRenderer(TextFieldRenderer):
"""
Renderer for :class:`rattail.db.model.User` instance fields.
"""
pass

View file

@ -28,9 +28,29 @@ Product Field Renderers
from formalchemy import TextFieldRenderer from formalchemy import TextFieldRenderer
from rattail.gpc import GPC from rattail.gpc import GPC
from .common import AutocompleteFieldRenderer
from webhelpers.html import literal
from edbob.pyramid.forms import pretty_datetime
__all__ = ['GPCFieldRenderer', 'ProductFieldRenderer'] __all__ = ['ProductFieldRenderer', 'GPCFieldRenderer',
'BrandFieldRenderer', 'VendorFieldRenderer',
'PriceFieldRenderer', 'PriceWithExpirationFieldRenderer']
class ProductFieldRenderer(AutocompleteFieldRenderer):
"""
Renderer for :class:`rattail.db.model.Product` instance fields.
"""
service_route = 'products.autocomplete'
@property
def field_display(self):
product = self.raw_value
if product:
return product.full_description
return ''
class GPCFieldRenderer(TextFieldRenderer): class GPCFieldRenderer(TextFieldRenderer):
@ -44,13 +64,55 @@ class GPCFieldRenderer(TextFieldRenderer):
return len(str(GPC(0))) return len(str(GPC(0)))
class ProductFieldRenderer(TextFieldRenderer): class BrandFieldRenderer(AutocompleteFieldRenderer):
""" """
Renderer for fields which represent :class:`rattail.db.Product` instances. Renderer for :class:`rattail.db.model.Brand` instance fields.
"""
service_route = 'brands.autocomplete'
class VendorFieldRenderer(AutocompleteFieldRenderer):
"""
Renderer for :class:`rattail.db.model.Vendor` instance fields.
"""
service_route = 'vendors.autocomplete'
class PriceFieldRenderer(TextFieldRenderer):
"""
Renderer for fields which reference a :class:`ProductPrice` instance.
""" """
def render_readonly(self, **kwargs): def render_readonly(self, **kwargs):
product = self.raw_value price = self.field.raw_value
if product is None: if price:
return '' if price.price is not None and price.pack_price is not None:
return product.full_description if price.multiple > 1:
return literal('$ %0.2f / %u&nbsp; ($ %0.2f / %u)' % (
price.price, price.multiple,
price.pack_price, price.pack_multiple))
return literal('$ %0.2f&nbsp; ($ %0.2f / %u)' % (
price.price, price.pack_price, price.pack_multiple))
if price.price is not None:
if price.multiple > 1:
return '$ %0.2f / %u' % (price.price, price.multiple)
return '$ %0.2f' % price.price
if price.pack_price is not None:
return '$ %0.2f / %u' % (price.pack_price, price.pack_multiple)
return ''
class PriceWithExpirationFieldRenderer(PriceFieldRenderer):
"""
Price field renderer which also displays the expiration date, if present.
"""
def render_readonly(self, **kwargs):
result = super(PriceWithExpirationFieldRenderer, self).render_readonly(**kwargs)
if result:
price = self.field.raw_value
if price.ends:
result += '&nbsp; (%s)' % pretty_datetime(price.ends, from_='utc')
return result

View file

@ -1,44 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2012 Lance Edgar
#
# This file is part of Rattail.
#
# Rattail is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option)
# any later version.
#
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
# more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
User Field Renderers
"""
from formalchemy.fields import TextFieldRenderer
__all__ = ['UserFieldRenderer']
class UserFieldRenderer(TextFieldRenderer):
"""
Renderer for fields which represent ``User`` instances.
"""
def render_readonly(self, **kwargs):
user = self.raw_value
if user is None:
return u''
return unicode(user.display_name)

View file

@ -50,7 +50,7 @@ from rattail.gpc import GPC
from rattail.db.api import get_product_by_upc from rattail.db.api import get_product_by_upc
from ..db import Session from ..db import Session
from ..forms import AutocompleteFieldRenderer, GPCFieldRenderer, PriceFieldRenderer from ..forms import GPCFieldRenderer, BrandFieldRenderer, PriceFieldRenderer
from . import CrudView from . import CrudView
@ -225,14 +225,13 @@ class ProductCrud(CrudView):
def fieldset(self, model): def fieldset(self, model):
fs = self.make_fieldset(model) fs = self.make_fieldset(model)
fs.upc.set(renderer=GPCFieldRenderer) fs.upc.set(renderer=GPCFieldRenderer)
fs.brand.set(renderer=AutocompleteFieldRenderer( fs.brand.set(options=[])
self.request.route_url('brands.autocomplete')))
fs.regular_price.set(renderer=PriceFieldRenderer) fs.regular_price.set(renderer=PriceFieldRenderer)
fs.current_price.set(renderer=PriceFieldRenderer) fs.current_price.set(renderer=PriceFieldRenderer)
fs.configure( fs.configure(
include=[ include=[
fs.upc.label("UPC"), fs.upc.label("UPC"),
fs.brand, fs.brand.with_renderer(BrandFieldRenderer),
fs.description, fs.description,
fs.size, fs.size,
fs.department, fs.department,

View file

@ -32,7 +32,7 @@ from formalchemy.fields import SelectFieldRenderer
from edbob.pyramid.views import users from edbob.pyramid.views import users
from . import SearchableAlchemyGridView, CrudView from . import SearchableAlchemyGridView, CrudView
from ..forms import PersonFieldRenderer from ..forms import PersonFieldLinkRenderer
from ..db import Session from ..db import Session
from rattail.db.model import User, Person, Role from rattail.db.model import User, Person, Role
from rattail.db.auth import guest_role from rattail.db.auth import guest_role
@ -143,8 +143,6 @@ class UserCrud(CrudView):
# Must set Person options to empty set to avoid unwanted magic. # Must set Person options to empty set to avoid unwanted magic.
fs.person.set(options=[]) fs.person.set(options=[])
fs.person.set(renderer=PersonFieldRenderer(
self.request.route_url('people.autocomplete')))
fs.append(users.PasswordField('password')) fs.append(users.PasswordField('password'))
fs.append(Field('confirm_password', fs.append(Field('confirm_password',
@ -155,7 +153,7 @@ class UserCrud(CrudView):
fs.configure( fs.configure(
include=[ include=[
fs.username, fs.username,
fs.person, fs.person.with_renderer(PersonFieldLinkRenderer),
fs.password.label("Set Password"), fs.password.label("Set Password"),
fs.confirm_password, fs.confirm_password,
fs.roles, fs.roles,

View file

@ -81,8 +81,6 @@ class VendorCrud(CrudView):
def fieldset(self, model): def fieldset(self, model):
fs = self.make_fieldset(model) fs = self.make_fieldset(model)
fs.append(AssociationProxyField('contact')) fs.append(AssociationProxyField('contact'))
fs.contact.set(renderer=PersonFieldRenderer(
self.request.route_url('people.autocomplete')))
fs.configure( fs.configure(
include=[ include=[
fs.id.label("ID"), fs.id.label("ID"),
@ -90,7 +88,7 @@ class VendorCrud(CrudView):
fs.special_discount, fs.special_discount,
fs.phone.label("Phone Number").readonly(), fs.phone.label("Phone Number").readonly(),
fs.email.label("Email Address").readonly(), fs.email.label("Email Address").readonly(),
fs.contact.readonly(), fs.contact.with_renderer(PersonFieldRenderer).readonly(),
]) ])
return fs return fs