2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 11:10:54 +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:
Oliver
2023-10-30 06:57:40 +11:00
committed by GitHub
parent a1f9260da6
commit fd0a57c4a1
6 changed files with 254 additions and 114 deletions

View File

@ -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 = [

View File

@ -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.