From dcbcf81c401647b947c2b278f0ffcbef16365bb1 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 16 Aug 2012 10:31:24 -0700 Subject: [PATCH] update grid, crud views per edbob changes --- .../index.mako | 2 +- .../pyramid/templates/customers/index.mako | 2 +- rattail/pyramid/templates/customers/read.mako | 10 ++-- .../pyramid/templates/departments/base.mako | 2 - .../pyramid/templates/departments/index.mako | 3 +- rattail/pyramid/templates/employees/base.mako | 2 - rattail/pyramid/templates/employees/crud.mako | 8 ---- rattail/pyramid/templates/employees/edit.mako | 2 - .../pyramid/templates/employees/index.mako | 9 +--- rattail/pyramid/templates/employees/new.mako | 2 - rattail/pyramid/templates/products/base.mako | 2 - rattail/pyramid/templates/products/crud.mako | 2 +- rattail/pyramid/templates/products/index.mako | 11 ++--- rattail/pyramid/templates/products/read.mako | 17 ++----- .../templates/subdepartments/index.mako | 2 +- rattail/pyramid/templates/vendors/base.mako | 2 - rattail/pyramid/templates/vendors/index.mako | 3 +- .../{customergroups.py => customer_groups.py} | 11 +++-- rattail/pyramid/views/customers.py | 31 ++++++------ rattail/pyramid/views/departments.py | 38 ++++----------- rattail/pyramid/views/employees.py | 39 +++++---------- rattail/pyramid/views/products.py | 48 ++++++++++--------- rattail/pyramid/views/subdepartments.py | 13 ++--- rattail/pyramid/views/vendors.py | 35 +++++--------- 24 files changed, 110 insertions(+), 186 deletions(-) rename rattail/pyramid/templates/{customergroups => customer_groups}/index.mako (66%) delete mode 100644 rattail/pyramid/templates/departments/base.mako delete mode 100644 rattail/pyramid/templates/employees/base.mako delete mode 100644 rattail/pyramid/templates/employees/crud.mako delete mode 100644 rattail/pyramid/templates/employees/edit.mako delete mode 100644 rattail/pyramid/templates/employees/new.mako delete mode 100644 rattail/pyramid/templates/products/base.mako delete mode 100644 rattail/pyramid/templates/vendors/base.mako rename rattail/pyramid/views/{customergroups.py => customer_groups.py} (86%) diff --git a/rattail/pyramid/templates/customergroups/index.mako b/rattail/pyramid/templates/customer_groups/index.mako similarity index 66% rename from rattail/pyramid/templates/customergroups/index.mako rename to rattail/pyramid/templates/customer_groups/index.mako index 7205a21e..c7849cbb 100644 --- a/rattail/pyramid/templates/customergroups/index.mako +++ b/rattail/pyramid/templates/customer_groups/index.mako @@ -1,4 +1,4 @@ -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Customer Groups diff --git a/rattail/pyramid/templates/customers/index.mako b/rattail/pyramid/templates/customers/index.mako index a73d411c..29394f2f 100644 --- a/rattail/pyramid/templates/customers/index.mako +++ b/rattail/pyramid/templates/customers/index.mako @@ -1,4 +1,4 @@ -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Customers diff --git a/rattail/pyramid/templates/customers/read.mako b/rattail/pyramid/templates/customers/read.mako index 261765a3..10bf43c9 100644 --- a/rattail/pyramid/templates/customers/read.mako +++ b/rattail/pyramid/templates/customers/read.mako @@ -2,8 +2,10 @@ ${parent.body()} +<% customer = form.fieldset.model %> +

People

-% if fieldset.model.people: +% if customer.people:

Customer account is associated with the following people:

@@ -12,7 +14,7 @@ ${parent.body()} - % for i, person in enumerate(fieldset.model.people, 1): + % for i, person in enumerate(customer.people, 1): @@ -26,7 +28,7 @@ ${parent.body()} % endif

Groups

-% if fieldset.model.groups: +% if customer.groups:

Customer account belongs to the following groups:

