Extensive commit; see notes.

* Replaced `forms` module with subpackage; added some initial goodies (many of
  which are currently just imports from `edbob`).

* Added/edited various CRUD templates for consistency.

* Renamed `customer_groups` module and template folder to `customergroups`.

* Modified several view modules so their Pyramid configuration is more
  "extensible."  This just means routes and views are defined as two separate
  steps, so that derived applications may inherit the route definitions if they
  so choose.

* Added Employee CRUD views; added Email Address field to index view.

* Updated `people` view module so it no longer derives from that of `edbob`.

* Added support for, and some implementations of, extra key lookup abilities to
  CRUD views.  This allows URLs to use a "natural" key (e.g. Customer ID
  instead of UUID), for cases where that is more helpful.

* Product CRUD now uses autocomplete for Brand field.  Also, price fields no
  longer appear within an editable fieldset.

* Within Store index view, default sort is now ID instead of Name.

* Added Contact and Phone Number fields to Vendor CRUD views; added Contact and
  Email Address fields to index view.
This commit is contained in:
Lance Edgar 2013-05-21 21:51:41 -07:00
parent 931700131f
commit c422b900c6
22 changed files with 471 additions and 105 deletions

View file

@ -0,0 +1,30 @@
#!/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/>.
#
################################################################################
"""
``rattail.pyramid.forms`` -- Forms
"""
from rattail.pyramid.forms.fields import *
from rattail.pyramid.forms.renderers import *

View file

@ -0,0 +1,54 @@
#!/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/>.
#
################################################################################
"""
``rattail.pyramid.forms.fields`` -- FormAlchemy Fields
"""
from formalchemy import Field
__all__ = ['AssociationProxyField']
def AssociationProxyField(name, **kwargs):
"""
Returns a ``Field`` class which is aware of SQLAlchemy association proxies.
"""
class ProxyField(Field):
def sync(self):
if not self.is_readonly():
setattr(self.parent.model, self.name,
self.renderer.deserialize())
def value(model):
try:
return getattr(model, name)
except AttributeError:
return None
kwargs.setdefault('value', value)
return ProxyField(name, **kwargs)

View file

