mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +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:
		| @@ -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. | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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()) | ||||
|   | ||||
| @@ -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') | ||||
|   | ||||
| @@ -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 | ||||
|                 ) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user