Last Name
${person.first_name or ''} ${person.last_name or ''}
@@ -35,7 +37,7 @@ ${parent.body()} - % for i, group in enumerate(fieldset.model.groups, 1): + % for i, group in enumerate(customer.groups, 1): diff --git a/rattail/pyramid/templates/departments/base.mako b/rattail/pyramid/templates/departments/base.mako deleted file mode 100644 index 27f7dd90..00000000 --- a/rattail/pyramid/templates/departments/base.mako +++ /dev/null @@ -1,2 +0,0 @@ -<%inherit file="/base.mako" /> -${parent.body()} diff --git a/rattail/pyramid/templates/departments/index.mako b/rattail/pyramid/templates/departments/index.mako index edcc3fce..c723848d 100644 --- a/rattail/pyramid/templates/departments/index.mako +++ b/rattail/pyramid/templates/departments/index.mako @@ -1,5 +1,4 @@ -<%inherit file="/departments/base.mako" /> -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Departments diff --git a/rattail/pyramid/templates/employees/base.mako b/rattail/pyramid/templates/employees/base.mako deleted file mode 100644 index 27f7dd90..00000000 --- a/rattail/pyramid/templates/employees/base.mako +++ /dev/null @@ -1,2 +0,0 @@ -<%inherit file="/base.mako" /> -${parent.body()} diff --git a/rattail/pyramid/templates/employees/crud.mako b/rattail/pyramid/templates/employees/crud.mako deleted file mode 100644 index 71b2e0b4..00000000 --- a/rattail/pyramid/templates/employees/crud.mako +++ /dev/null @@ -1,8 +0,0 @@ -<%inherit file="/employees/base.mako" /> -<%inherit file="/crud.mako" /> - -<%def name="menu()"> -

${h.link_to("Back to Employees", url('employees.list'))}

- - -${parent.body()} diff --git a/rattail/pyramid/templates/employees/edit.mako b/rattail/pyramid/templates/employees/edit.mako deleted file mode 100644 index 47d8b103..00000000 --- a/rattail/pyramid/templates/employees/edit.mako +++ /dev/null @@ -1,2 +0,0 @@ -<%inherit file="/employees/crud.mako" /> -${parent.body()} diff --git a/rattail/pyramid/templates/employees/index.mako b/rattail/pyramid/templates/employees/index.mako index 9595775f..b51b80b8 100644 --- a/rattail/pyramid/templates/employees/index.mako +++ b/rattail/pyramid/templates/employees/index.mako @@ -1,12 +1,5 @@ -<%inherit file="/employees/base.mako" /> -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Employees -<%def name="menu()"> - % if request.has_perm('employees.create'): -

${h.link_to("Create a new Employee", url('employees.new'))}

- % endif - - ${parent.body()} diff --git a/rattail/pyramid/templates/employees/new.mako b/rattail/pyramid/templates/employees/new.mako deleted file mode 100644 index 47d8b103..00000000 --- a/rattail/pyramid/templates/employees/new.mako +++ /dev/null @@ -1,2 +0,0 @@ -<%inherit file="/employees/crud.mako" /> -${parent.body()} diff --git a/rattail/pyramid/templates/products/base.mako b/rattail/pyramid/templates/products/base.mako deleted file mode 100644 index 27f7dd90..00000000 --- a/rattail/pyramid/templates/products/base.mako +++ /dev/null @@ -1,2 +0,0 @@ -<%inherit file="/base.mako" /> -${parent.body()} diff --git a/rattail/pyramid/templates/products/crud.mako b/rattail/pyramid/templates/products/crud.mako index 359d3e8f..27b773b8 100644 --- a/rattail/pyramid/templates/products/crud.mako +++ b/rattail/pyramid/templates/products/crud.mako @@ -1,7 +1,7 @@ <%inherit file="/crud.mako" /> <%def name="context_menu_items()"> -

${h.link_to("Back to Products", url('products'))}