@ -23,19 +23,27 @@
################################################################################
"""
``rattail.pyramid.forms`` -- Rattail Forms
``rattail.pyramid.forms.renderers`` -- FormAlchemy Field Renderers
"""
from webhelpers.html import literal
from webhelpers.html import tags
import formalchemy
from edbob.pyramid.forms import pretty_datetime
from edbob.pyramid.forms.formalchemy.renderers import (
AutocompleteFieldRenderer, EnumFieldRenderer, YesNoFieldRenderer)
import rattail
from rattail.gpc import GPC
__all__ = ['AutocompleteFieldRenderer', 'EnumFieldRenderer', 'YesNoFieldRenderer',
'GPCFieldRenderer', 'PersonFieldRenderer', 'PriceFieldRenderer',
'PriceWithExpirationFieldRenderer']
class GPCFieldRenderer(formalchemy.TextFieldRenderer):
"""
Renderer for :class:`rattail.gpc.GPC` fields.
@ -47,6 +55,23 @@ class GPCFieldRenderer(formalchemy.TextFieldRenderer):
return len(str(GPC(0)))
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.

View file

@ -1,7 +0,0 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Customer Groups", url('customer_groups'))}</li>
</%def>
${parent.body()}

View file

@ -1,5 +0,0 @@
<%inherit file="/grid.mako" />
<%def name="title()">Customer Groups</%def>
${parent.body()}

View file

@ -0,0 +1,12 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Customer Groups", url('customer_groups'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Customer Group", url('customer_group.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Customer Group", url('customer_group.read', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,11 @@
<%inherit file="/grid.mako" />
<%def name="title()">Customer Groups</%def>
<%def name="context_menu_items()">
% if request.has_perm('customer_groups.create'):
<li>${h.link_to("Create a new Customer Group", url('customer_group.create'))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,7 +1,12 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<p>${h.link_to("Back to Customers", url('customers'))}</p>
<li>${h.link_to("Back to Customers", url('customers'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Customer", url('customer.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Customer", url('customer.read', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,12 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Employees", url('employees'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Employee", url('employee.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Employee", url('employee.read', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,12 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to People", url('people'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Person", url('person.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Person", url('person.read', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -2,6 +2,11 @@
<%def name="context_menu_items()">
<li>${h.link_to("Back to Stores", url('stores'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this Store", url('store.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this Store", url('store.read', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,12 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Users", url('users'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this User", url('user.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this User", url('user.read', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -33,7 +33,7 @@ from rattail.pyramid.views.autocomplete import *
def includeme(config):
config.include('rattail.pyramid.views.batches')
# config.include('rattail.pyramid.views.categories')
config.include('rattail.pyramid.views.customer_groups')
config.include('rattail.pyramid.views.customergroups')
config.include('rattail.pyramid.views.customers')
config.include('rattail.pyramid.views.departments')
config.include('rattail.pyramid.views.employees')

View file

@ -88,39 +88,38 @@ class BrandsAutocomplete(AutocompleteView):
fieldname = 'name'
def includeme(config):
def add_routes(config):
config.add_route('brands', '/brands')
config.add_route('brands.autocomplete', '/brands/autocomplete')
config.add_route('brand.create', '/brands/new')
config.add_route('brand.read', '/brands/{uuid}')
config.add_route('brand.update', '/brands/{uuid}/edit')
config.add_route('brand.delete', '/brands/{uuid}/delete')
def includeme(config):
add_routes(config)
config.add_view(BrandsGrid,
route_name='brands',
renderer='/brands/index.mako',
permission='brands.list')
config.add_route('brands.autocomplete', '/brands/autocomplete')
config.add_view(BrandsAutocomplete,
route_name='brands.autocomplete',
renderer='json',
permission='brands.list')
config.add_route('brand.create', '/brands/new')
config.add_view(BrandCrud, attr='create',
route_name='brand.create',
renderer='/brands/crud.mako',
permission='brands.create')
config.add_route('brand.read', '/brands/{uuid}')
config.add_view(BrandCrud, attr='read',
route_name='brand.read',
renderer='/brands/crud.mako',
permission='brands.read')
config.add_route('brand.update', '/brands/{uuid}/edit')
config.add_view(BrandCrud, attr='update',
route_name='brand.update',
renderer='/brands/crud.mako',
permission='brands.update')
config.add_route('brand.delete', '/brands/{uuid}/delete')
config.add_view(BrandCrud, attr='delete',
route_name='brand.delete',
permission='brands.delete')

View file

@ -56,6 +56,15 @@ class CustomerGroupsGrid(SearchableAlchemyGridView):
g.name,
],
readonly=True)
if self.request.has_perm('customer_groups.read'):
g.clickable = True
g.click_route_name = 'customer_group.read'
if self.request.has_perm('customer_groups.update'):
g.editable = True
g.edit_route_name = 'customer_group.update'
if self.request.has_perm('customer_groups.delete'):
g.deletable = True
g.delete_route_name = 'customer_group.delete'
return g
@ -75,14 +84,28 @@ class CustomerGroupCrud(CrudView):
return fs
def add_routes(config):
config.add_route('customer_groups', '/customer-groups')
config.add_route('customer_group.create', '/customer-groups/new')
config.add_route('customer_group.read', '/customer-groups/{uuid}')
config.add_route('customer_group.update', '/customer-groups/{uuid}/edit')
config.add_route('customer_group.delete', '/customer-groups/{uuid}/delete')
def includeme(config):
add_routes(config)
config.add_route('customer_groups', '/customer-groups')
config.add_view(CustomerGroupsGrid, route_name='customer_groups',
renderer='/customer_groups/index.mako',
renderer='/customergroups/index.mako',
permission='customer_groups.list')
config.add_route('customer_group.read', '/customer-groups/{uuid}')
config.add_view(CustomerGroupCrud, attr='create', route_name='customer_group.create',
renderer='/customergroups/crud.mako',
permission='customer_groups.create')
config.add_view(CustomerGroupCrud, attr='read', route_name='customer_group.read',
renderer='/customer_groups/crud.mako',
renderer='/customergroups/crud.mako',
permission='customer_groups.read')
config.add_view(CustomerGroupCrud, attr='update', route_name='customer_group.update',
renderer='/customergroups/crud.mako',
permission='customer_groups.update')
config.add_view(CustomerGroupCrud, attr='delete', route_name='customer_group.delete',
permission='customer_groups.delete')

View file

@ -90,7 +90,17 @@ class CustomersGrid(SearchableAlchemyGridView):
g.email.label("Email Address"),
],
readonly=True)
g.click_route_name = 'customer.read'
if self.request.has_perm('customers.read'):
g.clickable = True
g.click_route_name = 'customer.read'
if self.request.has_perm('customers.update'):
g.editable = True
g.edit_route_name = 'customer.update'
if self.request.has_perm('customers.delete'):
g.deletable = True
g.delete_route_name = 'customer.delete'
return g
@ -121,16 +131,19 @@ class CustomerCrud(CrudView):
include=[
fs.id.label("ID"),
fs.name,
fs.phone.label("Phone Number"),
fs.email.label("Email Address"),
fs.phone.label("Phone Number").readonly(),
fs.email.label("Email Address").readonly(),
fs.email_preference,
])
return fs
def add_routes(config):
config.add_route('customers', '/customers')
config.add_route('customer.read', '/customers/{uuid}')
config.add_route('customers', '/customers')
config.add_route('customer.create', '/customers/new')
config.add_route('customer.read', '/customers/{uuid}')
config.add_route('customer.update', '/customers/{uuid}/edit')
config.add_route('customer.delete', '/customers/{uuid}/delete')
def includeme(config):
@ -139,6 +152,14 @@ def includeme(config):
config.add_view(CustomersGrid, route_name='customers',
renderer='/customers/index.mako',
permission='customers.list')
config.add_view(CustomerCrud, attr='create', route_name='customer.create',
renderer='/customers/crud.mako',
permission='customers.create')
config.add_view(CustomerCrud, attr='read', route_name='customer.read',
renderer='/customers/read.mako',
permission='customers.read')
config.add_view(CustomerCrud, attr='update', route_name='customer.update',
renderer='/customers/crud.mako',
permission='customers.update')
config.add_view(CustomerCrud, attr='delete', route_name='customer.delete',
permission='customers.delete')

View file

@ -28,33 +28,40 @@
from sqlalchemy import and_
import edbob
from edbob.pyramid.forms import AssociationProxyField, EnumFieldRenderer
from edbob.pyramid.views import SearchableAlchemyGridView
import rattail
from rattail.pyramid.views import CrudView
from rattail.pyramid.grids import EnumSearchFilter
from rattail.pyramid.forms import AssociationProxyField, EnumFieldRenderer
from rattail.db.model import (
Employee, EmployeePhoneNumber, EmployeeEmailAddress, Person)
from rattail.enum import EMPLOYEE_STATUS, EMPLOYEE_STATUS_CURRENT
class EmployeesGrid(SearchableAlchemyGridView):
mapped_class = rattail.Employee
mapped_class = Employee
config_prefix = 'employees'
sort = 'first_name'
def join_map(self):
return {
'phone':
lambda q: q.outerjoin(rattail.EmployeePhoneNumber, and_(
rattail.EmployeePhoneNumber.parent_uuid == rattail.Employee.uuid,
rattail.EmployeePhoneNumber.preference == 1)),
lambda q: q.outerjoin(EmployeePhoneNumber, and_(
EmployeePhoneNumber.parent_uuid == Employee.uuid,
EmployeePhoneNumber.preference == 1)),
'email':
lambda q: q.outerjoin(EmployeeEmailAddress, and_(
EmployeeEmailAddress.parent_uuid == Employee.uuid,
EmployeeEmailAddress.preference == 1)),
}
def filter_map(self):
kwargs = dict(
first_name=self.filter_ilike(edbob.Person.first_name),
last_name=self.filter_ilike(edbob.Person.last_name),
phone=self.filter_ilike(rattail.EmployeePhoneNumber.number))
first_name=self.filter_ilike(Person.first_name),
last_name=self.filter_ilike(Person.last_name),
phone=self.filter_ilike(EmployeePhoneNumber.number),
email=self.filter_ilike(EmployeeEmailAddress.address))
if self.request.has_perm('employees.edit'):
kwargs.update(dict(
exact=['id', 'status']))
@ -66,27 +73,29 @@ class EmployeesGrid(SearchableAlchemyGridView):
filter_type_first_name='lk',
include_filter_last_name=True,
filter_type_last_name='lk',
filter_label_phone="Phone Number")
filter_label_phone="Phone Number",
filter_label_email="Email Address")
if self.request.has_perm('employees.edit'):
kwargs.update(dict(
filter_label_id="ID",
include_filter_status=True,
filter_type_status='is',
filter_factory_status=EnumSearchFilter(rattail.EMPLOYEE_STATUS),
status=rattail.EMPLOYEE_STATUS_CURRENT))
filter_factory_status=EnumSearchFilter(EMPLOYEE_STATUS),
status=EMPLOYEE_STATUS_CURRENT))
return self.make_filter_config(**kwargs)
def sort_map(self):
return self.make_sort_map(
first_name=self.sorter(edbob.Person.first_name),
last_name=self.sorter(edbob.Person.last_name),
phone=self.sorter(rattail.EmployeePhoneNumber.number))
first_name=self.sorter(Person.first_name),
last_name=self.sorter(Person.last_name),
phone=self.sorter(EmployeePhoneNumber.number),
email=self.sorter(EmployeeEmailAddress.address))
def query(self):
q = self.make_query()
q = q.join(edbob.Person)
q = q.join(Person)
if not self.request.has_perm('employees.edit'):
q = q.filter(rattail.Employee.status == rattail.EMPLOYEE_STATUS_CURRENT)
q = q.filter(Employee.status == EMPLOYEE_STATUS_CURRENT)
return q
def grid(self):
@ -99,18 +108,73 @@ class EmployeesGrid(SearchableAlchemyGridView):
g.first_name,
g.last_name,
g.phone.label("Phone Number"),
g.status.with_renderer(EnumFieldRenderer(rattail.EMPLOYEE_STATUS)),
g.email.label("Email Address"),
g.status.with_renderer(EnumFieldRenderer(EMPLOYEE_STATUS)),
],
readonly=True)
# Hide ID and Status fields for unprivileged users.
if not self.request.has_perm('employees.edit'):
del g.id
del g.status
if self.request.has_perm('employees.read'):
g.clickable = True
g.click_route_name = 'employee.read'
if self.request.has_perm('employees.update'):
g.editable = True
g.edit_route_name = 'employee.update'
if self.request.has_perm('employees.delete'):
g.deletable = True
g.delete_route_name = 'employee.delete'
return g
def includeme(config):
class EmployeeCrud(CrudView):
mapped_class = Employee
home_route = 'employees'
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.append(AssociationProxyField('first_name'))
fs.append(AssociationProxyField('last_name'))
fs.append(AssociationProxyField('display_name'))
fs.configure(
include=[
fs.id.label("ID"),
fs.first_name,
fs.last_name,
fs.phone.label("Phone Number").readonly(),
fs.email.label("Email Address").readonly(),
fs.status.with_renderer(EnumFieldRenderer(EMPLOYEE_STATUS)),
])
return fs
def add_routes(config):
config.add_route('employees', '/employees')
config.add_route('employee.create', '/employees/new')
config.add_route('employee.read', '/employees/{uuid}')
config.add_route('employee.update', '/employees/{uuid}/edit')
config.add_route('employee.delete', '/employees/{uuid}/delete')
def includeme(config):
add_routes(config)
config.add_route('employees', '/employees')
config.add_view(EmployeesGrid, route_name='employees',
renderer='/employees/index.mako',
permission='employees.list')
config.add_view(EmployeeCrud, attr='create', route_name='employee.create',
renderer='/employees/crud.mako',
permission='employees.create')
config.add_view(EmployeeCrud, attr='read', route_name='employee.read',
renderer='/employees/crud.mako',
permission='employees.read')
config.add_view(EmployeeCrud, attr='update', route_name='employee.update',
renderer='/employees/crud.mako',
permission='employees.update')
config.add_view(EmployeeCrud, attr='delete', route_name='employee.delete',
permission='employees.delete')

View file

@ -26,31 +26,132 @@
``rattail.pyramid.views.people`` -- Person Views
"""
import edbob
from edbob.pyramid.views import people
from sqlalchemy import and_
from rattail.pyramid.views import AutocompleteView
from edbob.pyramid.views import SearchableAlchemyGridView
from rattail.pyramid.views import CrudView, AutocompleteView
from rattail.pyramid import Session
from rattail.db.model import (Person, PersonEmailAddress, PersonPhoneNumber,
VendorContact)
class PeopleGrid(SearchableAlchemyGridView):
mapped_class = Person
config_prefix = 'people'
sort = 'first_name'
def join_map(self):
return {
'email':
lambda q: q.outerjoin(PersonEmailAddress, and_(
PersonEmailAddress.parent_uuid == Person.uuid,
PersonEmailAddress.preference == 1)),
'phone':
lambda q: q.outerjoin(PersonPhoneNumber, and_(
PersonPhoneNumber.parent_uuid == Person.uuid,
PersonPhoneNumber.preference == 1)),
}
def filter_map(self):
return self.make_filter_map(
ilike=['first_name', 'last_name', 'display_name'],
email=self.filter_ilike(PersonEmailAddress.address),
phone=self.filter_ilike(PersonPhoneNumber.number))
def filter_config(self):
return self.make_filter_config(
include_filter_first_name=True,
filter_type_first_name='lk',
include_filter_last_name=True,
filter_type_last_name='lk',
filter_label_phone="Phone Number",
filter_label_email="Email Address")
def sort_map(self):
return self.make_sort_map(
'first_name', 'last_name', 'display_name',
email=self.sorter(PersonEmailAddress.address),
phone=self.sorter(PersonPhoneNumber.number))
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.first_name,
g.last_name,
g.display_name,
g.phone.label("Phone Number"),
g.email.label("Email Address"),
],
readonly=True)
if self.request.has_perm('people.read'):
g.clickable = True
g.click_route_name = 'person.read'
if self.request.has_perm('people.update'):
g.editable = True
g.edit_route_name = 'person.update'
# if self.request.has_perm('products.delete'):
# g.deletable = True
# g.delete_route_name = 'product.delete'
return g
class PersonCrud(CrudView):
mapped_class = Person
home_route = 'people'
def get_model(self, key):
model = super(PersonCrud, self).get_model(key)
if model:
return model
model = Session.query(VendorContact).get(key)
if model:
return model.person
return None
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.configure(
include=[
fs.first_name,
fs.last_name,
fs.display_name,
fs.phone.label("Phone Number").readonly(),
fs.email.label("Email Address").readonly(),
])
return fs
class PeopleAutocomplete(AutocompleteView):
mapped_class = edbob.Person
mapped_class = Person
fieldname = 'display_name'
def includeme(config):
def add_routes(config):
config.add_route('people', '/people')
config.add_route('people.autocomplete', '/people/autocomplete')
config.add_route('person.read', '/people/{uuid}')
config.add_route('person.update', '/people/{uuid}/edit')
config.add_route('people', '/people')
config.add_view(people.PeopleGrid, route_name='people',
def includeme(config):
add_routes(config)
config.add_view(PeopleGrid, route_name='people',
renderer='/people/index.mako',
permission='people.list')
config.add_route('people.autocomplete', '/people/autocomplete')
config.add_view(PersonCrud, attr='read', route_name='person.read',
renderer='/people/crud.mako',
permission='people.read')
config.add_view(PersonCrud, attr='update', route_name='person.update',
renderer='/people/crud.mako',
permission='people.update')
config.add_view(PeopleAutocomplete, route_name='people.autocomplete',
renderer='json',
permission='people.list')
config.add_route('person.read', '/people/{uuid}')
config.add_view(people.PersonCrud, attr='read', route_name='person.read',
renderer='/people/crud.mako',
permission='people.read')

View file

@ -47,7 +47,8 @@ from rattail.exceptions import LabelPrintingError
from rattail.db.model import ProductPrice
from rattail.pyramid import Session
from rattail.pyramid.forms import GPCFieldRenderer, PriceFieldRenderer
from rattail.pyramid.forms import (AutocompleteFieldRenderer,
GPCFieldRenderer, PriceFieldRenderer)
from rattail.pyramid.views import CrudView
@ -212,6 +213,8 @@ class ProductCrud(CrudView):
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.upc.set(renderer=GPCFieldRenderer)
fs.brand.set(renderer=AutocompleteFieldRenderer(
self.request.route_url('brands.autocomplete')))
fs.regular_price.set(renderer=PriceFieldRenderer)
fs.current_price.set(renderer=PriceFieldRenderer)
fs.configure(
@ -225,9 +228,9 @@ class ProductCrud(CrudView):
fs.regular_price,
fs.current_price,
])
# if not self.readonly:
# del fs.regular_price
# del fs.current_price
if not self.readonly:
del fs.regular_price
del fs.current_price
return fs

