Add new "master" API view class; refactor products and batches to use it
This commit is contained in:
		
							parent
							
								
									df00dd600a
								
							
						
					
					
						commit
						113c0af49d
					
				
					 8 changed files with 183 additions and 90 deletions
				
			
		| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Rattail -- Retail Software Framework
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
#  Copyright © 2010-2018 Lance Edgar
 | 
					#  Copyright © 2010-2020 Lance Edgar
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  This file is part of Rattail.
 | 
					#  This file is part of Rattail.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,7 @@ from __future__ import unicode_literals, absolute_import
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .core import APIView, api
 | 
					from .core import APIView, api
 | 
				
			||||||
from .master import APIMasterView, SortColumn
 | 
					from .master import APIMasterView, SortColumn
 | 
				
			||||||
 | 
					from .master2 import APIMasterView2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def includeme(config):
 | 
					def includeme(config):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Rattail -- Retail Software Framework
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
#  Copyright © 2010-2019 Lance Edgar
 | 
					#  Copyright © 2010-2020 Lance Edgar
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  This file is part of Rattail.
 | 
					#  This file is part of Rattail.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ from rattail.util import load_object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from cornice import resource
 | 
					from cornice import resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from tailbone.api import APIMasterView
 | 
					from tailbone.api import APIMasterView2 as APIMasterView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APIBatchMixin(object):
 | 
					class APIBatchMixin(object):
 | 
				
			||||||
