diff --git a/rattail/pyramid/forms/__init__.py b/rattail/pyramid/forms/__init__.py
new file mode 100644
index 00000000..6f77ba06
--- /dev/null
+++ b/rattail/pyramid/forms/__init__.py
@@ -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 .
+#
+################################################################################
+
+"""
+``rattail.pyramid.forms`` -- Forms
+"""
+
+from rattail.pyramid.forms.fields import *
+from rattail.pyramid.forms.renderers import *
diff --git a/rattail/pyramid/forms/fields.py b/rattail/pyramid/forms/fields.py
new file mode 100644
index 00000000..0ea31b1c
--- /dev/null
+++ b/rattail/pyramid/forms/fields.py
@@ -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 .
+#
+################################################################################
+
+"""
+``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)
diff --git a/rattail/pyramid/forms.py b/rattail/pyramid/forms/renderers.py
similarity index 77%
rename from rattail/pyramid/forms.py
rename to rattail/pyramid/forms/renderers.py
index 0005f0ce..c444e0a9 100644
--- a/rattail/pyramid/forms.py
+++ b/rattail/pyramid/forms/renderers.py
@@ -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.
diff --git a/rattail/pyramid/templates/customer_groups/crud.mako b/rattail/pyramid/templates/customer_groups/crud.mako
deleted file mode 100644
index 0c0ba7cb..00000000
--- a/rattail/pyramid/templates/customer_groups/crud.mako
+++ /dev/null
@@ -1,7 +0,0 @@
-<%inherit file="/crud.mako" />
-
-<%def name="context_menu_items()">
-
${h.link_to("Back to Customer Groups", url('customer_groups'))}
-%def>
-
-${parent.body()}
diff --git a/rattail/pyramid/templates/customer_groups/index.mako b/rattail/pyramid/templates/customer_groups/index.mako
deleted file mode 100644
index c7849cbb..00000000
--- a/rattail/pyramid/templates/customer_groups/index.mako
+++ /dev/null
@@ -1,5 +0,0 @@
-<%inherit file="/grid.mako" />
-
-<%def name="title()">Customer Groups%def>
-
-${parent.body()}
diff --git a/rattail/pyramid/templates/customergroups/crud.mako b/rattail/pyramid/templates/customergroups/crud.mako
new file mode 100644
index 00000000..f616ce8b
--- /dev/null
+++ b/rattail/pyramid/templates/customergroups/crud.mako
@@ -0,0 +1,12 @@
+<%inherit file="/crud.mako" />
+
+<%def name="context_menu_items()">
+ ${h.link_to("Back to Customer Groups", url('customer_groups'))}
+ % if form.readonly:
+ ${h.link_to("Edit this Customer Group", url('customer_group.update', uuid=form.fieldset.model.uuid))}
+ % elif form.updating:
+ ${h.link_to("View this Customer Group", url('customer_group.read', uuid=form.fieldset.model.uuid))}
+ % endif
+%def>
+
+${parent.body()}
diff --git a/rattail/pyramid/templates/customergroups/index.mako b/rattail/pyramid/templates/customergroups/index.mako
new file mode 100644
index 00000000..dc777762
--- /dev/null
+++ b/rattail/pyramid/templates/customergroups/index.mako
@@ -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'):
+ ${h.link_to("Create a new Customer Group", url('customer_group.create'))}
+ % endif
+%def>
+
+${parent.body()}
diff --git a/rattail/pyramid/templates/customers/crud.mako b/rattail/pyramid/templates/customers/crud.mako
index c8393e94..98d13572 100644
--- a/rattail/pyramid/templates/customers/crud.mako
+++ b/rattail/pyramid/templates/customers/crud.mako
@@ -1,7 +1,12 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
- ${h.link_to("Back to Customers", url('customers'))}
+ ${h.link_to("Back to Customers", url('customers'))}
+ % if form.readonly:
+ ${h.link_to("Edit this Customer", url('customer.update', uuid=form.fieldset.model.uuid))}
+ % elif form.updating:
+ ${h.link_to("View this Customer", url('customer.read', uuid=form.fieldset.model.uuid))}
+ % endif
%def>
${parent.body()}
diff --git a/rattail/pyramid/templates/employees/crud.mako b/rattail/pyramid/templates/employees/crud.mako
new file mode 100644
index 00000000..9a6bbe0a
--- /dev/null
+++ b/rattail/pyramid/templates/employees/crud.mako
@@ -0,0 +1,12 @@
+<%inherit file="/crud.mako" />
+
+<%def name="context_menu_items()">
+ ${h.link_to("Back to Employees", url('employees'))}
+ % if form.readonly:
+ ${h.link_to("Edit this Employee", url('employee.update', uuid=form.fieldset.model.uuid))}
+ % elif form.updating:
+ ${h.link_to("View this Employee", url('employee.read', uuid=form.fieldset.model.uuid))}
+ % endif
+%def>
+
+${parent.body()}
diff --git a/rattail/pyramid/templates/people/crud.mako b/rattail/pyramid/templates/people/crud.mako
new file mode 100644
index 00000000..9f7a5b0d
--- /dev/null
+++ b/rattail/pyramid/templates/people/crud.mako
@@ -0,0 +1,12 @@
+<%inherit file="/crud.mako" />
+
+<%def name="context_menu_items()">
+ ${h.link_to("Back to People", url('people'))}
+ % if form.readonly:
+ ${h.link_to("Edit this Person", url('person.update', uuid=form.fieldset.model.uuid))}
+ % elif form.updating:
+ ${h.link_to("View this Person", url('person.read', uuid=form.fieldset.model.uuid))}
+ % endif
+%def>
+
+${parent.body()}
diff --git a/rattail/pyramid/templates/stores/crud.mako b/rattail/pyramid/templates/stores/crud.mako
index 7c690077..9f885fbc 100644
--- a/rattail/pyramid/templates/stores/crud.mako
+++ b/rattail/pyramid/templates/stores/crud.mako
@@ -2,6 +2,11 @@
<%def name="context_menu_items()">
${h.link_to("Back to Stores", url('stores'))}
+ % if form.readonly:
+ ${h.link_to("Edit this Store", url('store.update', uuid=form.fieldset.model.uuid))}
+ % elif form.updating:
+ ${h.link_to("View this Store", url('store.read', uuid=form.fieldset.model.uuid))}
+ % endif
%def>
${parent.body()}
diff --git a/rattail/pyramid/templates/users/crud.mako b/rattail/pyramid/templates/users/crud.mako
new file mode 100644
index 00000000..2e5a70ef
--- /dev/null
+++ b/rattail/pyramid/templates/users/crud.mako
@@ -0,0 +1,12 @@
+<%inherit file="/crud.mako" />
+
+<%def name="context_menu_items()">
+ ${h.link_to("Back to Users", url('users'))}
+ % if form.readonly:
+ ${h.link_to("Edit this User", url('user.update', uuid=form.fieldset.model.uuid))}
+ % elif form.updating:
+ ${h.link_to("View this User", url('user.read', uuid=form.fieldset.model.uuid))}
+ % endif
+%def>
+
+${parent.body()}
diff --git a/rattail/pyramid/views/__init__.py b/rattail/pyramid/views/__init__.py
index a8b3fc05..e5820a78 100644
--- a/rattail/pyramid/views/__init__.py
+++ b/rattail/pyramid/views/__init__.py
@@ -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')
diff --git a/rattail/pyramid/views/brands.py b/rattail/pyramid/views/brands.py
index 2a6ee618..5cc9009b 100644
--- a/rattail/pyramid/views/brands.py
+++ b/rattail/pyramid/views/brands.py
@@ -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')
diff --git a/rattail/pyramid/views/customer_groups.py b/rattail/pyramid/views/customergroups.py
similarity index 61%
rename from rattail/pyramid/views/customer_groups.py
rename to rattail/pyramid/views/customergroups.py
index 83056dcc..c3832410 100644
--- a/rattail/pyramid/views/customer_groups.py
+++ b/rattail/pyramid/views/customergroups.py
@@ -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')
diff --git a/rattail/pyramid/views/customers.py b/rattail/pyramid/views/customers.py
index 32591646..99206072 100644
--- a/rattail/pyramid/views/customers.py
+++ b/rattail/pyramid/views/customers.py
@@ -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')
diff --git a/rattail/pyramid/views/employees.py b/rattail/pyramid/views/employees.py
index 8fd7cbac..56fad799 100644
--- a/rattail/pyramid/views/employees.py
+++ b/rattail/pyramid/views/employees.py
@@ -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')
diff --git a/rattail/pyramid/views/people.py b/rattail/pyramid/views/people.py
index 0c0417f2..1d29e421 100644
--- a/rattail/pyramid/views/people.py
+++ b/rattail/pyramid/views/people.py
@@ -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')
diff --git a/rattail/pyramid/views/products.py b/rattail/pyramid/views/products.py
index d670b687..c9ce401b 100644
--- a/rattail/pyramid/views/products.py
+++ b/rattail/pyramid/views/products.py
@@ -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
diff --git a/rattail/pyramid/views/stores.py b/rattail/pyramid/views/stores.py
index 476acadd..197c055f 100644
--- a/rattail/pyramid/views/stores.py
+++ b/rattail/pyramid/views/stores.py
@@ -37,7 +37,7 @@ class StoresGrid(SearchableAlchemyGridView):
mapped_class = rattail.Store
config_prefix = 'stores'
- sort = 'name'
+ sort = 'id'
def join_map(self):
return {
diff --git a/rattail/pyramid/views/users.py b/rattail/pyramid/views/users.py
index e2b5ae66..169de42f 100644
--- a/rattail/pyramid/views/users.py
+++ b/rattail/pyramid/views/users.py
@@ -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):
diff --git a/rattail/pyramid/views/vendors.py b/rattail/pyramid/views/vendors.py
index 97097a9e..2b8d7c96 100644
--- a/rattail/pyramid/views/vendors.py
+++ b/rattail/pyramid/views/vendors.py
@@ -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'