feat: add config option to show/hide Store ID; default value

This commit is contained in:
Lance Edgar 2025-01-27 20:33:14 -06:00
parent 3ef84ff706
commit 89e3445ace
19 changed files with 445 additions and 64 deletions

View file

@ -0,0 +1,6 @@
``sideshow.app``
================
.. automodule:: sideshow.app
:members:

View file

@ -30,6 +30,7 @@ For an online demo see https://demo.wuttaproject.org/
:caption: Package API:
api/sideshow
api/sideshow.app
api/sideshow.batch
api/sideshow.batch.neworder
api/sideshow.cli

View file

@ -51,6 +51,9 @@ sideshow_libcache = "sideshow.web.static:libcache"
[project.entry-points."paste.app_factory"]
"main" = "sideshow.web.app:main"
[project.entry-points."wutta.app.providers"]
sideshow = "sideshow.app:SideshowAppProvider"
[project.entry-points."wutta.batch.neworder"]
"sideshow" = "sideshow.batch.neworder:NewOrderBatchHandler"

56
src/sideshow/app.py Normal file
View file

@ -0,0 +1,56 @@
# -*- coding: utf-8; -*-
################################################################################
#
# Sideshow -- Case/Special Order Tracker
# Copyright © 2024 Lance Edgar
#
# This file is part of Sideshow.
#
# Sideshow is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Sideshow 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Sideshow. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
Sideshow app provider
"""
from wuttjamaican import app as base
class SideshowAppProvider(base.AppProvider):
"""
The :term:`app provider` for Sideshow.
This adds the :meth:`get_order_handler()` method to the :term:`app
handler`.
"""
def get_order_handler(self, **kwargs):
"""
Get the configured :term:`order handler` for the app.
You can specify a custom handler in your :term:`config file`
like:
.. code-block:: ini
[sideshow]
orders.handler_spec = poser.orders:PoserOrderHandler
:returns: Instance of :class:`~sideshow.orders.OrderHandler`.
"""
if 'order_handler' not in self.__dict__:
spec = self.config.get('sideshow.orders.handler_spec',
default='sideshow.orders:OrderHandler')
self.order_handler = self.app.load_object(spec)(self.config)
return self.order_handler

View file

@ -50,6 +50,14 @@ class NewOrderBatchHandler(BatchHandler):
"""
model_class = NewOrderBatch
def get_default_store_id(self):
"""
Returns the configured default value for
:attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.store_id`,
or ``None``.
"""
return self.config.get('sideshow.orders.default_store_id')
def use_local_customers(self):
"""
Returns boolean indicating whether :term:`local customer`
@ -165,6 +173,18 @@ class NewOrderBatchHandler(BatchHandler):
'label': customer.full_name}
return [result(c) for c in customers]
def init_batch(self, batch, session=None, progress=None, **kwargs):
"""
Initialize a new batch.
This sets the
:attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.store_id`,
if the batch does not yet have one and a default is
configured.
"""
if not batch.store_id:
batch.store_id = self.get_default_store_id()
def set_customer(self, batch, customer_info, user=None):
"""
Set/update customer info for the batch.

View file

@ -62,6 +62,15 @@ class Order(model.Base):
ID of the store to which the order pertains, if applicable.
""")
store = orm.relationship(
'Store',
primaryjoin='Store.store_id == Order.store_id',
foreign_keys='Order.store_id',
doc="""
Reference to the :class:`~sideshow.db.model.stores.Store`
record, if applicable.
""")
customer_id = sa.Column(sa.String(length=20), nullable=True, doc="""
Proper account ID for the :term:`external customer` to which the
order pertains, if applicable.

View file

@ -51,4 +51,12 @@ class Store(model.Base):
""")
def __str__(self):
return self.name or ""
return self.get_display()
def get_display(self):
"""
Returns the display string for the store, e.g. "001 Acme Goods".
"""
return ' '.join([(self.store_id or '').strip(),
(self.name or '').strip()])\
.strip()

View file

@ -37,6 +37,14 @@ class OrderHandler(GenericHandler):
handler is responsible for creation logic.)
"""
def expose_store_id(self):
"""
Returns boolean indicating whether the ``store_id`` field
should be exposed at all. This is false by default.
"""
return self.config.get_bool('sideshow.orders.expose_store_id',
default=False)
def get_order_qty_uom_text(self, order_qty, order_uom, case_size=None, html=False):
"""
Return the display text for a given order quantity.

View file

@ -29,6 +29,17 @@
<b-field horizontal label="ID">
<span>${h.link_to(f"Order ID {order.order_id}", url('orders.view', uuid=order.uuid))} &mdash; Item #${item.sequence}</span>
</b-field>
% if expose_store_id:
<b-field horizontal label="Store">
<span>
% if order.store:
${h.link_to(order.store.get_display(), url('stores.view', uuid=order.store.uuid))}
% elif order.store_id:
${order.store_id}
% endif
</span>
</b-field>
% endif
<b-field horizontal label="Order Qty">
<span>${order_qty_uom_text|n}</span>
</b-field>

View file

@ -3,6 +3,28 @@
<%def name="form_content()">
<h3 class="block is-size-3">Stores</h3>
<div class="block" style="padding-left: 2rem;">
<b-field>
<b-checkbox name="sideshow.orders.expose_store_id"
v-model="simpleSettings['sideshow.orders.expose_store_id']"
native-value="true"
@input="settingsNeedSaved = true">
Show/choose the Store ID for each order
</b-checkbox>
</b-field>
<b-field v-show="simpleSettings['sideshow.orders.expose_store_id']"
label="Default Store ID">
<b-input name="sideshow.orders.default_store_id"
v-model="simpleSettings['sideshow.orders.default_store_id']"
@input="settingsNeedSaved = true"
style="width: 25rem;" />
</b-field>
</div>
<h3 class="block is-size-3">Customers</h3>
<div class="block" style="padding-left: 2rem;">
@ -14,6 +36,7 @@
<option value="false">External Customers (e.g. in POS)</option>
</b-select>
</b-field>
</div>
<h3 class="block is-size-3">Products</h3>

View file

@ -42,7 +42,25 @@
<script type="text/x-template" id="order-creator-template">
<div>
${self.order_form_buttons()}
<div style="display: flex; justify-content: space-between; margin-bottom: 1.5rem;">
<div>
% if expose_store_id:
<b-loading v-model="storeLoading" is-full-page />
<b-field label="Store" horizontal
:type="storeID ? null : 'is-danger'">
<b-select v-model="storeID"
@input="storeChanged">
<option v-for="store in stores"
:key="store.store_id"
:value="store.store_id">
{{ store.display }}
</option>
</b-select>
</b-field>
% endif
</div>
${self.order_form_buttons()}
</div>
<${b}-collapse class="panel"
:class="customerPanelType"
@ -837,6 +855,12 @@
batchTotalPriceDisplay: ${json.dumps(normalized_batch['total_price_display'])|n},
% if expose_store_id:
stores: ${json.dumps(stores)|n},
storeID: ${json.dumps(batch.store_id)|n},
storeLoading: false,
% endif
customerPanelOpen: false,
customerLoading: false,
customerIsKnown: ${json.dumps(customer_is_known)|n},
@ -1160,6 +1184,28 @@
})
},
% if expose_store_id:
storeChanged(storeID) {
this.storeLoading = true
const params = {
action: 'set_store',
store_id: storeID,
}
this.submitBatchData(params, ({data}) => {
this.storeLoading = false
}, response => {
this.$buefy.toast.open({
message: "Update failed: " + (response.data.error || "(unknown error)"),
type: 'is-danger',
duration: 2000, // 2 seconds
})
this.storeLoading = false
})
},
% endif
customerChanged(customerID, callback) {
this.customerLoading = true

View file

@ -126,6 +126,10 @@ class NewOrderBatchView(BatchMasterView):
'status_code',
]
def __init__(self, request, context=None):
super().__init__(request, context=context)
self.order_handler = self.app.get_order_handler()
def get_batch_handler(self):
""" """
# TODO: call self.app.get_batch_handler()
@ -135,6 +139,10 @@ class NewOrderBatchView(BatchMasterView):
""" """
super().configure_grid(g)
# store_id
if not self.order_handler.expose_store_id():
g.remove('store_id')
# total_price
g.set_renderer('total_price', 'currency')
@ -142,6 +150,10 @@ class NewOrderBatchView(BatchMasterView):
""" """
super().configure_form(f)
# store_id
if not self.order_handler.expose_store_id():
f.remove('store_id')
# local_customer
f.set_node('local_customer', LocalCustomerRef(self.request))

View file

@ -37,7 +37,6 @@ from wuttaweb.views import MasterView
from wuttaweb.forms.schema import UserRef, WuttaMoney, WuttaQuantity, WuttaEnum, WuttaDictEnum
from sideshow.db.model import Order, OrderItem
from sideshow.orders import OrderHandler
from sideshow.batch.neworder import NewOrderBatchHandler
from sideshow.web.forms.schema import (OrderRef,
LocalCustomerRef, LocalProductRef,
@ -155,20 +154,7 @@ class OrderView(MasterView):
def __init__(self, request, context=None):
super().__init__(request, context=context)
self.order_handler = self.get_order_handler()
def get_order_handler(self):
"""
Returns the configured :term:`order handler`.
You normally would not need to call this, and can use
:attr:`order_handler` instead.
:rtype: :class:`~sideshow.orders.OrderHandler`
"""
if hasattr(self, 'order_handler'):
return self.order_handler
return OrderHandler(self.config)
self.order_handler = self.app.get_order_handler()
def get_batch_handler(self):
"""
@ -190,6 +176,10 @@ class OrderView(MasterView):
""" """
super().configure_grid(g)
# store_id
if not self.order_handler.expose_store_id():
g.remove('store_id')
# order_id
g.set_link('order_id')
@ -223,6 +213,7 @@ class OrderView(MasterView):
* :meth:`start_over()`
* :meth:`cancel_order()`
* :meth:`set_store()`
* :meth:`assign_customer()`
* :meth:`unassign_customer()`
* :meth:`set_pending_customer()`
@ -232,10 +223,12 @@ class OrderView(MasterView):
* :meth:`delete_item()`
* :meth:`submit_order()`
"""
model = self.app.model
enum = self.app.enum
self.creating = True
session = self.Session()
self.batch_handler = self.get_batch_handler()
batch = self.get_current_batch()
self.creating = True
context = self.get_context_customer(batch)
@ -254,6 +247,7 @@ class OrderView(MasterView):
data = dict(self.request.json_body)
action = data.pop('action')
json_actions = [
'set_store',
'assign_customer',
'unassign_customer',
# 'update_phone_number',
@ -285,12 +279,25 @@ class OrderView(MasterView):
for row in batch.rows],
'default_uom_choices': self.get_default_uom_choices(),
'default_uom': None, # TODO?
'expose_store_id': self.order_handler.expose_store_id(),
'allow_item_discounts': self.batch_handler.allow_item_discounts(),
'allow_unknown_products': (self.batch_handler.allow_unknown_products()
and self.has_perm('create_unknown_product')),
'pending_product_required_fields': self.get_pending_product_required_fields(),
})
if context['expose_store_id']:
stores = session.query(model.Store)\
.filter(model.Store.archived == False)\
.order_by(model.Store.store_id)\
.all()
context['stores'] = [{'store_id': store.store_id, 'display': store.get_display()}
for store in stores]
# set default so things just work
if not batch.store_id:
batch.store_id = self.batch_handler.get_default_store_id()
if context['allow_item_discounts']:
context['allow_item_discounts_if_on_sale'] = self.batch_handler\
.allow_item_discounts_if_on_sale()
@ -436,9 +443,26 @@ class OrderView(MasterView):
url = self.get_index_url()
return self.redirect(url)
def set_store(self, batch, data):
"""
Assign the
:attr:`~sideshow.db.model.batch.neworder.NewOrderBatch.store_id`
for a batch.
This is a "batch action" method which may be called from
:meth:`create()`.
"""
store_id = data.get('store_id')
if not store_id:
return {'error': "Must provide store_id"}
batch.store_id = store_id
return self.get_context_customer(batch)
def get_context_customer(self, batch):
""" """
context = {
'store_id': batch.store_id,
'customer_is_known': True,
'customer_id': None,
'customer_name': batch.customer_name,
@ -810,6 +834,10 @@ class OrderView(MasterView):
super().configure_form(f)
order = f.model_instance
# store_id
if not self.order_handler.expose_store_id():
f.remove('store_id')
# local_customer
if order.customer_id and not order.local_customer:
f.remove('local_customer')
@ -910,8 +938,10 @@ class OrderView(MasterView):
""" """
settings = [
# batches
{'name': 'wutta.batch.neworder.handler.spec'},
# stores
{'name': 'sideshow.orders.expose_store_id',
'type': bool},
{'name': 'sideshow.orders.default_store_id'},
# customers
{'name': 'sideshow.orders.use_local_customers',
@ -933,6 +963,9 @@ class OrderView(MasterView):
{'name': 'sideshow.orders.allow_unknown_products',
'type': bool,
'default': True},
# batches
{'name': 'wutta.batch.neworder.handler.spec'},
]
# required fields for new product entry
@ -1037,6 +1070,7 @@ class OrderItemView(MasterView):
labels = {
'order_id': "Order ID",
'store_id': "Store ID",
'product_id': "Product ID",
'product_scancode': "Scancode",
'product_brand': "Brand",
@ -1050,6 +1084,7 @@ class OrderItemView(MasterView):
grid_columns = [
'order_id',
'store_id',
'customer_name',
# 'sequence',
'product_scancode',
@ -1099,20 +1134,7 @@ class OrderItemView(MasterView):
def __init__(self, request, context=None):
super().__init__(request, context=context)
self.order_handler = self.get_order_handler()
def get_order_handler(self):
"""
Returns the configured :term:`order handler`.
You normally would not need to call this, and can use
:attr:`order_handler` instead.
:rtype: :class:`~sideshow.orders.OrderHandler`
"""
if hasattr(self, 'order_handler'):
return self.order_handler
return OrderHandler(self.config)
self.order_handler = self.app.get_order_handler()
def get_fallback_templates(self, template):
""" """
@ -1132,11 +1154,19 @@ class OrderItemView(MasterView):
model = self.app.model
# enum = self.app.enum
# store_id
if not self.order_handler.expose_store_id():
g.remove('store_id')
# order_id
g.set_sorter('order_id', model.Order.order_id)
g.set_renderer('order_id', self.render_order_id)
g.set_renderer('order_id', self.render_order_attr)
g.set_link('order_id')
# store_id
g.set_sorter('store_id', model.Order.store_id)
g.set_renderer('store_id', self.render_order_attr)
# customer_name
g.set_label('customer_name', "Customer", column_only=True)
@ -1165,9 +1195,10 @@ class OrderItemView(MasterView):
# status_code
g.set_renderer('status_code', self.render_status_code)
def render_order_id(self, item, key, value):
def render_order_attr(self, item, key, value):
""" """
return item.order.order_id
order = item.order
return getattr(order, key)
def render_status_code(self, item, key, value):
""" """
@ -1237,6 +1268,8 @@ class OrderItemView(MasterView):
item = context['instance']
form = context['form']
context['expose_store_id'] = self.order_handler.expose_store_id()
context['item'] = item
context['order'] = item.order
context['order_qty_uom_text'] = self.order_handler.get_order_qty_uom_text(

View file

@ -20,6 +20,16 @@ class TestNewOrderBatchHandler(DataTestCase):
def make_handler(self):
return mod.NewOrderBatchHandler(self.config)
def test_get_default_store_id(self):
handler = self.make_handler()
# null by default
self.assertIsNone(handler.get_default_store_id())
# whatever is configured
self.config.setdefault('sideshow.orders.default_store_id', '042')
self.assertEqual(handler.get_default_store_id(), '042')
def test_use_local_customers(self):
handler = self.make_handler()
@ -108,6 +118,23 @@ class TestNewOrderBatchHandler(DataTestCase):
# search for sally finds nothing
self.assertEqual(handler.autocomplete_customers_local(self.session, 'sally'), [])
def test_init_batch(self):
model = self.app.model
handler = self.make_handler()
# store_id is null by default
batch = handler.model_class()
self.assertIsNone(batch.store_id)
handler.init_batch(batch)
self.assertIsNone(batch.store_id)
# but default can be configured
self.config.setdefault('sideshow.orders.default_store_id', '042')
batch = handler.model_class()
self.assertIsNone(batch.store_id)
handler.init_batch(batch)
self.assertEqual(batch.store_id, '042')
def test_set_customer(self):
model = self.app.model
handler = self.make_handler()

View file

@ -13,3 +13,16 @@ class TestPendingCustomer(DataTestCase):
store.name = "Acme Goods"
self.assertEqual(str(store), "Acme Goods")
store.store_id = "001"
self.assertEqual(str(store), "001 Acme Goods")
def test_get_display(self):
store = mod.Store()
self.assertEqual(store.get_display(), "")
store.name = "Acme Goods"
self.assertEqual(store.get_display(), "Acme Goods")
store.store_id = "001"
self.assertEqual(store.get_display(), "001 Acme Goods")

17
tests/test_app.py Normal file
View file

@ -0,0 +1,17 @@
# -*- coding: utf-8; -*-
from wuttjamaican.testing import ConfigTestCase
from sideshow import app as mod
from sideshow.orders import OrderHandler
class TestSideshowAppProvider(ConfigTestCase):
def make_provider(self):
return mod.SideshowAppProvider(self.config)
def test_get_order_handler(self):
provider = self.make_provider()
handler = provider.get_order_handler()
self.assertIsInstance(handler, OrderHandler)

View file

@ -16,6 +16,16 @@ class TestOrderHandler(DataTestCase):
def make_handler(self):
return mod.OrderHandler(self.config)
def test_expose_store_id(self):
handler = self.make_handler()
# false by default
self.assertFalse(handler.expose_store_id())
# config can enable
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
self.assertTrue(handler.expose_store_id())
def test_get_order_qty_uom_text(self):
enum = self.app.enum
handler = self.make_handler()

View file

@ -30,10 +30,19 @@ class TestNewOrderBatchView(WebTestCase):
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
# store_id not exposed by default
grid = view.make_grid(model_class=model.NewOrderBatch)
self.assertNotIn('total_price', grid.renderers)
self.assertIn('store_id', grid.columns)
view.configure_grid(grid)
self.assertIn('total_price', grid.renderers)
self.assertNotIn('store_id', grid.columns)
# store_id is exposed if configured
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
grid = view.make_grid(model_class=model.NewOrderBatch)
self.assertIn('store_id', grid.columns)
view.configure_grid(grid)
self.assertIn('store_id', grid.columns)
def test_configure_form(self):
model = self.app.model
@ -58,6 +67,19 @@ class TestNewOrderBatchView(WebTestCase):
self.assertIsInstance(schema['pending_customer'].typ, PendingCustomerRef)
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
# store_id not exposed by default
form = view.make_form(model_instance=batch)
self.assertIn('store_id', form)
view.configure_form(form)
self.assertNotIn('store_id', form)
# store_id is exposed if configured
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
form = view.make_form(model_instance=batch)
self.assertIn('store_id', form)
view.configure_form(form)
self.assertIn('store_id', form)
def test_configure_row_grid(self):
model = self.app.model
view = self.make_view()

View file

@ -31,27 +31,28 @@ class TestOrderView(WebTestCase):
def make_handler(self):
return NewOrderBatchHandler(self.config)
def test_order_handler(self):
view = self.make_view()
handler = view.order_handler
self.assertIsInstance(handler, OrderHandler)
handler2 = view.get_order_handler()
self.assertIs(handler2, handler)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.PendingProduct)
self.assertNotIn('order_id', grid.linked_columns)
self.assertNotIn('total_price', grid.renderers)
# store_id hidden by default
grid = view.make_grid(model_class=model.Order, columns=['store_id', 'order_id'])
self.assertIn('store_id', grid.columns)
view.configure_grid(grid)
self.assertIn('order_id', grid.linked_columns)
self.assertIn('total_price', grid.renderers)
self.assertNotIn('store_id', grid.columns)
# store_id is shown if configured
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
grid = view.make_grid(model_class=model.Order, columns=['store_id', 'order_id'])
self.assertIn('store_id', grid.columns)
view.configure_grid(grid)
self.assertIn('store_id', grid.columns)
def test_create(self):
self.pyramid_config.include('sideshow.web.views')
self.config.setdefault('wutta.batch.neworder.handler.spec',
'sideshow.batch.neworder:NewOrderBatchHandler')
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
self.config.setdefault('sideshow.orders.allow_item_discounts', 'true')
model = self.app.model
enum = self.app.enum
@ -59,6 +60,10 @@ class TestOrderView(WebTestCase):
user = model.User(username='barney')
self.session.add(user)
store = model.Store(store_id='001', name='Acme Goods')
self.session.add(store)
store = model.Store(store_id='002', name='Acme Services')
self.session.add(store)
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
@ -101,6 +106,7 @@ class TestOrderView(WebTestCase):
self.assertIsInstance(response, Response)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json_body, {
'store_id': None,
'customer_is_known': False,
'customer_id': None,
'customer_name': 'Fred Flintstone',
@ -304,6 +310,7 @@ class TestOrderView(WebTestCase):
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'store_id': None,
'customer_is_known': True,
'customer_id': 42,
'customer_name': 'Fred Flintstone',
@ -321,6 +328,7 @@ class TestOrderView(WebTestCase):
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'store_id': None,
'customer_is_known': True,
'customer_id': local.uuid.hex,
'customer_name': 'Betty Boop',
@ -339,6 +347,7 @@ class TestOrderView(WebTestCase):
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'store_id': None,
'customer_is_known': False,
'customer_id': None,
'customer_name': 'Fred Flintstone',
@ -357,6 +366,7 @@ class TestOrderView(WebTestCase):
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'store_id': None,
'customer_is_known': True, # nb. this is for UI default
'customer_id': None,
'customer_name': None,
@ -408,6 +418,34 @@ class TestOrderView(WebTestCase):
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
def test_set_store(self):
model = self.app.model
view = self.make_view()
handler = NewOrderBatchHandler(self.config)
user = model.User(username='barney')
self.session.add(user)
self.session.flush()
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.assertIsNone(batch.store_id)
# store_id is required
result = view.set_store(batch, {})
self.assertEqual(result, {'error': "Must provide store_id"})
result = view.set_store(batch, {'store_id': ''})
self.assertEqual(result, {'error': "Must provide store_id"})
# store_id is set on batch
result = view.set_store(batch, {'store_id': '042'})
self.assertEqual(batch.store_id, '042')
self.assertIn('store_id', result)
self.assertEqual(result['store_id'], '042')
def test_assign_customer(self):
self.pyramid_config.add_route('orders.create', '/orders/new')
model = self.app.model
@ -432,6 +470,7 @@ class TestOrderView(WebTestCase):
self.assertIsNone(batch.pending_customer)
self.assertIs(batch.local_customer, weirdal)
self.assertEqual(context, {
'store_id': None,
'customer_is_known': True,
'customer_id': weirdal.uuid.hex,
'customer_name': 'Weird Al',
@ -470,6 +509,7 @@ class TestOrderView(WebTestCase):
self.assertIsNone(batch.customer_name)
self.assertIsNone(batch.local_customer)
self.assertEqual(context, {
'store_id': None,
'customer_is_known': True,
'customer_id': None,
'customer_name': None,
@ -510,6 +550,7 @@ class TestOrderView(WebTestCase):
context = view.set_pending_customer(batch, data)
self.assertIsInstance(batch.pending_customer, model.PendingCustomer)
self.assertEqual(context, {
'store_id': None,
'customer_is_known': False,
'customer_id': None,
'customer_name': 'Fred Flintstone',
@ -1078,7 +1119,11 @@ class TestOrderView(WebTestCase):
form = view.make_form(model_instance=order)
# nb. this is to avoid include/exclude ambiguity
form.remove('items')
# nb. store_id gets hidden by default
form.append('store_id')
self.assertIn('store_id', form)
view.configure_form(form)
self.assertNotIn('store_id', form)
schema = form.get_schema()
self.assertIn('pending_customer', form)
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
@ -1089,13 +1134,20 @@ class TestOrderView(WebTestCase):
self.session.add(local)
self.session.flush()
# nb. from now on we include store_id
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
# viewing (local customer)
with patch.object(view, 'viewing', new=True):
with patch.object(order, 'local_customer', new=local):
form = view.make_form(model_instance=order)
# nb. this is to avoid include/exclude ambiguity
form.remove('items')
# nb. store_id will now remain
form.append('store_id')
self.assertIn('store_id', form)
view.configure_form(form)
self.assertIn('store_id', form)
self.assertNotIn('pending_customer', form)
schema = form.get_schema()
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
@ -1272,13 +1324,6 @@ class TestOrderView(WebTestCase):
class OrderItemViewTestMixin:
def test_common_order_handler(self):
view = self.make_view()
handler = view.order_handler
self.assertIsInstance(handler, OrderHandler)
handler2 = view.get_order_handler()
self.assertIs(handler2, handler)
def test_common_get_fallback_templates(self):
view = self.make_view()
@ -1294,18 +1339,29 @@ class OrderItemViewTestMixin:
def test_common_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.OrderItem)
self.assertNotIn('order_id', grid.linked_columns)
view.configure_grid(grid)
self.assertIn('order_id', grid.linked_columns)
def test_common_render_order_id(self):
# store_id is removed by default
grid = view.make_grid(model_class=model.OrderItem)
grid.append('store_id')
self.assertIn('store_id', grid.columns)
view.configure_grid(grid)
self.assertNotIn('store_id', grid.columns)
# store_id is shown if configured
self.config.setdefault('sideshow.orders.expose_store_id', 'true')
grid = view.make_grid(model_class=model.OrderItem)
grid.append('store_id')
self.assertIn('store_id', grid.columns)
view.configure_grid(grid)
self.assertIn('store_id', grid.columns)
def test_common_render_order_attr(self):
model = self.app.model
view = self.make_view()
order = model.Order(order_id=42)
item = model.OrderItem()
order.items.append(item)
self.assertEqual(view.render_order_id(item, None, None), 42)
self.assertEqual(view.render_order_attr(item, 'order_id', None), 42)
def test_common_render_status_code(self):
enum = self.app.enum