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'))}
  • - - -${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 - -${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 + + +${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 name="context_menu_items()"> + % if request.has_perm('customer_groups.create'): +
  • ${h.link_to("Create a new Customer Group", url('customer_group.create'))}
  • + % endif + + +${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 ${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 + + +${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 + + +${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 ${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 + + +${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'