View file

@ -37,7 +37,7 @@ class StoresGrid(SearchableAlchemyGridView):
mapped_class = rattail.Store
config_prefix = 'stores'
sort = 'name'
sort = 'id'
def join_map(self):
return {

View file

@ -28,30 +28,11 @@
import formalchemy
from webhelpers.html import tags
import edbob
from edbob.pyramid.views import users
from edbob.pyramid.forms import AutocompleteFieldRenderer
from rattail.pyramid.views import CrudView
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
from rattail.pyramid.forms import PersonFieldRenderer
class UserCrud(CrudView):

View file

@ -26,15 +26,16 @@
``rattail.pyramid.views.vendors`` -- Vendor Views
"""
from edbob.pyramid.views import (SearchableAlchemyGridView, CrudView,
AutocompleteView)
from edbob.pyramid.views import SearchableAlchemyGridView
import rattail
from rattail.db.model import Vendor
from rattail.pyramid.views import CrudView, AutocompleteView
from rattail.pyramid.forms import AssociationProxyField, PersonFieldRenderer
class VendorsGrid(SearchableAlchemyGridView):
mapped_class = rattail.Vendor
mapped_class = Vendor
config_prefix = 'vendors'
sort = 'name'
@ -52,11 +53,13 @@ class VendorsGrid(SearchableAlchemyGridView):
def grid(self):
g = self.make_grid()
g.append(AssociationProxyField('contact'))
g.configure(
include=[
g.id.label("ID"),
g.name,
g.phone,
g.phone.label("Phone Number"),
g.email.label("Email Address"),
g.contact,
],
readonly=True)
@ -74,24 +77,29 @@ class VendorsGrid(SearchableAlchemyGridView):
class VendorCrud(CrudView):
mapped_class = rattail.Vendor
mapped_class = Vendor
home_route = 'vendors'
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.append(AssociationProxyField('contact'))
fs.contact.set(renderer=PersonFieldRenderer(
self.request.route_url('people.autocomplete')))
fs.configure(
include=[
fs.id.label("ID"),
fs.name,
fs.special_discount,
fs.email.label("Email Address"),
fs.phone.label("Phone Number").readonly(),
fs.email.label("Email Address").readonly(),
fs.contact.readonly(),
])
return fs
class VendorsAutocomplete(AutocompleteView):
mapped_class = rattail.Vendor
mapped_class = Vendor
fieldname = 'name'