| 
						 | 
					@ -197,6 +197,7 @@ class APIBatchView(APIBatchMixin, APIMasterView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def defaults(cls, config):
 | 
					    def defaults(cls, config):
 | 
				
			||||||
 | 
					        cls._defaults(config)
 | 
				
			||||||
        cls._batch_defaults(config)
 | 
					        cls._batch_defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
| 
						 | 
					@ -206,14 +207,6 @@ class APIBatchView(APIBatchMixin, APIMasterView):
 | 
				
			||||||
        collection_url_prefix = cls.get_collection_url_prefix()
 | 
					        collection_url_prefix = cls.get_collection_url_prefix()
 | 
				
			||||||
        object_url_prefix = cls.get_object_url_prefix()
 | 
					        object_url_prefix = cls.get_object_url_prefix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # primary / typical API
 | 
					 | 
				
			||||||
        resource.add_view(cls.collection_get, permission='{}.list'.format(permission_prefix))
 | 
					 | 
				
			||||||
        resource.add_view(cls.collection_post, permission='{}.create'.format(permission_prefix))
 | 
					 | 
				
			||||||
        resource.add_view(cls.get, permission='{}.view'.format(permission_prefix))
 | 
					 | 
				
			||||||
        batch_resource = resource.add_resource(cls, collection_path=collection_url_prefix,
 | 
					 | 
				
			||||||
                                               path='{}/{{uuid}}'.format(object_url_prefix))
 | 
					 | 
				
			||||||
        config.add_cornice_resource(batch_resource)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if cls.supports_toggle_complete:
 | 
					        if cls.supports_toggle_complete:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # mark complete
 | 
					            # mark complete
 | 
				
			||||||
| 
						 | 
					@ -299,6 +292,7 @@ class APIBatchRowView(APIBatchMixin, APIMasterView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def defaults(cls, config):
 | 
					    def defaults(cls, config):
 | 
				
			||||||
 | 
					        cls._defaults(config)
 | 
				
			||||||
        cls._batch_row_defaults(config)
 | 
					        cls._batch_row_defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
| 
						 | 
					@ -308,14 +302,6 @@ class APIBatchRowView(APIBatchMixin, APIMasterView):
 | 
				
			||||||
        collection_url_prefix = cls.get_collection_url_prefix()
 | 
					        collection_url_prefix = cls.get_collection_url_prefix()
 | 
				
			||||||
        object_url_prefix = cls.get_object_url_prefix()
 | 
					        object_url_prefix = cls.get_object_url_prefix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        resource.add_view(cls.collection_get, permission='{}.view'.format(permission_prefix))
 | 
					 | 
				
			||||||
        resource.add_view(cls.get, permission='{}.view'.format(permission_prefix))
 | 
					 | 
				
			||||||
        if cls.editable:
 | 
					 | 
				
			||||||
            resource.add_view(cls.post, permission='{}.edit'.format(permission_prefix))
 | 
					 | 
				
			||||||
        rows_resource = resource.add_resource(cls, collection_path=collection_url_prefix,
 | 
					 | 
				
			||||||
                                              path='{}/{{uuid}}'.format(object_url_prefix))
 | 
					 | 
				
			||||||
        config.add_cornice_resource(rows_resource)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if cls.supports_quick_entry:
 | 
					        if cls.supports_quick_entry:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # quick entry
 | 
					            # quick entry
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Rattail -- Retail Software Framework
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
#  Copyright © 2010-2019 Lance Edgar
 | 
					#  Copyright © 2010-2020 Lance Edgar
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  This file is part of Rattail.
 | 
					#  This file is part of Rattail.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -43,15 +43,6 @@ class LabelBatchViews(APIBatchView):
 | 
				
			||||||
    object_url_prefix = '/label-batch'
 | 
					    object_url_prefix = '/label-batch'
 | 
				
			||||||
    supports_toggle_complete = True
 | 
					    supports_toggle_complete = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def collection_get(self):
 | 
					 | 
				
			||||||
        return self._collection_get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def collection_post(self):
 | 
					 | 
				
			||||||
        return self._collection_post()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self):
 | 
					 | 
				
			||||||
        return self._get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LabelBatchRowViews(APIBatchRowView):
 | 
					class LabelBatchRowViews(APIBatchRowView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,12 +65,6 @@ class LabelBatchRowViews(APIBatchRowView):
 | 
				
			||||||
        data['full_description'] = row.product.full_description if row.product else row.description
 | 
					        data['full_description'] = row.product.full_description if row.product else row.description
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def collection_get(self):
 | 
					 | 
				
			||||||
        return self._collection_get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self):
 | 
					 | 
				
			||||||
        return self._get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def includeme(config):
 | 
					def includeme(config):
 | 
				
			||||||
    LabelBatchViews.defaults(config)
 | 
					    LabelBatchViews.defaults(config)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -84,15 +84,6 @@ class OrderingBatchViews(APIBatchView):
 | 
				
			||||||
        batch = super(OrderingBatchViews, self).create_object(data)
 | 
					        batch = super(OrderingBatchViews, self).create_object(data)
 | 
				
			||||||
        return batch
 | 
					        return batch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def collection_get(self):
 | 
					 | 
				
			||||||
        return self._collection_get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def collection_post(self):
 | 
					 | 
				
			||||||
        return self._collection_post()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self):
 | 
					 | 
				
			||||||
        return self._get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OrderingBatchRowViews(APIBatchRowView):
 | 
					class OrderingBatchRowViews(APIBatchRowView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -103,6 +94,7 @@ class OrderingBatchRowViews(APIBatchRowView):
 | 
				
			||||||
    collection_url_prefix = '/ordering-batch-rows'
 | 
					    collection_url_prefix = '/ordering-batch-rows'
 | 
				
			||||||
    object_url_prefix = '/ordering-batch-row'
 | 
					    object_url_prefix = '/ordering-batch-row'
 | 
				
			||||||
    supports_quick_entry = True
 | 
					    supports_quick_entry = True
 | 
				
			||||||
 | 
					    editable = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def normalize(self, row):
 | 
					    def normalize(self, row):
 | 
				
			||||||
        batch = row.batch
 | 
					        batch = row.batch
 | 
				
			||||||
| 
						 | 
					@ -138,15 +130,6 @@ class OrderingBatchRowViews(APIBatchRowView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def collection_get(self):
 | 
					 | 
				
			||||||
        return self._collection_get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self):
 | 
					 | 
				
			||||||
        return self._get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def post(self):
 | 
					 | 
				
			||||||
        return self._post()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def update_object(self, row, data):
 | 
					    def update_object(self, row, data):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Overrides the default logic as follows:
 | 
					        Overrides the default logic as follows:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Rattail -- Retail Software Framework
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
#  Copyright © 2010-2019 Lance Edgar
 | 
					#  Copyright © 2010-2020 Lance Edgar
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  This file is part of Rattail.
 | 
					#  This file is part of Rattail.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -86,15 +86,6 @@ class ReceivingBatchViews(APIBatchView):
 | 
				
			||||||
        batch = super(ReceivingBatchViews, self).create_object(data)
 | 
					        batch = super(ReceivingBatchViews, self).create_object(data)
 | 
				
			||||||
        return batch
 | 
					        return batch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def collection_get(self):
 | 
					 | 
				
			||||||
        return self._collection_get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def collection_post(self):
 | 
					 | 
				
			||||||
        return self._collection_post()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self):
 | 
					 | 
				
			||||||
        return self._get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def mark_receiving_complete(self):
 | 
					    def mark_receiving_complete(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Mark the given batch as "receiving complete".
 | 
					        Mark the given batch as "receiving complete".
 | 
				
			||||||
| 
						 | 
					@ -148,6 +139,7 @@ class ReceivingBatchViews(APIBatchView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def defaults(cls, config):
 | 
					    def defaults(cls, config):
 | 
				
			||||||
 | 
					        cls._defaults(config)
 | 
				
			||||||
        cls._batch_defaults(config)
 | 
					        cls._batch_defaults(config)
 | 
				
			||||||
        cls._receiving_batch_defaults(config)
 | 
					        cls._receiving_batch_defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -340,12 +332,6 @@ class ReceivingBatchRowViews(APIBatchRowView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return data
 | 
					        return data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def collection_get(self):
 | 
					 | 
				
			||||||
        return self._collection_get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def get(self):
 | 
					 | 
				
			||||||
        return self._get()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def receive(self):
 | 
					    def receive(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        View which handles "receiving" against a particular batch row.
 | 
					        View which handles "receiving" against a particular batch row.
 | 
				
			||||||
| 
						 | 
					@ -375,6 +361,7 @@ class ReceivingBatchRowViews(APIBatchRowView):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @classmethod
 | 
					    @classmethod
 | 
				
			||||||
    def defaults(cls, config):
 | 
					    def defaults(cls, config):
 | 
				
			||||||
 | 
					        cls._defaults(config)
 | 
				
			||||||
        cls._batch_row_defaults(config)
 | 
					        cls._batch_row_defaults(config)
 | 
				
			||||||
        cls._receiving_batch_row_defaults(config)
 | 
					        cls._receiving_batch_row_defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Rattail -- Retail Software Framework
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
#  Copyright © 2010-2019 Lance Edgar
 | 
					#  Copyright © 2010-2020 Lance Edgar
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  This file is part of Rattail.
 | 
					#  This file is part of Rattail.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,7 @@ class APIMasterView(APIView):
 | 
				
			||||||
        Returns a prefix which (by default) applies to all permissions
 | 
					        Returns a prefix which (by default) applies to all permissions
 | 
				
			||||||
        leveraged by this view class.
 | 
					        leveraged by this view class.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        prefix = getattr(cls, 'permission_prefix')
 | 
					        prefix = getattr(cls, 'permission_prefix', None)
 | 
				
			||||||
        if prefix:
 | 
					        if prefix:
 | 
				
			||||||
            return prefix
 | 
					            return prefix
 | 
				
			||||||
        return cls.get_route_prefix()
 | 
					        return cls.get_route_prefix()
 | 
				
			||||||
| 
						 | 
					@ -371,6 +371,10 @@ class APIMasterView(APIView):
 | 
				
			||||||
    ##############################
 | 
					    ##############################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def autocomplete(self):
 | 
					    def autocomplete(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        View which accepts a single ``term`` param, and returns a list of
 | 
				
			||||||
 | 
					        autocomplete results to match.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
        term = self.request.params.get('term', '').strip()
 | 
					        term = self.request.params.get('term', '').strip()
 | 
				
			||||||
        term = self.prepare_autocomplete_term(term)
 | 
					        term = self.prepare_autocomplete_term(term)
 | 
				
			||||||
        if not term:
 | 
					        if not term:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										141
									
								
								tailbone/api/master2.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								tailbone/api/master2.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,141 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8; -*-
 | 
				
			||||||
 | 
					################################################################################
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
 | 
					#  Copyright © 2010-2020 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 <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					################################################################################
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Tailbone Web API - Master View (v2)
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from __future__ import unicode_literals, absolute_import
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from cornice import resource, Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from tailbone.api import APIMasterView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class APIMasterView2(APIMasterView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base class for data model REST API views.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    listable = True
 | 
				
			||||||
 | 
					    creatable = True
 | 
				
			||||||
 | 
					    viewable = True
 | 
				
			||||||
 | 
					    editable = True
 | 
				
			||||||
 | 
					    deletable = True
 | 
				
			||||||
 | 
					    supports_autocomplete = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def establish_method(cls, method_name):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Establish the given HTTP method for this Cornice Resource.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Cornice will auto-register any class methods for a resource, if they
 | 
				
			||||||
 | 
					        are named according to what it expects (i.e. 'get', 'collection_get'
 | 
				
			||||||
 | 
					        etc.).  Tailbone API tries to make things automagical for the sake of
 | 
				
			||||||
 | 
					        e.g. Poser logic, but in this case if we predefine all of these methods
 | 
				
			||||||
 | 
					        and then some subclass view wants to *not* allow one, it's not clear
 | 
				
			||||||
 | 
					        how to "undefine" it per se.  Or at least, the more straightforward
 | 
				
			||||||
 | 
					        thing (I think) is to not define such a method in the first place, if
 | 
				
			||||||
 | 
					        it was not wanted.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Enter ``establish_method()``, which is what finally "defines" each
 | 
				
			||||||
 | 
					        resource method according to what the subclass has declared via its
 | 
				
			||||||
 | 
					        various attributes (:attr:`creatable`, :attr:`deletable` etc.).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Note that you will not likely have any need to use this
 | 
				
			||||||
 | 
					        ``establish_method()`` yourself!  But we describe its purpose here, for
 | 
				
			||||||
 | 
					        clarity.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        def method(self):
 | 
				
			||||||
 | 
					            internal_method = getattr(self, '_{}'.format(method_name))
 | 
				
			||||||
 | 
					            return internal_method()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setattr(cls, method_name, method)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _delete(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        View to handle DELETE action for an existing record/object.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        obj = self.get_object()
 | 
				
			||||||
 | 
					        self.delete_object(obj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_object(self, obj):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Delete the object, or mark it as deleted, or whatever you need to do.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        # flush immediately to force any pending integrity errors etc.
 | 
				
			||||||
 | 
					        self.Session.delete(obj)
 | 
				
			||||||
 | 
					        self.Session.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def defaults(cls, config):
 | 
				
			||||||
 | 
					        cls._defaults(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _defaults(cls, config):
 | 
				
			||||||
 | 
					        route_prefix = cls.get_route_prefix()
 | 
				
			||||||
 | 
					        permission_prefix = cls.get_permission_prefix()
 | 
				
			||||||
 | 
					        collection_url_prefix = cls.get_collection_url_prefix()
 | 
				
			||||||
 | 
					        object_url_prefix = cls.get_object_url_prefix()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # first, the primary resource API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # list/search
 | 
				
			||||||
 | 
					        if cls.listable:
 | 
				
			||||||
 | 
					            cls.establish_method('collection_get')
 | 
				
			||||||
 | 
					            resource.add_view(cls.collection_get, permission='{}.list'.format(permission_prefix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # create
 | 
				
			||||||
 | 
					        if cls.creatable:
 | 
				
			||||||
 | 
					            cls.establish_method('collection_post')
 | 
				
			||||||
 | 
					            resource.add_view(cls.collection_post, permission='{}.create'.format(permission_prefix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # view
 | 
				
			||||||
 | 
					        if cls.viewable:
 | 
				
			||||||
 | 
					            cls.establish_method('get')
 | 
				
			||||||
 | 
					            resource.add_view(cls.get, permission='{}.view'.format(permission_prefix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # edit
 | 
				
			||||||
 | 
					        if cls.editable:
 | 
				
			||||||
 | 
					            cls.establish_method('post')
 | 
				
			||||||
 | 
					            resource.add_view(cls.post, permission='{}.edit'.format(permission_prefix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # delete
 | 
				
			||||||
 | 
					        if cls.deletable:
 | 
				
			||||||
 | 
					            cls.establish_method('delete')
 | 
				
			||||||
 | 
					            resource.add_view(cls.delete, permission='{}.delete'.format(permission_prefix))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # register primary resource API via cornice
 | 
				
			||||||
 | 
					        object_resource = resource.add_resource(
 | 
				
			||||||
 | 
					            cls,
 | 
				
			||||||
 | 
					            collection_path=collection_url_prefix,
 | 
				
			||||||
 | 
					            # TODO: probably should allow for other (composite?) key fields
 | 
				
			||||||
 | 
					            path='{}/{{uuid}}'.format(object_url_prefix))
 | 
				
			||||||
 | 
					        config.add_cornice_resource(object_resource)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # now for some more "custom" things, which are still somewhat generic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # autocomplete
 | 
				
			||||||
 | 
					        if cls.supports_autocomplete:
 | 
				
			||||||
 | 
					            autocomplete = Service(name='{}.autocomplete'.format(route_prefix),
 | 
				
			||||||
 | 
					                                   path='{}/autocomplete'.format(collection_url_prefix))
 | 
				
			||||||
 | 
					            autocomplete.add_view('GET', 'autocomplete', klass=cls)
 | 
				
			||||||
 | 
					            config.add_cornice_service(autocomplete)
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
################################################################################
 | 
					################################################################################
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  Rattail -- Retail Software Framework
 | 
					#  Rattail -- Retail Software Framework
 | 
				
			||||||
#  Copyright © 2010-2019 Lance Edgar
 | 
					#  Copyright © 2010-2020 Lance Edgar
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
#  This file is part of Rattail.
 | 
					#  This file is part of Rattail.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
| 
						 | 
					@ -27,18 +27,22 @@ Tailbone Web API - Product Views
 | 
				
			||||||
from __future__ import unicode_literals, absolute_import
 | 
					from __future__ import unicode_literals, absolute_import
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import six
 | 
					import six
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					from sqlalchemy import orm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from rattail.db import model
 | 
					from rattail.db import model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from cornice.resource import resource, view
 | 
					from tailbone.api import APIMasterView2 as APIMasterView
 | 
				
			||||||
 | 
					 | 
				
			||||||
from tailbone.api import APIMasterView
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@resource(collection_path='/products', path='/product/{uuid}')
 | 
					 | 
				
			||||||
class ProductView(APIMasterView):
 | 
					class ProductView(APIMasterView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    API views for Product data
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
    model_class = model.Product
 | 
					    model_class = model.Product
 | 
				
			||||||
 | 
					    collection_url_prefix = '/products'
 | 
				
			||||||
 | 
					    object_url_prefix = '/product'
 | 
				
			||||||
 | 
					    supports_autocomplete = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def normalize(self, product):
 | 
					    def normalize(self, product):
 | 
				
			||||||
        cost = product.cost
 | 
					        cost = product.cost
 | 
				
			||||||
| 
						 | 
					@ -55,22 +59,24 @@ class ProductView(APIMasterView):
 | 
				
			||||||
            'default_unit_cost_display': "${:0.2f}".format(cost.unit_cost) if cost and cost.unit_cost is not None else None,
 | 
					            'default_unit_cost_display': "${:0.2f}".format(cost.unit_cost) if cost and cost.unit_cost is not None else None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @view(permission='products.list')
 | 
					    def make_autocomplete_query(self, term):
 | 
				
			||||||
    def collection_get(self):
 | 
					        query = self.Session.query(model.Product)\
 | 
				
			||||||
        return self._collection_get()
 | 
					                            .outerjoin(model.Brand)\
 | 
				
			||||||
 | 
					                            .filter(sa.or_(
 | 
				
			||||||
 | 
					                                model.Brand.name.ilike('%{}%'.format(term)),
 | 
				
			||||||
 | 
					                                model.Product.description.ilike('%{}%'.format(term))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @view(permission='products.create')
 | 
					        if not self.request.has_perm('products.view_deleted'):
 | 
				
			||||||
    def collection_post(self):
 | 
					            query = query.filter(model.Product.deleted == False)
 | 
				
			||||||
        return self._collection_post()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @view(permission='products.view')
 | 
					        query = query.order_by(model.Brand.name,
 | 
				
			||||||
    def get(self):
 | 
					                               model.Product.description)\
 | 
				
			||||||
        return self._get()
 | 
					                     .options(orm.joinedload(model.Product.brand))
 | 
				
			||||||
 | 
					        return query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @view(permission='products.edit')
 | 
					    def autocomplete_display(self, product):
 | 
				
			||||||
    def post(self):
 | 
					        return product.full_description
 | 
				
			||||||
        return self._post()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def includeme(config):
 | 
					def includeme(config):
 | 
				
			||||||
    config.scan(__name__)
 | 
					    ProductView.defaults(config)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue