mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Improve deletion behaviour for InvenTreeTree model (#5806)
* Improve deletion behaviour for InvenTreeTree model - Remove recursive call to function - Handle database operations as bulk queries - Ensure child nodes have their pathstring updated correctly - Remove old @receiver hook - Refactor StockLocation.delete method - Refactor PartCategory.delete method - Atomic transactions potentially problematic here * Add docstring * Fix method name * Use bulk-update instead of recursive save when pathstring changes * Improvements for tree delete method - Handle case where item has already been deleted * Raise exception rather than simply logging * Update unit tests * Improvements to unrelated unit test * Fix urls.md * Fix typo
This commit is contained in:
		@@ -1418,13 +1418,19 @@ class LocationDetail(CustomRetrieveUpdateDestroyAPI):
 | 
			
		||||
 | 
			
		||||
    def destroy(self, request, *args, **kwargs):
 | 
			
		||||
        """Delete a Stock location instance via the API"""
 | 
			
		||||
        delete_stock_items = 'delete_stock_items' in request.data and request.data['delete_stock_items'] == '1'
 | 
			
		||||
        delete_sub_locations = 'delete_sub_locations' in request.data and request.data['delete_sub_locations'] == '1'
 | 
			
		||||
        return super().destroy(request,
 | 
			
		||||
                               *args,
 | 
			
		||||
                               **dict(kwargs,
 | 
			
		||||
                                      delete_sub_locations=delete_sub_locations,
 | 
			
		||||
                                      delete_stock_items=delete_stock_items))
 | 
			
		||||
 | 
			
		||||
        delete_stock_items = str(request.data.get('delete_stock_items', 0)) == '1'
 | 
			
		||||
        delete_sub_locations = str(request.data.get('delete_sub_locations', 0)) == '1'
 | 
			
		||||
 | 
			
		||||
        return super().destroy(
 | 
			
		||||
            request,
 | 
			
		||||
            *args,
 | 
			
		||||
            **dict(
 | 
			
		||||
                kwargs,
 | 
			
		||||
                delete_sub_locations=delete_sub_locations,
 | 
			
		||||
                delete_stock_items=delete_stock_items
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
stock_api_urls = [
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,8 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree):
 | 
			
		||||
    Stock locations can be hierarchical as required
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    ITEM_PARENT_KEY = 'location'
 | 
			
		||||
 | 
			
		||||
    objects = StockLocationManager()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
@@ -118,51 +120,16 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree):
 | 
			
		||||
 | 
			
		||||
    tags = TaggableManager(blank=True)
 | 
			
		||||
 | 
			
		||||
    def delete_recursive(self, *args, **kwargs):
 | 
			
		||||
        """This function handles the recursive deletion of sub-locations depending on kwargs contents"""
 | 
			
		||||
        delete_stock_items = kwargs.get('delete_stock_items', False)
 | 
			
		||||
        parent_location = kwargs.get('parent_location', None)
 | 
			
		||||
 | 
			
		||||
        if parent_location is None:
 | 
			
		||||
            # First iteration, (no parent_location kwargs passed)
 | 
			
		||||
            parent_location = self.parent
 | 
			
		||||
 | 
			
		||||
        for child_item in self.get_stock_items(False):
 | 
			
		||||
            if delete_stock_items:
 | 
			
		||||
                child_item.delete()
 | 
			
		||||
            else:
 | 
			
		||||
                child_item.location = parent_location
 | 
			
		||||
                child_item.save()
 | 
			
		||||
 | 
			
		||||
        for child_location in self.children.all():
 | 
			
		||||
            if kwargs.get('delete_sub_locations', False):
 | 
			
		||||
                child_location.delete_recursive(**{
 | 
			
		||||
                    "delete_sub_locations": True,
 | 
			
		||||
                    "delete_stock_items": delete_stock_items,
 | 
			
		||||
                    "parent_location": parent_location})
 | 
			
		||||
            else:
 | 
			
		||||
                child_location.parent = parent_location
 | 
			
		||||
                child_location.save()
 | 
			
		||||
 | 
			
		||||
        super().delete(*args, **{})
 | 
			
		||||
 | 
			
		||||
    def delete(self, *args, **kwargs):
 | 
			
		||||
        """Custom model deletion routine, which updates any child locations or items.
 | 
			
		||||
 | 
			
		||||
        This must be handled within a transaction.atomic(), otherwise the tree structure is damaged
 | 
			
		||||
        """
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
 | 
			
		||||
            self.delete_recursive(**{
 | 
			
		||||
                "delete_stock_items": kwargs.get('delete_stock_items', False),
 | 
			
		||||
                "delete_sub_locations": kwargs.get('delete_sub_locations', False),
 | 
			
		||||
                "parent_category": self.parent})
 | 
			
		||||
 | 
			
		||||
            if self.parent is not None:
 | 
			
		||||
                # Partially rebuild the tree (cheaper than a complete rebuild)
 | 
			
		||||
                StockLocation.objects.partial_rebuild(self.tree_id)
 | 
			
		||||
            else:
 | 
			
		||||
                StockLocation.objects.rebuild()
 | 
			
		||||
        super().delete(
 | 
			
		||||
            delete_children=kwargs.get('delete_sub_locations', False),
 | 
			
		||||
            delete_items=kwargs.get('delete_stock_items', False),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_api_url():
 | 
			
		||||
@@ -300,6 +267,10 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree):
 | 
			
		||||
        """
 | 
			
		||||
        return self.stock_item_count()
 | 
			
		||||
 | 
			
		||||
    def get_items(self, cascade=False):
 | 
			
		||||
        """Return a queryset for all stock items under this category"""
 | 
			
		||||
        return self.get_stock_items(cascade=cascade)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def generate_batch_code():
 | 
			
		||||
    """Generate a default 'batch code' for a new StockItem.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user