From 55338a2afe361b4f8d0f59fe84d8ea922d8edb6b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 22 Oct 2024 11:27:45 +0000 Subject: [PATCH 1/6] Add 'group' to offload_task - Make use of 'group' field in AsyncTask model - Allows better db filtering --- .../InvenTree/InvenTree/helpers_email.py | 1 + src/backend/InvenTree/InvenTree/tasks.py | 5 ++++- src/backend/InvenTree/build/models.py | 12 +++++++++--- src/backend/InvenTree/build/serializers.py | 4 +++- src/backend/InvenTree/build/tasks.py | 3 ++- src/backend/InvenTree/common/currency.py | 4 +++- src/backend/InvenTree/order/models.py | 1 + src/backend/InvenTree/part/models.py | 17 +++++++++++++---- src/backend/InvenTree/part/serializers.py | 1 + src/backend/InvenTree/part/tasks.py | 2 +- .../InvenTree/plugin/base/event/events.py | 6 ++++-- .../InvenTree/plugin/base/label/mixins.py | 7 ++++++- src/backend/InvenTree/plugin/base/locate/api.py | 7 ++++++- .../plugin/builtin/labels/inventree_machine.py | 4 +++- src/backend/InvenTree/plugin/models.py | 4 +++- src/backend/InvenTree/stock/models.py | 12 +++++++----- 16 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/helpers_email.py b/src/backend/InvenTree/InvenTree/helpers_email.py index 958ff9dd8840..679a25592850 100644 --- a/src/backend/InvenTree/InvenTree/helpers_email.py +++ b/src/backend/InvenTree/InvenTree/helpers_email.py @@ -86,4 +86,5 @@ def send_email(subject, body, recipients, from_email=None, html_message=None): recipients, fail_silently=False, html_message=html_message, + group='notification', ) diff --git a/src/backend/InvenTree/InvenTree/tasks.py b/src/backend/InvenTree/InvenTree/tasks.py index 76bcb2a3e3fc..85e89d64166c 100644 --- a/src/backend/InvenTree/InvenTree/tasks.py +++ b/src/backend/InvenTree/InvenTree/tasks.py @@ -174,6 +174,9 @@ def offload_task( """ from InvenTree.exceptions import log_error + # Extract group information from kwargs + group = kwargs.pop('group', 'inventree') + try: import importlib @@ -200,7 +203,7 @@ def offload_task( if force_async or (is_worker_running() and not force_sync): # Running as asynchronous task try: - task = AsyncTask(taskname, *args, **kwargs) + task = AsyncTask(taskname, *args, group=group, **kwargs) task.run() except ImportError: raise_warning(f"WARNING: '{taskname}' not offloaded - Function not found") diff --git a/src/backend/InvenTree/build/models.py b/src/backend/InvenTree/build/models.py index 21e1977d10f5..66a01c4004b9 100644 --- a/src/backend/InvenTree/build/models.py +++ b/src/backend/InvenTree/build/models.py @@ -645,7 +645,8 @@ def _action_complete(self, *args, **kwargs): if not InvenTree.tasks.offload_task( build.tasks.complete_build_allocations, self.pk, - user.pk if user else None + user.pk if user else None, + group='build' ): raise ValidationError(_("Failed to offload task to complete build allocations")) @@ -772,7 +773,8 @@ def _action_cancel(self, *args, **kwargs): if not InvenTree.tasks.offload_task( build.tasks.complete_build_allocations, self.pk, - user.pk if user else None + user.pk if user else None, + group='build', ): raise ValidationError(_("Failed to offload task to complete build allocations")) @@ -1441,7 +1443,11 @@ def after_save_build(sender, instance: Build, created: bool, **kwargs): instance.create_build_line_items() # Run checks on required parts - InvenTree.tasks.offload_task(build_tasks.check_build_stock, instance) + InvenTree.tasks.offload_task( + build_tasks.check_build_stock, + instance, + group='build' + ) # Notify the responsible users that the build order has been created InvenTree.helpers_model.notify_responsible(instance, sender, exclude=instance.issued_by) diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py index 4a7d6e971b8b..861277af16ca 100644 --- a/src/backend/InvenTree/build/serializers.py +++ b/src/backend/InvenTree/build/serializers.py @@ -191,6 +191,7 @@ def create(self, validated_data): InvenTree.tasks.offload_task( build.tasks.create_child_builds, build_order.pk, + group='build', ) return build_order @@ -1134,7 +1135,8 @@ def save(self): exclude_location=data.get('exclude_location', None), interchangeable=data['interchangeable'], substitutes=data['substitutes'], - optional_items=data['optional_items'] + optional_items=data['optional_items'], + group='build' ): raise ValidationError(_("Failed to start auto-allocation task")) diff --git a/src/backend/InvenTree/build/tasks.py b/src/backend/InvenTree/build/tasks.py index c347fc8b686a..d8db2db882f7 100644 --- a/src/backend/InvenTree/build/tasks.py +++ b/src/backend/InvenTree/build/tasks.py @@ -216,7 +216,8 @@ def create_child_builds(build_id: int) -> None: # Offload the child build order creation to the background task queue InvenTree.tasks.offload_task( create_child_builds, - sub_order.pk + sub_order.pk, + group='build' ) diff --git a/src/backend/InvenTree/common/currency.py b/src/backend/InvenTree/common/currency.py index 1769cf8c366e..c44d5019aab4 100644 --- a/src/backend/InvenTree/common/currency.py +++ b/src/backend/InvenTree/common/currency.py @@ -113,7 +113,9 @@ def after_change_currency(setting) -> None: InvenTree.tasks.update_exchange_rates(force=True) # Offload update of part prices to a background task - InvenTree.tasks.offload_task(part_tasks.check_missing_pricing, force_async=True) + InvenTree.tasks.offload_task( + part_tasks.check_missing_pricing, force_async=True, group='pricing' + ) def validate_currency_codes(value): diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 91186be139dc..3876822c0734 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -1919,6 +1919,7 @@ def complete_shipment(self, user, **kwargs): order.tasks.complete_sales_order_shipment, shipment_id=self.pk, user_id=user.pk if user else None, + group='sales_order', ) trigger_event('salesordershipment.completed', id=self.pk) diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 096bfc9c1bee..b0a1ffa004a9 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -2560,7 +2560,7 @@ def after_save_part(sender, instance: Part, created, **kwargs): # Run this check in the background try: InvenTree.tasks.offload_task( - part_tasks.notify_low_stock_if_required, instance + part_tasks.notify_low_stock_if_required, instance, group='notification' ) except PicklingError: # Can sometimes occur if the referenced Part has issues @@ -2568,7 +2568,10 @@ def after_save_part(sender, instance: Part, created, **kwargs): # Schedule a background task to rebuild any supplier parts InvenTree.tasks.offload_task( - part_tasks.rebuild_supplier_parts, instance.pk, force_async=True + part_tasks.rebuild_supplier_parts, + instance.pk, + force_async=True, + group='part', ) @@ -2705,6 +2708,7 @@ def schedule_for_update(self, counter: int = 0): self, counter=counter, force_async=background, + group='pricing', ) def update_pricing( @@ -3856,7 +3860,10 @@ def post_save_part_parameter_template(sender, instance, created, **kwargs): if not created: # Schedule a background task to rebuild the parameters against this template InvenTree.tasks.offload_task( - part_tasks.rebuild_parameters, instance.pk, force_async=True + part_tasks.rebuild_parameters, + instance.pk, + force_async=True, + group='part', ) @@ -4548,7 +4555,9 @@ def update_bom_build_lines(sender, instance, created, **kwargs): if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData(): import build.tasks - InvenTree.tasks.offload_task(build.tasks.update_build_order_lines, instance.pk) + InvenTree.tasks.offload_task( + build.tasks.update_build_order_lines, instance.pk, group='build' + ) @receiver(post_save, sender=BomItem, dispatch_uid='post_save_bom_item') diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py index ffc935abb380..760457ee20be 100644 --- a/src/backend/InvenTree/part/serializers.py +++ b/src/backend/InvenTree/part/serializers.py @@ -1316,6 +1316,7 @@ def save(self): exclude_external=data.get('exclude_external', True), generate_report=data.get('generate_report', True), update_parts=data.get('update_parts', True), + group='report', ) diff --git a/src/backend/InvenTree/part/tasks.py b/src/backend/InvenTree/part/tasks.py index 8ea76fd6a9cd..10f7652e3024 100644 --- a/src/backend/InvenTree/part/tasks.py +++ b/src/backend/InvenTree/part/tasks.py @@ -61,7 +61,7 @@ def notify_low_stock_if_required(part: part.models.Part): for p in parts: if p.is_part_low_on_stock(): - InvenTree.tasks.offload_task(notify_low_stock, p) + InvenTree.tasks.offload_task(notify_low_stock, p, group='notification') def update_part_pricing(pricing: part.models.PartPricing, counter: int = 0): diff --git a/src/backend/InvenTree/plugin/base/event/events.py b/src/backend/InvenTree/plugin/base/event/events.py index ab8c03017df7..992a0a50e250 100644 --- a/src/backend/InvenTree/plugin/base/event/events.py +++ b/src/backend/InvenTree/plugin/base/event/events.py @@ -40,7 +40,7 @@ def trigger_event(event, *args, **kwargs): if 'force_async' not in kwargs and not settings.PLUGIN_TESTING_EVENTS: kwargs['force_async'] = True - offload_task(register_event, event, *args, **kwargs) + offload_task(register_event, event, *args, group='plugin', **kwargs) def register_event(event, *args, **kwargs): @@ -77,7 +77,9 @@ def register_event(event, *args, **kwargs): kwargs['force_async'] = True # Offload a separate task for each plugin - offload_task(process_event, slug, event, *args, **kwargs) + offload_task( + process_event, slug, event, *args, group='plugin', **kwargs + ) def process_event(plugin_slug, event, *args, **kwargs): diff --git a/src/backend/InvenTree/plugin/base/label/mixins.py b/src/backend/InvenTree/plugin/base/label/mixins.py index 5e05247d637f..e6ef8ab5e7a1 100644 --- a/src/backend/InvenTree/plugin/base/label/mixins.py +++ b/src/backend/InvenTree/plugin/base/label/mixins.py @@ -185,7 +185,12 @@ def print_labels( # Exclude the 'context' object - cannot be pickled print_args.pop('context', None) - offload_task(plugin_label.print_label, self.plugin_slug(), **print_args) + offload_task( + plugin_label.print_label, + self.plugin_slug(), + group='plugin', + **print_args, + ) # Update the progress of the print job output.progress += int(100 / N) diff --git a/src/backend/InvenTree/plugin/base/locate/api.py b/src/backend/InvenTree/plugin/base/locate/api.py index 948d67bb6191..86962d543d62 100644 --- a/src/backend/InvenTree/plugin/base/locate/api.py +++ b/src/backend/InvenTree/plugin/base/locate/api.py @@ -59,7 +59,11 @@ def post(self, request, *args, **kwargs): StockItem.objects.get(pk=item_pk) offload_task( - registry.call_plugin_function, plugin, 'locate_stock_item', item_pk + registry.call_plugin_function, + plugin, + 'locate_stock_item', + item_pk, + group='plugin', ) data['item'] = item_pk @@ -78,6 +82,7 @@ def post(self, request, *args, **kwargs): plugin, 'locate_stock_location', location_pk, + group='plugin', ) data['location'] = location_pk diff --git a/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py b/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py index f147edad3c07..86ebcd71eceb 100644 --- a/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py +++ b/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py @@ -95,7 +95,9 @@ def print_labels(self, label: LabelTemplate, output, items, request, **kwargs): if driver.USE_BACKGROUND_WORKER is False: return driver.print_labels(machine, label, items, **print_kwargs) - offload_task(driver.print_labels, machine, label, items, **print_kwargs) + offload_task( + driver.print_labels, machine, label, items, group='plugin', **print_kwargs + ) return JsonResponse({ 'success': True, diff --git a/src/backend/InvenTree/plugin/models.py b/src/backend/InvenTree/plugin/models.py index 2d7af8860db8..58c08101fdbd 100644 --- a/src/backend/InvenTree/plugin/models.py +++ b/src/backend/InvenTree/plugin/models.py @@ -236,7 +236,9 @@ def activate(self, active: bool) -> None: if active: offload_task(check_for_migrations) - offload_task(plugin.staticfiles.copy_plugin_static_files, self.key) + offload_task( + plugin.staticfiles.copy_plugin_static_files, self.key, group='plugin' + ) class PluginSetting(common.models.BaseInvenTreeSetting): diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index e39f6511b4d1..b332eea03e11 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -1680,7 +1680,7 @@ def serializeStock(self, quantity, serials, user, notes='', location=None): # Rebuild the stock tree InvenTree.tasks.offload_task( - stock.tasks.rebuild_stock_item_tree, tree_id=self.tree_id + stock.tasks.rebuild_stock_item_tree, tree_id=self.tree_id, group='part' ) @transaction.atomic @@ -1915,7 +1915,7 @@ def merge_stock_items(self, other_items, raise_error=False, **kwargs): # Rebuild stock trees as required for tree_id in tree_ids: InvenTree.tasks.offload_task( - stock.tasks.rebuild_stock_item_tree, tree_id=tree_id + stock.tasks.rebuild_stock_item_tree, tree_id=tree_id, group='stock' ) @transaction.atomic @@ -2012,7 +2012,7 @@ def splitStock(self, quantity, location=None, user=None, **kwargs): # Rebuild the tree for this parent item InvenTree.tasks.offload_task( - stock.tasks.rebuild_stock_item_tree, tree_id=self.tree_id + stock.tasks.rebuild_stock_item_tree, tree_id=self.tree_id, group='stock' ) # Attempt to reload the new item from the database @@ -2405,7 +2405,7 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs): if InvenTree.ready.canAppAccessDatabase(allow_test=True): # Run this check in the background InvenTree.tasks.offload_task( - part_tasks.notify_low_stock_if_required, instance.part + part_tasks.notify_low_stock_if_required, instance.part, group='notification' ) if InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING): @@ -2422,7 +2422,9 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs): if created and not InvenTree.ready.isImportingData(): if InvenTree.ready.canAppAccessDatabase(allow_test=True): InvenTree.tasks.offload_task( - part_tasks.notify_low_stock_if_required, instance.part + part_tasks.notify_low_stock_if_required, + instance.part, + group='notification', ) if InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING): From 72493fd0c7b3f21454f259869b414687a1926a79 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 22 Oct 2024 11:35:55 +0000 Subject: [PATCH 2/6] Log error if low_stock check cannot be performed --- src/backend/InvenTree/part/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index b0a1ffa004a9..cb69e2e4861b 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -2564,7 +2564,7 @@ def after_save_part(sender, instance: Part, created, **kwargs): ) except PicklingError: # Can sometimes occur if the referenced Part has issues - pass + log_error('after_save_part') # Schedule a background task to rebuild any supplier parts InvenTree.tasks.offload_task( From 3c265eeda7b317476fded339480c14a58a731991 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 22 Oct 2024 11:38:03 +0000 Subject: [PATCH 3/6] Ensure low-stock checks are performed by the background worker --- src/backend/InvenTree/part/models.py | 5 ++++- src/backend/InvenTree/stock/models.py | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index cb69e2e4861b..3a1f711183c8 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -2560,7 +2560,10 @@ def after_save_part(sender, instance: Part, created, **kwargs): # Run this check in the background try: InvenTree.tasks.offload_task( - part_tasks.notify_low_stock_if_required, instance, group='notification' + part_tasks.notify_low_stock_if_required, + instance, + group='notification', + force_async=True, ) except PicklingError: # Can sometimes occur if the referenced Part has issues diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index b332eea03e11..2428b898492f 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -2405,7 +2405,10 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs): if InvenTree.ready.canAppAccessDatabase(allow_test=True): # Run this check in the background InvenTree.tasks.offload_task( - part_tasks.notify_low_stock_if_required, instance.part, group='notification' + part_tasks.notify_low_stock_if_required, + instance.part, + group='notification', + force_async=True, ) if InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING): @@ -2425,6 +2428,7 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs): part_tasks.notify_low_stock_if_required, instance.part, group='notification', + force_async=True, ) if InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING): From 306afb516fd6c74b6b1742c16a2482835bcebb7f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 22 Oct 2024 11:42:02 +0000 Subject: [PATCH 4/6] Change encoding of arguments to 'notify_low_stock_if_required' - Pass part ID, not part instance - Safer, but requires DB hit --- src/backend/InvenTree/part/models.py | 2 +- src/backend/InvenTree/part/tasks.py | 37 +++++++++++++++++---------- src/backend/InvenTree/stock/models.py | 4 +-- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 3a1f711183c8..feef00fe6182 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -2561,7 +2561,7 @@ def after_save_part(sender, instance: Part, created, **kwargs): try: InvenTree.tasks.offload_task( part_tasks.notify_low_stock_if_required, - instance, + instance.pk, group='notification', force_async=True, ) diff --git a/src/backend/InvenTree/part/tasks.py b/src/backend/InvenTree/part/tasks.py index 10f7652e3024..43f2db6fa03c 100644 --- a/src/backend/InvenTree/part/tasks.py +++ b/src/backend/InvenTree/part/tasks.py @@ -8,13 +8,14 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ +import part_models as part_models + import common.currency import common.notifications import company.models import InvenTree.helpers import InvenTree.helpers_model import InvenTree.tasks -import part.models import part.stocktake from common.settings import get_global_setting from InvenTree.tasks import ( @@ -27,7 +28,7 @@ logger = logging.getLogger('inventree') -def notify_low_stock(part: part.models.Part): +def notify_low_stock(part: part_models.Part): """Notify interested users that a part is 'low stock'. Rules: @@ -51,11 +52,19 @@ def notify_low_stock(part: part.models.Part): ) -def notify_low_stock_if_required(part: part.models.Part): +def notify_low_stock_if_required(part_id: int): """Check if the stock quantity has fallen below the minimum threshold of part. If true, notify the users who have subscribed to the part """ + try: + part = part_models.Part.objects.get(pk=part_id) + except part_models.Part.DoesNotExist: + logger.warning( + 'notify_low_stock_if_required: Part with ID %s does not exist', part_id + ) + return + # Run "up" the tree, to allow notification for "parent" parts parts = part.get_ancestors(include_self=True, ascending=True) @@ -64,7 +73,7 @@ def notify_low_stock_if_required(part: part.models.Part): InvenTree.tasks.offload_task(notify_low_stock, p, group='notification') -def update_part_pricing(pricing: part.models.PartPricing, counter: int = 0): +def update_part_pricing(pricing: part_models.PartPricing, counter: int = 0): """Update cached pricing data for the specified PartPricing instance. Arguments: @@ -93,7 +102,7 @@ def check_missing_pricing(limit=250): limit: Maximum number of parts to process at once """ # Find parts for which pricing information has never been updated - results = part.models.PartPricing.objects.filter(updated=None)[:limit] + results = part_models.PartPricing.objects.filter(updated=None)[:limit] if results.count() > 0: logger.info('Found %s parts with empty pricing', results.count()) @@ -105,7 +114,7 @@ def check_missing_pricing(limit=250): days = int(get_global_setting('PRICING_UPDATE_DAYS', 30)) stale_date = datetime.now().date() - timedelta(days=days) - results = part.models.PartPricing.objects.filter(updated__lte=stale_date)[:limit] + results = part_models.PartPricing.objects.filter(updated__lte=stale_date)[:limit] if results.count() > 0: logger.info('Found %s stale pricing entries', results.count()) @@ -115,7 +124,7 @@ def check_missing_pricing(limit=250): # Find any pricing data which is in the wrong currency currency = common.currency.currency_code_default() - results = part.models.PartPricing.objects.exclude(currency=currency) + results = part_models.PartPricing.objects.exclude(currency=currency) if results.count() > 0: logger.info('Found %s pricing entries in the wrong currency', results.count()) @@ -124,7 +133,7 @@ def check_missing_pricing(limit=250): pp.schedule_for_update() # Find any parts which do not have pricing information - results = part.models.Part.objects.filter(pricing_data=None)[:limit] + results = part_models.Part.objects.filter(pricing_data=None)[:limit] if results.count() > 0: logger.info('Found %s parts without pricing', results.count()) @@ -152,7 +161,7 @@ def scheduled_stocktake_reports(): get_global_setting('STOCKTAKE_DELETE_REPORT_DAYS', 30, cache=False) ) threshold = datetime.now() - timedelta(days=delete_n_days) - old_reports = part.models.PartStocktakeReport.objects.filter(date__lt=threshold) + old_reports = part_models.PartStocktakeReport.objects.filter(date__lt=threshold) if old_reports.count() > 0: logger.info('Deleting %s stale stocktake reports', old_reports.count()) @@ -187,11 +196,11 @@ def rebuild_parameters(template_id): which may cause the base unit to be adjusted. """ try: - template = part.models.PartParameterTemplate.objects.get(pk=template_id) - except part.models.PartParameterTemplate.DoesNotExist: + template = part_models.PartParameterTemplate.objects.get(pk=template_id) + except part_models.PartParameterTemplate.DoesNotExist: return - parameters = part.models.PartParameter.objects.filter(template=template) + parameters = part_models.PartParameter.objects.filter(template=template) n = 0 @@ -216,8 +225,8 @@ def rebuild_supplier_parts(part_id): which may cause the native units of any supplier parts to be updated """ try: - prt = part.models.Part.objects.get(pk=part_id) - except part.models.Part.DoesNotExist: + prt = part_models.Part.objects.get(pk=part_id) + except part_models.Part.DoesNotExist: return supplier_parts = company.models.SupplierPart.objects.filter(part=prt) diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 2428b898492f..148c86057fe3 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -2406,7 +2406,7 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs): # Run this check in the background InvenTree.tasks.offload_task( part_tasks.notify_low_stock_if_required, - instance.part, + instance.part.pk, group='notification', force_async=True, ) @@ -2426,7 +2426,7 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs): if InvenTree.ready.canAppAccessDatabase(allow_test=True): InvenTree.tasks.offload_task( part_tasks.notify_low_stock_if_required, - instance.part, + instance.part.pk, group='notification', force_async=True, ) From 28dbbed89e6499c979fb58299dbfce24d0d228df Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 22 Oct 2024 12:37:33 +0000 Subject: [PATCH 5/6] Fix typo --- src/backend/InvenTree/part/tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/InvenTree/part/tasks.py b/src/backend/InvenTree/part/tasks.py index 43f2db6fa03c..a7e569960e2e 100644 --- a/src/backend/InvenTree/part/tasks.py +++ b/src/backend/InvenTree/part/tasks.py @@ -8,14 +8,13 @@ from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ -import part_models as part_models - import common.currency import common.notifications import company.models import InvenTree.helpers import InvenTree.helpers_model import InvenTree.tasks +import part.models as part_models import part.stocktake from common.settings import get_global_setting from InvenTree.tasks import ( From 6a3ce9a040bf5ec97bed1c2e17205d84d37c8856 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 23 Oct 2024 12:33:11 +0000 Subject: [PATCH 6/6] Fix to allow tests to run --- src/backend/InvenTree/part/models.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index feef00fe6182..20758957ff62 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -2550,7 +2550,7 @@ def is_part_low_on_stock(self): @receiver(post_save, sender=Part, dispatch_uid='part_post_save_log') def after_save_part(sender, instance: Part, created, **kwargs): """Function to be executed after a Part is saved.""" - from pickle import PicklingError + from django.conf import settings from part import tasks as part_tasks @@ -2558,16 +2558,12 @@ def after_save_part(sender, instance: Part, created, **kwargs): # Check part stock only if we are *updating* the part (not creating it) # Run this check in the background - try: - InvenTree.tasks.offload_task( - part_tasks.notify_low_stock_if_required, - instance.pk, - group='notification', - force_async=True, - ) - except PicklingError: - # Can sometimes occur if the referenced Part has issues - log_error('after_save_part') + InvenTree.tasks.offload_task( + part_tasks.notify_low_stock_if_required, + instance.pk, + group='notification', + force_async=not settings.TESTING, # Force async unless in testing mode + ) # Schedule a background task to rebuild any supplier parts InvenTree.tasks.offload_task(