diff --git a/src/backend/InvenTree/InvenTree/exceptions.py b/src/backend/InvenTree/InvenTree/exceptions.py index efe941ddfd..c9ff0ea2c5 100644 --- a/src/backend/InvenTree/InvenTree/exceptions.py +++ b/src/backend/InvenTree/InvenTree/exceptions.py @@ -115,10 +115,9 @@ def exception_handler(exc, context): log_error(context['request'].path) - if response is not None: - # Convert errors returned under the label '__all__' to 'non_field_errors' - if '__all__' in response.data: - response.data['non_field_errors'] = response.data['__all__'] - del response.data['__all__'] + # Convert errors returned under the label '__all__' to 'non_field_errors' + if response is not None and '__all__' in response.data: + response.data['non_field_errors'] = response.data['__all__'] + del response.data['__all__'] return response diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index c8512d6168..2152187bbc 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -184,9 +184,12 @@ def getSplashScreen(custom=True): """Return the InvenTree splash screen, or a custom splash if available.""" static_storage = StaticFilesStorage() - if custom and settings.CUSTOM_SPLASH: - if static_storage.exists(settings.CUSTOM_SPLASH): - return static_storage.url(settings.CUSTOM_SPLASH) + if ( + custom + and settings.CUSTOM_SPLASH + and static_storage.exists(settings.CUSTOM_SPLASH) + ): + return static_storage.url(settings.CUSTOM_SPLASH) # No custom splash screen return static_storage.url('img/inventree_splash.jpg') diff --git a/src/backend/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py b/src/backend/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py index 93f12f3436..1337d781f6 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py +++ b/src/backend/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py @@ -34,10 +34,9 @@ class Command(BaseCommand): if x and x.path: img_paths.append(x.path) - if len(img_paths) > 0: - if all(os.path.exists(path) for path in img_paths): - # All images exist - skip further work - return + if len(img_paths) > 0 and all(os.path.exists(path) for path in img_paths): + # All images exist - skip further work + return logger.info("Generating thumbnail image for '%s'", img) diff --git a/src/backend/InvenTree/InvenTree/middleware.py b/src/backend/InvenTree/InvenTree/middleware.py index e528f4aa96..226fa85d12 100644 --- a/src/backend/InvenTree/InvenTree/middleware.py +++ b/src/backend/InvenTree/InvenTree/middleware.py @@ -28,10 +28,12 @@ def get_token_from_request(request): if auth_header := request.headers.get(k, None): auth_header = auth_header.strip().lower().split() - if len(auth_header) > 1: - if auth_header[0].strip().lower().replace(':', '') in token_keys: - token = auth_header[1] - return token + if ( + len(auth_header) > 1 + and auth_header[0].strip().lower().replace(':', '') in token_keys + ): + token = auth_header[1] + return token return None diff --git a/src/backend/InvenTree/InvenTree/sentry.py b/src/backend/InvenTree/InvenTree/sentry.py index 18f65892a2..eaf97dd459 100644 --- a/src/backend/InvenTree/InvenTree/sentry.py +++ b/src/backend/InvenTree/InvenTree/sentry.py @@ -63,11 +63,14 @@ def init_sentry(dsn, sample_rate, tags): def report_exception(exc): """Report an exception to sentry.io.""" - if settings.SENTRY_ENABLED and settings.SENTRY_DSN: - if not any(isinstance(exc, e) for e in sentry_ignore_errors()): - logger.info('Reporting exception to sentry.io: %s', exc) + if ( + settings.SENTRY_ENABLED + and settings.SENTRY_DSN + and not any(isinstance(exc, e) for e in sentry_ignore_errors()) + ): + logger.info('Reporting exception to sentry.io: %s', exc) - try: - sentry_sdk.capture_exception(exc) - except Exception: - logger.warning('Failed to report exception to sentry.io') + try: + sentry_sdk.capture_exception(exc) + except Exception: + logger.warning('Failed to report exception to sentry.io') diff --git a/src/backend/InvenTree/InvenTree/sso.py b/src/backend/InvenTree/InvenTree/sso.py index 9279d58198..6cd993738b 100644 --- a/src/backend/InvenTree/InvenTree/sso.py +++ b/src/backend/InvenTree/InvenTree/sso.py @@ -50,11 +50,10 @@ def check_provider(provider): if not app: return False - if allauth.app_settings.SITES_ENABLED: - # At least one matching site must be specified - if not app.sites.exists(): - logger.error('SocialApp %s has no sites configured', app) - return False + # At least one matching site must be specified + if allauth.app_settings.SITES_ENABLED and not app.sites.exists(): + logger.error('SocialApp %s has no sites configured', app) + return False # At this point, we assume that the provider is correctly configured return True diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py index 315211587d..adf8127d98 100644 --- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py +++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py @@ -136,12 +136,11 @@ def inventree_in_debug_mode(*args, **kwargs): @register.simple_tag() def inventree_show_about(user, *args, **kwargs): """Return True if the about modal should be shown.""" - if get_global_setting('INVENTREE_RESTRICT_ABOUT'): - # Return False if the user is not a superuser, or no user information is provided - if not user or not user.is_superuser: - return False - - return True + # Return False if the user is not a superuser, or no user information is provided + return not ( + (get_global_setting('INVENTREE_RESTRICT_ABOUT') and not user) + or not user.is_superuser + ) @register.simple_tag() diff --git a/src/backend/InvenTree/InvenTree/validators.py b/src/backend/InvenTree/InvenTree/validators.py index 8c4cb9c2ad..7be5c88d94 100644 --- a/src/backend/InvenTree/InvenTree/validators.py +++ b/src/backend/InvenTree/InvenTree/validators.py @@ -69,11 +69,10 @@ class AllowedURLValidator(validators.URLValidator): # Determine if 'strict' URL validation is required (i.e. if the URL must have a schema prefix) strict_urls = get_global_setting('INVENTREE_STRICT_URLS', cache=False) - if not strict_urls: - # Allow URLs which do not have a provided schema - if '://' not in value: - # Validate as if it were http - value = 'http://' + value + # Allow URLs which do not have a provided schema + if not strict_urls and '://' not in value: + # Validate as if it were http + value = 'http://' + value super().__call__(value) diff --git a/src/backend/InvenTree/InvenTree/views.py b/src/backend/InvenTree/InvenTree/views.py index b53990be20..ef9e6d5061 100644 --- a/src/backend/InvenTree/InvenTree/views.py +++ b/src/backend/InvenTree/InvenTree/views.py @@ -426,20 +426,21 @@ class SetPasswordView(AjaxUpdateView): old_password = request.POST.get('old_password', '') user = self.request.user - if valid: - # Passwords must match + # Passwords must match + if valid and p1 != p2: + error = _('Password fields must match') + form.add_error('enter_password', error) + form.add_error('confirm_password', error) + valid = False - if p1 != p2: - error = _('Password fields must match') - form.add_error('enter_password', error) - form.add_error('confirm_password', error) - valid = False - - if valid: - # Old password must be correct - if user.has_usable_password() and not user.check_password(old_password): - form.add_error('old_password', _('Wrong password provided')) - valid = False + # Old password must be correct + if ( + valid + and user.has_usable_password() + and not user.check_password(old_password) + ): + form.add_error('old_password', _('Wrong password provided')) + valid = False if valid: try: diff --git a/src/backend/InvenTree/common/tests.py b/src/backend/InvenTree/common/tests.py index 072cc06081..c2df112fde 100644 --- a/src/backend/InvenTree/common/tests.py +++ b/src/backend/InvenTree/common/tests.py @@ -444,11 +444,10 @@ class SettingsTest(InvenTreeTestCase): # Any fields marked as 'boolean' must have a default value specified setting = InvenTreeSetting.get_setting_object(key) - if setting.is_bool(): - if setting.default_value not in [True, False]: - raise ValueError( - f'Non-boolean default value specified for {key}' - ) # pragma: no cover + if setting.is_bool() and setting.default_value not in [True, False]: + raise ValueError( + f'Non-boolean default value specified for {key}' + ) # pragma: no cover def test_global_setting_caching(self): """Test caching operations for the global settings class.""" diff --git a/src/backend/InvenTree/company/models.py b/src/backend/InvenTree/company/models.py index 11c333c254..010fbc0a96 100644 --- a/src/backend/InvenTree/company/models.py +++ b/src/backend/InvenTree/company/models.py @@ -730,13 +730,16 @@ class SupplierPart( raise ValidationError({'pack_quantity': e.messages}) # Ensure that the linked manufacturer_part points to the same part! - if self.manufacturer_part and self.part: - if self.manufacturer_part.part != self.part: - raise ValidationError({ - 'manufacturer_part': _( - 'Linked manufacturer part must reference the same base part' - ) - }) + if ( + self.manufacturer_part + and self.part + and self.manufacturer_part.part != self.part + ): + raise ValidationError({ + 'manufacturer_part': _( + 'Linked manufacturer part must reference the same base part' + ) + }) def save(self, *args, **kwargs): """Overriding save method to connect an existing ManufacturerPart.""" @@ -1049,9 +1052,13 @@ class SupplierPriceBreak(common.models.PriceBreak): ) def after_save_supplier_price(sender, instance, created, **kwargs): """Callback function when a SupplierPriceBreak is created or updated.""" - if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData(): - if instance.part and instance.part.part: - instance.part.part.schedule_pricing_update(create=True) + if ( + InvenTree.ready.canAppAccessDatabase() + and not InvenTree.ready.isImportingData() + and instance.part + and instance.part.part + ): + instance.part.part.schedule_pricing_update(create=True) @receiver( @@ -1061,6 +1068,10 @@ def after_save_supplier_price(sender, instance, created, **kwargs): ) def after_delete_supplier_price(sender, instance, **kwargs): """Callback function when a SupplierPriceBreak is deleted.""" - if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData(): - if instance.part and instance.part.part: - instance.part.part.schedule_pricing_update(create=False) + if ( + InvenTree.ready.canAppAccessDatabase() + and not InvenTree.ready.isImportingData() + and instance.part + and instance.part.part + ): + instance.part.part.schedule_pricing_update(create=False) diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 374941afbc..c332fbcfb1 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -1350,15 +1350,13 @@ class OrderCalendarExport(ICalFeed): # No login yet - check in headers if 'authorization' in request.headers: auth = request.headers['authorization'].split() - if len(auth) == 2: - # NOTE: We are only support basic authentication for now. - # - if auth[0].lower() == 'basic': - uname, passwd = base64.b64decode(auth[1]).decode('ascii').split(':') - user = authenticate(username=uname, password=passwd) - if user is not None and user.is_active: - login(request, user) - request.user = user + # NOTE: We are only support basic authentication for now. + if len(auth) == 2 and auth[0].lower() == 'basic': + uname, passwd = base64.b64decode(auth[1]).decode('ascii').split(':') + user = authenticate(username=uname, password=passwd) + if user is not None and user.is_active: + login(request, user) + request.user = user # Check again if request.user.is_authenticated: diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 10c17be50a..8ba6f174aa 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -232,19 +232,20 @@ class Order( super().clean() # Check if a responsible owner is required for this order type - if self.REQUIRE_RESPONSIBLE_SETTING: - if get_global_setting(self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False): - if not self.responsible: - raise ValidationError({ - 'responsible': _('Responsible user or group must be specified') - }) + if ( + self.REQUIRE_RESPONSIBLE_SETTING + and get_global_setting(self.REQUIRE_RESPONSIBLE_SETTING, backup_value=False) + and not self.responsible + ): + raise ValidationError({ + 'responsible': _('Responsible user or group must be specified') + }) # Check that the referenced 'contact' matches the correct 'company' - if self.company and self.contact: - if self.contact.company != self.company: - raise ValidationError({ - 'contact': _('Contact does not match selected company') - }) + if self.company and self.contact and self.contact.company != self.company: + raise ValidationError({ + 'contact': _('Contact does not match selected company') + }) def report_context(self): """Generate context data for the reporting interface.""" @@ -783,10 +784,9 @@ class PurchaseOrder(TotalPriceMixin, Order): # Extract optional packaging field packaging = kwargs.get('packaging', None) - if not packaging: - # Default to the packaging field for the linked supplier part - if line.part: - packaging = line.part.packaging + # Default to the packaging field for the linked supplier part + if not packaging and line.part: + packaging = line.part.packaging # Extract optional barcode field barcode = kwargs.get('barcode', None) @@ -866,10 +866,11 @@ class PurchaseOrder(TotalPriceMixin, Order): line.save() # Has this order been completed? - if len(self.pending_line_items()) == 0: - if get_global_setting('PURCHASEORDER_AUTO_COMPLETE', True): - self.received_by = user - self.complete_order() # This will save the model + if len(self.pending_line_items()) == 0 and get_global_setting( + 'PURCHASEORDER_AUTO_COMPLETE', True + ): + self.received_by = user + self.complete_order() # This will save the model # Issue a notification to interested parties, that this order has been "updated" notify_responsible( @@ -1455,10 +1456,13 @@ class PurchaseOrderLineItem(OrderLineItem): """ super().clean() - if self.order.supplier and self.part: - # Supplier part *must* point to the same supplier! - if self.part.supplier != self.order.supplier: - raise ValidationError({'part': _('Supplier part must match supplier')}) + # Supplier part *must* point to the same supplier! + if ( + self.order.supplier + and self.part + and self.part.supplier != self.order.supplier + ): + raise ValidationError({'part': _('Supplier part must match supplier')}) def __str__(self): """Render a string representation of a PurchaseOrderLineItem instance.""" diff --git a/src/backend/InvenTree/part/bom.py b/src/backend/InvenTree/part/bom.py index 58359e89df..bcda038adf 100644 --- a/src/backend/InvenTree/part/bom.py +++ b/src/backend/InvenTree/part/bom.py @@ -91,9 +91,10 @@ def ExportBom( bom_items.append(item) - if cascade and item.sub_part.assembly: - if max_levels is None or level < max_levels: - add_items(item.sub_part.bom_items.all().order_by('id'), level + 1) + if ( + cascade and item.sub_part.assembly and max_levels is None + ) or level < max_levels: + add_items(item.sub_part.bom_items.all().order_by('id'), level + 1) top_level_items = part.get_bom_items().order_by('id') diff --git a/src/backend/InvenTree/part/models.py b/src/backend/InvenTree/part/models.py index 887e8f176f..15cd9e9af9 100644 --- a/src/backend/InvenTree/part/models.py +++ b/src/backend/InvenTree/part/models.py @@ -515,11 +515,13 @@ class Part( if self.active: raise ValidationError(_('Cannot delete this part as it is still active')) - if not get_global_setting('PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False): - if BomItem.objects.filter(sub_part=self).exists(): - raise ValidationError( - _('Cannot delete this part as it is used in an assembly') - ) + if ( + not get_global_setting('PART_ALLOW_DELETE_FROM_ASSEMBLY', cache=False) + and BomItem.objects.filter(sub_part=self).exists() + ): + raise ValidationError( + _('Cannot delete this part as it is used in an assembly') + ) super().delete() @@ -711,13 +713,12 @@ class Part( 'revision': _('Revision code must be specified') }) - if get_global_setting('PART_REVISION_ASSEMBLY_ONLY'): - if not self.assembly or not self.revision_of.assembly: - raise ValidationError({ - 'revision_of': _( - 'Revisions are only allowed for assembly parts' - ) - }) + if ( + get_global_setting('PART_REVISION_ASSEMBLY_ONLY') and not self.assembly + ) or not self.revision_of.assembly: + raise ValidationError({ + 'revision_of': _('Revisions are only allowed for assembly parts') + }) # Cannot have a revision of a "template" part if self.revision_of.is_template: @@ -2753,13 +2754,15 @@ class PartPricing(common.models.MetaMixin): sub_part_min = self.convert(sub_part_pricing.overall_min) sub_part_max = self.convert(sub_part_pricing.overall_max) - if sub_part_min is not None: - if bom_item_min is None or sub_part_min < bom_item_min: - bom_item_min = sub_part_min + if ( + sub_part_min is not None and bom_item_min is None + ) or sub_part_min < bom_item_min: + bom_item_min = sub_part_min - if sub_part_max is not None: - if bom_item_max is None or sub_part_max > bom_item_max: - bom_item_max = sub_part_max + if ( + sub_part_max is not None and bom_item_max is None + ) or sub_part_max > bom_item_max: + bom_item_max = sub_part_max # Update cumulative totals if bom_item_min is not None: @@ -2962,13 +2965,11 @@ class PartPricing(common.models.MetaMixin): v_min = self.convert(v.pricing.overall_min) v_max = self.convert(v.pricing.overall_max) - if v_min is not None: - if variant_min is None or v_min < variant_min: - variant_min = v_min + if (v_min is not None and variant_min is None) or v_min < variant_min: + variant_min = v_min - if v_max is not None: - if variant_max is None or v_max > variant_max: - variant_max = v_max + if (v_max is not None and variant_max is None) or v_max > variant_max: + variant_max = v_max if self.variant_cost_min != variant_min or self.variant_cost_max != variant_max: self.price_modified = True @@ -3800,12 +3801,15 @@ def post_save_part_parameter_template(sender, instance, created, **kwargs): """Callback function when a PartParameterTemplate is created or saved.""" import part.tasks as part_tasks - if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData(): - 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 - ) + if ( + InvenTree.ready.canAppAccessDatabase() + and not InvenTree.ready.isImportingData() + and 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 + ) class PartParameter(InvenTree.models.InvenTreeMetadataModel): @@ -3922,11 +3926,14 @@ class PartParameter(InvenTree.models.InvenTreeMetadataModel): except ValueError: self.data_numeric = None - if self.data_numeric is not None and type(self.data_numeric) is float: - # Prevent out of range numbers, etc - # Ref: https://github.com/inventree/InvenTree/issues/7593 - if math.isnan(self.data_numeric) or math.isinf(self.data_numeric): - self.data_numeric = None + # Prevent out of range numbers, etc + # Ref: https://github.com/inventree/InvenTree/issues/7593 + if ( + self.data_numeric is not None + and type(self.data_numeric) is float + and math.isnan(self.data_numeric) + ) or math.isinf(self.data_numeric): + self.data_numeric = None part = models.ForeignKey( Part, @@ -4509,9 +4516,12 @@ def update_bom_build_lines(sender, instance, created, **kwargs): def update_pricing_after_edit(sender, instance, created, **kwargs): """Callback function when a part price break is created or updated.""" # Update part pricing *unless* we are importing data - if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData(): - if instance.part: - instance.part.schedule_pricing_update(create=True) + if ( + InvenTree.ready.canAppAccessDatabase() + and not InvenTree.ready.isImportingData() + and instance.part + ): + instance.part.schedule_pricing_update(create=True) @receiver(post_delete, sender=BomItem, dispatch_uid='post_delete_bom_item') @@ -4526,9 +4536,12 @@ def update_pricing_after_edit(sender, instance, created, **kwargs): def update_pricing_after_delete(sender, instance, **kwargs): """Callback function when a part price break is deleted.""" # Update part pricing *unless* we are importing data - if InvenTree.ready.canAppAccessDatabase() and not InvenTree.ready.isImportingData(): - if instance.part: - instance.part.schedule_pricing_update(create=False) + if ( + InvenTree.ready.canAppAccessDatabase() + and not InvenTree.ready.isImportingData() + and instance.part + ): + instance.part.schedule_pricing_update(create=False) class BomItemSubstitute(InvenTree.models.InvenTreeMetadataModel): diff --git a/src/backend/InvenTree/plugin/base/barcodes/api.py b/src/backend/InvenTree/plugin/base/barcodes/api.py index a497c79a62..fe7101a748 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/api.py +++ b/src/backend/InvenTree/plugin/base/barcodes/api.py @@ -563,10 +563,13 @@ class BarcodeSOAllocate(BarcodeView): sales_order = kwargs['sales_order'] shipment = self.get_shipment(**kwargs) - if stock_item is not None and line_item is not None: - if stock_item.part != line_item.part: - result['error'] = _('Stock item does not match line item') - raise ValidationError(result) + if ( + stock_item is not None + and line_item is not None + and stock_item.part != line_item.part + ): + result['error'] = _('Stock item does not match line item') + raise ValidationError(result) quantity = kwargs.get('quantity', None) @@ -587,10 +590,13 @@ class BarcodeSOAllocate(BarcodeView): 'quantity': quantity, } - if stock_item is not None and quantity is not None: - if stock_item.unallocated_quantity() < quantity: - response['error'] = _('Insufficient stock available') - raise ValidationError(response) + if ( + stock_item is not None + and quantity is not None + and stock_item.unallocated_quantity() < quantity + ): + response['error'] = _('Insufficient stock available') + raise ValidationError(response) # If we have sufficient information, we can allocate the stock item if all(x is not None for x in [line_item, sales_order, shipment, quantity]): diff --git a/src/backend/InvenTree/plugin/base/barcodes/mixins.py b/src/backend/InvenTree/plugin/base/barcodes/mixins.py index 1d53a027e6..ab969c7c3d 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/mixins.py +++ b/src/backend/InvenTree/plugin/base/barcodes/mixins.py @@ -477,9 +477,10 @@ class SupplierBarcodeMixin(BarcodeMixin): location := supplier_part.part.get_default_location() ): pass - elif StockLocation.objects.count() <= 1: - if not (location := StockLocation.objects.first()): - no_stock_locations = True + elif StockLocation.objects.count() <= 1 and not ( + location := StockLocation.objects.first() + ): + no_stock_locations = True response = { 'lineitem': {'pk': line_item.pk, 'purchase_order': purchase_order.pk} diff --git a/src/backend/InvenTree/plugin/registry.py b/src/backend/InvenTree/plugin/registry.py index 4b49eb5433..38e50a2a28 100644 --- a/src/backend/InvenTree/plugin/registry.py +++ b/src/backend/InvenTree/plugin/registry.py @@ -195,15 +195,13 @@ class PluginsRegistry: for plugin in self.plugins.values(): if plugin.mixin_enabled(mixin): - if active is not None: - # Filter by 'active' status of plugin - if active != plugin.is_active(): - continue + # Filter by 'active' status of plugin + if active is not None and active != plugin.is_active(): + continue - if builtin is not None: - # Filter by 'builtin' status of plugin - if builtin != plugin.is_builtin: - continue + # Filter by 'builtin' status of plugin + if builtin is not None and builtin != plugin.is_builtin: + continue result.append(plugin) @@ -396,21 +394,20 @@ class PluginsRegistry: collected_plugins.append(item) # From this point any plugins are considered "external" and only loaded if plugins are explicitly enabled - if settings.PLUGINS_ENABLED: - # Check if not running in testing mode and apps should be loaded from hooks - if (not settings.PLUGIN_TESTING) or ( - settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP - ): - # Collect plugins from setup entry points - for entry in get_entrypoints(): - try: - plugin = entry.load() - plugin.is_package = True - plugin.package_name = getattr(entry.dist, 'name', None) - plugin._get_package_metadata() - collected_plugins.append(plugin) - except Exception as error: # pragma: no cover - handle_error(error, do_raise=False, log_name='discovery') + # Check if not running in testing mode and apps should be loaded from hooks + if (settings.PLUGINS_ENABLED and (not settings.PLUGIN_TESTING)) or ( + settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP + ): + # Collect plugins from setup entry points + for entry in get_entrypoints(): + try: + plugin = entry.load() + plugin.is_package = True + plugin.package_name = getattr(entry.dist, 'name', None) + plugin._get_package_metadata() + collected_plugins.append(plugin) + except Exception as error: # pragma: no cover + handle_error(error, do_raise=False, log_name='discovery') # Log collected plugins logger.info('Collected %s plugins', len(collected_plugins)) diff --git a/src/backend/InvenTree/plugin/samples/integration/validation_sample.py b/src/backend/InvenTree/plugin/samples/integration/validation_sample.py index 09362b0135..588d8ae6e3 100644 --- a/src/backend/InvenTree/plugin/samples/integration/validation_sample.py +++ b/src/backend/InvenTree/plugin/samples/integration/validation_sample.py @@ -66,24 +66,26 @@ class SampleValidatorPlugin(SettingsMixin, ValidationMixin, InvenTreePlugin): # Print debug message to console (intentional) print('Validating model instance:', instance.__class__, f'<{instance.pk}>') - if isinstance(instance, part.models.BomItem): - if self.get_setting('BOM_ITEM_INTEGER'): - if float(instance.quantity) != int(instance.quantity): - self.raise_error({ - 'quantity': 'Bom item quantity must be an integer' - }) + if ( + isinstance(instance, part.models.BomItem) + and self.get_setting('BOM_ITEM_INTEGER') + and float(instance.quantity) != int(instance.quantity) + ): + self.raise_error({'quantity': 'Bom item quantity must be an integer'}) - if isinstance(instance, part.models.Part): - # If the part description is being updated, prevent it from being reduced in length + # If the part description is being updated, prevent it from being reduced in length + if ( + isinstance(instance, part.models.Part) + and deltas + and 'description' in deltas + ): + old_desc = deltas['description']['old'] + new_desc = deltas['description']['new'] - if deltas and 'description' in deltas: - old_desc = deltas['description']['old'] - new_desc = deltas['description']['new'] - - if len(new_desc) < len(old_desc): - self.raise_error({ - 'description': 'Part description cannot be shortened' - }) + if len(new_desc) < len(old_desc): + self.raise_error({ + 'description': 'Part description cannot be shortened' + }) def validate_part_name(self, name: str, part): """Custom validation for Part name field. @@ -126,14 +128,12 @@ class SampleValidatorPlugin(SettingsMixin, ValidationMixin, InvenTreePlugin): These examples are silly, but serve to demonstrate how the feature could be used """ - if self.get_setting('SERIAL_MUST_BE_PALINDROME'): - if serial != serial[::-1]: - self.raise_error('Serial must be a palindrome') + if self.get_setting('SERIAL_MUST_BE_PALINDROME') and serial != serial[::-1]: + self.raise_error('Serial must be a palindrome') - if self.get_setting('SERIAL_MUST_MATCH_PART'): - # Serial must start with the same letter as the linked part, for some reason - if serial[0] != part.name[0]: - self.raise_error('Serial number must start with same letter as part') + # Serial must start with the same letter as the linked part, for some reason + if self.get_setting('SERIAL_MUST_MATCH_PART') and serial[0] != part.name[0]: + self.raise_error('Serial number must start with same letter as part') def validate_batch_code(self, batch_code: str, item): """Ensure that a particular batch code meets specification. diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 20e35a916a..6cfe6b7907 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -700,39 +700,37 @@ class StockItem( # The 'supplier_part' field must point to the same part! try: - if self.supplier_part is not None: - if self.supplier_part.part != self.part: + if self.supplier_part is not None and self.supplier_part.part != self.part: + raise ValidationError({ + 'supplier_part': _( + f"Part type ('{self.supplier_part.part}') must be {self.part}" + ) + }) + + # A part with a serial number MUST have the quantity set to 1 + if self.part is not None and self.serial: + if self.quantity > 1: raise ValidationError({ - 'supplier_part': _( - f"Part type ('{self.supplier_part.part}') must be {self.part}" + 'quantity': _( + 'Quantity must be 1 for item with a serial number' + ), + 'serial': _( + 'Serial number cannot be set if quantity greater than 1' + ), + }) + + if self.quantity == 0: + self.quantity = 1 + + elif self.quantity > 1: + raise ValidationError({ + 'quantity': _( + 'Quantity must be 1 for item with a serial number' ) }) - if self.part is not None: - # A part with a serial number MUST have the quantity set to 1 - if self.serial: - if self.quantity > 1: - raise ValidationError({ - 'quantity': _( - 'Quantity must be 1 for item with a serial number' - ), - 'serial': _( - 'Serial number cannot be set if quantity greater than 1' - ), - }) - - if self.quantity == 0: - self.quantity = 1 - - elif self.quantity > 1: - raise ValidationError({ - 'quantity': _( - 'Quantity must be 1 for item with a serial number' - ) - }) - - # Serial numbered items cannot be deleted on depletion - self.delete_on_deplete = False + # Serial numbered items cannot be deleted on depletion + self.delete_on_deplete = False except PartModels.Part.DoesNotExist: pass diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py index f4f841e542..3250485593 100644 --- a/src/backend/InvenTree/stock/serializers.py +++ b/src/backend/InvenTree/stock/serializers.py @@ -795,14 +795,11 @@ class InstallStockItemSerializer(serializers.Serializer): parent_item = self.context['item'] parent_part = parent_item.part + # Check if the selected part is in the Bill of Materials of the parent item if get_global_setting( 'STOCK_ENFORCE_BOM_INSTALLATION', backup_value=True, cache=False - ): - # Check if the selected part is in the Bill of Materials of the parent item - if not parent_part.check_if_part_in_bom(stock_item.part): - raise ValidationError( - _('Selected part is not in the Bill of Materials') - ) + ) and not parent_part.check_if_part_in_bom(stock_item.part): + raise ValidationError(_('Selected part is not in the Bill of Materials')) return stock_item diff --git a/src/backend/InvenTree/users/api.py b/src/backend/InvenTree/users/api.py index c8cfb12807..dead819f8e 100644 --- a/src/backend/InvenTree/users/api.py +++ b/src/backend/InvenTree/users/api.py @@ -95,13 +95,12 @@ class OwnerList(ListAPI): if not search_match: continue - if is_active is not None: - # Skip any users which do not match the required *is_active* value - if ( - result.owner_type.name == 'user' - and result.owner_id not in matching_user_ids - ): - continue + # Skip any users which do not match the required *is_active* value + if is_active is not None and ( + result.owner_type.name == 'user' + and result.owner_id not in matching_user_ids + ): + continue # If we get here, there is no reason *not* to include this result results.append(result) diff --git a/src/backend/InvenTree/users/models.py b/src/backend/InvenTree/users/models.py index 88f74db69c..7f0f88fe4c 100644 --- a/src/backend/InvenTree/users/models.py +++ b/src/backend/InvenTree/users/models.py @@ -34,9 +34,10 @@ logger = logging.getLogger('inventree') # string representation of a user def user_model_str(self): """Function to override the default Django User __str__.""" - if get_global_setting('DISPLAY_FULL_NAMES', cache=True): - if self.first_name or self.last_name: - return f'{self.first_name} {self.last_name}' + if ( + get_global_setting('DISPLAY_FULL_NAMES', cache=True) and self.first_name + ) or self.last_name: + return f'{self.first_name} {self.last_name}' return self.username @@ -421,19 +422,19 @@ class RuleSet(models.Model): # Work out which roles touch the given table for role in cls.RULESET_NAMES: - if table in cls.get_ruleset_models()[role]: - if check_user_role(user, role, permission): - return True + if table in cls.get_ruleset_models()[role] and check_user_role( + user, role, permission + ): + return True # Check for children models which inherits from parent role for parent, child in cls.RULESET_CHANGE_INHERIT: # Get child model name parent_child_string = f'{parent}_{child}' - if parent_child_string == table: - # Check if parent role has change permission - if check_user_role(user, parent, 'change'): - return True + # Check if parent role has change permission + if parent_child_string == table and check_user_role(user, parent, 'change'): + return True # Print message instead of throwing an error name = getattr(user, 'name', user.pk) diff --git a/tasks.py b/tasks.py index 4a345b46f5..f18c5b4d58 100644 --- a/tasks.py +++ b/tasks.py @@ -628,9 +628,11 @@ def export_records( model_name = entry.get('model', None) # Ignore any temporary settings (start with underscore) - if model_name in ['common.inventreesetting', 'common.inventreeusersetting']: - if entry['fields'].get('key', '').startswith('_'): - continue + if model_name in [ + 'common.inventreesetting', + 'common.inventreeusersetting', + ] and entry['fields'].get('key', '').startswith('_'): + continue if model_name == 'auth.group': entry['fields']['permissions'] = []