2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-18 02:36:31 +00:00

MPTT rebuild tweak (#10020)

* More efficient call

* Tree save fix

- Do not rebuild tree when not required
- Reduce DB count for API test

* Reduce query count even further

* Reduce query limit for other tests

* Fix for parent getter

* Added condition

* Remove duplicate check

* Don't pre-fill tree values

* perform atomic delete

* Adjust unit tests
This commit is contained in:
Oliver
2025-07-15 14:48:02 +10:00
committed by GitHub
parent f12479dd37
commit 24bff424d9
7 changed files with 32 additions and 22 deletions

View File

@@ -718,6 +718,7 @@ class InvenTreeTree(MPTTModel):
if parent:
# If we have a parent, use the parent's tree_id
self.tree_id = parent.tree_id
self.level = parent.level + 1
else:
# Otherwise, we need to generate a new tree_id
self.tree_id = self.getNextTreeID()
@@ -736,23 +737,27 @@ class InvenTreeTree(MPTTModel):
trees = set()
parent = getattr(self, self.NODE_PARENT_KEY, None)
if db_instance:
# If the tree_id or parent has changed, we need to rebuild the tree
if getattr(db_instance, self.NODE_PARENT_KEY) != getattr(
self, self.NODE_PARENT_KEY
):
if getattr(db_instance, self.NODE_PARENT_KEY) != parent:
trees.add(db_instance.tree_id)
if db_instance.tree_id != self.tree_id:
trees.add(self.tree_id)
trees.add(db_instance.tree_id)
else:
# New instance, so we need to rebuild the tree
elif parent:
# New instance, so we need to rebuild the tree (if it has a parent)
trees.add(self.tree_id)
for tree_id in trees:
if tree_id:
self.partial_rebuild(tree_id)
if len(trees) > 0:
# A tree update was performed, so we need to refresh the instance
self.refresh_from_db()
def partial_rebuild(self, tree_id: int) -> bool:
"""Perform a partial rebuild of the tree structure.

View File

@@ -166,6 +166,7 @@ class BuildTest(BuildAPITest):
# We shall complete 4 of these outputs
outputs = self.build.incomplete_outputs.all()
# TODO: (2025-07-15) Try to optimize this API query to reduce DB hits
self.post(
self.url,
{
@@ -174,7 +175,7 @@ class BuildTest(BuildAPITest):
'status': StockStatus.ATTENTION.value,
},
expected_code=201,
max_query_count=600, # TODO: Try to optimize this
max_query_count=400,
)
self.assertEqual(self.build.incomplete_outputs.count(), 0)
@@ -976,7 +977,7 @@ class BuildOverallocationTest(BuildAPITest):
self.url,
{'accept_overallocated': 'accept'},
expected_code=201,
max_query_count=1000, # TODO: Come back and refactor this
max_query_count=375,
)
self.build.refresh_from_db()
@@ -995,7 +996,7 @@ class BuildOverallocationTest(BuildAPITest):
self.url,
{'accept_overallocated': 'trim'},
expected_code=201,
max_query_count=1000, # TODO: Come back and refactor this
max_query_count=375,
)
# Note: Large number of queries is due to pricing recalculation for each stock item

View File

@@ -199,6 +199,8 @@ class BuildTreeTest(InvenTreeTestCase):
# Test the tree structure for each node
for idx, child in enumerate(builds):
child.refresh_from_db()
# Check parent-child relationships
expected_parent = builds[idx - 1] if idx > 0 else None
self.assertEqual(child.parent, expected_parent)
@@ -278,12 +280,16 @@ class BuildTreeTest(InvenTreeTestCase):
self.assertEqual(grandchild.tree_id, self.build.tree_id)
self.assertEqual(grandchild.level, 2)
child.refresh_from_db()
self.assertEqual(child.get_children().count(), 3)
self.assertEqual(child.get_descendants(include_self=False).count(), 3)
self.assertEqual(child.level, 1)
self.assertEqual(child.tree_id, self.build.tree_id)
self.build.refresh_from_db()
# Basic tests
self.assertEqual(Build.objects.count(), 13)
self.assertEqual(self.build.get_children().count(), 3)

View File

@@ -1182,8 +1182,7 @@ class PurchaseOrderReceiveTest(OrderTest):
n = StockItem.objects.count()
# TODO: 2024-12-10 - This API query needs to be refactored!
self.post(self.url, data, expected_code=201, max_query_count=500)
self.post(self.url, data, expected_code=201, max_query_count=275)
# Check that the expected number of stock items has been created
self.assertEqual(n + 11, StockItem.objects.count())

View File

@@ -267,7 +267,13 @@ class CategoryTest(TestCase):
and the correct ancestor tree is observed.
"""
# Clear out any existing parts
Part.objects.all().delete()
for p in Part.objects.all():
if p.active:
p.refresh_from_db()
p.active = False
p.save()
p.delete()
# First, create a structured tree of part categories
A = PartCategory.objects.create(name='A', description='Top level category')

View File

@@ -73,19 +73,12 @@ def register_event(event, *args, **kwargs):
registry.check_reload()
with transaction.atomic():
for slug, plugin in registry.plugins.items():
if not plugin.mixin_enabled(PluginMixinEnum.EVENTS):
continue
# Only allow event registering for 'active' plugins
if not plugin.is_active():
continue
for plugin in registry.with_mixin(PluginMixinEnum.EVENTS, active=True):
# Let the plugin decide if it wants to process this event
if not plugin.wants_process_event(event):
continue
logger.debug("Registering callback for plugin '%s'", slug)
logger.debug("Registering callback for plugin '%s'", plugin.slug)
# This task *must* be processed by the background worker,
# unless we are running CI tests
@@ -94,7 +87,7 @@ def register_event(event, *args, **kwargs):
# Offload a separate task for each plugin
offload_task(
process_event, slug, event, *args, group='plugin', **kwargs
process_event, plugin.slug, event, *args, group='plugin', **kwargs
)

View File

@@ -811,7 +811,7 @@ class StockItem(
super().save(*args, **kwargs)
# If user information is provided, and no existing note exists, create one!
if user and self.tracking_info.count() == 0:
if user and add_note and self.tracking_info.count() == 0:
tracking_info = {'status': self.status}
self.add_tracking_entry(