From bd09acd0fd233dc63c29fc9967e79edb1e6a9735 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Mon, 11 Nov 2019 11:26:42 -0600 Subject: [PATCH] Add support for label batch "quick entry" API plus other general improvements to API core/master views and config --- tailbone/api/batch/__init__.py | 2 + tailbone/api/batch/core.py | 108 +++++++++++++++++++++++++++++++++ tailbone/api/batch/labels.py | 18 +++++- tailbone/api/master.py | 34 +++++++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tailbone/api/batch/core.py diff --git a/tailbone/api/batch/__init__.py b/tailbone/api/batch/__init__.py index 2f866d7b..ba15fd11 100644 --- a/tailbone/api/batch/__init__.py +++ b/tailbone/api/batch/__init__.py @@ -25,3 +25,5 @@ Tailbone Web API - Batches """ from __future__ import unicode_literals, absolute_import + +from .core import BatchAPIMasterView diff --git a/tailbone/api/batch/core.py b/tailbone/api/batch/core.py new file mode 100644 index 00000000..55d1baeb --- /dev/null +++ b/tailbone/api/batch/core.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2019 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 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 General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# Rattail. If not, see . +# +################################################################################ +""" +Tailbone Web API - Batch Views +""" + +from __future__ import unicode_literals, absolute_import + +import six + +from rattail.util import load_object + +from tailbone.api import APIMasterView + + +class BatchAPIMasterView(APIMasterView): + """ + Base class for all API views which are meant to handle "batch" *and/or* + "batch row" data. + """ + supports_quick_entry = False + + def __init__(self, request, **kwargs): + super(BatchAPIMasterView, self).__init__(request, **kwargs) + self.handler = self.get_handler() + + @classmethod + def get_batch_class(cls): + model_class = cls.get_model_class() + if hasattr(model_class, '__batch_class__'): + return model_class.__batch_class__ + return model_class + + def get_handler(self): + """ + Returns a `BatchHandler` instance for the view. All (?) custom batch + API views should define a default handler class; however this may in all + (?) cases be overridden by config also. The specific setting required + to do so will depend on the 'key' for the type of batch involved, e.g. + assuming the 'vendor_catalog' batch: + + .. code-block:: ini + + [rattail.batch] + vendor_catalog.handler = myapp.batch.vendorcatalog:CustomCatalogHandler + + Note that the 'key' for a batch is generally the same as its primary + table name, although technically it is whatever value returns from the + ``batch_key`` attribute of the main batch model class. + """ + key = self.get_batch_class().batch_key + spec = self.rattail_config.get('rattail.batch', '{}.handler'.format(key), + default=self.default_handler_spec) + return load_object(spec)(self.rattail_config) + + def quick_entry(self): + """ + View for handling "quick entry" user input, for a batch. + """ + data = self.request.json_body + + uuid = data['batch_uuid'] + batch = self.Session.query(self.get_batch_class()).get(uuid) + if not batch: + raise self.notfound() + + entry = data['quick_entry'] + + try: + row = self.handler.quick_entry(self.Session(), batch, entry) + except Exception as error: + return {'error': six.text_type(error)} + + result = self._get(obj=row) + result['ok'] = True + return result + + @classmethod + def _batch_defaults(cls, config): + route_prefix = cls.get_route_prefix() + url_prefix = cls.get_url_prefix() + + # quick entry + if cls.supports_quick_entry: + config.add_route('{}.quick_entry'.format(route_prefix), '{}/quick-entry'.format(url_prefix), + request_method=('OPTIONS', 'POST')) + config.add_view(cls, attr='quick_entry', route_name='{}.quick_entry'.format(route_prefix), + renderer='json') diff --git a/tailbone/api/batch/labels.py b/tailbone/api/batch/labels.py index d6ba226d..b18a733b 100644 --- a/tailbone/api/batch/labels.py +++ b/tailbone/api/batch/labels.py @@ -34,6 +34,7 @@ from rattail.time import localtime from cornice import resource from tailbone.api import APIMasterView +from tailbone.api.batch import BatchAPIMasterView class LabelBatchViews(APIMasterView): @@ -107,9 +108,14 @@ class LabelBatchViews(APIMasterView): config.add_cornice_resource(batch_resource) -class LabelBatchRowViews(APIMasterView): +class LabelBatchRowViews(BatchAPIMasterView): model_class = model.LabelBatchRow + default_handler_spec = 'rattail.batch.labels:LabelBatchHandler' + supports_quick_entry = True + route_prefix = 'api.label_batch_rows' + permission_prefix = 'labels.batch' + url_prefix = '/label-batch-rows' def normalize(self, row): batch = row.batch @@ -124,7 +130,10 @@ class LabelBatchRowViews(APIMasterView): 'batch_description': batch.description, 'sequence': row.sequence, 'item_id': row.item_id, + 'upc': six.text_type(row.upc), + 'upc_pretty': row.upc.pretty() if row.upc else None, 'description': row.description, + 'full_description': row.product.full_description if row.product else row.description, 'status_code': row.status_code, 'status_display': row.STATUS.get(row.status_code, six.text_type(row.status_code)), } @@ -137,13 +146,16 @@ class LabelBatchRowViews(APIMasterView): @classmethod def defaults(cls, config): + permission_prefix = cls.get_permission_prefix() # label batch rows - resource.add_view(cls.collection_get, permission='labels.batch.view') - resource.add_view(cls.get, permission='labels.batch.view') + resource.add_view(cls.collection_get, permission='{}.view'.format(permission_prefix)) + resource.add_view(cls.get, permission='{}.view'.format(permission_prefix)) rows_resource = resource.add_resource(cls, collection_path='/label-batch-rows', path='/label-batch-row/{uuid}') config.add_cornice_resource(rows_resource) + cls._batch_defaults(config) + def includeme(config): LabelBatchViews.defaults(config) diff --git a/tailbone/api/master.py b/tailbone/api/master.py index 1d810fb6..1c3a0d08 100644 --- a/tailbone/api/master.py +++ b/tailbone/api/master.py @@ -62,6 +62,40 @@ class APIMasterView(APIView): return cls.normalized_model_name return cls.get_model_class().__name__.lower() + @classmethod + def get_route_prefix(cls): + """ + Returns a prefix which (by default) applies to all routes provided by + this view class. + """ + prefix = getattr(cls, 'route_prefix', None) + if prefix: + return prefix + model_name = cls.get_normalized_model_name() + return 'api.{}s'.format(model_name) + + @classmethod + def get_permission_prefix(cls): + """ + Returns a prefix which (by default) applies to all permissions + leveraged by this view class. + """ + prefix = getattr(cls, 'permission_prefix') + if prefix: + return prefix + return cls.get_route_prefix() + + @classmethod + def get_url_prefix(cls): + """ + Returns a prefix which (by default) applies to all URLs provided by + this view class. + """ + prefix = getattr(cls, 'url_prefix', None) + if prefix: + return prefix + return '/{}'.format(cls.get_route_prefix()) + @classmethod def get_object_key(cls): if hasattr(cls, 'object_key'):