+
  • ${h.link_to("Back to Products", url('products'))}
  • ${parent.body()} diff --git a/rattail/pyramid/templates/products/index.mako b/rattail/pyramid/templates/products/index.mako index 0c5e0a96..36992e85 100644 --- a/rattail/pyramid/templates/products/index.mako +++ b/rattail/pyramid/templates/products/index.mako @@ -1,5 +1,4 @@ -<%inherit file="/products/base.mako" /> -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Products @@ -7,17 +6,17 @@ ${parent.head_tags()} - - ${parent.body()} +<% product = form.fieldset.model %> +

    Product Costs:

    - % if fieldset.model.costs: + % if product.costs:
    Name
    ${group.id} ${group.name or ''}
    @@ -27,7 +18,7 @@ ${parent.body()} - % for i, cost in enumerate(fieldset.model.costs, 1): + % for i, cost in enumerate(product.costs, 1): diff --git a/rattail/pyramid/templates/subdepartments/index.mako b/rattail/pyramid/templates/subdepartments/index.mako index 3a229064..251b9c60 100644 --- a/rattail/pyramid/templates/subdepartments/index.mako +++ b/rattail/pyramid/templates/subdepartments/index.mako @@ -1,4 +1,4 @@ -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Subdepartments diff --git a/rattail/pyramid/templates/vendors/base.mako b/rattail/pyramid/templates/vendors/base.mako deleted file mode 100644 index 27f7dd90..00000000 --- a/rattail/pyramid/templates/vendors/base.mako +++ /dev/null @@ -1,2 +0,0 @@ -<%inherit file="/base.mako" /> -${parent.body()} diff --git a/rattail/pyramid/templates/vendors/index.mako b/rattail/pyramid/templates/vendors/index.mako index 1e637857..e0e9c0d1 100644 --- a/rattail/pyramid/templates/vendors/index.mako +++ b/rattail/pyramid/templates/vendors/index.mako @@ -1,5 +1,4 @@ -<%inherit file="/vendors/base.mako" /> -<%inherit file="/index.mako" /> +<%inherit file="/grid.mako" /> <%def name="title()">Vendors diff --git a/rattail/pyramid/views/customergroups.py b/rattail/pyramid/views/customer_groups.py similarity index 86% rename from rattail/pyramid/views/customergroups.py rename to rattail/pyramid/views/customer_groups.py index 1d42a4a0..9b561604 100644 --- a/rattail/pyramid/views/customergroups.py +++ b/rattail/pyramid/views/customer_groups.py @@ -34,10 +34,7 @@ import rattail class CustomerGroupsGrid(SearchableAlchemyGridView): mapped_class = rattail.CustomerGroup - route_name = 'customergroups' - route_url = '/customergroups' - renderer = '/customergroups/index.mako' - permission = 'customergroups.list' + config_prefix = 'customer_groups' sort = 'name' def filter_map(self): @@ -63,4 +60,8 @@ class CustomerGroupsGrid(SearchableAlchemyGridView): def includeme(config): - CustomerGroupsGrid.add_route(config) + + config.add_route('customer_groups', '/customer-groups') + config.add_view(CustomerGroupsGrid, route_name='customer_groups', + renderer='/customer_groups/index.mako', + permission='customer_groups.list') diff --git a/rattail/pyramid/views/customers.py b/rattail/pyramid/views/customers.py index 564adb65..589f5c2a 100644 --- a/rattail/pyramid/views/customers.py +++ b/rattail/pyramid/views/customers.py @@ -28,8 +28,7 @@ from sqlalchemy import and_ -from edbob.pyramid.views import SearchableAlchemyGridView -from edbob.pyramid.views.crud import Crud +from edbob.pyramid.views import SearchableAlchemyGridView, CrudView import rattail @@ -37,10 +36,7 @@ import rattail class CustomersGrid(SearchableAlchemyGridView): mapped_class = rattail.Customer - route_name = 'customers' - route_url = '/customers' - renderer = '/customers/index.mako' - permission = 'customers.list' + config_prefix = 'customers' sort = 'name' clickable = True @@ -87,20 +83,17 @@ class CustomersGrid(SearchableAlchemyGridView): g.email.label("Email Address"), ], readonly=True) - - g.row_route_name = 'customer.read' - g.row_route_kwargs = lambda x: {'uuid': x.uuid} - + g.click_route_name = 'customer.read' return g -class CustomerCrud(Crud): +class CustomerCrud(CrudView): mapped_class = rattail.Customer home_route = 'customers' - def fieldset(self, obj): - fs = self.make_fieldset(obj) + def fieldset(self, model): + fs = self.make_fieldset(model) fs.configure( include=[ fs.id.label("ID"), @@ -112,5 +105,13 @@ class CustomerCrud(Crud): def includeme(config): - CustomersGrid.add_route(config) - CustomerCrud.add_routes(config) + + config.add_route('customers', '/customers') + config.add_view(CustomersGrid, route_name='customers', + renderer='/customers/index.mako', + permission='customers.list') + + config.add_route('customer.read', '/customers/{uuid}') + config.add_view(CustomerCrud, attr='read', route_name='customer.read', + renderer='/customers/read.mako', + permission='customers.read') diff --git a/rattail/pyramid/views/departments.py b/rattail/pyramid/views/departments.py index 8f6c9d5b..4a7ba673 100644 --- a/rattail/pyramid/views/departments.py +++ b/rattail/pyramid/views/departments.py @@ -26,37 +26,16 @@ ``rattail.pyramid.views.departments`` -- Department Views """ -# import transaction -# from pyramid.httpexceptions import HTTPFound -# from pyramid.view import view_config -# from edbob.pyramid import Session from edbob.pyramid.views import SearchableAlchemyGridView, AlchemyGridView import rattail -# @view_config(route_name='department.delete') -# def delete_department(context, request): -# uuid = request.matchdict['uuid'] -# dept = Session.query(rattail.Department).get(uuid) if uuid else None -# assert dept -# with transaction.manager: -# q = Session.query(rattail.Product) -# q = q.filter(rattail.Product.department_uuid == dept.uuid) -# if q.count(): -# q.update({'department_uuid': None}, synchronize_session=False) -# Session.delete(dept) -# return HTTPFound(location=request.route_url('departments.list')) - - class DepartmentsGrid(SearchableAlchemyGridView): mapped_class = rattail.Department - route_name = 'departments' - route_url = '/departments' - renderer = '/departments/index.mako' - permission = 'departments.list' + config_prefix = 'departments' sort = 'name' def filter_map(self): @@ -84,8 +63,7 @@ class DepartmentsGrid(SearchableAlchemyGridView): class DepartmentsByVendorGrid(AlchemyGridView): mapped_class = rattail.Department - route_name = 'departments.by_vendor' - route_url = '/departments/by-vendor' + config_prefix = 'departments.by_vendor' checkboxes = True partial_only = True @@ -110,8 +88,12 @@ class DepartmentsByVendorGrid(AlchemyGridView): def includeme(config): - # config.add_route('department.delete', '/department/{uuid}/delete') - # config.scan(__name__) - DepartmentsGrid.add_route(config) - DepartmentsByVendorGrid.add_route(config) + config.add_route('departments', '/departments') + config.add_view(DepartmentsGrid, route_name='departments', + renderer='/departments/index.mako', + permission='departments.list') + + config.add_route('departments.by_vendor', '/departments/by-vendor') + config.add_view(DepartmentsByVendorGrid, route_name='departments.by_vendor', + permission='departments.list') diff --git a/rattail/pyramid/views/employees.py b/rattail/pyramid/views/employees.py index 9be23509..aedd9c6b 100644 --- a/rattail/pyramid/views/employees.py +++ b/rattail/pyramid/views/employees.py @@ -29,10 +29,8 @@ from sqlalchemy import and_ import edbob -from edbob.pyramid import grids from edbob.pyramid.forms import AssociationProxyField from edbob.pyramid.views import SearchableAlchemyGridView -from edbob.pyramid.views.crud import Crud import rattail @@ -40,9 +38,7 @@ import rattail class EmployeesGrid(SearchableAlchemyGridView): mapped_class = rattail.Employee - route_name = 'employees' - route_url = '/employees' - renderer = '/employees/index.mako' + config_prefix = 'employees' sort = 'first_name' def join_map(self): @@ -55,9 +51,9 @@ class EmployeesGrid(SearchableAlchemyGridView): def filter_map(self): return self.make_filter_map( - first_name=grids.search.filter_ilike(edbob.Person.first_name), - last_name=grids.search.filter_ilike(edbob.Person.last_name), - phone=grids.search.filter_ilike(rattail.EmployeePhoneNumber.number)) + 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)) def filter_config(self): return self.make_filter_config( @@ -69,9 +65,9 @@ class EmployeesGrid(SearchableAlchemyGridView): def sort_map(self): return self.make_sort_map( - first_name=grids.util.sorter(edbob.Person.first_name), - last_name=grids.util.sorter(edbob.Person.last_name), - phone=grids.util.sorter(rattail.EmployeePhoneNumber.number)) + first_name=self.sorter(edbob.Person.first_name), + last_name=self.sorter(edbob.Person.last_name), + phone=self.sorter(rattail.EmployeePhoneNumber.number)) def query(self): q = self.make_query() @@ -94,20 +90,9 @@ class EmployeesGrid(SearchableAlchemyGridView): return g -class EmployeeCrud(Crud): - - mapped_class = rattail.Employee - home_route = 'employees.list' - - def fieldset(self, obj): - fs = self.make_fieldset(obj) - fs.configure( - include=[ - fs.person, - ]) - return fs - - def includeme(config): - EmployeesGrid.add_route(config) - EmployeeCrud.add_routes(config) + + config.add_route('employees', '/employees') + config.add_view(EmployeesGrid, route_name='employees', + renderer='/employees/index.mako', + permission='employees.list') diff --git a/rattail/pyramid/views/products.py b/rattail/pyramid/views/products.py index 8c354779..a32976ae 100644 --- a/rattail/pyramid/views/products.py +++ b/rattail/pyramid/views/products.py @@ -32,9 +32,7 @@ from sqlalchemy.orm import joinedload import edbob from edbob.pyramid import Session -from edbob.pyramid import grids -from edbob.pyramid.views import SearchableAlchemyGridView -from edbob.pyramid.views.crud import Crud +from edbob.pyramid.views import SearchableAlchemyGridView, CrudView import rattail import rattail.labels @@ -44,9 +42,7 @@ from rattail.pyramid.forms import UpcFieldRenderer, PriceFieldRenderer class ProductsGrid(SearchableAlchemyGridView): mapped_class = rattail.Product - route_name = 'products' - route_url = '/products' - renderer = '/products/index.mako' + config_prefix = 'products' sort = 'description' clickable = True @@ -72,9 +68,9 @@ class ProductsGrid(SearchableAlchemyGridView): return self.make_filter_map( exact=['upc'], ilike=['description', 'size'], - brand=grids.search.filter_ilike(rattail.Brand.name), - department=grids.search.filter_ilike(rattail.Department.name), - subdepartment=grids.search.filter_ilike(rattail.Subdepartment.name)) + brand=self.filter_ilike(rattail.Brand.name), + department=self.filter_ilike(rattail.Department.name), + subdepartment=self.filter_ilike(rattail.Subdepartment.name)) def filter_config(self): return self.make_filter_config( @@ -91,11 +87,11 @@ class ProductsGrid(SearchableAlchemyGridView): def sort_map(self): return self.make_sort_map( 'upc', 'description', 'size', - brand=grids.util.sorter(rattail.Brand.name), - department=grids.util.sorter(rattail.Department.name), - subdepartment=grids.util.sorter(rattail.Subdepartment.name), - regular_price=grids.util.sorter(rattail.ProductPrice.price), - current_price=grids.util.sorter(rattail.ProductPrice.price)) + brand=self.sorter(rattail.Brand.name), + department=self.sorter(rattail.Department.name), + subdepartment=self.sorter(rattail.Subdepartment.name), + regular_price=self.sorter(rattail.ProductPrice.price), + current_price=self.sorter(rattail.ProductPrice.price)) def query(self): q = self.make_query() @@ -123,8 +119,7 @@ class ProductsGrid(SearchableAlchemyGridView): ], readonly=True) - g.row_route_name = 'product.read' - g.row_route_kwargs = lambda x: {'uuid': x.uuid} + g.click_route_name = 'product.read' if edbob.config.getboolean('rattail.labels', 'enabled', default=False): def labels(row): @@ -134,13 +129,13 @@ class ProductsGrid(SearchableAlchemyGridView): return g -class ProductCrud(Crud): +class ProductCrud(CrudView): mapped_class = rattail.Product home_route = 'products' - def fieldset(self, obj): - fs = self.make_fieldset(obj) + def fieldset(self, model): + fs = self.make_fieldset(model) fs.upc.set(renderer=UpcFieldRenderer) fs.regular_price.set(renderer=PriceFieldRenderer) fs.current_price.set(renderer=PriceFieldRenderer) @@ -177,11 +172,20 @@ def print_label(request): def includeme(config): - ProductsGrid.add_route(config) - ProductCrud.add_routes(config) + + config.add_route('products', '/products') + config.add_view(ProductsGrid, route_name='products', + renderer='/products/index.mako', + permission='products.list') config.add_route('products.print_label', '/products/label') - config.add_view(print_label, route_name='products.print_label', renderer='json') + config.add_view(print_label, route_name='products.print_label', + renderer='json') + + config.add_route('product.read', '/products/{uuid}') + config.add_view(ProductCrud, attr='read', route_name='product.read', + renderer='/products/read.mako', + permission='products.read') # from sqlalchemy.orm import joinedload diff --git a/rattail/pyramid/views/subdepartments.py b/rattail/pyramid/views/subdepartments.py index ae4836be..1d6e4b67 100644 --- a/rattail/pyramid/views/subdepartments.py +++ b/rattail/pyramid/views/subdepartments.py @@ -26,7 +26,7 @@ ``rattail.pyramid.views.subdepartments`` -- Subdepartment Views """ -from edbob.pyramid.views import SearchableAlchemyGridView, AlchemyGridView +from edbob.pyramid.views import SearchableAlchemyGridView import rattail @@ -34,10 +34,7 @@ import rattail class SubdepartmentsGrid(SearchableAlchemyGridView): mapped_class = rattail.Subdepartment - route_name = 'subdepartments' - route_url = '/subdepartments' - renderer = '/subdepartments/index.mako' - permission = 'subdepartments.list' + config_prefix = 'subdepartments' sort = 'name' def filter_map(self): @@ -63,4 +60,8 @@ class SubdepartmentsGrid(SearchableAlchemyGridView): def includeme(config): - SubdepartmentsGrid.add_route(config) + + config.add_route('subdepartments', '/subdepartments') + config.add_view(SubdepartmentsGrid, route_name='subdepartments', + renderer='/subdepartments/index.mako', + permission='subdepartments.list') diff --git a/rattail/pyramid/views/vendors.py b/rattail/pyramid/views/vendors.py index a776df6b..8962f18f 100644 --- a/rattail/pyramid/views/vendors.py +++ b/rattail/pyramid/views/vendors.py @@ -27,7 +27,6 @@ """ from edbob.pyramid.views import SearchableAlchemyGridView, AutocompleteView -from edbob.pyramid.views.crud import Crud import rattail @@ -35,10 +34,7 @@ import rattail class VendorsGrid(SearchableAlchemyGridView): mapped_class = rattail.Vendor - route_name = 'vendors' - route_url = '/vendors' - renderer = '/vendors/index.mako' - permission = 'vendors.list' + config_prefix = 'vendors' sort = 'name' def filter_map(self): @@ -66,28 +62,19 @@ class VendorsGrid(SearchableAlchemyGridView): return g -class VendorCrud(Crud): - - mapped_class = rattail.Vendor - home_route = 'vendors.list' - - def fieldset(self, obj): - fs = self.make_fieldset(obj) - fs.configure( - include=[ - fs.id, - fs.name, - ]) - return fs - - -class VendorAutocomplete(AutocompleteView): +class VendorsAutocomplete(AutocompleteView): mapped_class = rattail.Vendor fieldname = 'name' def includeme(config): - VendorsGrid.add_route(config) - VendorCrud.add_routes(config) - VendorAutocomplete.add_route(config) + + config.add_route('vendors', '/vendors') + config.add_view(VendorsGrid, route_name='vendors', + renderer='/vendors/index.mako', + permission='vendors.list') + + config.add_route('vendors.autocomplete', '/vendors/autocomplete') + config.add_view(VendorsAutocomplete, route_name='vendors.autocomplete', + renderer='json', permission='vendors.list')
    Unit Cost
    ${'X' if cost.preference == 1 else ''} ${cost.vendor}