From f83fedbbb8de261ff8c706e179519e58e7a91064 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Sun, 7 Jan 2024 21:03:14 +0100 Subject: [PATCH] switched to single quotes everywhere --- InvenTree/InvenTree/admin.py | 4 +- InvenTree/InvenTree/api.py | 12 +- InvenTree/InvenTree/apps.py | 34 +-- InvenTree/InvenTree/ci_render_js.py | 4 +- InvenTree/InvenTree/config.py | 16 +- InvenTree/InvenTree/conversion.py | 2 +- InvenTree/InvenTree/email.py | 10 +- InvenTree/InvenTree/exceptions.py | 2 +- InvenTree/InvenTree/exchange.py | 16 +- InvenTree/InvenTree/filters.py | 2 +- InvenTree/InvenTree/format.py | 12 +- InvenTree/InvenTree/forms.py | 24 +- InvenTree/InvenTree/helpers.py | 50 ++-- InvenTree/InvenTree/helpers_model.py | 20 +- InvenTree/InvenTree/magic_login.py | 16 +- .../management/commands/clean_settings.py | 4 +- .../management/commands/prerender.py | 4 +- .../management/commands/rebuild_models.py | 20 +- .../management/commands/rebuild_thumbnails.py | 8 +- .../management/commands/wait_for_db.py | 10 +- InvenTree/InvenTree/metadata.py | 8 +- InvenTree/InvenTree/middleware.py | 4 +- InvenTree/InvenTree/models.py | 80 +++--- InvenTree/InvenTree/permissions.py | 6 +- InvenTree/InvenTree/ready.py | 4 +- InvenTree/InvenTree/sentry.py | 6 +- InvenTree/InvenTree/serializers.py | 46 +-- InvenTree/InvenTree/settings.py | 194 ++++++------- InvenTree/InvenTree/social_auth_urls.py | 4 +- InvenTree/InvenTree/sso.py | 2 +- InvenTree/InvenTree/status.py | 8 +- InvenTree/InvenTree/status_codes.py | 70 ++--- InvenTree/InvenTree/tasks.py | 68 ++--- InvenTree/InvenTree/test_api.py | 8 +- InvenTree/InvenTree/test_urls.py | 12 +- InvenTree/InvenTree/test_views.py | 20 +- InvenTree/InvenTree/tests.py | 244 ++++++++-------- InvenTree/InvenTree/unit_test.py | 8 +- InvenTree/InvenTree/urls.py | 16 +- InvenTree/InvenTree/validators.py | 8 +- InvenTree/InvenTree/version.py | 44 +-- InvenTree/InvenTree/views.py | 48 ++-- InvenTree/InvenTree/wsgi.py | 2 +- InvenTree/common/api.py | 4 +- InvenTree/common/apps.py | 2 +- InvenTree/common/models.py | 52 ++-- InvenTree/common/notifications.py | 14 +- InvenTree/common/tasks.py | 8 +- InvenTree/common/tests.py | 56 ++-- InvenTree/common/views.py | 2 +- InvenTree/company/models.py | 20 +- InvenTree/company/serializers.py | 2 +- InvenTree/company/test_api.py | 14 +- InvenTree/company/test_migrations.py | 10 +- InvenTree/company/tests.py | 20 +- InvenTree/generic/states/api.py | 4 +- InvenTree/generic/states/tests.py | 10 +- InvenTree/gunicorn.conf.py | 2 +- InvenTree/label/api.py | 8 +- InvenTree/label/apps.py | 2 +- InvenTree/label/models.py | 18 +- InvenTree/label/tests.py | 8 +- InvenTree/manage.py | 8 +- InvenTree/order/api.py | 48 ++-- InvenTree/order/models.py | 52 ++-- InvenTree/order/serializers.py | 56 ++-- InvenTree/order/tasks.py | 2 +- InvenTree/order/test_api.py | 50 ++-- InvenTree/order/test_migrations.py | 18 +- InvenTree/order/test_sales_order.py | 4 +- InvenTree/order/tests.py | 4 +- InvenTree/order/views.py | 10 +- InvenTree/part/api.py | 46 +-- InvenTree/part/apps.py | 6 +- InvenTree/part/bom.py | 14 +- InvenTree/part/helpers.py | 4 +- InvenTree/part/models.py | 100 +++---- InvenTree/part/serializers.py | 22 +- InvenTree/part/stocktake.py | 10 +- InvenTree/part/tasks.py | 22 +- InvenTree/part/templatetags/i18n.py | 40 +-- .../part/templatetags/inventree_extras.py | 12 +- InvenTree/part/test_api.py | 88 +++--- InvenTree/part/test_bom_import.py | 6 +- InvenTree/part/test_bom_item.py | 22 +- InvenTree/part/test_category.py | 4 +- InvenTree/part/test_migrations.py | 2 +- InvenTree/part/test_param.py | 2 +- InvenTree/part/test_part.py | 2 +- InvenTree/part/test_pricing.py | 8 +- InvenTree/part/views.py | 10 +- InvenTree/plugin/admin.py | 2 +- InvenTree/plugin/api.py | 20 +- InvenTree/plugin/apps.py | 2 +- InvenTree/plugin/base/action/api.py | 4 +- InvenTree/plugin/base/action/mixins.py | 8 +- InvenTree/plugin/base/action/test_action.py | 10 +- InvenTree/plugin/base/barcodes/api.py | 64 ++--- InvenTree/plugin/base/barcodes/mixins.py | 110 +++---- InvenTree/plugin/base/barcodes/serializers.py | 10 +- .../plugin/base/barcodes/test_barcode.py | 4 +- .../plugin/base/integration/APICallMixin.py | 6 +- .../base/integration/CurrencyExchangeMixin.py | 4 +- .../plugin/base/integration/ScheduleMixin.py | 16 +- .../base/integration/ValidationMixin.py | 2 +- InvenTree/plugin/base/integration/mixins.py | 6 +- .../plugin/base/integration/test_mixins.py | 10 +- InvenTree/plugin/base/label/mixins.py | 2 +- .../plugin/base/label/test_label_mixin.py | 18 +- InvenTree/plugin/base/locate/api.py | 2 +- InvenTree/plugin/base/locate/mixins.py | 6 +- .../builtin/barcodes/inventree_barcode.py | 10 +- .../barcodes/test_inventree_barcode.py | 2 +- .../builtin/integration/core_notifications.py | 40 +-- .../builtin/integration/currency_exchange.py | 12 +- .../plugin/builtin/labels/inventree_label.py | 10 +- .../plugin/builtin/labels/label_sheet.py | 44 +-- InvenTree/plugin/builtin/suppliers/digikey.py | 20 +- InvenTree/plugin/builtin/suppliers/lcsc.py | 32 +-- InvenTree/plugin/builtin/suppliers/mouser.py | 20 +- .../suppliers/test_supplier_barcodes.py | 256 ++++++++--------- InvenTree/plugin/builtin/suppliers/tme.py | 42 +-- InvenTree/plugin/helpers.py | 2 +- InvenTree/plugin/installer.py | 42 +-- InvenTree/plugin/mock/simple.py | 2 +- InvenTree/plugin/models.py | 6 +- InvenTree/plugin/plugin.py | 2 +- InvenTree/plugin/registry.py | 28 +- .../plugin/samples/event/event_sample.py | 10 +- .../samples/event/filtered_event_sample.py | 12 +- .../plugin/samples/event/test_event_sample.py | 2 +- .../event/test_filtered_event_sample.py | 4 +- .../samples/integration/another_sample.py | 4 +- .../plugin/samples/integration/api_caller.py | 2 +- .../integration/custom_panel_sample.py | 10 +- .../samples/integration/label_sample.py | 12 +- .../integration/report_plugin_sample.py | 10 +- .../plugin/samples/integration/sample.py | 10 +- .../integration/sample_currency_exchange.py | 10 +- .../samples/integration/scheduled_task.py | 14 +- .../samples/integration/simpleactionplugin.py | 8 +- .../plugin/samples/integration/test_sample.py | 2 +- .../integration/test_scheduled_task.py | 8 +- .../integration/test_simpleactionplugin.py | 12 +- .../plugin/samples/integration/transition.py | 4 +- .../samples/integration/validation_sample.py | 20 +- .../plugin/samples/integration/version.py | 6 +- .../plugin/samples/locate/locate_sample.py | 18 +- InvenTree/plugin/serializers.py | 12 +- InvenTree/plugin/test_plugin.py | 2 +- InvenTree/report/api.py | 10 +- InvenTree/report/apps.py | 2 +- InvenTree/report/helpers.py | 4 +- InvenTree/report/models.py | 30 +- InvenTree/report/templatetags/barcode.py | 2 +- InvenTree/report/templatetags/report.py | 16 +- InvenTree/report/tests.py | 12 +- InvenTree/script/translation_stats.py | 10 +- InvenTree/stock/admin.py | 2 +- InvenTree/stock/api.py | 34 +-- InvenTree/stock/models.py | 90 +++--- InvenTree/stock/serializers.py | 62 ++-- InvenTree/stock/test_api.py | 136 ++++----- InvenTree/stock/tests.py | 106 +++---- InvenTree/users/admin.py | 6 +- InvenTree/users/apps.py | 2 +- InvenTree/users/authentication.py | 4 +- InvenTree/users/models.py | 20 +- InvenTree/users/tests.py | 22 +- InvenTree/web/templatetags/spa_helper.py | 20 +- InvenTree/web/tests.py | 2 +- InvenTree/web/urls.py | 14 +- ci/check_api_endpoint.py | 10 +- ci/check_js_templates.py | 16 +- ci/check_locale_files.py | 6 +- ci/check_migration_files.py | 6 +- ci/version_check.py | 36 +-- docker/gunicorn.conf.py | 2 +- docs/docs/hooks.py | 30 +- pyproject.toml | 2 +- tasks.py | 270 +++++++++--------- 181 files changed, 2080 insertions(+), 2080 deletions(-) diff --git a/InvenTree/InvenTree/admin.py b/InvenTree/InvenTree/admin.py index 6da1d9a2d5..e044e8ae54 100644 --- a/InvenTree/InvenTree/admin.py +++ b/InvenTree/InvenTree/admin.py @@ -32,12 +32,12 @@ class InvenTreeResource(ModelResource): """Override the default import_data_inner function to provide better error handling""" if len(dataset) > self.MAX_IMPORT_ROWS: raise ImportExportError( - f"Dataset contains too many rows (max {self.MAX_IMPORT_ROWS})" + f'Dataset contains too many rows (max {self.MAX_IMPORT_ROWS})' ) if len(dataset.headers) > self.MAX_IMPORT_COLS: raise ImportExportError( - f"Dataset contains too many columns (max {self.MAX_IMPORT_COLS})" + f'Dataset contains too many columns (max {self.MAX_IMPORT_COLS})' ) return super().import_data_inner( diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index 24f9b58765..3e449398b5 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -232,19 +232,19 @@ class BulkDeleteMixin: if not items and not filters: raise ValidationError({ - "non_field_errors": [ - "List of items or filters must be provided for bulk deletion" + 'non_field_errors': [ + 'List of items or filters must be provided for bulk deletion' ] }) if items and type(items) is not list: raise ValidationError({ - "items": ["'items' must be supplied as a list object"] + 'items': ["'items' must be supplied as a list object"] }) if filters and type(filters) is not dict: raise ValidationError({ - "filters": ["'filters' must be supplied as a dict object"] + 'filters': ["'filters' must be supplied as a dict object"] }) # Keep track of how many items we deleted @@ -266,7 +266,7 @@ class BulkDeleteMixin: n_deleted = queryset.count() queryset.delete() - return Response({'success': f"Deleted {n_deleted} items"}, status=204) + return Response({'success': f'Deleted {n_deleted} items'}, status=204) class ListCreateDestroyAPIView(BulkDeleteMixin, ListCreateAPI): @@ -308,7 +308,7 @@ class APIDownloadMixin: def download_queryset(self, queryset, export_format): """This function must be implemented to provide a downloadFile request.""" - raise NotImplementedError("download_queryset method not implemented!") + raise NotImplementedError('download_queryset method not implemented!') class AttachmentMixin: diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index 1f22d4e98d..87328169c4 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -21,7 +21,7 @@ from InvenTree.ready import ( isPluginRegistryLoaded, ) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class InvenTreeConfig(AppConfig): @@ -82,11 +82,11 @@ class InvenTreeConfig(AppConfig): try: Schedule.objects.filter(func__in=obsolete).delete() except Exception: - logger.exception("Failed to remove obsolete tasks - database not ready") + logger.exception('Failed to remove obsolete tasks - database not ready') def start_background_tasks(self): """Start all background tests for InvenTree.""" - logger.info("Starting background tasks...") + logger.info('Starting background tasks...') from django_q.models import Schedule @@ -130,17 +130,17 @@ class InvenTreeConfig(AppConfig): if len(tasks_to_create) > 0: Schedule.objects.bulk_create(tasks_to_create) - logger.info("Created %s new scheduled tasks", len(tasks_to_create)) + logger.info('Created %s new scheduled tasks', len(tasks_to_create)) if len(tasks_to_update) > 0: Schedule.objects.bulk_update(tasks_to_update, ['schedule_type', 'minutes']) - logger.info("Updated %s existing scheduled tasks", len(tasks_to_update)) + logger.info('Updated %s existing scheduled tasks', len(tasks_to_update)) # Put at least one task onto the background worker stack, # which will be processed as soon as the worker comes online InvenTree.tasks.offload_task(InvenTree.tasks.heartbeat, force_async=True) - logger.info("Started %s scheduled background tasks...", len(tasks)) + logger.info('Started %s scheduled background tasks...', len(tasks)) def collect_tasks(self): """Collect all background tasks.""" @@ -152,7 +152,7 @@ class InvenTreeConfig(AppConfig): try: import_module(f'{app.module.__package__}.tasks') except Exception as e: # pragma: no cover - logger.exception("Error loading tasks for %s: %s", app_name, e) + logger.exception('Error loading tasks for %s: %s', app_name, e) def update_exchange_rates(self): # pragma: no cover """Update exchange rates each time the server is started. @@ -183,20 +183,20 @@ class InvenTreeConfig(AppConfig): if last_update is None: # Never been updated - logger.info("Exchange backend has never been updated") + logger.info('Exchange backend has never been updated') update = True # Backend currency has changed? if base_currency != backend.base_currency: logger.info( - "Base currency changed from %s to %s", + 'Base currency changed from %s to %s', backend.base_currency, base_currency, ) update = True except ExchangeBackend.DoesNotExist: - logger.info("Exchange backend not found - updating") + logger.info('Exchange backend not found - updating') update = True except Exception: @@ -207,9 +207,9 @@ class InvenTreeConfig(AppConfig): try: update_exchange_rates() except OperationalError: - logger.warning("Could not update exchange rates - database not ready") + logger.warning('Could not update exchange rates - database not ready') except Exception as e: - logger.exception("Error updating exchange rates: %s (%s)", e, type(e)) + logger.exception('Error updating exchange rates: %s (%s)', e, type(e)) def add_user_on_startup(self): """Add a user on startup.""" @@ -222,7 +222,7 @@ class InvenTreeConfig(AppConfig): add_email = get_setting('INVENTREE_ADMIN_EMAIL', 'admin_email') add_password = get_setting('INVENTREE_ADMIN_PASSWORD', 'admin_password') add_password_file = get_setting( - "INVENTREE_ADMIN_PASSWORD_FILE", "admin_password_file", None + 'INVENTREE_ADMIN_PASSWORD_FILE', 'admin_password_file', None ) # check if all values are present @@ -260,7 +260,7 @@ class InvenTreeConfig(AppConfig): try: with transaction.atomic(): if user.objects.filter(username=add_user).exists(): - logger.info("User %s already exists - skipping creation", add_user) + logger.info('User %s already exists - skipping creation', add_user) else: new_user = user.objects.create_superuser( add_user, add_email, add_password @@ -272,12 +272,12 @@ class InvenTreeConfig(AppConfig): def add_user_from_file(self): """Add the superuser from a file.""" # stop if checks were already created - if hasattr(settings, "USER_ADDED_FILE") and settings.USER_ADDED_FILE: + if hasattr(settings, 'USER_ADDED_FILE') and settings.USER_ADDED_FILE: return # get values add_password_file = get_setting( - "INVENTREE_ADMIN_PASSWORD_FILE", "admin_password_file", None + 'INVENTREE_ADMIN_PASSWORD_FILE', 'admin_password_file', None ) # no variable set -> do not try anything @@ -296,7 +296,7 @@ class InvenTreeConfig(AppConfig): self._create_admin_user( get_setting('INVENTREE_ADMIN_USER', 'admin_user', 'admin'), get_setting('INVENTREE_ADMIN_EMAIL', 'admin_email', ''), - add_password_file.read_text(encoding="utf-8"), + add_password_file.read_text(encoding='utf-8'), ) # do not try again diff --git a/InvenTree/InvenTree/ci_render_js.py b/InvenTree/InvenTree/ci_render_js.py index 72e493c720..6aa3847895 100644 --- a/InvenTree/InvenTree/ci_render_js.py +++ b/InvenTree/InvenTree/ci_render_js.py @@ -63,9 +63,9 @@ class RenderJavascriptFiles(InvenTreeTestCase): # pragma: no cover """Look for all javascript files.""" n = 0 - print("Rendering javascript files...") + print('Rendering javascript files...') n += self.download_files('translated', '/js/i18n') n += self.download_files('dynamic', '/js/dynamic') - print(f"Rendered {n} javascript files.") + print(f'Rendered {n} javascript files.') diff --git a/InvenTree/InvenTree/config.py b/InvenTree/InvenTree/config.py index 609ea6e71d..40c849bb36 100644 --- a/InvenTree/InvenTree/config.py +++ b/InvenTree/InvenTree/config.py @@ -99,9 +99,9 @@ def get_config_file(create=True) -> Path: ) ensure_dir(cfg_filename.parent) - cfg_template = base_dir.joinpath("config_template.yaml") + cfg_template = base_dir.joinpath('config_template.yaml') shutil.copyfile(cfg_template, cfg_filename) - print(f"Created config file {cfg_filename}") + print(f'Created config file {cfg_filename}') return cfg_filename @@ -293,14 +293,14 @@ def get_plugin_file(): if not plugin_file.exists(): logger.warning( - "Plugin configuration file does not exist - creating default file" + 'Plugin configuration file does not exist - creating default file' ) logger.info("Creating plugin file at '%s'", plugin_file) ensure_dir(plugin_file.parent) # If opening the file fails (no write permission, for example), then this will throw an error plugin_file.write_text( - "# InvenTree Plugins (uses PIP framework to install)\n\n" + '# InvenTree Plugins (uses PIP framework to install)\n\n' ) return plugin_file @@ -323,7 +323,7 @@ def get_secret_key(): """ # Look for environment variable if secret_key := get_setting('INVENTREE_SECRET_KEY', 'secret_key'): - logger.info("SECRET_KEY loaded by INVENTREE_SECRET_KEY") # pragma: no cover + logger.info('SECRET_KEY loaded by INVENTREE_SECRET_KEY') # pragma: no cover return secret_key # Look for secret key file @@ -331,7 +331,7 @@ def get_secret_key(): secret_key_file = Path(secret_key_file).resolve() else: # Default location for secret key file - secret_key_file = get_base_dir().joinpath("secret_key.txt").resolve() + secret_key_file = get_base_dir().joinpath('secret_key.txt').resolve() if not secret_key_file.exists(): logger.info("Generating random key file at '%s'", secret_key_file) @@ -367,9 +367,9 @@ def get_custom_file( static_storage = StaticFilesStorage() if static_storage.exists(value): - logger.info("Loading %s from %s directory: %s", log_ref, 'static', value) + logger.info('Loading %s from %s directory: %s', log_ref, 'static', value) elif lookup_media and default_storage.exists(value): - logger.info("Loading %s from %s directory: %s", log_ref, 'media', value) + logger.info('Loading %s from %s directory: %s', log_ref, 'media', value) else: add_dir_str = ' or media' if lookup_media else '' logger.warning( diff --git a/InvenTree/InvenTree/conversion.py b/InvenTree/InvenTree/conversion.py index cba34ff460..b87d98c8a5 100644 --- a/InvenTree/InvenTree/conversion.py +++ b/InvenTree/InvenTree/conversion.py @@ -127,7 +127,7 @@ def convert_physical_value(value: str, unit: str = None, strip_units=True): if unit: raise ValidationError(_(f'Could not convert {original} to {unit}')) else: - raise ValidationError(_("Invalid quantity supplied")) + raise ValidationError(_('Invalid quantity supplied')) # Calculate the "magnitude" of the value, as a float # If the value is specified strangely (e.g. as a fraction or a dozen), this can cause issues diff --git a/InvenTree/InvenTree/email.py b/InvenTree/InvenTree/email.py index b93c39f63b..a5f7b283df 100644 --- a/InvenTree/InvenTree/email.py +++ b/InvenTree/InvenTree/email.py @@ -30,22 +30,22 @@ def is_email_configured(): # Display warning unless in test mode if not testing: # pragma: no cover - logger.debug("EMAIL_HOST is not configured") + logger.debug('EMAIL_HOST is not configured') # Display warning unless in test mode if not settings.EMAIL_HOST_USER and not testing: # pragma: no cover - logger.debug("EMAIL_HOST_USER is not configured") + logger.debug('EMAIL_HOST_USER is not configured') # Display warning unless in test mode if not settings.EMAIL_HOST_PASSWORD and testing: # pragma: no cover - logger.debug("EMAIL_HOST_PASSWORD is not configured") + logger.debug('EMAIL_HOST_PASSWORD is not configured') # Email sender must be configured if not settings.DEFAULT_FROM_EMAIL: configured = False if not testing: # pragma: no cover - logger.debug("DEFAULT_FROM_EMAIL is not configured") + logger.debug('DEFAULT_FROM_EMAIL is not configured') return configured @@ -75,7 +75,7 @@ def send_email(subject, body, recipients, from_email=None, html_message=None): if settings.TESTING: from_email = 'from@test.com' else: - logger.error("send_email failed: DEFAULT_FROM_EMAIL not specified") + logger.error('send_email failed: DEFAULT_FROM_EMAIL not specified') return InvenTree.tasks.offload_task( diff --git a/InvenTree/InvenTree/exceptions.py b/InvenTree/InvenTree/exceptions.py index 75cbaff125..b2f014390d 100644 --- a/InvenTree/InvenTree/exceptions.py +++ b/InvenTree/InvenTree/exceptions.py @@ -86,7 +86,7 @@ def exception_handler(exc, context): # If in DEBUG mode, provide error information in the response error_detail = str(exc) else: - error_detail = _("Error details can be found in the admin panel") + error_detail = _('Error details can be found in the admin panel') response_data = { 'error': type(exc).__name__, diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index f9db24e5d8..9a0d7ecf12 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -18,7 +18,7 @@ class InvenTreeExchange(SimpleExchangeBackend): Uses the plugin system to actually fetch the rates from an external API. """ - name = "InvenTreeExchange" + name = 'InvenTreeExchange' def get_rates(self, **kwargs) -> None: """Set the requested currency codes and get rates.""" @@ -55,19 +55,19 @@ class InvenTreeExchange(SimpleExchangeBackend): try: rates = plugin.update_exchange_rates(base_currency, symbols) except Exception as exc: - logger.exception("Exchange rate update failed: %s", exc) + logger.exception('Exchange rate update failed: %s', exc) return {} if not rates: logger.warning( - "Exchange rate update failed - no data returned from plugin %s", slug + 'Exchange rate update failed - no data returned from plugin %s', slug ) return {} # Update exchange rates based on returned data if type(rates) is not dict: logger.warning( - "Invalid exchange rate data returned from plugin %s (type %s)", + 'Invalid exchange rate data returned from plugin %s (type %s)', slug, type(rates), ) @@ -82,7 +82,7 @@ class InvenTreeExchange(SimpleExchangeBackend): def update_rates(self, base_currency=None, **kwargs): """Call to update all exchange rates""" backend, _ = ExchangeBackend.objects.update_or_create( - name=self.name, defaults={"base_currency": base_currency} + name=self.name, defaults={'base_currency': base_currency} ) if base_currency is None: @@ -91,7 +91,7 @@ class InvenTreeExchange(SimpleExchangeBackend): symbols = currency_codes() logger.info( - "Updating exchange rates for %s (%s currencies)", + 'Updating exchange rates for %s (%s currencies)', base_currency, len(symbols), ) @@ -110,7 +110,7 @@ class InvenTreeExchange(SimpleExchangeBackend): ]) else: logger.info( - "No exchange rates returned from backend - currencies not updated" + 'No exchange rates returned from backend - currencies not updated' ) - logger.info("Updated exchange rates for %s", base_currency) + logger.info('Updated exchange rates for %s', base_currency) diff --git a/InvenTree/InvenTree/filters.py b/InvenTree/InvenTree/filters.py index 38287a5667..a3866cd0a0 100644 --- a/InvenTree/InvenTree/filters.py +++ b/InvenTree/InvenTree/filters.py @@ -76,7 +76,7 @@ class InvenTreeSearchFilter(filters.SearchFilter): if whole: # Wrap the search term to enable word-boundary matching - term = r"\y" + term + r"\y" + term = r'\y' + term + r'\y' terms.append(term) diff --git a/InvenTree/InvenTree/format.py b/InvenTree/InvenTree/format.py index b2cd45a23d..aa733cd094 100644 --- a/InvenTree/InvenTree/format.py +++ b/InvenTree/InvenTree/format.py @@ -64,7 +64,7 @@ def construct_format_regex(fmt_string: str) -> str: Raises: ValueError: Format string is invalid """ - pattern = "^" + pattern = '^' for group in string.Formatter().parse(fmt_string): prefix = group[0] # Prefix (literal text appearing before this group) @@ -87,7 +87,7 @@ def construct_format_regex(fmt_string: str) -> str: ':', ';', '|', - '\'', + "'", '"', ] @@ -115,9 +115,9 @@ def construct_format_regex(fmt_string: str) -> str: # TODO: Introspect required width w = '+' - pattern += f"(?P<{name}>{chr}{w})" + pattern += f'(?P<{name}>{chr}{w})' - pattern += "$" + pattern += '$' return pattern @@ -172,7 +172,7 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str: if not result: raise ValueError( - _("Provided value does not match required pattern: ") + fmt_string + _('Provided value does not match required pattern: ') + fmt_string ) # And return the value we are interested in @@ -198,7 +198,7 @@ def format_money(money: Money, decimal_places: int = None, format: str = None) - if format: pattern = parse_pattern(format) else: - pattern = locale.currency_formats["standard"] + pattern = locale.currency_formats['standard'] if decimal_places is not None: pattern.frac_prec = (decimal_places, decimal_places) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 9ae110eea3..c33eaa8fae 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -140,7 +140,7 @@ class SetPasswordForm(HelperForm): ) old_password = forms.CharField( - label=_("Old password"), + label=_('Old password'), strip=False, required=False, widget=forms.PasswordInput( @@ -178,23 +178,23 @@ class CustomSignupForm(SignupForm): # check for two mail fields if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'): - self.fields["email2"] = forms.EmailField( - label=_("Email (again)"), + self.fields['email2'] = forms.EmailField( + label=_('Email (again)'), widget=forms.TextInput( attrs={ - "type": "email", - "placeholder": _("Email address confirmation"), + 'type': 'email', + 'placeholder': _('Email address confirmation'), } ), ) # check for two password fields if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'): - self.fields.pop("password2") + self.fields.pop('password2') # reorder fields set_form_field_order( - self, ["username", "email", "email2", "password1", "password2"] + self, ['username', 'email', 'email2', 'password1', 'password2'] ) def clean(self): @@ -203,10 +203,10 @@ class CustomSignupForm(SignupForm): # check for two mail fields if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'): - email = cleaned_data.get("email") - email2 = cleaned_data.get("email2") + email = cleaned_data.get('email') + email2 = cleaned_data.get('email2') if (email and email2) and email != email2: - self.add_error("email2", _("You must type the same email each time.")) + self.add_error('email2', _('You must type the same email each time.')) return cleaned_data @@ -221,7 +221,7 @@ def registration_enabled(): return True else: logger.error( - "Registration cannot be enabled, because EMAIL_HOST is not configured." + 'Registration cannot be enabled, because EMAIL_HOST is not configured.' ) return False @@ -292,7 +292,7 @@ class CustomUrlMixin: def get_email_confirmation_url(self, request, emailconfirmation): """Custom email confirmation (activation) url.""" - url = reverse("account_confirm_email", args=[emailconfirmation.key]) + url = reverse('account_confirm_email', args=[emailconfirmation.key]) return Site.objects.get_current().domain + url diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 6b332912bf..3e8b575f41 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -36,7 +36,7 @@ def generateTestKey(test_name): Tests must be named such that they will have unique keys. """ key = test_name.strip().lower() - key = key.replace(" ", "") + key = key.replace(' ', '') # Remove any characters that cannot be used to represent a variable key = re.sub(r'[^a-zA-Z0-9]', '', key) @@ -56,7 +56,7 @@ def constructPathString(path, max_chars=250): # Replace middle elements to limit the pathstring if len(pathstring) > max_chars: n = int(max_chars / 2 - 2) - pathstring = pathstring[:n] + "..." + pathstring[-n:] + pathstring = pathstring[:n] + '...' + pathstring[-n:] return pathstring @@ -82,12 +82,12 @@ def TestIfImage(img): def getBlankImage(): """Return the qualified path for the 'blank image' placeholder.""" - return getStaticUrl("img/blank_image.png") + return getStaticUrl('img/blank_image.png') def getBlankThumbnail(): """Return the qualified path for the 'blank image' thumbnail placeholder.""" - return getStaticUrl("img/blank_image.thumbnail.png") + return getStaticUrl('img/blank_image.thumbnail.png') def getLogoImage(as_file=False, custom=True): @@ -105,13 +105,13 @@ def getLogoImage(as_file=False, custom=True): if storage is not None: if as_file: - return f"file://{storage.path(settings.CUSTOM_LOGO)}" + return f'file://{storage.path(settings.CUSTOM_LOGO)}' return storage.url(settings.CUSTOM_LOGO) # If we have got to this point, return the default logo if as_file: path = settings.STATIC_ROOT.joinpath('img/inventree.png') - return f"file://{path}" + return f'file://{path}' return getStaticUrl('img/inventree.png') @@ -124,7 +124,7 @@ def getSplashScreen(custom=True): return static_storage.url(settings.CUSTOM_SPLASH) # No custom splash screen - return static_storage.url("img/inventree_splash.jpg") + return static_storage.url('img/inventree_splash.jpg') def TestIfImageURL(url): @@ -234,7 +234,7 @@ def increment(value): # Provide a default value if provided with a null input return '1' - pattern = r"(.*?)(\d+)?$" + pattern = r'(.*?)(\d+)?$' result = re.search(pattern, value) @@ -293,7 +293,7 @@ def decimal2string(d): if '.' not in s: return s - return s.rstrip("0").rstrip(".") + return s.rstrip('0').rstrip('.') def decimal2money(d, currency=None): @@ -395,7 +395,7 @@ def DownloadFile( length = len(bytes(data, response.charset)) response['Content-Length'] = length - disposition = "inline" if inline else "attachment" + disposition = 'inline' if inline else 'attachment' response['Content-Disposition'] = f'{disposition}; filename={filename}' @@ -455,7 +455,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= try: expected_quantity = int(expected_quantity) except ValueError: - raise ValidationError([_("Invalid quantity provided")]) + raise ValidationError([_('Invalid quantity provided')]) if input_string: input_string = str(input_string).strip() @@ -463,7 +463,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= input_string = '' if len(input_string) == 0: - raise ValidationError([_("Empty serial number string")]) + raise ValidationError([_('Empty serial number string')]) next_value = increment_serial_number(starting_value) @@ -473,7 +473,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= next_value = increment_serial_number(next_value) # Split input string by whitespace or comma (,) characters - groups = re.split(r"[\s,]+", input_string) + groups = re.split(r'[\s,]+', input_string) serials = [] errors = [] @@ -493,7 +493,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= return if serial in serials: - add_error(_("Duplicate serial") + f": {serial}") + add_error(_('Duplicate serial') + f': {serial}') else: serials.append(serial) @@ -525,7 +525,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= if a == b: # Invalid group - add_error(_(f"Invalid group range: {group}")) + add_error(_(f'Invalid group range: {group}')) continue group_items = [] @@ -556,7 +556,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= if len(group_items) > remaining: add_error( _( - f"Group range {group} exceeds allowed quantity ({expected_quantity})" + f'Group range {group} exceeds allowed quantity ({expected_quantity})' ) ) elif ( @@ -568,7 +568,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= for item in group_items: add_serial(item) else: - add_error(_(f"Invalid group range: {group}")) + add_error(_(f'Invalid group range: {group}')) else: # In the case of a different number of hyphens, simply add the entire group @@ -586,14 +586,14 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= sequence_count = max(0, expected_quantity - len(serials)) if len(items) > 2 or len(items) == 0: - add_error(_(f"Invalid group sequence: {group}")) + add_error(_(f'Invalid group sequence: {group}')) continue elif len(items) == 2: try: if items[1]: sequence_count = int(items[1]) + 1 except ValueError: - add_error(_(f"Invalid group sequence: {group}")) + add_error(_(f'Invalid group sequence: {group}')) continue value = items[0] @@ -612,7 +612,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= for item in sequence_items: add_serial(item) else: - add_error(_(f"Invalid group sequence: {group}")) + add_error(_(f'Invalid group sequence: {group}')) else: # At this point, we assume that the 'group' is just a single serial value @@ -622,12 +622,12 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= raise ValidationError(errors) if len(serials) == 0: - raise ValidationError([_("No serial numbers found")]) + raise ValidationError([_('No serial numbers found')]) if len(errors) == 0 and len(serials) != expected_quantity: raise ValidationError([ _( - f"Number of unique serial numbers ({len(serials)}) must match quantity ({expected_quantity})" + f'Number of unique serial numbers ({len(serials)}) must match quantity ({expected_quantity})' ) ]) @@ -666,7 +666,7 @@ def validateFilterString(value, model=None): pair = group.split('=') if len(pair) != 2: - raise ValidationError(f"Invalid group: {group}") + raise ValidationError(f'Invalid group: {group}') k, v = pair @@ -674,7 +674,7 @@ def validateFilterString(value, model=None): v = v.strip() if not k or not v: - raise ValidationError(f"Invalid group: {group}") + raise ValidationError(f'Invalid group: {group}') results[k] = v @@ -745,7 +745,7 @@ def strip_html_tags(value: str, raise_error=True, field_name=None): if len(cleaned) != len(value) and raise_error: field = field_name or 'non_field_errors' - raise ValidationError({field: [_("Remove HTML tags from this value")]}) + raise ValidationError({field: [_('Remove HTML tags from this value')]}) return cleaned diff --git a/InvenTree/InvenTree/helpers_model.py b/InvenTree/InvenTree/helpers_model.py index 09e4481ca9..ab76bf00db 100644 --- a/InvenTree/InvenTree/helpers_model.py +++ b/InvenTree/InvenTree/helpers_model.py @@ -120,7 +120,7 @@ def download_image_from_url(remote_url, timeout=2.5): 'INVENTREE_DOWNLOAD_FROM_URL_USER_AGENT' ) if user_agent: - headers = {"User-Agent": user_agent} + headers = {'User-Agent': user_agent} else: headers = None @@ -135,28 +135,28 @@ def download_image_from_url(remote_url, timeout=2.5): # Throw an error if anything goes wrong response.raise_for_status() except requests.exceptions.ConnectionError as exc: - raise Exception(_("Connection error") + f": {str(exc)}") + raise Exception(_('Connection error') + f': {str(exc)}') except requests.exceptions.Timeout as exc: raise exc except requests.exceptions.HTTPError: raise requests.exceptions.HTTPError( - _("Server responded with invalid status code") + f": {response.status_code}" + _('Server responded with invalid status code') + f': {response.status_code}' ) except Exception as exc: - raise Exception(_("Exception occurred") + f": {str(exc)}") + raise Exception(_('Exception occurred') + f': {str(exc)}') if response.status_code != 200: raise Exception( - _("Server responded with invalid status code") + f": {response.status_code}" + _('Server responded with invalid status code') + f': {response.status_code}' ) try: content_length = int(response.headers.get('Content-Length', 0)) except ValueError: - raise ValueError(_("Server responded with invalid Content-Length value")) + raise ValueError(_('Server responded with invalid Content-Length value')) if content_length > max_size: - raise ValueError(_("Image size is too large")) + raise ValueError(_('Image size is too large')) # Download the file, ensuring we do not exceed the reported size file = io.BytesIO() @@ -168,12 +168,12 @@ def download_image_from_url(remote_url, timeout=2.5): dl_size += len(chunk) if dl_size > max_size: - raise ValueError(_("Image download exceeded maximum size")) + raise ValueError(_('Image download exceeded maximum size')) file.write(chunk) if dl_size == 0: - raise ValueError(_("Remote server returned empty response")) + raise ValueError(_('Remote server returned empty response')) # Now, attempt to convert the downloaded data to a valid image file # img.verify() will throw an exception if the image is not valid @@ -181,7 +181,7 @@ def download_image_from_url(remote_url, timeout=2.5): img = Image.open(file).convert() img.verify() except Exception: - raise TypeError(_("Supplied URL is not a valid image file")) + raise TypeError(_('Supplied URL is not a valid image file')) return img diff --git a/InvenTree/InvenTree/magic_login.py b/InvenTree/InvenTree/magic_login.py index 2456da92b6..725b913bbf 100644 --- a/InvenTree/InvenTree/magic_login.py +++ b/InvenTree/InvenTree/magic_login.py @@ -18,13 +18,13 @@ def send_simple_login_email(user, link): """Send an email with the login link to this user.""" site = Site.objects.get_current() - context = {"username": user.username, "site_name": site.name, "link": link} + context = {'username': user.username, 'site_name': site.name, 'link': link} email_plaintext_message = render_to_string( - "InvenTree/user_simple_login.txt", context + 'InvenTree/user_simple_login.txt', context ) send_mail( - _(f"[{site.name}] Log in to the app"), + _(f'[{site.name}] Log in to the app'), email_plaintext_message, settings.DEFAULT_FROM_EMAIL, [user.email], @@ -34,7 +34,7 @@ def send_simple_login_email(user, link): class GetSimpleLoginSerializer(serializers.Serializer): """Serializer for the simple login view.""" - email = serializers.CharField(label=_("Email")) + email = serializers.CharField(label=_('Email')) class GetSimpleLoginView(APIView): @@ -47,14 +47,14 @@ class GetSimpleLoginView(APIView): """Get the token for the current user or fail.""" serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - self.email_submitted(email=serializer.data["email"]) - return Response({"status": "ok"}) + self.email_submitted(email=serializer.data['email']) + return Response({'status': 'ok'}) def email_submitted(self, email): """Notify user about link.""" user = self.get_user(email) if user is None: - print("user not found:", email) + print('user not found:', email) return link = self.create_link(user) send_simple_login_email(user, link) @@ -68,7 +68,7 @@ class GetSimpleLoginView(APIView): def create_link(self, user): """Create a login link for this user.""" - link = reverse("sesame-login") + link = reverse('sesame-login') link = self.request.build_absolute_uri(link) link += sesame.utils.get_query_string(user) return link diff --git a/InvenTree/InvenTree/management/commands/clean_settings.py b/InvenTree/InvenTree/management/commands/clean_settings.py index 4ef8269f24..45f99e98ab 100644 --- a/InvenTree/InvenTree/management/commands/clean_settings.py +++ b/InvenTree/InvenTree/management/commands/clean_settings.py @@ -12,7 +12,7 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): """Cleanup old (undefined) settings in the database.""" - logger.info("Collecting settings") + logger.info('Collecting settings') from common.models import InvenTreeSetting, InvenTreeUserSetting # general settings @@ -35,4 +35,4 @@ class Command(BaseCommand): setting.delete() logger.info("deleted user setting '%s'", setting.key) - logger.info("checked all settings") + logger.info('checked all settings') diff --git a/InvenTree/InvenTree/management/commands/prerender.py b/InvenTree/InvenTree/management/commands/prerender.py index 09c0c3b565..466b6666c7 100644 --- a/InvenTree/InvenTree/management/commands/prerender.py +++ b/InvenTree/InvenTree/management/commands/prerender.py @@ -58,10 +58,10 @@ class Command(BaseCommand): for file in os.listdir(SOURCE_DIR): path = os.path.join(SOURCE_DIR, file) if os.path.exists(path) and os.path.isfile(path): - print(f"render {file}") + print(f'render {file}') render_file(file, SOURCE_DIR, TARGET_DIR, locales, ctx) else: raise NotImplementedError( 'Using multi-level directories is not implemented at this point' ) # TODO multilevel dir if needed - print(f"rendered all files in {SOURCE_DIR}") + print(f'rendered all files in {SOURCE_DIR}') diff --git a/InvenTree/InvenTree/management/commands/rebuild_models.py b/InvenTree/InvenTree/management/commands/rebuild_models.py index b735c43735..02af71f3a5 100644 --- a/InvenTree/InvenTree/management/commands/rebuild_models.py +++ b/InvenTree/InvenTree/management/commands/rebuild_models.py @@ -13,50 +13,50 @@ class Command(BaseCommand): """Rebuild all database models which leverage the MPTT structure.""" # Part model try: - print("Rebuilding Part objects") + print('Rebuilding Part objects') from part.models import Part Part.objects.rebuild() except Exception: - print("Error rebuilding Part objects") + print('Error rebuilding Part objects') # Part category try: - print("Rebuilding PartCategory objects") + print('Rebuilding PartCategory objects') from part.models import PartCategory PartCategory.objects.rebuild() except Exception: - print("Error rebuilding PartCategory objects") + print('Error rebuilding PartCategory objects') # StockItem model try: - print("Rebuilding StockItem objects") + print('Rebuilding StockItem objects') from stock.models import StockItem StockItem.objects.rebuild() except Exception: - print("Error rebuilding StockItem objects") + print('Error rebuilding StockItem objects') # StockLocation model try: - print("Rebuilding StockLocation objects") + print('Rebuilding StockLocation objects') from stock.models import StockLocation StockLocation.objects.rebuild() except Exception: - print("Error rebuilding StockLocation objects") + print('Error rebuilding StockLocation objects') # Build model try: - print("Rebuilding Build objects") + print('Rebuilding Build objects') from build.models import Build Build.objects.rebuild() except Exception: - print("Error rebuilding Build objects") + print('Error rebuilding Build objects') diff --git a/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py b/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py index 102a9e1bf1..8b54c98cc7 100644 --- a/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py +++ b/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py @@ -37,20 +37,20 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): """Rebuild all thumbnail images.""" - logger.info("Rebuilding Part thumbnails") + logger.info('Rebuilding Part thumbnails') for part in Part.objects.exclude(image=None): try: self.rebuild_thumbnail(part) except (OperationalError, ProgrammingError): - logger.exception("ERROR: Database read error.") + logger.exception('ERROR: Database read error.') break - logger.info("Rebuilding Company thumbnails") + logger.info('Rebuilding Company thumbnails') for company in Company.objects.exclude(image=None): try: self.rebuild_thumbnail(company) except (OperationalError, ProgrammingError): - logger.exception("ERROR: abase read error.") + logger.exception('ERROR: abase read error.') break diff --git a/InvenTree/InvenTree/management/commands/wait_for_db.py b/InvenTree/InvenTree/management/commands/wait_for_db.py index 16223d4f77..6bfdc98b58 100644 --- a/InvenTree/InvenTree/management/commands/wait_for_db.py +++ b/InvenTree/InvenTree/management/commands/wait_for_db.py @@ -12,7 +12,7 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): """Wait till the database is ready.""" - self.stdout.write("Waiting for database...") + self.stdout.write('Waiting for database...') connected = False @@ -25,12 +25,12 @@ class Command(BaseCommand): connected = True except OperationalError as e: - self.stdout.write(f"Could not connect to database: {e}") + self.stdout.write(f'Could not connect to database: {e}') except ImproperlyConfigured as e: - self.stdout.write(f"Improperly configured: {e}") + self.stdout.write(f'Improperly configured: {e}') else: if not connection.is_usable(): - self.stdout.write("Database configuration is not usable") + self.stdout.write('Database configuration is not usable') if connected: - self.stdout.write("Database connection successful!") + self.stdout.write('Database connection successful!') diff --git a/InvenTree/InvenTree/metadata.py b/InvenTree/InvenTree/metadata.py index 3bde8f84e5..11a61b3a69 100644 --- a/InvenTree/InvenTree/metadata.py +++ b/InvenTree/InvenTree/metadata.py @@ -69,7 +69,7 @@ class InvenTreeMetadata(SimpleMetadata): metadata['model'] = tbl_label - table = f"{app_label}_{tbl_label}" + table = f'{app_label}_{tbl_label}' actions = metadata.get('actions', None) @@ -87,7 +87,7 @@ class InvenTreeMetadata(SimpleMetadata): } # let the view define a custom rolemap - if hasattr(view, "rolemap"): + if hasattr(view, 'rolemap'): rolemap.update(view.rolemap) # Remove any HTTP methods that the user does not have permission for @@ -264,7 +264,7 @@ class InvenTreeMetadata(SimpleMetadata): model = field.queryset.model else: logger.debug( - "Could not extract model for:", field_info.get('label'), '->', field + 'Could not extract model for:', field_info.get('label'), '->', field ) model = None @@ -286,4 +286,4 @@ class InvenTreeMetadata(SimpleMetadata): return field_info -InvenTreeMetadata.label_lookup[DependentField] = "dependent field" +InvenTreeMetadata.label_lookup[DependentField] = 'dependent field' diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index 50a632c6ec..52428cf3ee 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -15,7 +15,7 @@ from error_report.middleware import ExceptionProcessor from InvenTree.urls import frontendpatterns from users.models import ApiToken -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class AuthRequiredMiddleware(object): @@ -91,7 +91,7 @@ class AuthRequiredMiddleware(object): authorized = True except ApiToken.DoesNotExist: - logger.warning("Access denied for unknown token %s", token_key) + logger.warning('Access denied for unknown token %s', token_key) # No authorization was found for the request if not authorized: diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index ef6ef5b4dd..65ca835ce6 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -303,7 +303,7 @@ class ReferenceIndexingMixin(models.Model): if recent: reference = recent.reference else: - reference = "" + reference = '' return reference @@ -316,20 +316,20 @@ class ReferenceIndexingMixin(models.Model): info = InvenTree.format.parse_format_string(pattern) except Exception as exc: raise ValidationError({ - "value": _("Improperly formatted pattern") + ": " + str(exc) + 'value': _('Improperly formatted pattern') + ': ' + str(exc) }) # Check that only 'allowed' keys are provided for key in info.keys(): if key not in ctx.keys(): raise ValidationError({ - "value": _("Unknown format key specified") + f": '{key}'" + 'value': _('Unknown format key specified') + f": '{key}'" }) # Check that the 'ref' variable is specified if 'ref' not in info.keys(): raise ValidationError({ - 'value': _("Missing required format key") + ": 'ref'" + 'value': _('Missing required format key') + ": 'ref'" }) @classmethod @@ -340,7 +340,7 @@ class ReferenceIndexingMixin(models.Model): value = str(value).strip() if len(value) == 0: - raise ValidationError(_("Reference field cannot be empty")) + raise ValidationError(_('Reference field cannot be empty')) # An 'empty' pattern means no further validation is required if not pattern: @@ -348,7 +348,7 @@ class ReferenceIndexingMixin(models.Model): if not InvenTree.format.validate_string(value, pattern): raise ValidationError( - _("Reference must match required pattern") + ": " + pattern + _('Reference must match required pattern') + ': ' + pattern ) # Check that the reference field can be rebuild @@ -380,7 +380,7 @@ class ReferenceIndexingMixin(models.Model): if validate: if reference_int > models.BigIntegerField.MAX_BIGINT: - raise ValidationError({"reference": _("Reference number is too large")}) + raise ValidationError({'reference': _('Reference number is too large')}) return reference_int @@ -399,7 +399,7 @@ def extract_int(reference, clip=0x7FFFFFFF, allow_negative=False): return 0 # Look at the start of the string - can it be "integerized"? - result = re.match(r"^(\d+)", reference) + result = re.match(r'^(\d+)', reference) if result and len(result.groups()) == 1: ref = result.groups()[0] @@ -455,7 +455,7 @@ class InvenTreeAttachment(models.Model): Note: Re-implement this for each subclass of InvenTreeAttachment """ - return "attachments" + return 'attachments' def save(self, *args, **kwargs): """Provide better validation error.""" @@ -547,7 +547,7 @@ class InvenTreeAttachment(models.Model): logger.error( "Attempted to rename attachment outside valid directory: '%s'", new_file ) - raise ValidationError(_("Invalid attachment directory")) + raise ValidationError(_('Invalid attachment directory')) # Ignore further checks if the filename is not actually being renamed if new_file == old_file: @@ -556,23 +556,23 @@ class InvenTreeAttachment(models.Model): forbidden = [ "'", '"', - "#", - "@", - "!", - "&", - "^", - "<", - ">", - ":", - ";", - "/", - "\\", - "|", - "?", - "*", - "%", - "~", - "`", + '#', + '@', + '!', + '&', + '^', + '<', + '>', + ':', + ';', + '/', + '\\', + '|', + '?', + '*', + '%', + '~', + '`', ] for c in forbidden: @@ -580,7 +580,7 @@ class InvenTreeAttachment(models.Model): raise ValidationError(_(f"Filename contains illegal character '{c}'")) if len(fn.split('.')) < 2: - raise ValidationError(_("Filename missing extension")) + raise ValidationError(_('Filename missing extension')) if not old_file.exists(): logger.error( @@ -589,14 +589,14 @@ class InvenTreeAttachment(models.Model): return if new_file.exists(): - raise ValidationError(_("Attachment with this filename already exists")) + raise ValidationError(_('Attachment with this filename already exists')) try: os.rename(old_file, new_file) self.attachment.name = os.path.join(self.getSubdir(), fn) self.save() except Exception: - raise ValidationError(_("Error renaming file")) + raise ValidationError(_('Error renaming file')) def fully_qualified_url(self): """Return a 'fully qualified' URL for this attachment. @@ -656,7 +656,7 @@ class InvenTreeTree(MPTTModel): except self.__class__.DoesNotExist: # If the object no longer exists, raise a ValidationError raise ValidationError( - "Object %s of type %s no longer exists", str(self), str(self.__class__) + 'Object %s of type %s no longer exists', str(self), str(self.__class__) ) # Cache node ID values for lower nodes, before we delete this one @@ -791,7 +791,7 @@ class InvenTreeTree(MPTTModel): super().save(*args, **kwargs) except InvalidMove: # Provide better error for parent selection - raise ValidationError({'parent': _("Invalid choice")}) + raise ValidationError({'parent': _('Invalid choice')}) # Re-calculate the 'pathstring' field pathstring = self.construct_pathstring() @@ -821,14 +821,14 @@ class InvenTreeTree(MPTTModel): self.__class__.objects.bulk_update(nodes_to_update, ['pathstring']) name = models.CharField( - blank=False, max_length=100, verbose_name=_("Name"), help_text=_("Name") + blank=False, max_length=100, verbose_name=_('Name'), help_text=_('Name') ) description = models.CharField( blank=True, max_length=250, - verbose_name=_("Description"), - help_text=_("Description (optional)"), + verbose_name=_('Description'), + help_text=_('Description (optional)'), ) # When a category is deleted, graft the children onto its parent @@ -837,7 +837,7 @@ class InvenTreeTree(MPTTModel): on_delete=models.DO_NOTHING, blank=True, null=True, - verbose_name=_("parent"), + verbose_name=_('parent'), related_name='children', ) @@ -854,7 +854,7 @@ class InvenTreeTree(MPTTModel): The default implementation returns an empty list """ - raise NotImplementedError(f"items() method not implemented for {type(self)}") + raise NotImplementedError(f'items() method not implemented for {type(self)}') def getUniqueParents(self): """Return a flat set of all parent items that exist above this node. @@ -929,7 +929,7 @@ class InvenTreeTree(MPTTModel): def __str__(self): """String representation of a category is the full path to that category.""" - return f"{self.pathstring} - {self.description}" + return f'{self.pathstring} - {self.description}' class InvenTreeNotesMixin(models.Model): @@ -1008,7 +1008,7 @@ class InvenTreeBarcodeMixin(models.Model): if hasattr(self, 'get_api_url'): api_url = self.get_api_url() - data['api_url'] = f"{api_url}{self.pk}/" + data['api_url'] = f'{api_url}{self.pk}/' if hasattr(self, 'get_absolute_url'): data['web_url'] = self.get_absolute_url() @@ -1040,7 +1040,7 @@ class InvenTreeBarcodeMixin(models.Model): # Check for existing item if self.__class__.lookup_barcode(barcode_hash) is not None: if raise_error: - raise ValidationError(_("Existing barcode found")) + raise ValidationError(_('Existing barcode found')) else: return False diff --git a/InvenTree/InvenTree/permissions.py b/InvenTree/InvenTree/permissions.py index 6aae0d3c23..74b844f008 100644 --- a/InvenTree/InvenTree/permissions.py +++ b/InvenTree/InvenTree/permissions.py @@ -18,7 +18,7 @@ def get_model_for_view(view, raise_error=True): if hasattr(view, 'get_serializer_class'): return view.get_serializr_class().Meta.model - raise AttributeError(f"Serializer class not specified for {view.__class__}") + raise AttributeError(f'Serializer class not specified for {view.__class__}') class RolePermission(permissions.BasePermission): @@ -62,7 +62,7 @@ class RolePermission(permissions.BasePermission): } # let the view define a custom rolemap - if hasattr(view, "rolemap"): + if hasattr(view, 'rolemap'): rolemap.update(view.rolemap) permission = rolemap[request.method] @@ -78,7 +78,7 @@ class RolePermission(permissions.BasePermission): app_label = model._meta.app_label model_name = model._meta.model_name - table = f"{app_label}_{model_name}" + table = f'{app_label}_{model_name}' except AttributeError: # We will assume that if the serializer class does *not* have a Meta, # then we don't need a permission diff --git a/InvenTree/InvenTree/ready.py b/InvenTree/InvenTree/ready.py index f6217c15e4..318d624fd9 100644 --- a/InvenTree/InvenTree/ready.py +++ b/InvenTree/InvenTree/ready.py @@ -25,8 +25,8 @@ def isInMainThread(): - The RUN_MAIN env is set in that case. However if --noreload is applied, this variable is not set because there are no different threads. """ - if "runserver" in sys.argv and "--noreload" not in sys.argv: - return os.environ.get('RUN_MAIN', None) == "true" + if 'runserver' in sys.argv and '--noreload' not in sys.argv: + return os.environ.get('RUN_MAIN', None) == 'true' return True diff --git a/InvenTree/InvenTree/sentry.py b/InvenTree/InvenTree/sentry.py index 8dc8c9305e..cd265f199d 100644 --- a/InvenTree/InvenTree/sentry.py +++ b/InvenTree/InvenTree/sentry.py @@ -37,7 +37,7 @@ def sentry_ignore_errors(): def init_sentry(dsn, sample_rate, tags): """Initialize sentry.io error reporting""" - logger.info("Initializing sentry.io integration") + logger.info('Initializing sentry.io integration') sentry_sdk.init( dsn=dsn, @@ -65,9 +65,9 @@ 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) + 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") + logger.warning('Failed to report exception to sentry.io') diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index c57b78e98d..824f11f25b 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -37,9 +37,9 @@ class InvenTreeMoneySerializer(MoneyField): def __init__(self, *args, **kwargs): """Override default values.""" - kwargs["max_digits"] = kwargs.get("max_digits", 19) - self.decimal_places = kwargs["decimal_places"] = kwargs.get("decimal_places", 6) - kwargs["required"] = kwargs.get("required", False) + kwargs['max_digits'] = kwargs.get('max_digits', 19) + self.decimal_places = kwargs['decimal_places'] = kwargs.get('decimal_places', 6) + kwargs['required'] = kwargs.get('required', False) super().__init__(*args, **kwargs) @@ -57,7 +57,7 @@ class InvenTreeMoneySerializer(MoneyField): amount = Decimal(amount) amount = round(amount, self.decimal_places) except Exception: - raise ValidationError({self.field_name: [_("Must be a valid number")]}) + raise ValidationError({self.field_name: [_('Must be a valid number')]}) currency = data.get( get_currency_field_name(self.field_name), self.default_currency @@ -134,7 +134,7 @@ class DependentField(serializers.Field): def get_child(self, raise_exception=False): """This method tries to extract the child based on the provided data in the request by the client.""" - data = deepcopy(self.context["request"].data) + data = deepcopy(self.context['request'].data) def visit_parent(node): """Recursively extract the data for the parent field/serializer in reverse.""" @@ -144,7 +144,7 @@ class DependentField(serializers.Field): visit_parent(node.parent) # only do for composite fields and stop right before the current field - if hasattr(node, "child") and node is not self and isinstance(data, dict): + if hasattr(node, 'child') and node is not self and isinstance(data, dict): data = data.get(node.field_name, None) visit_parent(self) @@ -424,7 +424,7 @@ class ExendedUserSerializer(UserSerializer): pass else: raise PermissionDenied( - _("You do not have permission to change this user role.") + _('You do not have permission to change this user role.') ) return super().validate(attrs) @@ -436,7 +436,7 @@ class UserCreateSerializer(ExendedUserSerializer): """Expanded valiadation for auth.""" # Check that the user trying to create a new user is a superuser if not self.context['request'].user.is_superuser: - raise serializers.ValidationError(_("Only superusers can create new users")) + raise serializers.ValidationError(_('Only superusers can create new users')) # Generate a random password password = User.objects.make_random_password(length=14) @@ -453,9 +453,9 @@ class UserCreateSerializer(ExendedUserSerializer): current_site = Site.objects.get_current() domain = current_site.domain instance.email_user( - subject=_(f"Welcome to {current_site.name}"), + subject=_(f'Welcome to {current_site.name}'), message=_( - f"Your account has been created.\n\nPlease use the password reset function to get access (at https://{domain})." + f'Your account has been created.\n\nPlease use the password reset function to get access (at https://{domain}).' ), ) return instance @@ -551,7 +551,7 @@ class InvenTreeDecimalField(serializers.FloatField): try: return Decimal(str(data)) except Exception: - raise serializers.ValidationError(_("Invalid value")) + raise serializers.ValidationError(_('Invalid value')) class DataFileUploadSerializer(serializers.Serializer): @@ -571,8 +571,8 @@ class DataFileUploadSerializer(serializers.Serializer): fields = ['data_file'] data_file = serializers.FileField( - label=_("Data File"), - help_text=_("Select data file for upload"), + label=_('Data File'), + help_text=_('Select data file for upload'), required=True, allow_empty_file=False, ) @@ -589,13 +589,13 @@ class DataFileUploadSerializer(serializers.Serializer): accepted_file_types = ['xls', 'xlsx', 'csv', 'tsv', 'xml'] if ext not in accepted_file_types: - raise serializers.ValidationError(_("Unsupported file type")) + raise serializers.ValidationError(_('Unsupported file type')) # Impose a 50MB limit on uploaded BOM files max_upload_file_size = 50 * 1024 * 1024 if data_file.size > max_upload_file_size: - raise serializers.ValidationError(_("File is too large")) + raise serializers.ValidationError(_('File is too large')) # Read file data into memory (bytes object) try: @@ -616,10 +616,10 @@ class DataFileUploadSerializer(serializers.Serializer): raise serializers.ValidationError(str(e)) if len(self.dataset.headers) == 0: - raise serializers.ValidationError(_("No columns found in file")) + raise serializers.ValidationError(_('No columns found in file')) if len(self.dataset) == 0: - raise serializers.ValidationError(_("No data rows found in file")) + raise serializers.ValidationError(_('No data rows found in file')) return data_file @@ -732,10 +732,10 @@ class DataFileExtractSerializer(serializers.Serializer): self.rows = data.get('rows', []) if len(self.rows) == 0: - raise serializers.ValidationError(_("No data rows provided")) + raise serializers.ValidationError(_('No data rows provided')) if len(self.columns) == 0: - raise serializers.ValidationError(_("No data columns supplied")) + raise serializers.ValidationError(_('No data columns supplied')) self.validate_extracted_columns() @@ -758,7 +758,7 @@ class DataFileExtractSerializer(serializers.Serializer): processed_row = self.process_row(self.row_to_dict(row)) if processed_row: - rows.append({"original": row, "data": processed_row}) + rows.append({'original': row, 'data': processed_row}) return {'fields': model_fields, 'columns': self.columns, 'rows': rows} @@ -834,8 +834,8 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass): required=False, allow_blank=False, write_only=True, - label=_("Remote Image"), - help_text=_("URL of remote image file"), + label=_('Remote Image'), + help_text=_('URL of remote image file'), ) def validate_remote_image(self, url): @@ -851,7 +851,7 @@ class RemoteImageMixin(metaclass=serializers.SerializerMetaclass): 'INVENTREE_DOWNLOAD_FROM_URL' ): raise ValidationError( - _("Downloading images from remote URL is not enabled") + _('Downloading images from remote URL is not enabled') ) try: diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index cd0a294a2c..9e569bf069 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -52,7 +52,7 @@ if TESTING: site_packages = '/usr/local/lib/python3.9/site-packages' if site_packages not in sys.path: - print("Adding missing site-packages path:", site_packages) + print('Adding missing site-packages path:', site_packages) sys.path.append(site_packages) # Are environment variables manipulated by tests? Needs to be set by testing code @@ -87,7 +87,7 @@ ENABLE_PLATFORM_FRONTEND = get_boolean_setting( # Configure logging settings log_level = get_setting('INVENTREE_LOG_LEVEL', 'log_level', 'WARNING') -logging.basicConfig(level=log_level, format="%(asctime)s %(levelname)s %(message)s") +logging.basicConfig(level=log_level, format='%(asctime)s %(levelname)s %(message)s') if log_level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: log_level = 'WARNING' # pragma: no cover @@ -109,7 +109,7 @@ if get_setting('INVENTREE_DB_LOGGING', 'db_logging', False): LOGGING['loggers'] = {'django.db.backends': {'level': log_level or 'DEBUG'}} # Get a logger instance for this setup file -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') # Load SECRET_KEY SECRET_KEY = config.get_secret_key() @@ -122,7 +122,7 @@ MEDIA_ROOT = config.get_media_dir() # List of allowed hosts (default = allow all) ALLOWED_HOSTS = get_setting( - "INVENTREE_ALLOWED_HOSTS", + 'INVENTREE_ALLOWED_HOSTS', config_key='allowed_hosts', default_value=['*'], typecast=list, @@ -135,11 +135,11 @@ CORS_URLS_REGEX = r'^/(api|media|static)/.*$' # Extract CORS options from configuration file CORS_ORIGIN_ALLOW_ALL = get_boolean_setting( - "INVENTREE_CORS_ORIGIN_ALLOW_ALL", config_key='cors.allow_all', default_value=False + 'INVENTREE_CORS_ORIGIN_ALLOW_ALL', config_key='cors.allow_all', default_value=False ) CORS_ORIGIN_WHITELIST = get_setting( - "INVENTREE_CORS_ORIGIN_WHITELIST", + 'INVENTREE_CORS_ORIGIN_WHITELIST', config_key='cors.whitelist', default_value=[], typecast=list, @@ -279,43 +279,43 @@ AUTHENTICATION_BACKENDS = CONFIG.get( 'django.contrib.auth.backends.RemoteUserBackend', # proxy login 'django.contrib.auth.backends.ModelBackend', 'allauth.account.auth_backends.AuthenticationBackend', # SSO login via external providers - "sesame.backends.ModelBackend", # Magic link login django-sesame + 'sesame.backends.ModelBackend', # Magic link login django-sesame ], ) # LDAP support -LDAP_AUTH = get_boolean_setting("INVENTREE_LDAP_ENABLED", "ldap.enabled", False) +LDAP_AUTH = get_boolean_setting('INVENTREE_LDAP_ENABLED', 'ldap.enabled', False) if LDAP_AUTH: import ldap from django_auth_ldap.config import GroupOfUniqueNamesType, LDAPSearch - AUTHENTICATION_BACKENDS.append("django_auth_ldap.backend.LDAPBackend") + AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend') # debug mode to troubleshoot configuration - LDAP_DEBUG = get_boolean_setting("INVENTREE_LDAP_DEBUG", "ldap.debug", False) + LDAP_DEBUG = get_boolean_setting('INVENTREE_LDAP_DEBUG', 'ldap.debug', False) if LDAP_DEBUG: - if "loggers" not in LOGGING: - LOGGING["loggers"] = {} - LOGGING["loggers"]["django_auth_ldap"] = { - "level": "DEBUG", - "handlers": ["console"], + if 'loggers' not in LOGGING: + LOGGING['loggers'] = {} + LOGGING['loggers']['django_auth_ldap'] = { + 'level': 'DEBUG', + 'handlers': ['console'], } # get global options from dict and use ldap.OPT_* as keys and values global_options_dict = get_setting( - "INVENTREE_LDAP_GLOBAL_OPTIONS", "ldap.global_options", {}, dict + 'INVENTREE_LDAP_GLOBAL_OPTIONS', 'ldap.global_options', {}, dict ) global_options = {} for k, v in global_options_dict.items(): # keys are always ldap.OPT_* constants k_attr = getattr(ldap, k, None) - if not k.startswith("OPT_") or k_attr is None: + if not k.startswith('OPT_') or k_attr is None: print(f"[LDAP] ldap.global_options, key '{k}' not found, skipping...") continue # values can also be other strings, e.g. paths v_attr = v - if v.startswith("OPT_"): + if v.startswith('OPT_'): v_attr = getattr(ldap, v, None) if v_attr is None: @@ -325,55 +325,55 @@ if LDAP_AUTH: global_options[k_attr] = v_attr AUTH_LDAP_GLOBAL_OPTIONS = global_options if LDAP_DEBUG: - print("[LDAP] ldap.global_options =", global_options) + print('[LDAP] ldap.global_options =', global_options) - AUTH_LDAP_SERVER_URI = get_setting("INVENTREE_LDAP_SERVER_URI", "ldap.server_uri") + AUTH_LDAP_SERVER_URI = get_setting('INVENTREE_LDAP_SERVER_URI', 'ldap.server_uri') AUTH_LDAP_START_TLS = get_boolean_setting( - "INVENTREE_LDAP_START_TLS", "ldap.start_tls", False + 'INVENTREE_LDAP_START_TLS', 'ldap.start_tls', False ) - AUTH_LDAP_BIND_DN = get_setting("INVENTREE_LDAP_BIND_DN", "ldap.bind_dn") + AUTH_LDAP_BIND_DN = get_setting('INVENTREE_LDAP_BIND_DN', 'ldap.bind_dn') AUTH_LDAP_BIND_PASSWORD = get_setting( - "INVENTREE_LDAP_BIND_PASSWORD", "ldap.bind_password" + 'INVENTREE_LDAP_BIND_PASSWORD', 'ldap.bind_password' ) AUTH_LDAP_USER_SEARCH = LDAPSearch( - get_setting("INVENTREE_LDAP_SEARCH_BASE_DN", "ldap.search_base_dn"), + get_setting('INVENTREE_LDAP_SEARCH_BASE_DN', 'ldap.search_base_dn'), ldap.SCOPE_SUBTREE, str( get_setting( - "INVENTREE_LDAP_SEARCH_FILTER_STR", - "ldap.search_filter_str", - "(uid= %(user)s)", + 'INVENTREE_LDAP_SEARCH_FILTER_STR', + 'ldap.search_filter_str', + '(uid= %(user)s)', ) ), ) AUTH_LDAP_USER_DN_TEMPLATE = get_setting( - "INVENTREE_LDAP_USER_DN_TEMPLATE", "ldap.user_dn_template" + 'INVENTREE_LDAP_USER_DN_TEMPLATE', 'ldap.user_dn_template' ) AUTH_LDAP_USER_ATTR_MAP = get_setting( - "INVENTREE_LDAP_USER_ATTR_MAP", - "ldap.user_attr_map", + 'INVENTREE_LDAP_USER_ATTR_MAP', + 'ldap.user_attr_map', {'first_name': 'givenName', 'last_name': 'sn', 'email': 'mail'}, dict, ) AUTH_LDAP_ALWAYS_UPDATE_USER = get_boolean_setting( - "INVENTREE_LDAP_ALWAYS_UPDATE_USER", "ldap.always_update_user", True + 'INVENTREE_LDAP_ALWAYS_UPDATE_USER', 'ldap.always_update_user', True ) AUTH_LDAP_CACHE_TIMEOUT = get_setting( - "INVENTREE_LDAP_CACHE_TIMEOUT", "ldap.cache_timeout", 3600, int + 'INVENTREE_LDAP_CACHE_TIMEOUT', 'ldap.cache_timeout', 3600, int ) AUTH_LDAP_GROUP_SEARCH = LDAPSearch( - get_setting("INVENTREE_LDAP_GROUP_SEARCH", "ldap.group_search"), + get_setting('INVENTREE_LDAP_GROUP_SEARCH', 'ldap.group_search'), ldap.SCOPE_SUBTREE, - "(objectClass=groupOfUniqueNames)", + '(objectClass=groupOfUniqueNames)', ) - AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType(name_attr="cn") + AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType(name_attr='cn') AUTH_LDAP_REQUIRE_GROUP = get_setting( - "INVENTREE_LDAP_REQUIRE_GROUP", "ldap.require_group" + 'INVENTREE_LDAP_REQUIRE_GROUP', 'ldap.require_group' ) - AUTH_LDAP_DENY_GROUP = get_setting("INVENTREE_LDAP_DENY_GROUP", "ldap.deny_group") + AUTH_LDAP_DENY_GROUP = get_setting('INVENTREE_LDAP_DENY_GROUP', 'ldap.deny_group') AUTH_LDAP_USER_FLAGS_BY_GROUP = get_setting( - "INVENTREE_LDAP_USER_FLAGS_BY_GROUP", "ldap.user_flags_by_group", {}, dict + 'INVENTREE_LDAP_USER_FLAGS_BY_GROUP', 'ldap.user_flags_by_group', {}, dict ) AUTH_LDAP_FIND_GROUP_PERMS = True @@ -383,7 +383,7 @@ DEBUG_TOOLBAR_ENABLED = DEBUG and get_setting( # If the debug toolbar is enabled, add the modules if DEBUG_TOOLBAR_ENABLED: # pragma: no cover - logger.info("Running with DEBUG_TOOLBAR enabled") + logger.info('Running with DEBUG_TOOLBAR enabled') INSTALLED_APPS.append('debug_toolbar') MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware') @@ -401,9 +401,9 @@ DOCKER = get_boolean_setting('INVENTREE_DOCKER', default_value=False) if DOCKER: # pragma: no cover # Internal IP addresses are different when running under docker hostname, ___, ips = socket.gethostbyname_ex(socket.gethostname()) - INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + [ - "127.0.0.1", - "10.0.2.2", + INTERNAL_IPS = [ip[: ip.rfind('.')] + '.1' for ip in ips] + [ + '127.0.0.1', + '10.0.2.2', ] # Allow secure http developer server in debug mode @@ -521,7 +521,7 @@ Configure the database backend based on the user-specified values. - The following code lets the user "mix and match" database configuration """ -logger.debug("Configuring database backend:") +logger.debug('Configuring database backend:') # Extract database configuration from the config.yaml file db_config = CONFIG.get('database', {}) @@ -535,7 +535,7 @@ db_keys = ['ENGINE', 'NAME', 'USER', 'PASSWORD', 'HOST', 'PORT'] for key in db_keys: # First, check the environment variables - env_key = f"INVENTREE_DB_{key}" + env_key = f'INVENTREE_DB_{key}' env_var = os.environ.get(env_key, None) if env_var: @@ -544,7 +544,7 @@ for key in db_keys: try: env_var = int(env_var) except ValueError: - logger.exception("Invalid number for %s: %s", env_key, env_var) + logger.exception('Invalid number for %s: %s', env_key, env_var) # Override configuration value db_config[key] = env_var @@ -585,9 +585,9 @@ if 'sqlite' in db_engine: db_name = str(Path(db_name).resolve()) db_config['NAME'] = db_name -logger.info("DB_ENGINE: %s", db_engine) -logger.info("DB_NAME: %s", db_name) -logger.info("DB_HOST: %s", db_host) +logger.info('DB_ENGINE: %s', db_engine) +logger.info('DB_NAME: %s', db_name) +logger.info('DB_HOST: %s', db_host) """ In addition to base-level database configuration, we may wish to specify specific options to the database backend @@ -600,21 +600,21 @@ Ref: https://docs.djangoproject.com/en/3.2/ref/settings/#std:setting-OPTIONS # connecting to the database server (such as a replica failover) don't sit and # wait for possibly an hour or more, just tell the client something went wrong # and let the client retry when they want to. -db_options = db_config.get("OPTIONS", db_config.get("options", {})) +db_options = db_config.get('OPTIONS', db_config.get('options', {})) # Specific options for postgres backend -if "postgres" in db_engine: # pragma: no cover +if 'postgres' in db_engine: # pragma: no cover from psycopg2.extensions import ( ISOLATION_LEVEL_READ_COMMITTED, ISOLATION_LEVEL_SERIALIZABLE, ) # Connection timeout - if "connect_timeout" not in db_options: + if 'connect_timeout' not in db_options: # The DB server is in the same data center, it should not take very # long to connect to the database server # # seconds, 2 is minimum allowed by libpq - db_options["connect_timeout"] = int( + db_options['connect_timeout'] = int( get_setting('INVENTREE_DB_TIMEOUT', 'database.timeout', 2) ) @@ -624,36 +624,36 @@ if "postgres" in db_engine: # pragma: no cover # issue to resolve itself. It it that doesn't happen whatever happened # is probably fatal and no amount of waiting is going to fix it. # # 0 - TCP Keepalives disabled; 1 - enabled - if "keepalives" not in db_options: - db_options["keepalives"] = int( + if 'keepalives' not in db_options: + db_options['keepalives'] = int( get_setting('INVENTREE_DB_TCP_KEEPALIVES', 'database.tcp_keepalives', 1) ) # Seconds after connection is idle to send keep alive - if "keepalives_idle" not in db_options: - db_options["keepalives_idle"] = int( + if 'keepalives_idle' not in db_options: + db_options['keepalives_idle'] = int( get_setting( 'INVENTREE_DB_TCP_KEEPALIVES_IDLE', 'database.tcp_keepalives_idle', 1 ) ) # Seconds after missing ACK to send another keep alive - if "keepalives_interval" not in db_options: - db_options["keepalives_interval"] = int( + if 'keepalives_interval' not in db_options: + db_options['keepalives_interval'] = int( get_setting( - "INVENTREE_DB_TCP_KEEPALIVES_INTERVAL", - "database.tcp_keepalives_internal", - "1", + 'INVENTREE_DB_TCP_KEEPALIVES_INTERVAL', + 'database.tcp_keepalives_internal', + '1', ) ) # Number of missing ACKs before we close the connection - if "keepalives_count" not in db_options: - db_options["keepalives_count"] = int( + if 'keepalives_count' not in db_options: + db_options['keepalives_count'] = int( get_setting( - "INVENTREE_DB_TCP_KEEPALIVES_COUNT", - "database.tcp_keepalives_count", - "5", + 'INVENTREE_DB_TCP_KEEPALIVES_COUNT', + 'database.tcp_keepalives_count', + '5', ) ) @@ -668,18 +668,18 @@ if "postgres" in db_engine: # pragma: no cover # protect against simultaneous changes. # https://www.postgresql.org/docs/devel/transaction-iso.html # https://docs.djangoproject.com/en/3.2/ref/databases/#isolation-level - if "isolation_level" not in db_options: + if 'isolation_level' not in db_options: serializable = get_boolean_setting( 'INVENTREE_DB_ISOLATION_SERIALIZABLE', 'database.serializable', False ) - db_options["isolation_level"] = ( + db_options['isolation_level'] = ( ISOLATION_LEVEL_SERIALIZABLE if serializable else ISOLATION_LEVEL_READ_COMMITTED ) # Specific options for MySql / MariaDB backend -elif "mysql" in db_engine: # pragma: no cover +elif 'mysql' in db_engine: # pragma: no cover # TODO TCP time outs and keepalives # MariaDB's default isolation level is Repeatable Read which is @@ -688,16 +688,16 @@ elif "mysql" in db_engine: # pragma: no cover # protect against siumltaneous changes. # https://mariadb.com/kb/en/mariadb-transactions-and-isolation-levels-for-sql-server-users/#changing-the-isolation-level # https://docs.djangoproject.com/en/3.2/ref/databases/#mysql-isolation-level - if "isolation_level" not in db_options: + if 'isolation_level' not in db_options: serializable = get_boolean_setting( 'INVENTREE_DB_ISOLATION_SERIALIZABLE', 'database.serializable', False ) - db_options["isolation_level"] = ( - "serializable" if serializable else "read committed" + db_options['isolation_level'] = ( + 'serializable' if serializable else 'read committed' ) # Specific options for sqlite backend -elif "sqlite" in db_engine: +elif 'sqlite' in db_engine: # TODO: Verify timeouts are not an issue because no network is involved for SQLite # SQLite's default isolation level is Serializable due to SQLite's @@ -756,30 +756,30 @@ if cache_host: # pragma: no cover # so don't wait too long for the cache as nothing in the cache should be # irreplaceable. _cache_options = { - "CLIENT_CLASS": "django_redis.client.DefaultClient", - "SOCKET_CONNECT_TIMEOUT": int(os.getenv("CACHE_CONNECT_TIMEOUT", "2")), - "SOCKET_TIMEOUT": int(os.getenv("CACHE_SOCKET_TIMEOUT", "2")), - "CONNECTION_POOL_KWARGS": { - "socket_keepalive": config.is_true(os.getenv("CACHE_TCP_KEEPALIVE", "1")), - "socket_keepalive_options": { - socket.TCP_KEEPCNT: int(os.getenv("CACHE_KEEPALIVES_COUNT", "5")), - socket.TCP_KEEPIDLE: int(os.getenv("CACHE_KEEPALIVES_IDLE", "1")), - socket.TCP_KEEPINTVL: int(os.getenv("CACHE_KEEPALIVES_INTERVAL", "1")), + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + 'SOCKET_CONNECT_TIMEOUT': int(os.getenv('CACHE_CONNECT_TIMEOUT', '2')), + 'SOCKET_TIMEOUT': int(os.getenv('CACHE_SOCKET_TIMEOUT', '2')), + 'CONNECTION_POOL_KWARGS': { + 'socket_keepalive': config.is_true(os.getenv('CACHE_TCP_KEEPALIVE', '1')), + 'socket_keepalive_options': { + socket.TCP_KEEPCNT: int(os.getenv('CACHE_KEEPALIVES_COUNT', '5')), + socket.TCP_KEEPIDLE: int(os.getenv('CACHE_KEEPALIVES_IDLE', '1')), + socket.TCP_KEEPINTVL: int(os.getenv('CACHE_KEEPALIVES_INTERVAL', '1')), socket.TCP_USER_TIMEOUT: int( - os.getenv("CACHE_TCP_USER_TIMEOUT", "1000") + os.getenv('CACHE_TCP_USER_TIMEOUT', '1000') ), }, }, } CACHES = { - "default": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": f"redis://{cache_host}:{cache_port}/0", - "OPTIONS": _cache_options, + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': f'redis://{cache_host}:{cache_port}/0', + 'OPTIONS': _cache_options, } } else: - CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}} + CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}} _q_worker_timeout = int( get_setting('INVENTREE_BACKGROUND_TIMEOUT', 'background.timeout', 90) @@ -813,7 +813,7 @@ if SENTRY_ENABLED and SENTRY_DSN: if cache_host: # pragma: no cover # If using external redis cache, make the cache the broker for Django Q # as well - Q_CLUSTER["django_redis"] = "worker" + Q_CLUSTER['django_redis'] = 'worker' # database user sessions SESSION_ENGINE = 'user_sessions.backends.db' @@ -840,7 +840,7 @@ AUTH_PASSWORD_VALIDATORS = [ EXTRA_URL_SCHEMES = get_setting('INVENTREE_EXTRA_URL_SCHEMES', 'extra_url_schemes', []) if type(EXTRA_URL_SCHEMES) not in [list]: # pragma: no cover - logger.warning("extra_url_schemes not correctly formatted") + logger.warning('extra_url_schemes not correctly formatted') EXTRA_URL_SCHEMES = [] # Internationalization @@ -912,7 +912,7 @@ CURRENCIES = get_setting( # Ensure that at least one currency value is available if len(CURRENCIES) == 0: # pragma: no cover - logger.warning("No currencies selected: Defaulting to USD") + logger.warning('No currencies selected: Defaulting to USD') CURRENCIES = ['USD'] # Maximum number of decimal places for currency rendering @@ -965,7 +965,7 @@ USE_L10N = True if not TESTING: USE_TZ = True # pragma: no cover -DATE_INPUT_FORMATS = ["%Y-%m-%d"] +DATE_INPUT_FORMATS = ['%Y-%m-%d'] # crispy forms use the bootstrap templates CRISPY_TEMPLATE_PACK = 'bootstrap4' @@ -1090,7 +1090,7 @@ PLUGIN_FILE_CHECKED = False # Was the plugin file checked? SITE_URL = get_setting('INVENTREE_SITE_URL', 'site_url', None) if SITE_URL: - logger.info("Site URL: %s", SITE_URL) + logger.info('Site URL: %s', SITE_URL) # Check that the site URL is valid validator = URLValidator() @@ -1111,7 +1111,7 @@ FRONTEND_SETTINGS = config.get_frontend_settings(debug=DEBUG) FRONTEND_URL_BASE = FRONTEND_SETTINGS.get('base_url', 'platform') if DEBUG: - logger.info("InvenTree running with DEBUG enabled") + logger.info('InvenTree running with DEBUG enabled') logger.info("MEDIA_ROOT: '%s'", MEDIA_ROOT) logger.info("STATIC_ROOT: '%s'", STATIC_ROOT) @@ -1131,12 +1131,12 @@ FLAGS = { CUSTOM_FLAGS = get_setting('INVENTREE_FLAGS', 'flags', None, typecast=dict) if CUSTOM_FLAGS: if not isinstance(CUSTOM_FLAGS, dict): - logger.error("Invalid custom flags, must be valid dict: %s", str(CUSTOM_FLAGS)) + logger.error('Invalid custom flags, must be valid dict: %s', str(CUSTOM_FLAGS)) else: - logger.info("Custom flags: %s", str(CUSTOM_FLAGS)) + logger.info('Custom flags: %s', str(CUSTOM_FLAGS)) FLAGS.update(CUSTOM_FLAGS) # Magic login django-sesame SESAME_MAX_AGE = 300 # LOGIN_REDIRECT_URL = f"/{FRONTEND_URL_BASE}/logged-in/" -LOGIN_REDIRECT_URL = "/index/" +LOGIN_REDIRECT_URL = '/index/' diff --git a/InvenTree/InvenTree/social_auth_urls.py b/InvenTree/InvenTree/social_auth_urls.py index a5ca10c777..e323b1892d 100644 --- a/InvenTree/InvenTree/social_auth_urls.py +++ b/InvenTree/InvenTree/social_auth_urls.py @@ -74,9 +74,9 @@ provider_urlpatterns = [] for name, provider in providers.registry.provider_map.items(): try: - prov_mod = import_module(provider.get_package() + ".views") + prov_mod = import_module(provider.get_package() + '.views') except ImportError: - logger.exception("Could not import authentication provider %s", name) + logger.exception('Could not import authentication provider %s', name) continue # Try to extract the adapter class diff --git a/InvenTree/InvenTree/sso.py b/InvenTree/InvenTree/sso.py index 57fffa367d..d77a7dfc1b 100644 --- a/InvenTree/InvenTree/sso.py +++ b/InvenTree/InvenTree/sso.py @@ -48,7 +48,7 @@ def check_provider(provider, raise_error=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) + logger.error('SocialApp %s has no sites configured', app) return False # At this point, we assume that the provider is correctly configured diff --git a/InvenTree/InvenTree/status.py b/InvenTree/InvenTree/status.py index bd23ac1fa6..cd6d357928 100644 --- a/InvenTree/InvenTree/status.py +++ b/InvenTree/InvenTree/status.py @@ -13,7 +13,7 @@ from django_q.status import Stat import InvenTree.email import InvenTree.ready -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') def is_worker_running(**kwargs): @@ -63,13 +63,13 @@ def check_system_health(**kwargs): if not is_worker_running(**kwargs): # pragma: no cover result = False - logger.warning(_("Background worker check failed")) + logger.warning(_('Background worker check failed')) if not InvenTree.email.is_email_configured(): # pragma: no cover result = False - logger.warning(_("Email backend not configured")) + logger.warning(_('Email backend not configured')) if not result: # pragma: no cover - logger.warning(_("InvenTree system health checks failed")) + logger.warning(_('InvenTree system health checks failed')) return result diff --git a/InvenTree/InvenTree/status_codes.py b/InvenTree/InvenTree/status_codes.py index 966833f78f..65a02fb4b2 100644 --- a/InvenTree/InvenTree/status_codes.py +++ b/InvenTree/InvenTree/status_codes.py @@ -9,12 +9,12 @@ class PurchaseOrderStatus(StatusCode): """Defines a set of status codes for a PurchaseOrder.""" # Order status codes - PENDING = 10, _("Pending"), 'secondary' # Order is pending (not yet placed) - PLACED = 20, _("Placed"), 'primary' # Order has been placed with supplier - COMPLETE = 30, _("Complete"), 'success' # Order has been completed - CANCELLED = 40, _("Cancelled"), 'danger' # Order was cancelled - LOST = 50, _("Lost"), 'warning' # Order was lost - RETURNED = 60, _("Returned"), 'warning' # Order was returned + PENDING = 10, _('Pending'), 'secondary' # Order is pending (not yet placed) + PLACED = 20, _('Placed'), 'primary' # Order has been placed with supplier + COMPLETE = 30, _('Complete'), 'success' # Order has been completed + CANCELLED = 40, _('Cancelled'), 'danger' # Order was cancelled + LOST = 50, _('Lost'), 'warning' # Order was lost + RETURNED = 60, _('Returned'), 'warning' # Order was returned class PurchaseOrderStatusGroups: @@ -34,16 +34,16 @@ class PurchaseOrderStatusGroups: class SalesOrderStatus(StatusCode): """Defines a set of status codes for a SalesOrder.""" - PENDING = 10, _("Pending"), 'secondary' # Order is pending + PENDING = 10, _('Pending'), 'secondary' # Order is pending IN_PROGRESS = ( 15, - _("In Progress"), + _('In Progress'), 'primary', ) # Order has been issued, and is in progress - SHIPPED = 20, _("Shipped"), 'success' # Order has been shipped to customer - CANCELLED = 40, _("Cancelled"), 'danger' # Order has been cancelled - LOST = 50, _("Lost"), 'warning' # Order was lost - RETURNED = 60, _("Returned"), 'warning' # Order was returned + SHIPPED = 20, _('Shipped'), 'success' # Order has been shipped to customer + CANCELLED = 40, _('Cancelled'), 'danger' # Order has been cancelled + LOST = 50, _('Lost'), 'warning' # Order was lost + RETURNED = 60, _('Returned'), 'warning' # Order was returned class SalesOrderStatusGroups: @@ -59,18 +59,18 @@ class SalesOrderStatusGroups: class StockStatus(StatusCode): """Status codes for Stock.""" - OK = 10, _("OK"), 'success' # Item is OK - ATTENTION = 50, _("Attention needed"), 'warning' # Item requires attention - DAMAGED = 55, _("Damaged"), 'warning' # Item is damaged - DESTROYED = 60, _("Destroyed"), 'danger' # Item is destroyed - REJECTED = 65, _("Rejected"), 'danger' # Item is rejected - LOST = 70, _("Lost"), 'dark' # Item has been lost + OK = 10, _('OK'), 'success' # Item is OK + ATTENTION = 50, _('Attention needed'), 'warning' # Item requires attention + DAMAGED = 55, _('Damaged'), 'warning' # Item is damaged + DESTROYED = 60, _('Destroyed'), 'danger' # Item is destroyed + REJECTED = 65, _('Rejected'), 'danger' # Item is rejected + LOST = 70, _('Lost'), 'dark' # Item has been lost QUARANTINED = ( 75, - _("Quarantined"), + _('Quarantined'), 'info', ) # Item has been quarantined and is unavailable - RETURNED = 85, _("Returned"), 'warning' # Item has been returned from a customer + RETURNED = 85, _('Returned'), 'warning' # Item has been returned from a customer class StockStatusGroups: @@ -129,7 +129,7 @@ class StockHistoryCode(StatusCode): BUILD_CONSUMED = 57, _('Consumed by build order') # Sales order codes - SHIPPED_AGAINST_SALES_ORDER = 60, _("Shipped against Sales Order") + SHIPPED_AGAINST_SALES_ORDER = 60, _('Shipped against Sales Order') # Purchase order codes RECEIVED_AGAINST_PURCHASE_ORDER = 70, _('Received against Purchase Order') @@ -145,10 +145,10 @@ class StockHistoryCode(StatusCode): class BuildStatus(StatusCode): """Build status codes.""" - PENDING = 10, _("Pending"), 'secondary' # Build is pending / active - PRODUCTION = 20, _("Production"), 'primary' # BuildOrder is in production - CANCELLED = 30, _("Cancelled"), 'danger' # Build was cancelled - COMPLETE = 40, _("Complete"), 'success' # Build is complete + PENDING = 10, _('Pending'), 'secondary' # Build is pending / active + PRODUCTION = 20, _('Production'), 'primary' # BuildOrder is in production + CANCELLED = 30, _('Cancelled'), 'danger' # Build was cancelled + COMPLETE = 40, _('Complete'), 'success' # Build is complete class BuildStatusGroups: @@ -161,13 +161,13 @@ class ReturnOrderStatus(StatusCode): """Defines a set of status codes for a ReturnOrder""" # Order is pending, waiting for receipt of items - PENDING = 10, _("Pending"), 'secondary' + PENDING = 10, _('Pending'), 'secondary' # Items have been received, and are being inspected - IN_PROGRESS = 20, _("In Progress"), 'primary' + IN_PROGRESS = 20, _('In Progress'), 'primary' - COMPLETE = 30, _("Complete"), 'success' - CANCELLED = 40, _("Cancelled"), 'danger' + COMPLETE = 30, _('Complete'), 'success' + CANCELLED = 40, _('Cancelled'), 'danger' class ReturnOrderStatusGroups: @@ -179,19 +179,19 @@ class ReturnOrderStatusGroups: class ReturnOrderLineStatus(StatusCode): """Defines a set of status codes for a ReturnOrderLineItem""" - PENDING = 10, _("Pending"), 'secondary' + PENDING = 10, _('Pending'), 'secondary' # Item is to be returned to customer, no other action - RETURN = 20, _("Return"), 'success' + RETURN = 20, _('Return'), 'success' # Item is to be repaired, and returned to customer - REPAIR = 30, _("Repair"), 'primary' + REPAIR = 30, _('Repair'), 'primary' # Item is to be replaced (new item shipped) - REPLACE = 40, _("Replace"), 'warning' + REPLACE = 40, _('Replace'), 'warning' # Item is to be refunded (cannot be repaired) - REFUND = 50, _("Refund"), 'info' + REFUND = 50, _('Refund'), 'info' # Item is rejected - REJECT = 60, _("Reject"), 'danger' + REJECT = 60, _('Reject'), 'danger' diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 975065a295..7f67ab9f63 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -31,7 +31,7 @@ from plugin import registry from .version import isInvenTreeUpToDate -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') def schedule_task(taskname, **kwargs): @@ -46,7 +46,7 @@ def schedule_task(taskname, **kwargs): try: from django_q.models import Schedule except AppRegistryNotReady: # pragma: no cover - logger.info("Could not start background tasks - App registry not ready") + logger.info('Could not start background tasks - App registry not ready') return try: @@ -278,13 +278,13 @@ class ScheduledTask: interval: str minutes: int = None - MINUTES = "I" - HOURLY = "H" - DAILY = "D" - WEEKLY = "W" - MONTHLY = "M" - QUARTERLY = "Q" - YEARLY = "Y" + MINUTES = 'I' + HOURLY = 'H' + DAILY = 'D' + WEEKLY = 'W' + MONTHLY = 'M' + QUARTERLY = 'Q' + YEARLY = 'Y' TYPE = [MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY] @@ -349,7 +349,7 @@ def heartbeat(): try: from django_q.models import Success except AppRegistryNotReady: # pragma: no cover - logger.info("Could not perform heartbeat task - App registry not ready") + logger.info('Could not perform heartbeat task - App registry not ready') return threshold = timezone.now() - timedelta(minutes=30) @@ -378,7 +378,7 @@ def delete_successful_tasks(): results = Success.objects.filter(started__lte=threshold) if results.count() > 0: - logger.info("Deleting %s successful task records", results.count()) + logger.info('Deleting %s successful task records', results.count()) results.delete() except AppRegistryNotReady: # pragma: no cover @@ -402,7 +402,7 @@ def delete_failed_tasks(): results = Failure.objects.filter(started__lte=threshold) if results.count() > 0: - logger.info("Deleting %s failed task records", results.count()) + logger.info('Deleting %s failed task records', results.count()) results.delete() except AppRegistryNotReady: # pragma: no cover @@ -423,7 +423,7 @@ def delete_old_error_logs(): errors = Error.objects.filter(when__lte=threshold) if errors.count() > 0: - logger.info("Deleting %s old error logs", errors.count()) + logger.info('Deleting %s old error logs', errors.count()) errors.delete() except AppRegistryNotReady: # pragma: no cover @@ -449,13 +449,13 @@ def delete_old_notifications(): items = NotificationEntry.objects.filter(updated__lte=threshold) if items.count() > 0: - logger.info("Deleted %s old notification entries", items.count()) + logger.info('Deleted %s old notification entries', items.count()) items.delete() items = NotificationMessage.objects.filter(creation__lte=threshold) if items.count() > 0: - logger.info("Deleted %s old notification messages", items.count()) + logger.info('Deleted %s old notification messages', items.count()) items.delete() except AppRegistryNotReady: @@ -485,7 +485,7 @@ def check_for_updates(): if not check_daily_holdoff('check_for_updates', interval): return - logger.info("Checking for InvenTree software updates") + logger.info('Checking for InvenTree software updates') headers = {} @@ -494,7 +494,7 @@ def check_for_updates(): token = os.getenv('GITHUB_TOKEN', None) if token: - headers['Authorization'] = f"Bearer {token}" + headers['Authorization'] = f'Bearer {token}' response = requests.get( 'https://api.github.com/repos/inventree/inventree/releases/latest', @@ -513,7 +513,7 @@ def check_for_updates(): if not tag: raise ValueError("'tag_name' missing from GitHub response") # pragma: no cover - match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag) + match = re.match(r'^.*(\d+)\.(\d+)\.(\d+).*$', tag) if len(match.groups()) != 3: # pragma: no cover logger.warning("Version '%s' did not match expected pattern", tag) @@ -534,15 +534,15 @@ def check_for_updates(): # Send notification if there is a new version if not isInvenTreeUpToDate(): - logger.warning("InvenTree is not up-to-date, sending notification") + logger.warning('InvenTree is not up-to-date, sending notification') plg = registry.get_plugin('InvenTreeCoreNotificationsPlugin') if not plg: - logger.warning("Cannot send notification - plugin not found") + logger.warning('Cannot send notification - plugin not found') return plg = plg.plugin_config() if not plg: - logger.warning("Cannot send notification - plugin config not found") + logger.warning('Cannot send notification - plugin config not found') return # Send notification trigger_superuser_notification( @@ -579,7 +579,7 @@ def update_exchange_rates(force: bool = False): ) if not check_daily_holdoff('update_exchange_rates', interval): - logger.info("Skipping exchange rate update (interval not reached)") + logger.info('Skipping exchange rate update (interval not reached)') return backend = InvenTreeExchange() @@ -590,7 +590,7 @@ def update_exchange_rates(force: bool = False): backend.update_rates(base_currency=base) # Remove any exchange rates which are not in the provided currencies - Rate.objects.filter(backend="InvenTreeExchange").exclude( + Rate.objects.filter(backend='InvenTreeExchange').exclude( currency__in=currency_codes() ).delete() @@ -598,9 +598,9 @@ def update_exchange_rates(force: bool = False): record_task_success('update_exchange_rates') except (AppRegistryNotReady, OperationalError, ProgrammingError): - logger.warning("Could not update exchange rates - database not ready") + logger.warning('Could not update exchange rates - database not ready') except Exception as e: # pragma: no cover - logger.exception("Error updating exchange rates: %s", str(type(e))) + logger.exception('Error updating exchange rates: %s', str(type(e))) @scheduled_task(ScheduledTask.DAILY) @@ -620,11 +620,11 @@ def run_backup(): if not check_daily_holdoff('run_backup', interval): return - logger.info("Performing automated database backup task") + logger.info('Performing automated database backup task') - call_command("dbbackup", noinput=True, clean=True, compress=True, interactive=False) + call_command('dbbackup', noinput=True, clean=True, compress=True, interactive=False) call_command( - "mediabackup", noinput=True, clean=True, compress=True, interactive=False + 'mediabackup', noinput=True, clean=True, compress=True, interactive=False ) # Record that this task was successful @@ -653,7 +653,7 @@ def check_for_migrations(): logger.info('There are %s pending migrations', n) InvenTreeSetting.set_setting('_PENDING_MIGRATIONS', n, None) - logger.info("Checking for pending database migrations") + logger.info('Checking for pending database migrations') # Force plugin registry reload registry.check_reload() @@ -671,12 +671,12 @@ def check_for_migrations(): # Test if auto-updates are enabled if not get_setting('INVENTREE_AUTO_UPDATE', 'auto_update'): - logger.info("Auto-update is disabled - skipping migrations") + logger.info('Auto-update is disabled - skipping migrations') return # Log open migrations for migration in plan: - logger.info("- %s", str(migration[0])) + logger.info('- %s', str(migration[0])) # Set the application to maintenance mode - no access from now on. set_maintenance_mode(True) @@ -694,13 +694,13 @@ def check_for_migrations(): else: set_pending_migrations(0) - logger.info("Completed %s migrations", n) + logger.info('Completed %s migrations', n) # Make sure we are out of maintenance mode if get_maintenance_mode(): - logger.warning("Maintenance mode was not disabled - forcing it now") + logger.warning('Maintenance mode was not disabled - forcing it now') set_maintenance_mode(False) - logger.info("Manually released maintenance mode") + logger.info('Manually released maintenance mode') # We should be current now - triggering full reload to make sure all models # are loaded fully in their new state. diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index 4df2dc6907..72ea009853 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -69,11 +69,11 @@ class APITests(InvenTreeAPITestCase): """Helper function to use basic auth.""" # Use basic authentication - authstring = bytes("{u}:{p}".format(u=self.username, p=self.password), "ascii") + authstring = bytes('{u}:{p}'.format(u=self.username, p=self.password), 'ascii') # Use "basic" auth by default - auth = b64encode(authstring).decode("ascii") - self.client.credentials(HTTP_AUTHORIZATION="Basic {auth}".format(auth=auth)) + auth = b64encode(authstring).decode('ascii') + self.client.credentials(HTTP_AUTHORIZATION='Basic {auth}'.format(auth=auth)) def tokenAuth(self): """Helper function to use token auth.""" @@ -274,7 +274,7 @@ class BulkDeleteTests(InvenTreeAPITestCase): ) # DELETE with invalid 'items' - response = self.delete(url, {'items': {"hello": "world"}}, expected_code=400) + response = self.delete(url, {'items': {'hello': 'world'}}, expected_code=400) self.assertIn("'items' must be supplied as a list object", str(response.data)) diff --git a/InvenTree/InvenTree/test_urls.py b/InvenTree/InvenTree/test_urls.py index 6229d39a81..d15493a750 100644 --- a/InvenTree/InvenTree/test_urls.py +++ b/InvenTree/InvenTree/test_urls.py @@ -67,7 +67,7 @@ class URLTest(TestCase): """Search for all instances of {% url %} in supplied template file.""" urls = [] - pattern = "{% url ['\"]([^'\"]+)['\"]([^%]*)%}" + pattern = '{% url [\'"]([^\'"]+)[\'"]([^%]*)%}' with open(input_file, 'r') as f: data = f.read() @@ -91,16 +91,16 @@ class URLTest(TestCase): pk = None # TODO: Handle reverse lookup of admin URLs! - if url.startswith("admin:"): + if url.startswith('admin:'): return # TODO can this be more elegant? - if url.startswith("account_"): + if url.startswith('account_'): return if pk: # We will assume that there is at least one item in the database - reverse(url, kwargs={"pk": 1}) + reverse(url, kwargs={'pk': 1}) else: reverse(url) @@ -113,14 +113,14 @@ class URLTest(TestCase): def test_html_templates(self): """Test all HTML templates for broken url tags.""" - template_files = self.find_files("*.html") + template_files = self.find_files('*.html') for f in template_files: self.check_file(f) def test_js_templates(self): """Test all JS templates for broken url tags.""" - template_files = self.find_files("*.js") + template_files = self.find_files('*.js') for f in template_files: self.check_file(f) diff --git a/InvenTree/InvenTree/test_views.py b/InvenTree/InvenTree/test_views.py index 6defdd9338..48c8db6924 100644 --- a/InvenTree/InvenTree/test_views.py +++ b/InvenTree/InvenTree/test_views.py @@ -23,13 +23,13 @@ class ViewTests(InvenTreeTestCase): def test_index_redirect(self): """Top-level URL should redirect to "index" page.""" - response = self.client.get("/") + response = self.client.get('/') self.assertEqual(response.status_code, 302) def get_index_page(self): """Retrieve the index page (used for subsequent unit tests)""" - response = self.client.get("/index/") + response = self.client.get('/index/') self.assertEqual(response.status_code, 200) @@ -68,8 +68,8 @@ class ViewTests(InvenTreeTestCase): # Default user has staff access, so all panels will be present for panel in user_panels + staff_panels + plugin_panels: - self.assertIn(f"select-{panel}", content) - self.assertIn(f"panel-{panel}", content) + self.assertIn(f'select-{panel}', content) + self.assertIn(f'panel-{panel}', content) # Now create a user who does not have staff access pleb_user = get_user_model().objects.create_user( @@ -93,24 +93,24 @@ class ViewTests(InvenTreeTestCase): # Normal user still has access to user-specific panels for panel in user_panels: - self.assertIn(f"select-{panel}", content) - self.assertIn(f"panel-{panel}", content) + self.assertIn(f'select-{panel}', content) + self.assertIn(f'panel-{panel}', content) # Normal user does NOT have access to global or plugin settings for panel in staff_panels + plugin_panels: - self.assertNotIn(f"select-{panel}", content) - self.assertNotIn(f"panel-{panel}", content) + self.assertNotIn(f'select-{panel}', content) + self.assertNotIn(f'panel-{panel}', content) def test_url_login(self): """Test logging in via arguments""" # Log out self.client.logout() - response = self.client.get("/index/") + response = self.client.get('/index/') self.assertEqual(response.status_code, 302) # Try login with url response = self.client.get( - f"/accounts/login/?next=/&login={self.username}&password={self.password}" + f'/accounts/login/?next=/&login={self.username}&password={self.password}' ) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, '/') diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 833fd55b4a..2488c07685 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -45,12 +45,12 @@ class ConversionTest(TestCase): def test_prefixes(self): """Test inputs where prefixes are used""" tests = { - "3": 3, - "3m": 3, - "3mm": 0.003, - "3k": 3000, - "3u": 0.000003, - "3 inch": 0.0762, + '3': 3, + '3m': 3, + '3mm': 0.003, + '3k': 3000, + '3u': 0.000003, + '3 inch': 0.0762, } for val, expected in tests.items(): @@ -60,13 +60,13 @@ class ConversionTest(TestCase): def test_base_units(self): """Test conversion to specified base units""" tests = { - "3": 3, - "3 dozen": 36, - "50 dozen kW": 600000, - "1 / 10": 0.1, - "1/2 kW": 500, - "1/2 dozen kW": 6000, - "0.005 MW": 5000, + '3': 3, + '3 dozen': 36, + '50 dozen kW': 600000, + '1 / 10': 0.1, + '1/2 kW': 500, + '1/2 dozen kW': 6000, + '0.005 MW': 5000, } for val, expected in tests.items(): @@ -173,24 +173,24 @@ class ValidatorTest(TestCase): def test_overage(self): """Test overage validator.""" - validate_overage("100%") - validate_overage("10") - validate_overage("45.2 %") + validate_overage('100%') + validate_overage('10') + validate_overage('45.2 %') with self.assertRaises(django_exceptions.ValidationError): - validate_overage("-1") + validate_overage('-1') with self.assertRaises(django_exceptions.ValidationError): - validate_overage("-2.04 %") + validate_overage('-2.04 %') with self.assertRaises(django_exceptions.ValidationError): - validate_overage("105%") + validate_overage('105%') with self.assertRaises(django_exceptions.ValidationError): - validate_overage("xxx %") + validate_overage('xxx %') with self.assertRaises(django_exceptions.ValidationError): - validate_overage("aaaa") + validate_overage('aaaa') def test_url_validation(self): """Test for AllowedURLValidator""" @@ -230,7 +230,7 @@ class FormatTest(TestCase): def test_parse(self): """Tests for the 'parse_format_string' function""" # Extract data from a valid format string - fmt = "PO-{abc:02f}-{ref:04d}-{date}-???" + fmt = 'PO-{abc:02f}-{ref:04d}-{date}-???' info = InvenTree.format.parse_format_string(fmt) @@ -246,10 +246,10 @@ class FormatTest(TestCase): def test_create_regex(self): """Test function for creating a regex from a format string""" tests = { - "PO-123-{ref:04f}": r"^PO\-123\-(?P.+)$", - "{PO}-???-{ref}-{date}-22": r"^(?P.+)\-...\-(?P.+)\-(?P.+)\-22$", - "ABC-123-###-{ref}": r"^ABC\-123\-\d\d\d\-(?P.+)$", - "ABC-123": r"^ABC\-123$", + 'PO-123-{ref:04f}': r'^PO\-123\-(?P.+)$', + '{PO}-???-{ref}-{date}-22': r'^(?P.+)\-...\-(?P.+)\-(?P.+)\-22$', + 'ABC-123-###-{ref}': r'^ABC\-123\-\d\d\d\-(?P.+)$', + 'ABC-123': r'^ABC\-123$', } for fmt, reg in tests.items(): @@ -259,28 +259,28 @@ class FormatTest(TestCase): """Test that string validation works as expected""" # These tests should pass for value, pattern in { - "ABC-hello-123": "???-{q}-###", - "BO-1234": "BO-{ref}", - "111.222.fred.china": "???.###.{name}.{place}", - "PO-1234": "PO-{ref:04d}", + 'ABC-hello-123': '???-{q}-###', + 'BO-1234': 'BO-{ref}', + '111.222.fred.china': '???.###.{name}.{place}', + 'PO-1234': 'PO-{ref:04d}', }.items(): self.assertTrue(InvenTree.format.validate_string(value, pattern)) # These tests should fail for value, pattern in { - "ABC-hello-123": "###-{q}-???", - "BO-1234": "BO.{ref}", - "BO-####": "BO-{pattern}-{next}", - "BO-123d": "BO-{ref:04d}", + 'ABC-hello-123': '###-{q}-???', + 'BO-1234': 'BO.{ref}', + 'BO-####': 'BO-{pattern}-{next}', + 'BO-123d': 'BO-{ref:04d}', }.items(): self.assertFalse(InvenTree.format.validate_string(value, pattern)) def test_extract_value(self): """Test that we can extract named values based on a format string""" # Simple tests based on a straight-forward format string - fmt = "PO-###-{ref:04d}" + fmt = 'PO-###-{ref:04d}' - tests = {"123": "PO-123-123", "456": "PO-123-456", "789": "PO-123-789"} + tests = {'123': 'PO-123-123', '456': 'PO-123-456', '789': 'PO-123-789'} for k, v in tests.items(): self.assertEqual(InvenTree.format.extract_named_group('ref', v, fmt), k) @@ -293,8 +293,8 @@ class FormatTest(TestCase): InvenTree.format.extract_named_group('ref', v, fmt) # More complex tests - fmt = "PO-{date}-{test}-???-{ref}-###" - val = "PO-2022-02-01-hello-ABC-12345-222" + fmt = 'PO-{date}-{test}-???-{ref}-###' + val = 'PO-2022-02-01-hello-ABC-12345-222' data = {'date': '2022-02-01', 'test': 'hello', 'ref': '12345'} @@ -305,42 +305,42 @@ class FormatTest(TestCase): # Raises a ValueError as the format string is bad with self.assertRaises(ValueError): - InvenTree.format.extract_named_group("test", "PO-1234-5", "PO-{test}-{") + InvenTree.format.extract_named_group('test', 'PO-1234-5', 'PO-{test}-{') # Raises a NameError as the named group does not exist in the format string with self.assertRaises(NameError): - InvenTree.format.extract_named_group("missing", "PO-12345", "PO-{test}") + InvenTree.format.extract_named_group('missing', 'PO-12345', 'PO-{test}') # Raises a ValueError as the value does not match the format string with self.assertRaises(ValueError): - InvenTree.format.extract_named_group("test", "PO-1234", "PO-{test}-1234") + InvenTree.format.extract_named_group('test', 'PO-1234', 'PO-{test}-1234') with self.assertRaises(ValueError): - InvenTree.format.extract_named_group("test", "PO-ABC-xyz", "PO-###-{test}") + InvenTree.format.extract_named_group('test', 'PO-ABC-xyz', 'PO-###-{test}') def test_currency_formatting(self): """Test that currency formatting works correctly for multiple currencies""" test_data = ( - (Money(3651.285718, "USD"), 4, "$3,651.2857"), # noqa: E201,E202 - (Money(487587.849178, "CAD"), 5, "CA$487,587.84918"), # noqa: E201,E202 - (Money(0.348102, "EUR"), 1, "€0.3"), # noqa: E201,E202 - (Money(0.916530, "GBP"), 1, "£0.9"), # noqa: E201,E202 - (Money(61.031024, "JPY"), 3, "¥61.031"), # noqa: E201,E202 - (Money(49609.694602, "JPY"), 1, "¥49,609.7"), # noqa: E201,E202 - (Money(155565.264777, "AUD"), 2, "A$155,565.26"), # noqa: E201,E202 - (Money(0.820437, "CNY"), 4, "CN¥0.8204"), # noqa: E201,E202 - (Money(7587.849178, "EUR"), 0, "€7,588"), # noqa: E201,E202 - (Money(0.348102, "GBP"), 3, "£0.348"), # noqa: E201,E202 - (Money(0.652923, "CHF"), 0, "CHF1"), # noqa: E201,E202 - (Money(0.820437, "CNY"), 1, "CN¥0.8"), # noqa: E201,E202 - (Money(98789.5295680, "CHF"), 0, "CHF98,790"), # noqa: E201,E202 - (Money(0.585787, "USD"), 1, "$0.6"), # noqa: E201,E202 - (Money(0.690541, "CAD"), 3, "CA$0.691"), # noqa: E201,E202 - (Money(427.814104, "AUD"), 5, "A$427.81410"), # noqa: E201,E202 + (Money(3651.285718, 'USD'), 4, '$3,651.2857'), # noqa: E201,E202 + (Money(487587.849178, 'CAD'), 5, 'CA$487,587.84918'), # noqa: E201,E202 + (Money(0.348102, 'EUR'), 1, '€0.3'), # noqa: E201,E202 + (Money(0.916530, 'GBP'), 1, '£0.9'), # noqa: E201,E202 + (Money(61.031024, 'JPY'), 3, '¥61.031'), # noqa: E201,E202 + (Money(49609.694602, 'JPY'), 1, '¥49,609.7'), # noqa: E201,E202 + (Money(155565.264777, 'AUD'), 2, 'A$155,565.26'), # noqa: E201,E202 + (Money(0.820437, 'CNY'), 4, 'CN¥0.8204'), # noqa: E201,E202 + (Money(7587.849178, 'EUR'), 0, '€7,588'), # noqa: E201,E202 + (Money(0.348102, 'GBP'), 3, '£0.348'), # noqa: E201,E202 + (Money(0.652923, 'CHF'), 0, 'CHF1'), # noqa: E201,E202 + (Money(0.820437, 'CNY'), 1, 'CN¥0.8'), # noqa: E201,E202 + (Money(98789.5295680, 'CHF'), 0, 'CHF98,790'), # noqa: E201,E202 + (Money(0.585787, 'USD'), 1, '$0.6'), # noqa: E201,E202 + (Money(0.690541, 'CAD'), 3, 'CA$0.691'), # noqa: E201,E202 + (Money(427.814104, 'AUD'), 5, 'A$427.81410'), # noqa: E201,E202 ) - with self.settings(LANGUAGE_CODE="en-us"): + with self.settings(LANGUAGE_CODE='en-us'): for value, decimal_places, expected_result in test_data: result = InvenTree.format.format_money( value, decimal_places=decimal_places @@ -353,22 +353,22 @@ class TestHelpers(TestCase): def test_absolute_url(self): """Test helper function for generating an absolute URL""" - base = "https://demo.inventree.org:12345" + base = 'https://demo.inventree.org:12345' InvenTreeSetting.set_setting('INVENTREE_BASE_URL', base, change_user=None) tests = { - "": base, - "api/": base + "/api/", - "/api/": base + "/api/", - "api": base + "/api", - "media/label/output/": base + "/media/label/output/", - "static/logo.png": base + "/static/logo.png", - "https://www.google.com": "https://www.google.com", - "https://demo.inventree.org:12345/out.html": "https://demo.inventree.org:12345/out.html", - "https://demo.inventree.org/test.html": "https://demo.inventree.org/test.html", - "http://www.cwi.nl:80/%7Eguido/Python.html": "http://www.cwi.nl:80/%7Eguido/Python.html", - "test.org": base + "/test.org", + '': base, + 'api/': base + '/api/', + '/api/': base + '/api/', + 'api': base + '/api', + 'media/label/output/': base + '/media/label/output/', + 'static/logo.png': base + '/static/logo.png', + 'https://www.google.com': 'https://www.google.com', + 'https://demo.inventree.org:12345/out.html': 'https://demo.inventree.org:12345/out.html', + 'https://demo.inventree.org/test.html': 'https://demo.inventree.org/test.html', + 'http://www.cwi.nl:80/%7Eguido/Python.html': 'http://www.cwi.nl:80/%7Eguido/Python.html', + 'test.org': base + '/test.org', } for url, expected in tests.items(): @@ -442,7 +442,7 @@ class TestHelpers(TestCase): def test_download_image(self): """Test function for downloading image from remote URL""" # Run check with a sequence of bad URLs - for url in ["blog", "htp://test.com/?", "google", "\\invalid-url"]: + for url in ['blog', 'htp://test.com/?', 'google', '\\invalid-url']: with self.assertRaises(django_exceptions.ValidationError): InvenTree.helpers_model.download_image_from_url(url) @@ -467,7 +467,7 @@ class TestHelpers(TestCase): # Re-throw this error raise exc else: - print("Unexpected error:", type(exc), exc) + print('Unexpected error:', type(exc), exc) tries += 1 time.sleep(10 * tries) @@ -480,7 +480,7 @@ class TestHelpers(TestCase): # TODO: Re-implement this test when we are happier with the external service # dl_helper("https://httpstat.us/200?sleep=5000", requests.exceptions.ReadTimeout, timeout=1) - large_img = "https://github.com/inventree/InvenTree/raw/master/InvenTree/InvenTree/static/img/paper_splash_large.jpg" + large_img = 'https://github.com/inventree/InvenTree/raw/master/InvenTree/InvenTree/static/img/paper_splash_large.jpg' InvenTreeSetting.set_setting( 'INVENTREE_DOWNLOAD_IMAGE_MAX_SIZE', 1, change_user=None @@ -527,14 +527,14 @@ class TestIncrement(TestCase): def tests(self): """Test 'intelligent' incrementing function.""" tests = [ - ("", '1'), - (1, "2"), - ("001", "002"), - ("1001", "1002"), - ("ABC123", "ABC124"), - ("XYZ0", "XYZ1"), - ("123Q", "123Q"), - ("QQQ", "QQQ"), + ('', '1'), + (1, '2'), + ('001', '002'), + ('1001', '1002'), + ('ABC123', 'ABC124'), + ('XYZ0', 'XYZ1'), + ('123Q', '123Q'), + ('QQQ', 'QQQ'), ] for test in tests: @@ -550,7 +550,7 @@ class TestMakeBarcode(TestCase): def test_barcode_extended(self): """Test creation of barcode with extended data.""" bc = helpers.MakeBarcode( - "part", 3, {"id": 3, "url": "www.google.com"}, brief=False + 'part', 3, {'id': 3, 'url': 'www.google.com'}, brief=False ) self.assertIn('part', bc) @@ -564,7 +564,7 @@ class TestMakeBarcode(TestCase): def test_barcode_brief(self): """Test creation of simple barcode.""" - bc = helpers.MakeBarcode("stockitem", 7) + bc = helpers.MakeBarcode('stockitem', 7) data = json.loads(bc) self.assertEqual(len(data), 1) @@ -576,8 +576,8 @@ class TestDownloadFile(TestCase): def test_download(self): """Tests for DownloadFile.""" - helpers.DownloadFile("hello world", "out.txt") - helpers.DownloadFile(bytes(b"hello world"), "out.bin") + helpers.DownloadFile('hello world', 'out.txt') + helpers.DownloadFile(bytes(b'hello world'), 'out.bin') class TestMPTT(TestCase): @@ -636,62 +636,62 @@ class TestSerialNumberExtraction(TestCase): e = helpers.extract_serial_numbers # Test a range of numbers - sn = e("1-5", 5, 1) + sn = e('1-5', 5, 1) self.assertEqual(len(sn), 5) for i in range(1, 6): self.assertIn(str(i), sn) - sn = e("11-30", 20, 1) + sn = e('11-30', 20, 1) self.assertEqual(len(sn), 20) - sn = e("1, 2, 3, 4, 5", 5, 1) + sn = e('1, 2, 3, 4, 5', 5, 1) self.assertEqual(len(sn), 5) # Test partially specifying serials - sn = e("1, 2, 4+", 5, 1) + sn = e('1, 2, 4+', 5, 1) self.assertEqual(len(sn), 5) self.assertEqual(sn, ['1', '2', '4', '5', '6']) # Test groups are not interpolated if enough serials are supplied - sn = e("1, 2, 3, AF5-69H, 5", 5, 1) + sn = e('1, 2, 3, AF5-69H, 5', 5, 1) self.assertEqual(len(sn), 5) self.assertEqual(sn, ['1', '2', '3', 'AF5-69H', '5']) # Test groups are not interpolated with more than one hyphen in a word - sn = e("1, 2, TG-4SR-92, 4+", 5, 1) + sn = e('1, 2, TG-4SR-92, 4+', 5, 1) self.assertEqual(len(sn), 5) - self.assertEqual(sn, ['1', '2', "TG-4SR-92", '4', '5']) + self.assertEqual(sn, ['1', '2', 'TG-4SR-92', '4', '5']) # Test multiple placeholders - sn = e("1 2 ~ ~ ~", 5, 2) + sn = e('1 2 ~ ~ ~', 5, 2) self.assertEqual(len(sn), 5) self.assertEqual(sn, ['1', '2', '3', '4', '5']) - sn = e("1-5, 10-15", 11, 1) + sn = e('1-5, 10-15', 11, 1) self.assertIn('3', sn) self.assertIn('13', sn) - sn = e("1+", 10, 1) + sn = e('1+', 10, 1) self.assertEqual(len(sn), 10) self.assertEqual(sn, [str(_) for _ in range(1, 11)]) - sn = e("4, 1+2", 4, 1) + sn = e('4, 1+2', 4, 1) self.assertEqual(len(sn), 4) self.assertEqual(sn, ['4', '1', '2', '3']) - sn = e("~", 1, 1) + sn = e('~', 1, 1) self.assertEqual(len(sn), 1) self.assertEqual(sn, ['2']) - sn = e("~", 1, 3) + sn = e('~', 1, 3) self.assertEqual(len(sn), 1) self.assertEqual(sn, ['4']) - sn = e("~+", 2, 4) + sn = e('~+', 2, 4) self.assertEqual(len(sn), 2) self.assertEqual(sn, ['5', '6']) - sn = e("~+3", 4, 4) + sn = e('~+3', 4, 4) self.assertEqual(len(sn), 4) self.assertEqual(sn, ['5', '6', '7', '8']) @@ -701,70 +701,70 @@ class TestSerialNumberExtraction(TestCase): # Test duplicates with self.assertRaises(ValidationError): - e("1,2,3,3,3", 5, 1) + e('1,2,3,3,3', 5, 1) # Test invalid length with self.assertRaises(ValidationError): - e("1,2,3", 5, 1) + e('1,2,3', 5, 1) # Test empty string with self.assertRaises(ValidationError): - e(", , ,", 0, 1) + e(', , ,', 0, 1) # Test incorrect sign in group with self.assertRaises(ValidationError): - e("10-2", 8, 1) + e('10-2', 8, 1) # Test invalid group with self.assertRaises(ValidationError): - e("1-5-10", 10, 1) + e('1-5-10', 10, 1) with self.assertRaises(ValidationError): - e("10, a, 7-70j", 4, 1) + e('10, a, 7-70j', 4, 1) # Test groups are not interpolated with word characters with self.assertRaises(ValidationError): - e("1, 2, 3, E-5", 5, 1) + e('1, 2, 3, E-5', 5, 1) # Extract a range of values with a smaller range with self.assertRaises(ValidationError) as exc: - e("11-50", 10, 1) + e('11-50', 10, 1) self.assertIn('Range quantity exceeds 10', str(exc)) # Test groups are not interpolated with alpha characters with self.assertRaises(ValidationError) as exc: - e("1, A-2, 3+", 5, 1) + e('1, A-2, 3+', 5, 1) self.assertIn('Invalid group range: A-2', str(exc)) def test_combinations(self): """Test complex serial number combinations.""" e = helpers.extract_serial_numbers - sn = e("1 3-5 9+2", 7, 1) + sn = e('1 3-5 9+2', 7, 1) self.assertEqual(len(sn), 7) self.assertEqual(sn, ['1', '3', '4', '5', '9', '10', '11']) - sn = e("1,3-5,9+2", 7, 1) + sn = e('1,3-5,9+2', 7, 1) self.assertEqual(len(sn), 7) self.assertEqual(sn, ['1', '3', '4', '5', '9', '10', '11']) - sn = e("~+2", 3, 13) + sn = e('~+2', 3, 13) self.assertEqual(len(sn), 3) self.assertEqual(sn, ['14', '15', '16']) - sn = e("~+", 2, 13) + sn = e('~+', 2, 13) self.assertEqual(len(sn), 2) self.assertEqual(sn, ['14', '15']) # Test multiple increment groups - sn = e("~+4, 20+4, 30+4", 15, 10) + sn = e('~+4, 20+4, 30+4', 15, 10) self.assertEqual(len(sn), 15) for v in [14, 24, 34]: self.assertIn(str(v), sn) # Test multiple range groups - sn = e("11-20, 41-50, 91-100", 30, 1) + sn = e('11-20, 41-50, 91-100', 30, 1) self.assertEqual(len(sn), 30) for v in range(11, 21): @@ -859,7 +859,7 @@ class CurrencyTests(TestCase): break else: # pragma: no cover - print("Exchange rate update failed - retrying") + print('Exchange rate update failed - retrying') print(f'Expected {currency_codes()}, got {[a.currency for a in rates]}') time.sleep(1) @@ -1030,7 +1030,7 @@ class TestSettings(InvenTreeTestCase): # test typecasting to dict - valid JSON string should be mapped to corresponding dict with self.in_env_context({TEST_ENV_NAME: '{"a": 1}'}): self.assertEqual( - config.get_setting(TEST_ENV_NAME, None, typecast=dict), {"a": 1} + config.get_setting(TEST_ENV_NAME, None, typecast=dict), {'a': 1} ) # test typecasting to dict - invalid JSON string should be mapped to empty dict @@ -1047,8 +1047,8 @@ class TestInstanceName(InvenTreeTestCase): self.assertEqual(version.inventreeInstanceTitle(), 'InvenTree') # set up required setting - InvenTreeSetting.set_setting("INVENTREE_INSTANCE_TITLE", True, self.user) - InvenTreeSetting.set_setting("INVENTREE_INSTANCE", "Testing title", self.user) + InvenTreeSetting.set_setting('INVENTREE_INSTANCE_TITLE', True, self.user) + InvenTreeSetting.set_setting('INVENTREE_INSTANCE', 'Testing title', self.user) self.assertEqual(version.inventreeInstanceTitle(), 'Testing title') @@ -1060,7 +1060,7 @@ class TestInstanceName(InvenTreeTestCase): """Test instance url settings.""" # Set up required setting InvenTreeSetting.set_setting( - "INVENTREE_BASE_URL", "http://127.1.2.3", self.user + 'INVENTREE_BASE_URL', 'http://127.1.2.3', self.user ) # The site should also be changed @@ -1107,7 +1107,7 @@ class TestOffloadTask(InvenTreeTestCase): offload_task('dummy_task.numbers', 1, 1, 1, force_sync=True) ) - self.assertIn("Malformed function path", str(log.output)) + self.assertIn('Malformed function path', str(log.output)) # Offload dummy task with a Part instance # This should succeed, ensuring that the Part instance is correctly pickled diff --git a/InvenTree/InvenTree/unit_test.py b/InvenTree/InvenTree/unit_test.py index 52e84bb589..85af6d9dd2 100644 --- a/InvenTree/InvenTree/unit_test.py +++ b/InvenTree/InvenTree/unit_test.py @@ -39,7 +39,7 @@ def getMigrationFileNames(app): files = local_dir.joinpath('..', app, 'migrations').iterdir() # Regex pattern for migration files - regex = re.compile(r"^[\d]+_.*\.py$") + regex = re.compile(r'^[\d]+_.*\.py$') migration_files = [] @@ -241,14 +241,14 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase): yield # your test will be run here if verbose: - msg = "\r\n%s" % json.dumps(context.captured_queries, indent=4) + msg = '\r\n%s' % json.dumps(context.captured_queries, indent=4) else: msg = None n = len(context.captured_queries) if debug: - print(f"Expected less than {value} queries, got {n} queries") + print(f'Expected less than {value} queries, got {n} queries') self.assertLess(n, value, msg=msg) @@ -357,7 +357,7 @@ class InvenTreeAPITestCase(ExchangeRateMixin, UserMixin, APITestCase): # Check that the response is of the correct type if not isinstance(response, StreamingHttpResponse): raise ValueError( - "Response is not a StreamingHttpResponse object as expected" + 'Response is not a StreamingHttpResponse object as expected' ) # Extract filename diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 3f5f5179a5..be6f6be6f7 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -67,7 +67,7 @@ from .views import ( auth_request, ) -admin.site.site_header = "InvenTree Admin" +admin.site.site_header = 'InvenTree Admin' apipatterns = [ @@ -96,7 +96,7 @@ apipatterns = [ ), # InvenTree information endpoints path( - "version-text", VersionTextView.as_view(), name="api-version-text" + 'version-text', VersionTextView.as_view(), name='api-version-text' ), # version text path('version/', VersionView.as_view(), name='api-version'), # version info path('', InfoView.as_view(), name='api-inventree-info'), # server info @@ -153,11 +153,11 @@ apipatterns = [ ), # Magic login URLs path( - "email/generate/", + 'email/generate/', csrf_exempt(GetSimpleLoginView().as_view()), - name="sesame-generate", + name='sesame-generate', ), - path("email/login/", LoginView.as_view(), name="sesame-login"), + path('email/login/', LoginView.as_view(), name='sesame-login'), # Unknown endpoint re_path(r'^.*$', NotFoundView.as_view(), name='api-404'), ] @@ -403,12 +403,12 @@ classic_frontendpatterns = [ name='socialaccount_connections', ), re_path( - r"^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$", + r'^accounts/password/reset/key/(?P[0-9A-Za-z]+)-(?P.+)/$', CustomPasswordResetFromKeyView.as_view(), - name="account_reset_password_from_key", + name='account_reset_password_from_key', ), # Override login page - re_path("accounts/login/", CustomLoginView.as_view(), name="account_login"), + re_path('accounts/login/', CustomLoginView.as_view(), name='account_login'), re_path(r'^accounts/', include('allauth_2fa.urls')), # MFA support re_path(r'^accounts/', include('allauth.urls')), # included urlpatterns ] diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index bec31d2f09..74f44dc9b2 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -119,7 +119,7 @@ def validate_overage(value): i = Decimal(value) if i < 0: - raise ValidationError(_("Overage value must not be negative")) + raise ValidationError(_('Overage value must not be negative')) # Looks like a number return True @@ -135,15 +135,15 @@ def validate_overage(value): f = float(v) if f < 0: - raise ValidationError(_("Overage value must not be negative")) + raise ValidationError(_('Overage value must not be negative')) elif f > 100: - raise ValidationError(_("Overage must not exceed 100%")) + raise ValidationError(_('Overage must not exceed 100%')) return True except ValueError: pass - raise ValidationError(_("Invalid value for overage")) + raise ValidationError(_('Invalid value for overage')) def validate_part_name_format(value): diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 997b4fdddd..869c9c8e99 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -19,7 +19,7 @@ from dulwich.repo import NotGitRepository, Repo from .api_version import INVENTREE_API_TEXT, INVENTREE_API_VERSION # InvenTree software version -INVENTREE_SW_VERSION = "0.14.0 dev" +INVENTREE_SW_VERSION = '0.14.0 dev' # Discover git try: @@ -32,8 +32,8 @@ except (NotGitRepository, FileNotFoundError): def checkMinPythonVersion(): """Check that the Python version is at least 3.9""" - version = sys.version.split(" ")[0] - docs = "https://docs.inventree.org/en/stable/start/intro/#python-requirements" + version = sys.version.split(' ')[0] + docs = 'https://docs.inventree.org/en/stable/start/intro/#python-requirements' msg = f""" InvenTree requires Python 3.9 or above - you are running version {version}. @@ -47,22 +47,22 @@ def checkMinPythonVersion(): if sys.version_info.major == 3 and sys.version_info.minor < 9: raise RuntimeError(msg) - print(f"Python version {version} - {sys.executable}") + print(f'Python version {version} - {sys.executable}') def inventreeInstanceName(): """Returns the InstanceName settings for the current database.""" import common.models - return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "") + return common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE', '') def inventreeInstanceTitle(): """Returns the InstanceTitle for the current database.""" import common.models - if common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE_TITLE", False): - return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "") + if common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE_TITLE', False): + return common.models.InvenTreeSetting.get_setting('INVENTREE_INSTANCE', '') return 'InvenTree' @@ -76,7 +76,7 @@ def inventreeVersionTuple(version=None): if version is None: version = INVENTREE_SW_VERSION - match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", str(version)) + match = re.match(r'^.*(\d+)\.(\d+)\.(\d+).*$', str(version)) return [int(g) for g in match.groups()] @@ -93,14 +93,14 @@ def inventreeDocsVersion(): Release -> "major.minor.sub" e.g. "0.5.2" """ if isInvenTreeDevelopmentVersion(): - return "latest" + return 'latest' return INVENTREE_SW_VERSION # pragma: no cover def inventreeDocUrl(): """Return URL for InvenTree documentation site.""" tag = inventreeDocsVersion() - return f"https://docs.inventree.org/en/{tag}" + return f'https://docs.inventree.org/en/{tag}' def inventreeAppUrl(): @@ -110,12 +110,12 @@ def inventreeAppUrl(): def inventreeCreditsUrl(): """Return URL for InvenTree credits site.""" - return "https://docs.inventree.org/en/latest/credits/" + return 'https://docs.inventree.org/en/latest/credits/' def inventreeGithubUrl(): """Return URL for InvenTree github site.""" - return "https://github.com/InvenTree/InvenTree/" + return 'https://github.com/InvenTree/InvenTree/' def isInvenTreeUpToDate(): @@ -147,26 +147,26 @@ def inventreeApiVersion(): def parse_version_text(): """Parse the version text to structured data.""" - patched_data = INVENTREE_API_TEXT.split("\n\n") + patched_data = INVENTREE_API_TEXT.split('\n\n') # Remove first newline on latest version - patched_data[0] = patched_data[0].replace("\n", "", 1) + patched_data[0] = patched_data[0].replace('\n', '', 1) version_data = {} for version in patched_data: - data = version.split("\n") + data = version.split('\n') version_split = data[0].split(' -> ') version_detail = ( version_split[1].split(':', 1) if len(version_split) > 1 else [''] ) new_data = { - "version": version_split[0].strip(), - "date": version_detail[0].strip(), - "gh": version_detail[1].strip() if len(version_detail) > 1 else None, - "text": data[1:], - "latest": False, + 'version': version_split[0].strip(), + 'date': version_detail[0].strip(), + 'gh': version_detail[1].strip() if len(version_detail) > 1 else None, + 'text': data[1:], + 'latest': False, } - version_data[new_data["version"]] = new_data + version_data[new_data['version']] = new_data return version_data @@ -188,7 +188,7 @@ def inventreeApiText(versions: int = 10, start_version: int = 0): start_version = INVENTREE_API_VERSION - versions return { - f"v{a}": version_data.get(f"v{a}", None) + f'v{a}': version_data.get(f'v{a}', None) for a in range(start_version, start_version + versions) } diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 7c7767c763..a4e318591e 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -135,13 +135,13 @@ class InvenTreeRoleMixin(PermissionRequiredMixin): app_label = model._meta.app_label model_name = model._meta.model_name - table = f"{app_label}_{model_name}" + table = f'{app_label}_{model_name}' permission = self.get_permission_class() if not permission: raise AttributeError( - f"permission_class not defined for {type(self).__name__}" + f'permission_class not defined for {type(self).__name__}' ) # Check if the user has the required permission @@ -396,8 +396,8 @@ class AjaxUpdateView(AjaxMixin, UpdateView): class EditUserView(AjaxUpdateView): """View for editing user information.""" - ajax_template_name = "modal_form.html" - ajax_form_title = _("Edit User Information") + ajax_template_name = 'modal_form.html' + ajax_form_title = _('Edit User Information') form_class = EditUserForm def get_object(self): @@ -408,8 +408,8 @@ class EditUserView(AjaxUpdateView): class SetPasswordView(AjaxUpdateView): """View for setting user password.""" - ajax_template_name = "InvenTree/password.html" - ajax_form_title = _("Set Password") + ajax_template_name = 'InvenTree/password.html' + ajax_form_title = _('Set Password') form_class = SetPasswordForm def get_object(self): @@ -491,14 +491,14 @@ class SearchView(TemplateView): class DynamicJsView(TemplateView): """View for returning javacsript files, which instead of being served dynamically, are passed through the django translation engine!""" - template_name = "" + template_name = '' content_type = 'text/javascript' class SettingsView(TemplateView): """View for configuring User settings.""" - template_name = "InvenTree/settings/settings.html" + template_name = 'InvenTree/settings/settings.html' def get_context_data(self, **kwargs): """Add data for template.""" @@ -506,12 +506,12 @@ class SettingsView(TemplateView): ctx['settings'] = common_models.InvenTreeSetting.objects.all().order_by('key') - ctx["base_currency"] = common_settings.currency_code_default() - ctx["currencies"] = common_settings.currency_codes + ctx['base_currency'] = common_settings.currency_code_default() + ctx['currencies'] = common_settings.currency_codes - ctx["rates"] = Rate.objects.filter(backend="InvenTreeExchange") + ctx['rates'] = Rate.objects.filter(backend='InvenTreeExchange') - ctx["categories"] = PartCategory.objects.all().order_by( + ctx['categories'] = PartCategory.objects.all().order_by( 'tree_id', 'lft', 'name' ) @@ -520,16 +520,16 @@ class SettingsView(TemplateView): backend = ExchangeBackend.objects.filter(name='InvenTreeExchange') if backend.exists(): backend = backend.first() - ctx["rates_updated"] = backend.last_update + ctx['rates_updated'] = backend.last_update except Exception: - ctx["rates_updated"] = None + ctx['rates_updated'] = None # Forms and context for allauth ctx['add_email_form'] = AddEmailForm - ctx["can_add_email"] = EmailAddress.objects.can_add_email(self.request.user) + ctx['can_add_email'] = EmailAddress.objects.can_add_email(self.request.user) # Form and context for allauth social-accounts - ctx["request"] = self.request + ctx['request'] = self.request ctx['social_form'] = DisconnectForm(request=self.request) # user db sessions @@ -552,19 +552,19 @@ class AllauthOverrides(LoginRequiredMixin): class CustomEmailView(AllauthOverrides, EmailView): """Override of allauths EmailView to always show the settings but leave the functions allow.""" - success_url = reverse_lazy("settings") + success_url = reverse_lazy('settings') class CustomConnectionsView(AllauthOverrides, ConnectionsView): """Override of allauths ConnectionsView to always show the settings but leave the functions allow.""" - success_url = reverse_lazy("settings") + success_url = reverse_lazy('settings') class CustomPasswordResetFromKeyView(PasswordResetFromKeyView): """Override of allauths PasswordResetFromKeyView to always show the settings but leave the functions allow.""" - success_url = reverse_lazy("account_login") + success_url = reverse_lazy('account_login') class UserSessionOverride: @@ -646,18 +646,18 @@ class AppearanceSelectView(RedirectView): class DatabaseStatsView(AjaxView): """View for displaying database statistics.""" - ajax_template_name = "stats.html" - ajax_form_title = _("System Information") + ajax_template_name = 'stats.html' + ajax_form_title = _('System Information') class AboutView(AjaxView): """A view for displaying InvenTree version information""" - ajax_template_name = "about.html" - ajax_form_title = _("About InvenTree") + ajax_template_name = 'about.html' + ajax_form_title = _('About InvenTree') class NotificationsView(TemplateView): """View for showing notifications.""" - template_name = "InvenTree/notifications/notifications.html" + template_name = 'InvenTree/notifications/notifications.html' diff --git a/InvenTree/InvenTree/wsgi.py b/InvenTree/InvenTree/wsgi.py index 2a20d06c0d..4630c31182 100644 --- a/InvenTree/InvenTree/wsgi.py +++ b/InvenTree/InvenTree/wsgi.py @@ -11,7 +11,7 @@ import os # pragma: no cover from django.core.wsgi import get_wsgi_application # pragma: no cover os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "InvenTree.settings" + 'DJANGO_SETTINGS_MODULE', 'InvenTree.settings' ) # pragma: no cover application = get_wsgi_application() # pragma: no cover diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index 6187772569..9ccfc4b9d2 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -181,7 +181,7 @@ class SettingsList(ListAPI): class GlobalSettingsList(SettingsList): """API endpoint for accessing a list of global settings objects.""" - queryset = common.models.InvenTreeSetting.objects.exclude(key__startswith="_") + queryset = common.models.InvenTreeSetting.objects.exclude(key__startswith='_') serializer_class = common.serializers.GlobalSettingsSerializer def list(self, request, *args, **kwargs): @@ -214,7 +214,7 @@ class GlobalSettingsDetail(RetrieveUpdateAPI): """ lookup_field = 'key' - queryset = common.models.InvenTreeSetting.objects.exclude(key__startswith="_") + queryset = common.models.InvenTreeSetting.objects.exclude(key__startswith='_') serializer_class = common.serializers.GlobalSettingsSerializer def get_object(self): diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py index f05808a4d8..94e88f6159 100644 --- a/InvenTree/common/apps.py +++ b/InvenTree/common/apps.py @@ -29,7 +29,7 @@ class CommonConfig(AppConfig): if common.models.InvenTreeSetting.get_setting( 'SERVER_RESTART_REQUIRED', backup_value=False, create=False, cache=False ): - logger.info("Clearing SERVER_RESTART_REQUIRED flag") + logger.info('Clearing SERVER_RESTART_REQUIRED flag') if not isImportingData(): common.models.InvenTreeSetting.set_setting( diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 076e11cade..aa1e19b9ed 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -220,7 +220,7 @@ class BaseInvenTreeSetting(models.Model): If a particular setting is not present, create it with the default value """ - cache_key = f"BUILD_DEFAULT_VALUES:{str(cls.__name__)}" + cache_key = f'BUILD_DEFAULT_VALUES:{str(cls.__name__)}' if InvenTree.helpers.str2bool(cache.get(cache_key, False)): # Already built default values @@ -234,7 +234,7 @@ class BaseInvenTreeSetting(models.Model): if len(missing_keys) > 0: logger.info( - "Building %s default values for %s", len(missing_keys), str(cls) + 'Building %s default values for %s', len(missing_keys), str(cls) ) cls.objects.bulk_create([ cls(key=key, value=cls.get_setting_default(key), **kwargs) @@ -243,7 +243,7 @@ class BaseInvenTreeSetting(models.Model): ]) except Exception as exc: logger.exception( - "Failed to build default values for %s (%s)", str(cls), str(type(exc)) + 'Failed to build default values for %s (%s)', str(cls), str(type(exc)) ) pass @@ -299,12 +299,12 @@ class BaseInvenTreeSetting(models.Model): - The unique KEY string - Any key:value kwargs associated with the particular setting type (e.g. user-id) """ - key = f"{str(cls.__name__)}:{setting_key}" + key = f'{str(cls.__name__)}:{setting_key}' for k, v in kwargs.items(): - key += f"_{k}:{v}" + key += f'_{k}:{v}' - return key.replace(" ", "") + return key.replace(' ', '') @classmethod def get_filters(cls, **kwargs): @@ -366,14 +366,14 @@ class BaseInvenTreeSetting(models.Model): ) # remove any hidden settings - if exclude_hidden and setting.get("hidden", False): + if exclude_hidden and setting.get('hidden', False): del settings[key.upper()] # format settings values and remove protected for key, setting in settings.items(): validator = cls.get_setting_validator(key, **filters) - if cls.is_protected(key, **filters) and setting.value != "": + if cls.is_protected(key, **filters) and setting.value != '': setting.value = '***' elif cls.validator_is_bool(validator): setting.value = InvenTree.helpers.str2bool(setting.value) @@ -438,7 +438,7 @@ class BaseInvenTreeSetting(models.Model): if setting.required: value = setting.value or cls.get_setting_default(setting.key, **kwargs) - if value == "": + if value == '': missing_settings.append(setting.key.upper()) return len(missing_settings) == 0, missing_settings @@ -766,7 +766,7 @@ class BaseInvenTreeSetting(models.Model): options = self.valid_options() if options and self.value not in options: - raise ValidationError(_("Chosen value is not a valid option")) + raise ValidationError(_('Chosen value is not a valid option')) def run_validator(self, validator): """Run a validator against the 'value' field for this InvenTreeSetting object.""" @@ -1049,7 +1049,7 @@ class BaseInvenTreeSetting(models.Model): """Check if this setting value is required.""" setting = cls.get_setting_definition(key, **cls.get_filters(**kwargs)) - return setting.get("required", False) + return setting.get('required', False) @property def required(self): @@ -1131,8 +1131,8 @@ class InvenTreeSetting(BaseInvenTreeSetting): class Meta: """Meta options for InvenTreeSetting.""" - verbose_name = "InvenTree Setting" - verbose_name_plural = "InvenTree Settings" + verbose_name = 'InvenTree Setting' + verbose_name_plural = 'InvenTree Settings' def save(self, *args, **kwargs): """When saving a global setting, check to see if it requires a server restart. @@ -1454,7 +1454,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'name': _('Part Name Display Format'), 'description': _('Format to display the part name'), 'default': "{{ part.IPN if part.IPN }}{{ ' | ' if part.IPN }}{{ part.name }}{{ ' | ' if part.revision }}" - "{{ part.revision if part.revision }}", + '{{ part.revision if part.revision }}', 'validator': InvenTree.validators.validate_part_name_format, }, 'PART_CATEGORY_DEFAULT_ICON': { @@ -1860,7 +1860,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): 'validator': bool, 'after_save': reload_plugin_registry, }, - "PROJECT_CODES_ENABLED": { + 'PROJECT_CODES_ENABLED': { 'name': _('Enable project codes'), 'description': _('Enable project codes for tracking projects'), 'default': False, @@ -1946,8 +1946,8 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): class Meta: """Meta options for InvenTreeUserSetting.""" - verbose_name = "InvenTree User Setting" - verbose_name_plural = "InvenTree User Settings" + verbose_name = 'InvenTree User Setting' + verbose_name_plural = 'InvenTree User Settings' constraints = [ models.UniqueConstraint(fields=['key', 'user'], name='unique key and user') ] @@ -2069,7 +2069,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'default': False, 'validator': bool, }, - "LABEL_INLINE": { + 'LABEL_INLINE': { 'name': _('Inline label display'), 'description': _( 'Display PDF labels in the browser, instead of downloading as a file' @@ -2077,7 +2077,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'default': True, 'validator': bool, }, - "LABEL_DEFAULT_PRINTER": { + 'LABEL_DEFAULT_PRINTER': { 'name': _('Default label printer'), 'description': _( 'Configure which label printer should be selected by default' @@ -2085,7 +2085,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'default': '', 'choices': label_printer_options, }, - "REPORT_INLINE": { + 'REPORT_INLINE': { 'name': _('Inline report display'), 'description': _( 'Display PDF reports in the browser, instead of downloading as a file' @@ -2112,7 +2112,7 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'validator': bool, }, 'SEARCH_HIDE_INACTIVE_PARTS': { - 'name': _("Hide Inactive Parts"), + 'name': _('Hide Inactive Parts'), 'description': _('Excluded inactive parts from search preview window'), 'default': False, 'validator': bool, @@ -2360,7 +2360,7 @@ class PriceBreak(MetaMixin): converted = convert_money(self.price, currency_code) except MissingRate: logger.warning( - "No currency conversion rate available for %s -> %s", + 'No currency conversion rate available for %s -> %s', self.price_currency, currency_code, ) @@ -2510,11 +2510,11 @@ class WebhookEndpoint(models.Model): """ # Token - TOKEN_NAME = "Token" + TOKEN_NAME = 'Token' VERIFICATION_METHOD = VerificationMethod.NONE - MESSAGE_OK = "Message was received." - MESSAGE_TOKEN_ERROR = "Incorrect token in header." + MESSAGE_OK = 'Message was received.' + MESSAGE_TOKEN_ERROR = 'Incorrect token in header.' endpoint_id = models.CharField( max_length=255, @@ -2589,7 +2589,7 @@ class WebhookEndpoint(models.Model): This can be overridden to create your own token validation method. """ - token = headers.get(self.TOKEN_NAME, "") + token = headers.get(self.TOKEN_NAME, '') # no token if self.verify == VerificationMethod.NONE: diff --git a/InvenTree/common/notifications.py b/InvenTree/common/notifications.py index 3dfe2fb716..fadcdd7f05 100644 --- a/InvenTree/common/notifications.py +++ b/InvenTree/common/notifications.py @@ -311,23 +311,23 @@ class InvenTreeNotificationBodies: """ NewOrder = NotificationBody( - name=_("New {verbose_name}"), + name=_('New {verbose_name}'), slug='{app_label}.new_{model_name}', - message=_("A new order has been created and assigned to you"), + message=_('A new order has been created and assigned to you'), template='email/new_order_assigned.html', ) """Send when a new order (build, sale or purchase) was created.""" OrderCanceled = NotificationBody( - name=_("{verbose_name} canceled"), + name=_('{verbose_name} canceled'), slug='{app_label}.canceled_{model_name}', - message=_("A order that is assigned to you was canceled"), + message=_('A order that is assigned to you was canceled'), template='email/canceled_order_assigned.html', ) """Send when a order (sale, return or purchase) was canceled.""" ItemsReceived = NotificationBody( - name=_("Items Received"), + name=_('Items Received'), slug='purchase_order.items_received', message=_('Items have been received against a purchase order'), template='email/purchase_order_received.html', @@ -414,7 +414,7 @@ def trigger_notification(obj, category=None, obj_ref='pk', **kwargs): # Unhandled type else: logger.error( - "Unknown target passed to trigger_notification method: %s", target + 'Unknown target passed to trigger_notification method: %s', target ) if target_users: @@ -515,4 +515,4 @@ def deliver_notification( str(obj), ) if not success: - logger.info("There were some problems") + logger.info('There were some problems') diff --git a/InvenTree/common/tasks.py b/InvenTree/common/tasks.py index fca5a49f8c..fc20b9dbfe 100644 --- a/InvenTree/common/tasks.py +++ b/InvenTree/common/tasks.py @@ -51,7 +51,7 @@ def update_news_feed(): try: d = feedparser.parse(settings.INVENTREE_NEWS_URL) except Exception as entry: # pragma: no cover - logger.warning("update_news_feed: Error parsing the newsfeed", entry) + logger.warning('update_news_feed: Error parsing the newsfeed', entry) return # Get a reference list @@ -97,7 +97,7 @@ def delete_old_notes_images(): # Remove any notes which point to non-existent image files for note in NotesImage.objects.all(): if not os.path.exists(note.image.path): - logger.info("Deleting note %s - image file does not exist", note.image.path) + logger.info('Deleting note %s - image file does not exist', note.image.path) note.delete() note_classes = getModelsWithMixin(InvenTreeNotesMixin) @@ -116,7 +116,7 @@ def delete_old_notes_images(): break if not found: - logger.info("Deleting note %s - image file not linked to a note", img) + logger.info('Deleting note %s - image file not linked to a note', img) note.delete() # Finally, remove any images in the notes dir which are not linked to a note @@ -139,5 +139,5 @@ def delete_old_notes_images(): break if not found: - logger.info("Deleting note %s - image file not linked to a note", image) + logger.info('Deleting note %s - image file not linked to a note', image) os.remove(os.path.join(notes_dir, image)) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index dfd764c7bc..f612e0d5fb 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -136,19 +136,19 @@ class SettingsTest(InvenTreeTestCase): def test_all_settings(self): """Make sure that the all_settings function returns correctly""" result = InvenTreeSetting.all_settings() - self.assertIn("INVENTREE_INSTANCE", result) + self.assertIn('INVENTREE_INSTANCE', result) self.assertIsInstance(result['INVENTREE_INSTANCE'], InvenTreeSetting) - @mock.patch("common.models.InvenTreeSetting.get_setting_definition") + @mock.patch('common.models.InvenTreeSetting.get_setting_definition') def test_check_all_settings(self, get_setting_definition): """Make sure that the check_all_settings function returns correctly""" # define partial schema settings_definition = { - "AB": { # key that's has not already been accessed - "required": True + 'AB': { # key that's has not already been accessed + 'required': True }, - "CD": {"required": True, "protected": True}, - "EF": {}, + 'CD': {'required': True, 'protected': True}, + 'EF': {}, } def mocked(key, **kwargs): @@ -160,28 +160,28 @@ class SettingsTest(InvenTreeTestCase): InvenTreeSetting.check_all_settings( settings_definition=settings_definition ), - (False, ["AB", "CD"]), + (False, ['AB', 'CD']), ) - InvenTreeSetting.set_setting('AB', "hello", self.user) - InvenTreeSetting.set_setting('CD', "world", self.user) + InvenTreeSetting.set_setting('AB', 'hello', self.user) + InvenTreeSetting.set_setting('CD', 'world', self.user) self.assertEqual(InvenTreeSetting.check_all_settings(), (True, [])) - @mock.patch("common.models.InvenTreeSetting.get_setting_definition") + @mock.patch('common.models.InvenTreeSetting.get_setting_definition') def test_settings_validator(self, get_setting_definition): """Make sure that the validator function gets called on set setting.""" def validator(x): - if x == "hello": + if x == 'hello': return x - raise ValidationError(f"{x} is not valid") + raise ValidationError(f'{x} is not valid') mock_validator = mock.Mock(side_effect=validator) # define partial schema settings_definition = { - "AB": { # key that's has not already been accessed - "validator": mock_validator + 'AB': { # key that's has not already been accessed + 'validator': mock_validator } } @@ -190,12 +190,12 @@ class SettingsTest(InvenTreeTestCase): get_setting_definition.side_effect = mocked - InvenTreeSetting.set_setting("AB", "hello", self.user) - mock_validator.assert_called_with("hello") + InvenTreeSetting.set_setting('AB', 'hello', self.user) + mock_validator.assert_called_with('hello') with self.assertRaises(ValidationError): - InvenTreeSetting.set_setting("AB", "world", self.user) - mock_validator.assert_called_with("world") + InvenTreeSetting.set_setting('AB', 'world', self.user) + mock_validator.assert_called_with('world') def run_settings_check(self, key, setting): """Test that all settings are valid. @@ -322,7 +322,7 @@ class SettingsTest(InvenTreeTestCase): # Generate a number of new users for idx in range(5): get_user_model().objects.create( - username=f"User_{idx}", password="hunter42", email="email@dot.com" + username=f'User_{idx}', password='hunter42', email='email@dot.com' ) key = 'SEARCH_PREVIEW_RESULTS' @@ -333,7 +333,7 @@ class SettingsTest(InvenTreeTestCase): cache_key = setting.cache_key self.assertEqual( cache_key, - f"InvenTreeUserSetting:SEARCH_PREVIEW_RESULTS_user:{user.username}", + f'InvenTreeUserSetting:SEARCH_PREVIEW_RESULTS_user:{user.username}', ) InvenTreeUserSetting.set_setting(key, user.pk, None, user=user) self.assertIsNotNone(cache.get(cache_key)) @@ -399,7 +399,7 @@ class GlobalSettingsApiTest(InvenTreeAPITestCase): def test_api_detail(self): """Test that we can access the detail view for a setting based on the .""" # These keys are invalid, and should return 404 - for key in ["apple", "carrot", "dog"]: + for key in ['apple', 'carrot', 'dog']: response = self.get( reverse('api-global-setting-detail', kwargs={'key': key}), expected_code=404, @@ -770,7 +770,7 @@ class WebhookMessageTests(TestCase): """ response = self.client.post( self.url, - data={"this": "is a message"}, + data={'this': 'is a message'}, content_type=CONTENT_TYPE_JSON, **{'HTTP_TOKEN': str(self.endpoint_def.token)}, ) @@ -778,7 +778,7 @@ class WebhookMessageTests(TestCase): assert response.status_code == HTTPStatus.OK assert str(response.content, 'utf-8') == WebhookView.model_class.MESSAGE_OK message = WebhookMessage.objects.get() - assert message.body == {"this": "is a message"} + assert message.body == {'this': 'is a message'} class NotificationTest(InvenTreeAPITestCase): @@ -1033,7 +1033,7 @@ class CurrencyAPITests(InvenTreeAPITestCase): # Delay and try again time.sleep(10) - raise TimeoutError("Could not refresh currency exchange data after 5 attempts") + raise TimeoutError('Could not refresh currency exchange data after 5 attempts') class NotesImageTest(InvenTreeAPITestCase): @@ -1048,28 +1048,28 @@ class NotesImageTest(InvenTreeAPITestCase): reverse('api-notes-image-list'), data={ 'image': SimpleUploadedFile( - 'test.txt', b"this is not an image file", content_type='text/plain' + 'test.txt', b'this is not an image file', content_type='text/plain' ) }, format='multipart', expected_code=400, ) - self.assertIn("Upload a valid image", str(response.data['image'])) + self.assertIn('Upload a valid image', str(response.data['image'])) # Test upload of an invalid image file response = self.post( reverse('api-notes-image-list'), data={ 'image': SimpleUploadedFile( - 'test.png', b"this is not an image file", content_type='image/png' + 'test.png', b'this is not an image file', content_type='image/png' ) }, format='multipart', expected_code=400, ) - self.assertIn("Upload a valid image", str(response.data['image'])) + self.assertIn('Upload a valid image', str(response.data['image'])) # Check that no extra database entries have been created self.assertEqual(NotesImage.objects.count(), n) diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index 79e6d63ddc..3d9cd53c22 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -81,7 +81,7 @@ class FileManagementFormView(MultiStepFormView): ('fields', forms.MatchFieldForm), ('items', forms.MatchItemForm), ] - form_steps_description = [_("Upload File"), _("Match Fields"), _("Match Items")] + form_steps_description = [_('Upload File'), _('Match Fields'), _('Match Items')] media_folder = 'file_upload/' extra_context_data = {} diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index c4e4d1f5cc..9097708697 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -96,7 +96,7 @@ class Company(InvenTreeNotesMixin, MetadataMixin, models.Model): constraints = [ UniqueConstraint(fields=['name', 'email'], name='unique_name_email_pair') ] - verbose_name_plural = "Companies" + verbose_name_plural = 'Companies' @staticmethod def get_api_url(): @@ -215,7 +215,7 @@ class Company(InvenTreeNotesMixin, MetadataMixin, models.Model): def __str__(self): """Get string representation of a Company.""" - return f"{self.name} - {self.description}" + return f'{self.name} - {self.description}' def get_absolute_url(self): """Get the web URL for the detail view for this Company.""" @@ -318,7 +318,7 @@ class Address(models.Model): class Meta: """Metaclass defines extra model options""" - verbose_name_plural = "Addresses" + verbose_name_plural = 'Addresses' def __init__(self, *args, **kwargs): """Custom init function""" @@ -340,7 +340,7 @@ class Address(models.Model): if len(line) > 0: populated_lines.append(line) - return ", ".join(populated_lines) + return ', '.join(populated_lines) def save(self, *args, **kwargs): """Run checks when saving an address: @@ -564,7 +564,7 @@ class ManufacturerPartAttachment(InvenTreeAttachment): def getSubdir(self): """Return the subdirectory where attachment files for the ManufacturerPart model are located""" - return os.path.join("manufacturer_part_files", str(self.manufacturer_part.id)) + return os.path.join('manufacturer_part_files', str(self.manufacturer_part.id)) manufacturer_part = models.ForeignKey( ManufacturerPart, @@ -711,14 +711,14 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin ): raise ValidationError({ 'pack_quantity': _( - "Pack units must be compatible with the base part units" + 'Pack units must be compatible with the base part units' ) }) # Native value must be greater than zero if float(native_value.magnitude) <= 0: raise ValidationError({ - 'pack_quantity': _("Pack units must be greater than zero") + 'pack_quantity': _('Pack units must be greater than zero') }) # Update native pack units value @@ -732,7 +732,7 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin if self.manufacturer_part.part != self.part: raise ValidationError({ 'manufacturer_part': _( - "Linked manufacturer part must reference the same base part" + 'Linked manufacturer part must reference the same base part' ) }) @@ -787,7 +787,7 @@ class SupplierPart(MetadataMixin, InvenTreeBarcodeMixin, common.models.MetaMixin SKU = models.CharField( max_length=100, - verbose_name=__("SKU = Stock Keeping Unit (supplier part number)", 'SKU'), + verbose_name=__('SKU = Stock Keeping Unit (supplier part number)', 'SKU'), help_text=_('Supplier stock keeping unit'), ) @@ -1007,7 +1007,7 @@ class SupplierPriceBreak(common.models.PriceBreak): class Meta: """Metaclass defines extra model options""" - unique_together = ("part", "quantity") + unique_together = ('part', 'quantity') # This model was moved from the 'Part' app db_table = 'part_supplierpricebreak' diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 42d796affd..192187a3b5 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -168,7 +168,7 @@ class CompanySerializer(RemoteImageMixin, InvenTreeModelSerializer): remote_img.save(buffer, format=fmt) # Construct a simplified name for the image - filename = f"company_{company.pk}_image.{fmt.lower()}" + filename = f'company_{company.pk}_image.{fmt.lower()}' company.image.save(filename, ContentFile(buffer.getvalue())) diff --git a/InvenTree/company/test_api.py b/InvenTree/company/test_api.py index 934f9e4bb6..4bcae45bf6 100644 --- a/InvenTree/company/test_api.py +++ b/InvenTree/company/test_api.py @@ -107,8 +107,8 @@ class CompanyTest(InvenTreeAPITestCase): response = self.post( url, { - 'name': "Another Company", - 'description': "Also created via the API!", + 'name': 'Another Company', + 'description': 'Also created via the API!', 'currency': 'AUD', 'is_supplier': False, 'is_manufacturer': True, @@ -125,7 +125,7 @@ class CompanyTest(InvenTreeAPITestCase): # Attempt to create with invalid currency response = self.post( url, - {'name': "A name", 'description': 'A description', 'currency': 'POQD'}, + {'name': 'A name', 'description': 'A description', 'currency': 'POQD'}, expected_code=400, ) @@ -144,7 +144,7 @@ class ContactTest(InvenTreeAPITestCase): # Create some companies companies = [ - Company(name=f"Company {idx}", description="Some company") + Company(name=f'Company {idx}', description='Some company') for idx in range(3) ] @@ -155,7 +155,7 @@ class ContactTest(InvenTreeAPITestCase): # Create some contacts for cmp in Company.objects.all(): contacts += [ - Contact(company=cmp, name=f"My name {idx}") for idx in range(3) + Contact(company=cmp, name=f'My name {idx}') for idx in range(3) ] Contact.objects.bulk_create(contacts) @@ -251,7 +251,7 @@ class AddressTest(InvenTreeAPITestCase): cls.num_addr = 3 # Create some companies companies = [ - Company(name=f"Company {idx}", description="Some company") + Company(name=f'Company {idx}', description='Some company') for idx in range(cls.num_companies) ] @@ -262,7 +262,7 @@ class AddressTest(InvenTreeAPITestCase): # Create some contacts for cmp in Company.objects.all(): addresses += [ - Address(company=cmp, title=f"Address no. {idx}") + Address(company=cmp, title=f'Address no. {idx}') for idx in range(cls.num_addr) ] diff --git a/InvenTree/company/test_migrations.py b/InvenTree/company/test_migrations.py index 22cbd4d0e6..b4b291644a 100644 --- a/InvenTree/company/test_migrations.py +++ b/InvenTree/company/test_migrations.py @@ -228,8 +228,8 @@ class TestCurrencyMigration(MigratorTestCase): Part = self.old_state.apps.get_model('part', 'part') part = Part.objects.create( - name="PART", - description="A purchaseable part", + name='PART', + description='A purchaseable part', purchaseable=True, level=0, tree_id=0, @@ -309,7 +309,7 @@ class TestAddressMigration(MigratorTestCase): a2 = Address.objects.filter(company=c2.pk).first() self.assertEqual(a1.line1, self.short_l1) - self.assertEqual(a1.line2, "") + self.assertEqual(a1.line2, '') self.assertEqual(a2.line1, self.long_l1) self.assertEqual(a2.line2, self.l2) self.assertEqual(c1.address, '') @@ -329,8 +329,8 @@ class TestSupplierPartQuantity(MigratorTestCase): SupplierPart = self.old_state.apps.get_model('company', 'supplierpart') self.part = Part.objects.create( - name="PART", - description="A purchaseable part", + name='PART', + description='A purchaseable part', purchaseable=True, level=0, tree_id=0, diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index cbb235c2a4..9eb2190a98 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -103,9 +103,9 @@ class CompanySimpleTest(TestCase): """Unit tests for supplier part pricing""" m2x4 = Part.objects.get(name='M2x4 LPHS') - self.assertEqual(m2x4.get_price_info(5.5), "38.5 - 41.25") - self.assertEqual(m2x4.get_price_info(10), "70 - 75") - self.assertEqual(m2x4.get_price_info(100), "125 - 350") + self.assertEqual(m2x4.get_price_info(5.5), '38.5 - 41.25') + self.assertEqual(m2x4.get_price_info(10), '70 - 75') + self.assertEqual(m2x4.get_price_info(100), '125 - 350') pmin, pmax = m2x4.get_price_range(5) self.assertEqual(pmin, 35) @@ -222,13 +222,13 @@ class AddressTest(TestCase): def test_model_str(self): """Test value of __str__""" - t = "Test address" - l1 = "Busy street 56" - l2 = "Red building" - pcd = "12345" - pct = "City" - pv = "Province" - cn = "COUNTRY" + t = 'Test address' + l1 = 'Busy street 56' + l2 = 'Red building' + pcd = '12345' + pct = 'City' + pv = 'Province' + cn = 'COUNTRY' addr = Address.objects.create( company=self.c, title=t, diff --git a/InvenTree/generic/states/api.py b/InvenTree/generic/states/api.py index c5cee43611..1d7d3db59e 100644 --- a/InvenTree/generic/states/api.py +++ b/InvenTree/generic/states/api.py @@ -39,10 +39,10 @@ class StatusView(APIView): status_class = self.get_status_model() if not inspect.isclass(status_class): - raise NotImplementedError("`status_class` not a class") + raise NotImplementedError('`status_class` not a class') if not issubclass(status_class, StatusCode): - raise NotImplementedError("`status_class` not a valid StatusCode class") + raise NotImplementedError('`status_class` not a valid StatusCode class') data = {'class': status_class.__name__, 'values': status_class.dict()} diff --git a/InvenTree/generic/states/tests.py b/InvenTree/generic/states/tests.py index d20537ecfd..0d5f926f40 100644 --- a/InvenTree/generic/states/tests.py +++ b/InvenTree/generic/states/tests.py @@ -14,9 +14,9 @@ from .states import StatusCode class GeneralStatus(StatusCode): """Defines a set of status codes for tests.""" - PENDING = 10, _("Pending"), 'secondary' - PLACED = 20, _("Placed"), 'primary' - COMPLETE = 30, _("Complete"), 'success' + PENDING = 10, _('Pending'), 'secondary' + PLACED = 20, _('Placed'), 'primary' + COMPLETE = 30, _('Complete'), 'success' ABC = None # This should be ignored _DEF = None # This should be ignored jkl = None # This should be ignored @@ -183,11 +183,11 @@ class GeneralStateTest(InvenTreeTestCase): # Invalid call - not a class with self.assertRaises(NotImplementedError) as e: resp = view(rqst, **{StatusView.MODEL_REF: 'invalid'}) - self.assertEqual(str(e.exception), "`status_class` not a class") + self.assertEqual(str(e.exception), '`status_class` not a class') # Invalid call - not the right class with self.assertRaises(NotImplementedError) as e: resp = view(rqst, **{StatusView.MODEL_REF: object}) self.assertEqual( - str(e.exception), "`status_class` not a valid StatusCode class" + str(e.exception), '`status_class` not a valid StatusCode class' ) diff --git a/InvenTree/gunicorn.conf.py b/InvenTree/gunicorn.conf.py index c1b403de12..5c26c15631 100644 --- a/InvenTree/gunicorn.conf.py +++ b/InvenTree/gunicorn.conf.py @@ -2,7 +2,7 @@ import multiprocessing -bind = "0.0.0.0:8000" +bind = '0.0.0.0:8000' workers = multiprocessing.cpu_count() * 2 + 1 diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index f73a316918..92a9034433 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -130,12 +130,12 @@ class LabelListView(LabelFilterMixin, ListCreateAPI): class LabelPrintMixin(LabelFilterMixin): """Mixin for printing labels.""" - rolemap = {"GET": "view", "POST": "view"} + rolemap = {'GET': 'view', 'POST': 'view'} def check_permissions(self, request): """Override request method to GET so that also non superusers can print using a post request.""" - if request.method == "POST": - request = clone_request(request, "GET") + if request.method == 'POST': + request = clone_request(request, 'GET') return super().check_permissions(request) @method_decorator(never_cache) @@ -199,7 +199,7 @@ class LabelPrintMixin(LabelFilterMixin): if not plugin.is_active(): raise ValidationError(f"Plugin '{plugin_key}' is not enabled") - if not plugin.mixin_enabled("labels"): + if not plugin.mixin_enabled('labels'): raise ValidationError( f"Plugin '{plugin_key}' is not a label printing plugin" ) diff --git a/InvenTree/label/apps.py b/InvenTree/label/apps.py index d7677f68b0..595c7aa62c 100644 --- a/InvenTree/label/apps.py +++ b/InvenTree/label/apps.py @@ -19,7 +19,7 @@ from InvenTree.ready import ( isPluginRegistryLoaded, ) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') def hashFile(filename): diff --git a/InvenTree/label/models.py b/InvenTree/label/models.py index 89d8d0baf3..ccc5b03843 100644 --- a/InvenTree/label/models.py +++ b/InvenTree/label/models.py @@ -25,12 +25,12 @@ from plugin.registry import registry try: from django_weasyprint import WeasyTemplateResponseMixin except OSError as err: # pragma: no cover - print(f"OSError: {err}") - print("You may require some further system packages to be installed.") + print(f'OSError: {err}') + print('You may require some further system packages to be installed.') sys.exit(1) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') def rename_label(instance, filename): @@ -97,7 +97,7 @@ class LabelTemplate(MetadataMixin, models.Model): abstract = True # Each class of label files will be stored in a separate subdirectory - SUBDIR = "label" + SUBDIR = 'label' # Object we will be printing against (will be filled out later) object_to_print = None @@ -109,7 +109,7 @@ class LabelTemplate(MetadataMixin, models.Model): def __str__(self): """Format a string representation of a label instance""" - return f"{self.name} - {self.description}" + return f'{self.name} - {self.description}' name = models.CharField( blank=False, max_length=100, verbose_name=_('Name'), help_text=_('Label name') @@ -154,7 +154,7 @@ class LabelTemplate(MetadataMixin, models.Model): ) filename_pattern = models.CharField( - default="label.pdf", + default='label.pdf', verbose_name=_('Filename Pattern'), help_text=_('Pattern for generating label filenames'), max_length=100, @@ -265,7 +265,7 @@ class LabelTemplate(MetadataMixin, models.Model): wp = WeasyprintLabelMixin( request, self.template_name, - base_url=request.build_absolute_uri("/"), + base_url=request.build_absolute_uri('/'), presentational_hints=True, filename=self.generate_filename(request), **kwargs, @@ -304,7 +304,7 @@ class StockItemLabel(LabelTemplate): """Return the API URL associated with the StockItemLabel model""" return reverse('api-stockitem-label-list') # pragma: no cover - SUBDIR = "stockitem" + SUBDIR = 'stockitem' filters = models.CharField( blank=True, @@ -343,7 +343,7 @@ class StockLocationLabel(LabelTemplate): """Return the API URL associated with the StockLocationLabel model""" return reverse('api-stocklocation-label-list') # pragma: no cover - SUBDIR = "stocklocation" + SUBDIR = 'stocklocation' filters = models.CharField( blank=True, diff --git a/InvenTree/label/tests.py b/InvenTree/label/tests.py index 9f623a873b..3b64a7e68d 100644 --- a/InvenTree/label/tests.py +++ b/InvenTree/label/tests.py @@ -55,13 +55,13 @@ class LabelTest(InvenTreeAPITestCase): def test_filters(self): """Test the label filters.""" - filter_string = "part__pk=10" + filter_string = 'part__pk=10' filters = validateFilterString(filter_string, model=StockItem) self.assertEqual(type(filters), dict) - bad_filter_string = "part_pk=10" + bad_filter_string = 'part_pk=10' with self.assertRaises(ValidationError): validateFilterString(bad_filter_string, model=StockItem) @@ -107,7 +107,7 @@ class LabelTest(InvenTreeAPITestCase): buffer = io.StringIO() buffer.write(label_data) - template = ContentFile(buffer.getvalue(), "label.html") + template = ContentFile(buffer.getvalue(), 'label.html') # Construct a label template label = PartLabel.objects.create( @@ -140,7 +140,7 @@ class LabelTest(InvenTreeAPITestCase): content = f.read() # Test that each element has been rendered correctly - self.assertIn(f"part: {part_pk} - {part_name}", content) + self.assertIn(f'part: {part_pk} - {part_name}', content) self.assertIn(f'data: {{"part": {part_pk}}}', content) self.assertIn(f'http://testserver/part/{part_pk}/', content) diff --git a/InvenTree/manage.py b/InvenTree/manage.py index d2d21b0b23..9770d6ea35 100755 --- a/InvenTree/manage.py +++ b/InvenTree/manage.py @@ -7,17 +7,17 @@ import sys def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "InvenTree.settings") + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'InvenTree.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: # pragma: no cover raise ImportError( "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" + 'available on your PYTHONPATH environment variable? Did you ' + 'forget to activate a virtual environment?' ) from exc execute_from_command_line(sys.argv) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index e503efcd74..622cb32cae 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -86,7 +86,7 @@ class OrderFilter(rest_filters.FilterSet): """Base class for custom API filters for the OrderList endpoint.""" # Filter against order status - status = rest_filters.NumberFilter(label="Order Status", method='filter_status') + status = rest_filters.NumberFilter(label='Order Status', method='filter_status') def filter_status(self, queryset, name, value): """Filter by integer status code""" @@ -94,7 +94,7 @@ class OrderFilter(rest_filters.FilterSet): # Exact match for reference reference = rest_filters.CharFilter( - label='Filter by exact reference', field_name='reference', lookup_expr="iexact" + label='Filter by exact reference', field_name='reference', lookup_expr='iexact' ) assigned_to_me = rest_filters.BooleanFilter( @@ -155,7 +155,7 @@ class LineItemFilter(rest_filters.FilterSet): ) has_pricing = rest_filters.BooleanFilter( - label="Has Pricing", method='filter_has_pricing' + label='Has Pricing', method='filter_has_pricing' ) def filter_has_pricing(self, queryset, name, value): @@ -271,7 +271,7 @@ class PurchaseOrderList(PurchaseOrderMixin, APIDownloadMixin, ListCreateAPI): filedata = dataset.export(export_format) - filename = f"InvenTree_PurchaseOrders.{export_format}" + filename = f'InvenTree_PurchaseOrders.{export_format}' return DownloadFile(filedata, filename) @@ -518,7 +518,7 @@ class PurchaseOrderLineItemList( filedata = dataset.export(export_format) - filename = f"InvenTree_PurchaseOrderItems.{export_format}" + filename = f'InvenTree_PurchaseOrderItems.{export_format}' return DownloadFile(filedata, filename) @@ -567,7 +567,7 @@ class PurchaseOrderExtraLineList(GeneralExtraLineList, ListCreateAPI): """Download this queryset as a file""" dataset = PurchaseOrderExtraLineResource().export(queryset=queryset) filedata = dataset.export(export_format) - filename = f"InvenTree_ExtraPurchaseOrderLines.{export_format}" + filename = f'InvenTree_ExtraPurchaseOrderLines.{export_format}' return DownloadFile(filedata, filename) @@ -665,7 +665,7 @@ class SalesOrderList(SalesOrderMixin, APIDownloadMixin, ListCreateAPI): filedata = dataset.export(export_format) - filename = f"InvenTree_SalesOrders.{export_format}" + filename = f'InvenTree_SalesOrders.{export_format}' return DownloadFile(filedata, filename) @@ -809,7 +809,7 @@ class SalesOrderLineItemList(SalesOrderLineItemMixin, APIDownloadMixin, ListCrea dataset = SalesOrderLineItemResource().export(queryset=queryset) filedata = dataset.export(export_format) - filename = f"InvenTree_SalesOrderItems.{export_format}" + filename = f'InvenTree_SalesOrderItems.{export_format}' return DownloadFile(filedata, filename) @@ -836,7 +836,7 @@ class SalesOrderExtraLineList(GeneralExtraLineList, ListCreateAPI): """Download this queryset as a file""" dataset = SalesOrderExtraLineResource().export(queryset=queryset) filedata = dataset.export(export_format) - filename = f"InvenTree_ExtraSalesOrderLines.{export_format}" + filename = f'InvenTree_ExtraSalesOrderLines.{export_format}' return DownloadFile(filedata, filename) @@ -1127,7 +1127,7 @@ class ReturnOrderList(ReturnOrderMixin, APIDownloadMixin, ListCreateAPI): """Download this queryset as a file""" dataset = ReturnOrderResource().export(queryset=queryset) filedata = dataset.export(export_format) - filename = f"InvenTree_ReturnOrders.{export_format}" + filename = f'InvenTree_ReturnOrders.{export_format}' return DownloadFile(filedata, filename) @@ -1274,7 +1274,7 @@ class ReturnOrderLineItemList( def download_queryset(self, queryset, export_format): """Download the requested queryset as a file""" raise NotImplementedError( - "download_queryset not yet implemented for this endpoint" + 'download_queryset not yet implemented for this endpoint' ) filter_backends = SEARCH_ORDER_FILTER @@ -1303,7 +1303,7 @@ class ReturnOrderExtraLineList(GeneralExtraLineList, ListCreateAPI): def download_queryset(self, queryset, export_format): """Download this queryset as a file""" - raise NotImplementedError("download_queryset not yet implemented") + raise NotImplementedError('download_queryset not yet implemented') class ReturnOrderExtraLineDetail(RetrieveUpdateDestroyAPI): @@ -1339,9 +1339,9 @@ class OrderCalendarExport(ICalFeed): instance_url = get_base_url() - instance_url = instance_url.replace("http://", "").replace("https://", "") + instance_url = instance_url.replace('http://', '').replace('https://', '') timezone = settings.TIME_ZONE - file_name = "calendar.ics" + file_name = 'calendar.ics' def __call__(self, request, *args, **kwargs): """Overload call in order to check for authentication. @@ -1367,8 +1367,8 @@ class OrderCalendarExport(ICalFeed): 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(':') + 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: if user.is_active: @@ -1383,7 +1383,7 @@ class OrderCalendarExport(ICalFeed): # Still nothing - return Unauth. header with info on how to authenticate # Information is needed by client, eg Thunderbird response = JsonResponse({ - "detail": "Authentication credentials were not provided." + 'detail': 'Authentication credentials were not provided.' }) response['WWW-Authenticate'] = 'Basic realm="api"' response.status_code = 401 @@ -1402,11 +1402,11 @@ class OrderCalendarExport(ICalFeed): def title(self, obj): """Return calendar title.""" - if obj["ordertype"] == 'purchase-order': + if obj['ordertype'] == 'purchase-order': ordertype_title = _('Purchase Order') - elif obj["ordertype"] == 'sales-order': + elif obj['ordertype'] == 'sales-order': ordertype_title = _('Sales Order') - elif obj["ordertype"] == 'return-order': + elif obj['ordertype'] == 'return-order': ordertype_title = _('Return Order') else: ordertype_title = _('Unknown') @@ -1433,7 +1433,7 @@ class OrderCalendarExport(ICalFeed): ).filter(status__lt=PurchaseOrderStatus.COMPLETE.value) else: outlist = models.PurchaseOrder.objects.filter(target_date__isnull=False) - elif obj["ordertype"] == 'sales-order': + elif obj['ordertype'] == 'sales-order': if obj['include_completed'] is False: # Do not include completed (=shipped) orders from list in this case # Shipped status = 20 @@ -1442,7 +1442,7 @@ class OrderCalendarExport(ICalFeed): ).filter(status__lt=SalesOrderStatus.SHIPPED.value) else: outlist = models.SalesOrder.objects.filter(target_date__isnull=False) - elif obj["ordertype"] == 'return-order': + elif obj['ordertype'] == 'return-order': if obj['include_completed'] is False: # Do not include completed orders from list in this case # Complete status = 30 @@ -1458,11 +1458,11 @@ class OrderCalendarExport(ICalFeed): def item_title(self, item): """Set the event title to the order reference""" - return f"{item.reference}" + return f'{item.reference}' def item_description(self, item): """Set the event description""" - return f"Company: {item.company.name}\nStatus: {item.get_status_display()}\nDescription: {item.description}" + return f'Company: {item.company.name}\nStatus: {item.get_status_display()}\nDescription: {item.description}' def item_start_datetime(self, item): """Set event start to target date. Goal is all-day event.""" diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index 940e6ec08e..e139975f73 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -224,7 +224,7 @@ class Order( if self.company and self.contact: if self.contact.company != self.company: raise ValidationError({ - "contact": _("Contact does not match selected company") + 'contact': _('Contact does not match selected company') }) @classmethod @@ -327,7 +327,7 @@ class Order( @classmethod def get_status_class(cls): """Return the enumeration class which represents the 'status' field for this model""" - raise NotImplementedError(f"get_status_class() not implemented for {__class__}") + raise NotImplementedError(f'get_status_class() not implemented for {__class__}') class PurchaseOrder(TotalPriceMixin, Order): @@ -454,7 +454,7 @@ class PurchaseOrder(TotalPriceMixin, Order): max_length=64, blank=True, verbose_name=_('Supplier Reference'), - help_text=_("Supplier order reference code"), + help_text=_('Supplier order reference code'), ) received_by = models.ForeignKey( @@ -514,14 +514,14 @@ class PurchaseOrder(TotalPriceMixin, Order): quantity = int(quantity) if quantity <= 0: raise ValidationError({ - 'quantity': _("Quantity must be greater than zero") + 'quantity': _('Quantity must be greater than zero') }) except ValueError: - raise ValidationError({'quantity': _("Invalid quantity provided")}) + raise ValidationError({'quantity': _('Invalid quantity provided')}) if supplier_part.supplier != self.supplier: raise ValidationError({ - 'supplier': _("Part supplier must match PO supplier") + 'supplier': _('Part supplier must match PO supplier') }) if group: @@ -715,11 +715,11 @@ class PurchaseOrder(TotalPriceMixin, Order): try: if quantity < 0: raise ValidationError({ - "quantity": _("Quantity must be a positive number") + 'quantity': _('Quantity must be a positive number') }) quantity = InvenTree.helpers.clean_decimal(quantity) except TypeError: - raise ValidationError({"quantity": _("Invalid quantity provided")}) + raise ValidationError({'quantity': _('Invalid quantity provided')}) # Create a new stock item if line.part and quantity > 0: @@ -882,7 +882,7 @@ class SalesOrder(TotalPriceMixin, Order): limit_choices_to={'is_customer': True}, related_name='return_orders', verbose_name=_('Customer'), - help_text=_("Company to which the items are being sold"), + help_text=_('Company to which the items are being sold'), ) @property @@ -906,7 +906,7 @@ class SalesOrder(TotalPriceMixin, Order): max_length=64, blank=True, verbose_name=_('Customer Reference '), - help_text=_("Customer order reference code"), + help_text=_('Customer order reference code'), ) shipment_date = models.DateField( @@ -979,12 +979,12 @@ class SalesOrder(TotalPriceMixin, Order): elif self.pending_shipment_count > 0: raise ValidationError( - _("Order cannot be completed as there are incomplete shipments") + _('Order cannot be completed as there are incomplete shipments') ) elif not allow_incomplete_lines and self.pending_line_count > 0: raise ValidationError( - _("Order cannot be completed as there are incomplete line items") + _('Order cannot be completed as there are incomplete line items') ) except ValidationError as e: @@ -1174,10 +1174,10 @@ class PurchaseOrderAttachment(InvenTreeAttachment): def getSubdir(self): """Return the directory path where PurchaseOrderAttachment files are located""" - return os.path.join("po_files", str(self.order.id)) + return os.path.join('po_files', str(self.order.id)) order = models.ForeignKey( - PurchaseOrder, on_delete=models.CASCADE, related_name="attachments" + PurchaseOrder, on_delete=models.CASCADE, related_name='attachments' ) @@ -1191,7 +1191,7 @@ class SalesOrderAttachment(InvenTreeAttachment): def getSubdir(self): """Return the directory path where SalesOrderAttachment files are located""" - return os.path.join("so_files", str(self.order.id)) + return os.path.join('so_files', str(self.order.id)) order = models.ForeignKey( SalesOrder, on_delete=models.CASCADE, related_name='attachments' @@ -1342,7 +1342,7 @@ class PurchaseOrderLineItem(OrderLineItem): def __str__(self): """Render a string representation of a PurchaseOrderLineItem instance""" - return "{n} x {part} from {supplier} (for {po})".format( + return '{n} x {part} from {supplier} (for {po})'.format( n=decimal2string(self.quantity), part=self.part.SKU if self.part else 'unknown part', supplier=self.order.supplier.name if self.order.supplier else _('deleted'), @@ -1373,7 +1373,7 @@ class PurchaseOrderLineItem(OrderLineItem): null=True, related_name='purchase_order_line_items', verbose_name=_('Part'), - help_text=_("Supplier part"), + help_text=_('Supplier part'), ) received = models.DecimalField( @@ -1483,12 +1483,12 @@ class SalesOrderLineItem(OrderLineItem): if self.part: if self.part.virtual: raise ValidationError({ - 'part': _("Virtual part cannot be assigned to a sales order") + 'part': _('Virtual part cannot be assigned to a sales order') }) if not self.part.salable: raise ValidationError({ - 'part': _("Only salable parts can be assigned to a sales order") + 'part': _('Only salable parts can be assigned to a sales order') }) order = models.ForeignKey( @@ -1668,10 +1668,10 @@ class SalesOrderShipment(InvenTreeNotesMixin, MetadataMixin, models.Model): try: if self.shipment_date: # Shipment has already been sent! - raise ValidationError(_("Shipment has already been sent")) + raise ValidationError(_('Shipment has already been sent')) if self.allocations.count() == 0: - raise ValidationError(_("Shipment has no allocated stock items")) + raise ValidationError(_('Shipment has no allocated stock items')) except ValidationError as e: if raise_error: @@ -1807,7 +1807,7 @@ class SalesOrderAllocation(models.Model): # Ensure that we do not 'over allocate' a stock item build_allocation_count = self.item.build_allocation_count() sales_allocation_count = self.item.sales_order_allocation_count( - exclude_allocations={"pk": self.pk} + exclude_allocations={'pk': self.pk} ) total_allocation = ( @@ -1954,7 +1954,7 @@ class ReturnOrder(TotalPriceMixin, Order): limit_choices_to={'is_customer': True}, related_name='sales_orders', verbose_name=_('Customer'), - help_text=_("Company from which items are being returned"), + help_text=_('Company from which items are being returned'), ) @property @@ -1973,7 +1973,7 @@ class ReturnOrder(TotalPriceMixin, Order): max_length=64, blank=True, verbose_name=_('Customer Reference '), - help_text=_("Customer order reference code"), + help_text=_('Customer order reference code'), ) issue_date = models.DateField( @@ -2078,7 +2078,7 @@ class ReturnOrder(TotalPriceMixin, Order): """ # Prevent an item from being "received" multiple times if line.received_date is not None: - logger.warning("receive_line_item called with item already returned") + logger.warning('receive_line_item called with item already returned') return stock_item = line.item @@ -2144,7 +2144,7 @@ class ReturnOrderLineItem(OrderLineItem): if self.item and not self.item.serialized: raise ValidationError({ - 'item': _("Only serialized items can be assigned to a Return Order") + 'item': _('Only serialized items can be assigned to a Return Order') }) order = models.ForeignKey( diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 4620d31421..81630af7e6 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -261,7 +261,7 @@ class PurchaseOrderCancelSerializer(serializers.Serializer): order = self.context['order'] if not order.can_cancel: - raise ValidationError(_("Order cannot be cancelled")) + raise ValidationError(_('Order cannot be cancelled')) order.cancel_order() @@ -286,7 +286,7 @@ class PurchaseOrderCompleteSerializer(serializers.Serializer): order = self.context['order'] if not value and not order.is_complete: - raise ValidationError(_("Order has incomplete line items")) + raise ValidationError(_('Order has incomplete line items')) return value @@ -390,7 +390,7 @@ class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer): def validate_quantity(self, quantity): """Validation for the 'quantity' field""" if quantity <= 0: - raise ValidationError(_("Quantity must be greater than zero")) + raise ValidationError(_('Quantity must be greater than zero')) return quantity @@ -517,7 +517,7 @@ class PurchaseOrderLineItemReceiveSerializer(serializers.Serializer): def validate_quantity(self, quantity): """Validation for the 'quantity' field""" if quantity <= 0: - raise ValidationError(_("Quantity must be greater than zero")) + raise ValidationError(_('Quantity must be greater than zero')) return quantity @@ -647,7 +647,7 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer): if not item['location']: raise ValidationError({ - 'location': _("Destination location must be specified") + 'location': _('Destination location must be specified') }) # Ensure barcodes are unique @@ -1075,7 +1075,7 @@ class SalesOrderShipmentCompleteSerializer(serializers.ModelSerializer): shipment = self.context.get('shipment', None) if not shipment: - raise ValidationError(_("No shipment details provided")) + raise ValidationError(_('No shipment details provided')) shipment.check_can_complete(raise_error=True) @@ -1135,7 +1135,7 @@ class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer): # Ensure that the line item points to the correct order if line_item.order != order: - raise ValidationError(_("Line item is not associated with this order")) + raise ValidationError(_('Line item is not associated with this order')) return line_item @@ -1154,7 +1154,7 @@ class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer): def validate_quantity(self, quantity): """Custom validation for the 'quantity' field""" if quantity <= 0: - raise ValidationError(_("Quantity must be positive")) + raise ValidationError(_('Quantity must be positive')) return quantity @@ -1171,13 +1171,13 @@ class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer): if stock_item.serialized and quantity != 1: raise ValidationError({ - 'quantity': _("Quantity must be 1 for serialized stock item") + 'quantity': _('Quantity must be 1 for serialized stock item') }) q = normalize(stock_item.unallocated_quantity()) if quantity > q: - raise ValidationError({'quantity': _(f"Available quantity ({q}) exceeded")}) + raise ValidationError({'quantity': _(f'Available quantity ({q}) exceeded')}) return data @@ -1197,7 +1197,7 @@ class SalesOrderCompleteSerializer(serializers.Serializer): order = self.context['order'] if not value and not order.is_completed(): - raise ValidationError(_("Order has incomplete line items")) + raise ValidationError(_('Order has incomplete line items')) return value @@ -1274,7 +1274,7 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer): # Ensure that the line item points to the correct order if line_item.order != order: - raise ValidationError(_("Line item is not associated with this order")) + raise ValidationError(_('Line item is not associated with this order')) return line_item @@ -1283,8 +1283,8 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer): ) serial_numbers = serializers.CharField( - label=_("Serial Numbers"), - help_text=_("Enter serial numbers to allocate"), + label=_('Serial Numbers'), + help_text=_('Enter serial numbers to allocate'), required=True, allow_blank=False, ) @@ -1306,10 +1306,10 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer): order = self.context['order'] if shipment.shipment_date is not None: - raise ValidationError(_("Shipment has already been shipped")) + raise ValidationError(_('Shipment has already been shipped')) if shipment.order != order: - raise ValidationError(_("Shipment is not associated with this order")) + raise ValidationError(_('Shipment is not associated with this order')) return shipment @@ -1356,16 +1356,16 @@ class SalesOrderSerialAllocationSerializer(serializers.Serializer): serials_allocated.append(str(serial)) if len(serials_not_exist) > 0: - error_msg = _("No match found for the following serial numbers") - error_msg += ": " - error_msg += ",".join(serials_not_exist) + error_msg = _('No match found for the following serial numbers') + error_msg += ': ' + error_msg += ','.join(serials_not_exist) raise ValidationError({'serial_numbers': error_msg}) if len(serials_allocated) > 0: - error_msg = _("The following serial numbers are already allocated") - error_msg += ": " - error_msg += ",".join(serials_allocated) + error_msg = _('The following serial numbers are already allocated') + error_msg += ': ' + error_msg += ','.join(serials_allocated) raise ValidationError({'serial_numbers': error_msg}) @@ -1412,10 +1412,10 @@ class SalesOrderShipmentAllocationSerializer(serializers.Serializer): order = self.context['order'] if shipment.shipment_date is not None: - raise ValidationError(_("Shipment has already been shipped")) + raise ValidationError(_('Shipment has already been shipped')) if shipment.order != order: - raise ValidationError(_("Shipment is not associated with this order")) + raise ValidationError(_('Shipment is not associated with this order')) return shipment @@ -1596,10 +1596,10 @@ class ReturnOrderLineItemReceiveSerializer(serializers.Serializer): def validate_line_item(self, item): """Validation for a single line item""" if item.order != self.context['order']: - raise ValidationError(_("Line item does not match return order")) + raise ValidationError(_('Line item does not match return order')) if item.received: - raise ValidationError(_("Line item has already been received")) + raise ValidationError(_('Line item has already been received')) return item @@ -1628,7 +1628,7 @@ class ReturnOrderReceiveSerializer(serializers.Serializer): order = self.context['order'] if order.status != ReturnOrderStatus.IN_PROGRESS: raise ValidationError( - _("Items can only be received against orders which are in progress") + _('Items can only be received against orders which are in progress') ) data = super().validate(data) @@ -1636,7 +1636,7 @@ class ReturnOrderReceiveSerializer(serializers.Serializer): items = data.get('items', []) if len(items) == 0: - raise ValidationError(_("Line items must be provided")) + raise ValidationError(_('Line items must be provided')) return data diff --git a/InvenTree/order/tasks.py b/InvenTree/order/tasks.py index f97bcd2ac6..da621626c7 100644 --- a/InvenTree/order/tasks.py +++ b/InvenTree/order/tasks.py @@ -76,7 +76,7 @@ def notify_overdue_sales_order(so: order.models.SalesOrder): context = { 'order': so, 'name': name, - 'message': _(f"Sales order {so} is now overdue"), + 'message': _(f'Sales order {so} is now overdue'), 'link': InvenTree.helpers_model.construct_absolute_url(so.get_absolute_url()), 'template': {'html': 'email/overdue_sales_order.html', 'subject': name}, } diff --git a/InvenTree/order/test_api.py b/InvenTree/order/test_api.py index 7540e437ff..906a5c229a 100644 --- a/InvenTree/order/test_api.py +++ b/InvenTree/order/test_api.py @@ -237,7 +237,7 @@ class PurchaseOrderTest(OrderTest): self.assignRole('purchase_order.add') url = reverse('api-po-list') - huge_number = "PO-92233720368547758089999999999999999" + huge_number = 'PO-92233720368547758089999999999999999' response = self.post( url, @@ -333,7 +333,7 @@ class PurchaseOrderTest(OrderTest): response = self.delete(url, expected_code=403) # Now, add the "delete" permission! - self.assignRole("purchase_order.delete") + self.assignRole('purchase_order.delete') response = self.delete(url, expected_code=204) @@ -589,7 +589,7 @@ class PurchaseOrderTest(OrderTest): resp_dict = response.json() self.assertEqual( - resp_dict['detail'], "Authentication credentials were not provided." + resp_dict['detail'], 'Authentication credentials were not provided.' ) def test_po_calendar_auth(self): @@ -731,7 +731,7 @@ class PurchaseOrderReceiveTest(OrderTest): def test_no_items(self): """Test with an empty list of items.""" data = self.post( - self.url, {"items": [], "location": None}, expected_code=400 + self.url, {'items': [], 'location': None}, expected_code=400 ).data self.assertIn('Line items must be provided', str(data)) @@ -743,14 +743,14 @@ class PurchaseOrderReceiveTest(OrderTest): """Test than errors are returned as expected for invalid data.""" data = self.post( self.url, - {"items": [{"line_item": 12345, "location": 12345}]}, + {'items': [{'line_item': 12345, 'location': 12345}]}, expected_code=400, ).data items = data['items'][0] self.assertIn('Invalid pk "12345"', str(items['line_item'])) - self.assertIn("object does not exist", str(items['location'])) + self.assertIn('object does not exist', str(items['location'])) # No new stock items have been created self.assertEqual(self.n, StockItem.objects.count()) @@ -760,8 +760,8 @@ class PurchaseOrderReceiveTest(OrderTest): data = self.post( self.url, { - "items": [ - {"line_item": 22, "location": 1, "status": 99999, "quantity": 5} + 'items': [ + {'line_item': 22, 'location': 1, 'status': 99999, 'quantity': 5} ] }, expected_code=400, @@ -1330,7 +1330,7 @@ class SalesOrderTest(OrderTest): {'export': fmt}, decode=True if fmt == 'csv' else False, expected_code=200, - expected_fn=f"InvenTree_SalesOrders.{fmt}", + expected_fn=f'InvenTree_SalesOrders.{fmt}', ) @@ -1357,7 +1357,7 @@ class SalesOrderLineItemTest(OrderTest): order=so, part=part, quantity=(idx + 1) * 5, - reference=f"Order {so.reference} - line {idx}", + reference=f'Order {so.reference} - line {idx}', ) ) @@ -1376,7 +1376,7 @@ class SalesOrderLineItemTest(OrderTest): self.assertEqual(len(response.data), n) # List *all* lines, but paginate - response = self.get(self.url, {"limit": 5}, expected_code=200) + response = self.get(self.url, {'limit': 5}, expected_code=200) self.assertEqual(response.data['count'], n) self.assertEqual(len(response.data['results']), 5) @@ -1530,9 +1530,9 @@ class SalesOrderAllocateTest(OrderTest): data = { 'items': [ { - "line_item": line.pk, - "stock_item": part.stock_items.last().pk, - "quantity": 0, + 'line_item': line.pk, + 'stock_item': part.stock_items.last().pk, + 'quantity': 0, } ] } @@ -1576,16 +1576,16 @@ class SalesOrderAllocateTest(OrderTest): # First, check that there are no line items allocated against this SalesOrder self.assertEqual(self.order.stock_allocations.count(), 0) - data = {"items": [], "shipment": self.shipment.pk} + data = {'items': [], 'shipment': self.shipment.pk} for line in self.order.lines.all(): stock_item = line.part.stock_items.last() # Fully-allocate each line data['items'].append({ - "line_item": line.pk, - "stock_item": stock_item.pk, - "quantity": 5, + 'line_item': line.pk, + 'stock_item': stock_item.pk, + 'quantity': 5, }) self.post(self.url, data, expected_code=201) @@ -1603,7 +1603,7 @@ class SalesOrderAllocateTest(OrderTest): # First, check that there are no line items allocated against this SalesOrder self.assertEqual(self.order.stock_allocations.count(), 0) - data = {"items": [], "shipment": self.shipment.pk} + data = {'items': [], 'shipment': self.shipment.pk} def check_template(line_item): return line_item.part.is_template @@ -1619,9 +1619,9 @@ class SalesOrderAllocateTest(OrderTest): # Fully-allocate each line data['items'].append({ - "line_item": line.pk, - "stock_item": stock_item.pk, - "quantity": 5, + 'line_item': line.pk, + 'stock_item': stock_item.pk, + 'quantity': 5, }) self.post(self.url, data, expected_code=201) @@ -1719,8 +1719,8 @@ class SalesOrderAllocateTest(OrderTest): url, { 'order': order.pk, - 'reference': f"SH{idx + 1}", - 'tracking_number': f"TRK_{order.pk}_{idx}", + 'reference': f'SH{idx + 1}', + 'tracking_number': f'TRK_{order.pk}_{idx}', }, expected_code=201, ) @@ -1932,7 +1932,7 @@ class ReturnOrderTests(InvenTreeAPITestCase): # Issue the order (via the API) self.assertIsNone(rma.issue_date) self.post( - reverse("api-return-order-issue", kwargs={"pk": rma.pk}), expected_code=201 + reverse('api-return-order-issue', kwargs={'pk': rma.pk}), expected_code=201 ) rma.refresh_from_db() diff --git a/InvenTree/order/test_migrations.py b/InvenTree/order/test_migrations.py index b55d572939..407468cd51 100644 --- a/InvenTree/order/test_migrations.py +++ b/InvenTree/order/test_migrations.py @@ -30,8 +30,8 @@ class TestRefIntMigrations(MigratorTestCase): for ii in range(10): order = PurchaseOrder.objects.create( supplier=supplier, - reference=f"{ii}-abcde", - description="Just a test order", + reference=f'{ii}-abcde', + description='Just a test order', ) # Initially, the 'reference_int' field is unavailable @@ -40,8 +40,8 @@ class TestRefIntMigrations(MigratorTestCase): sales_order = SalesOrder.objects.create( customer=supplier, - reference=f"{ii}-xyz", - description="A test sales order", + reference=f'{ii}-xyz', + description='A test sales order', ) # Initially, the 'reference_int' field is unavailable @@ -67,8 +67,8 @@ class TestRefIntMigrations(MigratorTestCase): SalesOrder = self.new_state.apps.get_model('order', 'salesorder') for ii in range(10): - po = PurchaseOrder.objects.get(reference=f"{ii}-abcde") - so = SalesOrder.objects.get(reference=f"{ii}-xyz") + po = PurchaseOrder.objects.get(reference=f'{ii}-abcde') + so = SalesOrder.objects.get(reference=f'{ii}-xyz') # The integer reference field must have been correctly updated self.assertEqual(po.reference_int, ii) @@ -166,8 +166,8 @@ class TestAdditionalLineMigration(MigratorTestCase): for ii in range(10): order = PurchaseOrder.objects.create( supplier=supplier, - reference=f"{ii}-abcde", - description="Just a test order", + reference=f'{ii}-abcde', + description='Just a test order', ) order.lines.create(part=supplierpart, quantity=12, received=1) order.lines.create(quantity=12, received=1) @@ -188,7 +188,7 @@ class TestAdditionalLineMigration(MigratorTestCase): """Test that the the PO lines where converted correctly.""" PurchaseOrder = self.new_state.apps.get_model('order', 'purchaseorder') for ii in range(10): - po = PurchaseOrder.objects.get(reference=f"{ii}-abcde") + po = PurchaseOrder.objects.get(reference=f'{ii}-abcde') self.assertEqual(po.extra_lines.count(), 1) self.assertEqual(po.lines.count(), 1) diff --git a/InvenTree/order/test_sales_order.py b/InvenTree/order/test_sales_order.py index fe20b2c10c..9fda6261de 100644 --- a/InvenTree/order/test_sales_order.py +++ b/InvenTree/order/test_sales_order.py @@ -33,7 +33,7 @@ class SalesOrderTest(TestCase): """Initial setup for this set of unit tests""" # Create a Company to ship the goods to cls.customer = Company.objects.create( - name="ABC Co", description="My customer", is_customer=True + name='ABC Co', description='My customer', is_customer=True ) # Create a Part to ship @@ -72,7 +72,7 @@ class SalesOrderTest(TestCase): # Create an extra line cls.extraline = SalesOrderExtraLine.objects.create( - quantity=1, order=cls.order, reference="Extra line" + quantity=1, order=cls.order, reference='Extra line' ) def test_so_reference(self): diff --git a/InvenTree/order/tests.py b/InvenTree/order/tests.py index cba7904e1f..bdfc327f86 100644 --- a/InvenTree/order/tests.py +++ b/InvenTree/order/tests.py @@ -46,7 +46,7 @@ class OrderTest(TestCase): self.assertEqual(order.reference, f'PO-{pk:04d}') line = PurchaseOrderLineItem.objects.get(pk=1) - self.assertEqual(str(line), "100 x ACME0001 from ACME (for PO-0001 - ACME)") + self.assertEqual(str(line), '100 x ACME0001 from ACME (for PO-0001 - ACME)') def test_rebuild_reference(self): """Test that the reference_int field is correctly updated when the model is saved""" @@ -236,7 +236,7 @@ class OrderTest(TestCase): # Create a new PurchaseOrder po = PurchaseOrder.objects.create( - supplier=sup, reference=f"PO-{n + 1}", description='Some PO' + supplier=sup, reference=f'PO-{n + 1}', description='Some PO' ) # Add line items diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 49e50141e8..4c77374535 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -32,7 +32,7 @@ from .models import ( SalesOrderLineItem, ) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class PurchaseOrderIndex(InvenTreeRoleMixin, ListView): @@ -115,9 +115,9 @@ class PurchaseOrderUpload(FileManagementFormView): 'order/order_wizard/match_parts.html', ] form_steps_description = [ - _("Upload File"), - _("Match Fields"), - _("Match Supplier Parts"), + _('Upload File'), + _('Match Fields'), + _('Match Supplier Parts'), ] form_field_map = { 'item_select': 'part', @@ -294,7 +294,7 @@ class SalesOrderExport(AjaxView): export_format = request.GET.get('format', 'csv') - filename = f"{str(order)} - {order.customer.name}.{export_format}" + filename = f'{str(order)} - {order.customer.name}.{export_format}' dataset = SalesOrderLineItemResource().export(queryset=order.lines.all()) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index c7f744f46d..62e856af6b 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -114,7 +114,7 @@ class CategoryList(CategoryMixin, APIDownloadMixin, ListCreateAPI): """Download the filtered queryset as a data file""" dataset = PartCategoryResource().export(queryset=queryset) filedata = dataset.export(export_format) - filename = f"InvenTree_Categories.{export_format}" + filename = f'InvenTree_Categories.{export_format}' return DownloadFile(filedata, filename) @@ -682,23 +682,23 @@ class PartRequirements(RetrieveAPI): part = self.get_object() data = { - "available_stock": part.available_stock, - "on_order": part.on_order, - "required_build_order_quantity": part.required_build_order_quantity(), - "allocated_build_order_quantity": part.build_order_allocation_count(), - "required_sales_order_quantity": part.required_sales_order_quantity(), - "allocated_sales_order_quantity": part.sales_order_allocation_count( + 'available_stock': part.available_stock, + 'on_order': part.on_order, + 'required_build_order_quantity': part.required_build_order_quantity(), + 'allocated_build_order_quantity': part.build_order_allocation_count(), + 'required_sales_order_quantity': part.required_sales_order_quantity(), + 'allocated_sales_order_quantity': part.sales_order_allocation_count( pending=True ), } - data["allocated"] = ( - data["allocated_build_order_quantity"] - + data["allocated_sales_order_quantity"] + data['allocated'] = ( + data['allocated_build_order_quantity'] + + data['allocated_sales_order_quantity'] ) - data["required"] = ( - data["required_build_order_quantity"] - + data["required_sales_order_quantity"] + data['required'] = ( + data['required_build_order_quantity'] + + data['required_sales_order_quantity'] ) return Response(data) @@ -850,7 +850,7 @@ class PartFilter(rest_filters.FilterSet): IPN = rest_filters.CharFilter( label='Filter by exact IPN (internal part number)', field_name='IPN', - lookup_expr="iexact", + lookup_expr='iexact', ) # Regex match for IPN @@ -895,7 +895,7 @@ class PartFilter(rest_filters.FilterSet): return queryset.filter(Q(unallocated_stock__lte=0)) convert_from = rest_filters.ModelChoiceFilter( - label="Can convert from", + label='Can convert from', queryset=Part.objects.all(), method='filter_convert_from', ) @@ -909,7 +909,7 @@ class PartFilter(rest_filters.FilterSet): return queryset exclude_tree = rest_filters.ModelChoiceFilter( - label="Exclude Part tree", + label='Exclude Part tree', queryset=Part.objects.all(), method='filter_exclude_tree', ) @@ -947,7 +947,7 @@ class PartFilter(rest_filters.FilterSet): return queryset.filter(id__in=[p.pk for p in bom_parts]) has_pricing = rest_filters.BooleanFilter( - label="Has Pricing", method="filter_has_pricing" + label='Has Pricing', method='filter_has_pricing' ) def filter_has_pricing(self, queryset, name, value): @@ -961,7 +961,7 @@ class PartFilter(rest_filters.FilterSet): return queryset.filter(q_a | q_b).distinct() stocktake = rest_filters.BooleanFilter( - label="Has stocktake", method='filter_has_stocktake' + label='Has stocktake', method='filter_has_stocktake' ) def filter_has_stocktake(self, queryset, name, value): @@ -997,7 +997,7 @@ class PartFilter(rest_filters.FilterSet): return queryset.exclude(Q(in_stock=0) & ~Q(stock_item_count=0)) default_location = rest_filters.ModelChoiceFilter( - label="Default Location", queryset=StockLocation.objects.all() + label='Default Location', queryset=StockLocation.objects.all() ) is_template = rest_filters.BooleanFilter() @@ -1095,7 +1095,7 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI): dataset = PartResource().export(queryset=queryset) filedata = dataset.export(export_format) - filename = f"InvenTree_Parts.{export_format}" + filename = f'InvenTree_Parts.{export_format}' return DownloadFile(filedata, filename) @@ -1668,7 +1668,7 @@ class BomFilter(rest_filters.FilterSet): ) available_stock = rest_filters.BooleanFilter( - label="Has available stock", method="filter_available_stock" + label='Has available stock', method='filter_available_stock' ) def filter_available_stock(self, queryset, name, value): @@ -1677,7 +1677,7 @@ class BomFilter(rest_filters.FilterSet): return queryset.filter(available_stock__gt=0) return queryset.filter(available_stock=0) - on_order = rest_filters.BooleanFilter(label="On order", method="filter_on_order") + on_order = rest_filters.BooleanFilter(label='On order', method='filter_on_order') def filter_on_order(self, queryset, name, value): """Filter the queryset based on whether each line item has any stock on order""" @@ -1686,7 +1686,7 @@ class BomFilter(rest_filters.FilterSet): return queryset.filter(on_order=0) has_pricing = rest_filters.BooleanFilter( - label="Has Pricing", method="filter_has_pricing" + label='Has Pricing', method='filter_has_pricing' ) def filter_has_pricing(self, queryset, name, value): diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 550c18250a..b7f45bafd4 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -12,7 +12,7 @@ from InvenTree.ready import ( isPluginRegistryLoaded, ) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class PartConfig(AppConfig): @@ -67,11 +67,11 @@ class PartConfig(AppConfig): if items.count() > 0: # Find any pricing objects which have the 'scheduled_for_update' flag set logger.info( - "Resetting update flags for %s pricing objects...", items.count() + 'Resetting update flags for %s pricing objects...', items.count() ) for pricing in items: pricing.scheduled_for_update = False pricing.save() except Exception: - logger.exception("Failed to reset pricing flags - database not ready") + logger.exception('Failed to reset pricing flags - database not ready') diff --git a/InvenTree/part/bom.py b/InvenTree/part/bom.py index bbaeea4d42..88b67d17e0 100644 --- a/InvenTree/part/bom.py +++ b/InvenTree/part/bom.py @@ -269,14 +269,14 @@ def ExportBom( # Generate column names for this supplier k_sup = ( - str(_("Supplier")) - + "_" + str(_('Supplier')) + + '_' + str(mp_idx) - + "_" + + '_' + str(sp_idx) ) k_sku = ( - str(_("SKU")) + "_" + str(mp_idx) + "_" + str(sp_idx) + str(_('SKU')) + '_' + str(mp_idx) + '_' + str(sp_idx) ) try: @@ -307,8 +307,8 @@ def ExportBom( supplier_sku = sp_part.SKU # Generate column names for this supplier - k_sup = str(_("Supplier")) + "_" + str(sp_idx) - k_sku = str(_("SKU")) + "_" + str(sp_idx) + k_sup = str(_('Supplier')) + '_' + str(sp_idx) + k_sku = str(_('SKU')) + '_' + str(sp_idx) try: manufacturer_cols[k_sup].update({bom_idx: supplier_name}) @@ -322,6 +322,6 @@ def ExportBom( data = dataset.export(fmt) - filename = f"{part.full_name}_BOM.{fmt}" + filename = f'{part.full_name}_BOM.{fmt}' return DownloadFile(data, filename) diff --git a/InvenTree/part/helpers.py b/InvenTree/part/helpers.py index c95eaa83e4..e8960b29a7 100644 --- a/InvenTree/part/helpers.py +++ b/InvenTree/part/helpers.py @@ -69,7 +69,7 @@ def render_part_full_name(part) -> str: return template.render(part=part) except Exception as e: logger.warning( - "exception while trying to create full name for part %s: %s", + 'exception while trying to create full name for part %s: %s', part.name, e, ) @@ -80,7 +80,7 @@ def render_part_full_name(part) -> str: # Subdirectory for storing part images -PART_IMAGE_DIR = "part_images" +PART_IMAGE_DIR = 'part_images' def get_part_image_directory() -> str: diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index cb10243719..5dfe91e3e0 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -67,7 +67,7 @@ from InvenTree.status_codes import ( from order import models as OrderModels from stock import models as StockModels -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class PartCategory(MetadataMixin, InvenTreeTree): @@ -85,8 +85,8 @@ class PartCategory(MetadataMixin, InvenTreeTree): class Meta: """Metaclass defines extra model properties""" - verbose_name = _("Part Category") - verbose_name_plural = _("Part Categories") + verbose_name = _('Part Category') + verbose_name_plural = _('Part Categories') def delete(self, *args, **kwargs): """Custom model deletion routine, which updates any child categories or parts. @@ -101,7 +101,7 @@ class PartCategory(MetadataMixin, InvenTreeTree): default_location = TreeForeignKey( 'stock.StockLocation', - related_name="default_categories", + related_name='default_categories', null=True, blank=True, on_delete=models.SET_NULL, @@ -129,8 +129,8 @@ class PartCategory(MetadataMixin, InvenTreeTree): icon = models.CharField( blank=True, max_length=100, - verbose_name=_("Icon"), - help_text=_("Icon (optional)"), + verbose_name=_('Icon'), + help_text=_('Icon (optional)'), ) @staticmethod @@ -150,8 +150,8 @@ class PartCategory(MetadataMixin, InvenTreeTree): if self.pk and self.structural and self.partcount(False, False) > 0: raise ValidationError( _( - "You cannot make this part category structural because some parts " - "are already assigned to it!" + 'You cannot make this part category structural because some parts ' + 'are already assigned to it!' ) ) super().clean() @@ -387,8 +387,8 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) class Meta: """Metaclass defines extra model properties""" - verbose_name = _("Part") - verbose_name_plural = _("Parts") + verbose_name = _('Part') + verbose_name_plural = _('Parts') ordering = ['name'] constraints = [ UniqueConstraint(fields=['name', 'IPN', 'revision'], name='unique_part') @@ -482,7 +482,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) def __str__(self): """Return a string representation of the Part (for use in the admin interface)""" - return f"{self.full_name} - {self.description}" + return f'{self.full_name} - {self.description}' def get_parts_in_bom(self, **kwargs): """Return a list of all parts in the BOM for this part. @@ -686,8 +686,8 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) if stock.exists(): if raise_error: raise ValidationError( - _("Stock item with this serial number already exists") - + ": " + _('Stock item with this serial number already exists') + + ': ' + serial ) else: @@ -800,7 +800,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) .exists() ): raise ValidationError( - _("Part with this Name, IPN and Revision already exists.") + _('Part with this Name, IPN and Revision already exists.') ) def clean(self): @@ -815,7 +815,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) """ if self.category is not None and self.category.structural: raise ValidationError({ - 'category': _("Parts cannot be assigned to structural part categories!") + 'category': _('Parts cannot be assigned to structural part categories!') }) super().clean() @@ -989,7 +989,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) units = models.CharField( max_length=20, - default="", + default='', blank=True, null=True, verbose_name=_('Units'), @@ -1024,7 +1024,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) salable = models.BooleanField( default=part_settings.part_salable_default, verbose_name=_('Salable'), - help_text=_("Can this part be sold to customers?"), + help_text=_('Can this part be sold to customers?'), ) active = models.BooleanField( @@ -1823,7 +1823,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) min_price = normalize(min_price) max_price = normalize(max_price) - return f"{min_price} - {max_price}" + return f'{min_price} - {max_price}' def get_supplier_price_range(self, quantity=1): """Return the supplier price range of this part: @@ -1872,7 +1872,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel) for item in self.get_bom_items().select_related('sub_part'): if item.sub_part.pk == self.pk: - logger.warning("WARNING: BomItem ID %s contains itself in BOM", item.pk) + logger.warning('WARNING: BomItem ID %s contains itself in BOM', item.pk) continue q = decimal.Decimal(quantity) @@ -2402,7 +2402,7 @@ class PartPricing(common.models.MetaMixin): result = convert_money(money, target_currency) except MissingRate: logger.warning( - "No currency conversion rate available for %s -> %s", + 'No currency conversion rate available for %s -> %s', money.currency, target_currency, ) @@ -2432,7 +2432,7 @@ class PartPricing(common.models.MetaMixin): or not Part.objects.filter(pk=self.part.pk).exists() ): logger.warning( - "Referenced part instance does not exist - skipping pricing update." + 'Referenced part instance does not exist - skipping pricing update.' ) return @@ -2458,13 +2458,13 @@ class PartPricing(common.models.MetaMixin): if self.scheduled_for_update: # Ignore if the pricing is already scheduled to be updated - logger.debug("Pricing for %s already scheduled for update - skipping", p) + logger.debug('Pricing for %s already scheduled for update - skipping', p) return if counter > 25: # Prevent infinite recursion / stack depth issues logger.debug( - counter, f"Skipping pricing update for {p} - maximum depth exceeded" + counter, f'Skipping pricing update for {p} - maximum depth exceeded' ) return @@ -3260,7 +3260,7 @@ class PartAttachment(InvenTreeAttachment): def getSubdir(self): """Returns the media subdirectory where part attachments are stored""" - return os.path.join("part_files", str(self.part.id)) + return os.path.join('part_files', str(self.part.id)) part = models.ForeignKey( Part, @@ -3423,7 +3423,7 @@ class PartTestTemplate(MetadataMixin, models.Model): for test in tests: if test.key == key: raise ValidationError({ - 'test_name': _("Test with this name already exists for this part") + 'test_name': _('Test with this name already exists for this part') }) super().validate_unique(exclude) @@ -3444,35 +3444,35 @@ class PartTestTemplate(MetadataMixin, models.Model): test_name = models.CharField( blank=False, max_length=100, - verbose_name=_("Test Name"), - help_text=_("Enter a name for the test"), + verbose_name=_('Test Name'), + help_text=_('Enter a name for the test'), ) description = models.CharField( blank=False, null=True, max_length=100, - verbose_name=_("Test Description"), - help_text=_("Enter description for this test"), + verbose_name=_('Test Description'), + help_text=_('Enter description for this test'), ) required = models.BooleanField( default=True, - verbose_name=_("Required"), - help_text=_("Is this test required to pass?"), + verbose_name=_('Required'), + help_text=_('Is this test required to pass?'), ) requires_value = models.BooleanField( default=False, - verbose_name=_("Requires Value"), - help_text=_("Does this test require a value when adding a test result?"), + verbose_name=_('Requires Value'), + help_text=_('Does this test require a value when adding a test result?'), ) requires_attachment = models.BooleanField( default=False, - verbose_name=_("Requires Attachment"), + verbose_name=_('Requires Attachment'), help_text=_( - "Does this test require a file attachment when adding a test result?" + 'Does this test require a file attachment when adding a test result?' ), ) @@ -3503,7 +3503,7 @@ class PartParameterTemplate(MetadataMixin, models.Model): """Return a string representation of a PartParameterTemplate instance""" s = str(self.name) if self.units: - s += f" ({self.units})" + s += f' ({self.units})' return s def clean(self): @@ -3557,8 +3557,8 @@ class PartParameterTemplate(MetadataMixin, models.Model): ).exclude(pk=self.pk) if others.exists(): - msg = _("Parameter template name must be unique") - raise ValidationError({"name": msg}) + msg = _('Parameter template name must be unique') + raise ValidationError({'name': msg}) except PartParameterTemplate.DoesNotExist: pass @@ -3644,7 +3644,7 @@ class PartParameter(MetadataMixin, models.Model): def __str__(self): """String representation of a PartParameter (used in the admin interface)""" - return f"{self.part.full_name} : {self.template.name} = {self.data} ({self.template.units})" + return f'{self.part.full_name} : {self.template.name} = {self.data} ({self.template.units})' def save(self, *args, **kwargs): """Custom save method for the PartParameter model.""" @@ -3856,11 +3856,11 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model): class Meta: """Metaclass providing extra model definition""" - verbose_name = _("BOM Item") + verbose_name = _('BOM Item') def __str__(self): """Return a string representation of this BomItem instance""" - return f"{decimal2string(self.quantity)} x {self.sub_part.full_name} to make {self.part.full_name}" + return f'{decimal2string(self.quantity)} x {self.sub_part.full_name} to make {self.part.full_name}' @staticmethod def get_api_url(): @@ -3968,13 +3968,13 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model): optional = models.BooleanField( default=False, verbose_name=_('Optional'), - help_text=_("This BOM item is optional"), + help_text=_('This BOM item is optional'), ) consumable = models.BooleanField( default=False, verbose_name=_('Consumable'), - help_text=_("This BOM item is consumable (it is not tracked in build orders)"), + help_text=_('This BOM item is consumable (it is not tracked in build orders)'), ) overage = models.CharField( @@ -4106,8 +4106,8 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model): if self.sub_part.trackable: if self.quantity != int(self.quantity): raise ValidationError({ - "quantity": _( - "Quantity must be integer value for trackable parts" + 'quantity': _( + 'Quantity must be integer value for trackable parts' ) }) @@ -4204,7 +4204,7 @@ class BomItem(DataImportMixin, MetadataMixin, models.Model): pmin = decimal2money(pmin) pmax = decimal2money(pmax) - return f"{pmin} to {pmax}" + return f'{pmin} to {pmax}' @receiver(post_save, sender=BomItem, dispatch_uid='update_bom_build_lines') @@ -4259,7 +4259,7 @@ class BomItemSubstitute(MetadataMixin, models.Model): class Meta: """Metaclass providing extra model definition""" - verbose_name = _("BOM Item Substitute") + verbose_name = _('BOM Item Substitute') # Prevent duplication of substitute parts unique_together = ('part', 'bom_item') @@ -4280,7 +4280,7 @@ class BomItemSubstitute(MetadataMixin, models.Model): if self.part == self.bom_item.sub_part: raise ValidationError({ - "part": _("Substitute part cannot be the same as the master part") + 'part': _('Substitute part cannot be the same as the master part') }) @staticmethod @@ -4345,9 +4345,9 @@ class PartRelated(MetadataMixin, models.Model): if self.part_1 == self.part_2: raise ValidationError( - _("Part relationship cannot be created between a part and itself") + _('Part relationship cannot be created between a part and itself') ) # Check for inverse relationship if PartRelated.objects.filter(part_1=self.part_2, part_2=self.part_1).exists(): - raise ValidationError(_("Duplicate relationship already exists")) + raise ValidationError(_('Duplicate relationship already exists')) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 5a17740642..4e978e086f 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -55,7 +55,7 @@ from .models import ( PartTestTemplate, ) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class CategorySerializer(InvenTree.serializers.InvenTreeModelSerializer): @@ -220,7 +220,7 @@ class PartThumbSerializerUpdate(InvenTree.serializers.InvenTreeModelSerializer): """Check that file is an image.""" validate = imghdr.what(value) if not validate: - raise serializers.ValidationError("File is not an image") + raise serializers.ValidationError('File is not an image') return value image = InvenTree.serializers.InvenTreeAttachmentSerializerField(required=True) @@ -346,7 +346,7 @@ class PartSetCategorySerializer(serializers.Serializer): def validate_parts(self, parts): """Validate the selected parts""" if len(parts) == 0: - raise serializers.ValidationError(_("No parts selected")) + raise serializers.ValidationError(_('No parts selected')) return parts @@ -881,7 +881,7 @@ class PartSerializer( ) except IntegrityError: logger.exception( - "Could not create new PartParameter for part %s", instance + 'Could not create new PartParameter for part %s', instance ) # Create initial stock entry @@ -945,7 +945,7 @@ class PartSerializer( remote_img.save(buffer, format=fmt) # Construct a simplified name for the image - filename = f"part_{part.pk}_image.{fmt.lower()}" + filename = f'part_{part.pk}_image.{fmt.lower()}' part.image.save(filename, ContentFile(buffer.getvalue())) @@ -1071,12 +1071,12 @@ class PartStocktakeReportGenerateSerializer(serializers.Serializer): # Stocktake functionality must be enabled if not common.models.InvenTreeSetting.get_setting('STOCKTAKE_ENABLE', False): raise serializers.ValidationError( - _("Stocktake functionality is not enabled") + _('Stocktake functionality is not enabled') ) # Check that background worker is running if not InvenTree.status.is_worker_running(): - raise serializers.ValidationError(_("Background worker check failed")) + raise serializers.ValidationError(_('Background worker check failed')) return data @@ -1381,7 +1381,7 @@ class BomItemSerializer(InvenTree.serializers.InvenTreeModelSerializer): def validate_quantity(self, quantity): """Perform validation for the BomItem quantity field""" if quantity <= 0: - raise serializers.ValidationError(_("Quantity must be greater than zero")) + raise serializers.ValidationError(_('Quantity must be greater than zero')) return quantity @@ -1680,7 +1680,7 @@ class BomImportExtractSerializer(InvenTree.serializers.DataFileExtractSerializer if not any(col in self.columns for col in part_columns): # At least one part column is required! - raise serializers.ValidationError(_("No part column specified")) + raise serializers.ValidationError(_('No part column specified')) @staticmethod def process_row(row): @@ -1768,7 +1768,7 @@ class BomImportSubmitSerializer(serializers.Serializer): items = data['items'] if len(items) == 0: - raise serializers.ValidationError(_("At least one BOM item is required")) + raise serializers.ValidationError(_('At least one BOM item is required')) data = super().validate(data) @@ -1798,7 +1798,7 @@ class BomImportSubmitSerializer(serializers.Serializer): bom_items.append(BomItem(**item)) if len(bom_items) > 0: - logger.info("Importing %s BOM items", len(bom_items)) + logger.info('Importing %s BOM items', len(bom_items)) BomItem.objects.bulk_create(bom_items) except Exception as e: diff --git a/InvenTree/part/stocktake.py b/InvenTree/part/stocktake.py index 7b3d12f3b7..4d89b263ec 100644 --- a/InvenTree/part/stocktake.py +++ b/InvenTree/part/stocktake.py @@ -62,7 +62,7 @@ def perform_stocktake( if not pricing.is_valid: # If pricing is not valid, let's update - logger.info("Pricing not valid for %s - updating", target) + logger.info('Pricing not valid for %s - updating', target) pricing.update_pricing(cascade=False) pricing.refresh_from_db() @@ -204,10 +204,10 @@ def generate_stocktake_report(**kwargs): n_parts = parts.count() if n_parts == 0: - logger.info("No parts selected for stocktake report - exiting") + logger.info('No parts selected for stocktake report - exiting') return - logger.info("Generating new stocktake report for %s parts", n_parts) + logger.info('Generating new stocktake report for %s parts', n_parts) base_currency = common.settings.currency_code_default() @@ -266,7 +266,7 @@ def generate_stocktake_report(**kwargs): buffer.write(dataset.export('csv')) today = datetime.now().date().isoformat() - filename = f"InvenTree_Stocktake_{today}.csv" + filename = f'InvenTree_Stocktake_{today}.csv' report_file = ContentFile(buffer.getvalue(), name=filename) if generate_report: @@ -295,7 +295,7 @@ def generate_stocktake_report(**kwargs): t_stocktake = time.time() - t_start logger.info( - "Generated stocktake report for %s parts in %ss", + 'Generated stocktake report for %s parts in %ss', total_parts, round(t_stocktake, 2), ) diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index 2830b49640..604baaa907 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -24,7 +24,7 @@ from InvenTree.tasks import ( scheduled_task, ) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') def notify_low_stock(part: part.models.Part): @@ -33,7 +33,7 @@ def notify_low_stock(part: part.models.Part): - Triggered when the available stock for a given part falls be low the configured threhsold - A notification is delivered to any users who are 'subscribed' to this part """ - name = _("Low stock notification") + name = _('Low stock notification') message = _( f'The available stock for {part.name} has fallen below the configured minimum level' ) @@ -70,7 +70,7 @@ def update_part_pricing(pricing: part.models.PartPricing, counter: int = 0): pricing: The target PartPricing instance to be updated counter: How many times this function has been called in sequence """ - logger.info("Updating part pricing for %s", pricing.part) + logger.info('Updating part pricing for %s', pricing.part) pricing.update_pricing(counter=counter) @@ -90,7 +90,7 @@ def check_missing_pricing(limit=250): results = part.models.PartPricing.objects.filter(updated=None)[:limit] if results.count() > 0: - logger.info("Found %s parts with empty pricing", results.count()) + logger.info('Found %s parts with empty pricing', results.count()) for pp in results: pp.schedule_for_update() @@ -102,7 +102,7 @@ def check_missing_pricing(limit=250): results = part.models.PartPricing.objects.filter(updated__lte=stale_date)[:limit] if results.count() > 0: - logger.info("Found %s stale pricing entries", results.count()) + logger.info('Found %s stale pricing entries', results.count()) for pp in results: pp.schedule_for_update() @@ -112,7 +112,7 @@ def check_missing_pricing(limit=250): results = part.models.PartPricing.objects.exclude(currency=currency) if results.count() > 0: - logger.info("Found %s pricing entries in the wrong currency", results.count()) + logger.info('Found %s pricing entries in the wrong currency', results.count()) for pp in results: pp.schedule_for_update() @@ -121,7 +121,7 @@ def check_missing_pricing(limit=250): results = part.models.Part.objects.filter(pricing_data=None)[:limit] if results.count() > 0: - logger.info("Found %s parts without pricing", results.count()) + logger.info('Found %s parts without pricing', results.count()) for p in results: pricing = p.pricing @@ -151,14 +151,14 @@ def scheduled_stocktake_reports(): 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()) + logger.info('Deleting %s stale stocktake reports', old_reports.count()) old_reports.delete() # Next, check if stocktake functionality is enabled if not common.models.InvenTreeSetting.get_setting( 'STOCKTAKE_ENABLE', False, cache=False ): - logger.info("Stocktake functionality is not enabled - exiting") + logger.info('Stocktake functionality is not enabled - exiting') return report_n_days = int( @@ -168,11 +168,11 @@ def scheduled_stocktake_reports(): ) if report_n_days < 1: - logger.info("Stocktake auto reports are disabled, exiting") + logger.info('Stocktake auto reports are disabled, exiting') return if not check_daily_holdoff('STOCKTAKE_RECENT_REPORT', report_n_days): - logger.info("Stocktake report was recently generated - exiting") + logger.info('Stocktake report was recently generated - exiting') return # Let's start a new stocktake report for all parts diff --git a/InvenTree/part/templatetags/i18n.py b/InvenTree/part/templatetags/i18n.py index e15b5e9697..ab2d440a32 100644 --- a/InvenTree/part/templatetags/i18n.py +++ b/InvenTree/part/templatetags/i18n.py @@ -42,15 +42,15 @@ class CustomTranslateNode(TranslateNode): result = result.replace(c, '') # Escape any quotes contained in the string - result = result.replace("'", r"\'") + result = result.replace("'", r'\'') result = result.replace('"', r'\"') # Return the 'clean' resulting string return result -@register.tag("translate") -@register.tag("trans") +@register.tag('translate') +@register.tag('trans') def do_translate(parser, token): """Custom translation function, lifted from https://github.com/django/django/blob/main/django/templatetags/i18n.py @@ -66,7 +66,7 @@ def do_translate(parser, token): asvar = None message_context = None seen = set() - invalid_context = {"as", "noop"} + invalid_context = {'as', 'noop'} while remaining: option = remaining.pop(0) @@ -74,9 +74,9 @@ def do_translate(parser, token): raise TemplateSyntaxError( "The '%s' option was specified more than once." % option ) - elif option == "noop": + elif option == 'noop': noop = True - elif option == "context": + elif option == 'context': try: value = remaining.pop(0) except IndexError: @@ -87,10 +87,10 @@ def do_translate(parser, token): if value in invalid_context: raise TemplateSyntaxError( "Invalid argument '%s' provided to the '%s' tag for the context " - "option" % (value, bits[0]) + 'option' % (value, bits[0]) ) message_context = parser.compile_filter(value) - elif option == "as": + elif option == 'as': try: value = remaining.pop(0) except IndexError: @@ -110,26 +110,26 @@ def do_translate(parser, token): # Re-register tags which we have not explicitly overridden -register.tag("blocktrans", django.templatetags.i18n.do_block_translate) -register.tag("blocktranslate", django.templatetags.i18n.do_block_translate) +register.tag('blocktrans', django.templatetags.i18n.do_block_translate) +register.tag('blocktranslate', django.templatetags.i18n.do_block_translate) -register.tag("language", django.templatetags.i18n.language) +register.tag('language', django.templatetags.i18n.language) register.tag( - "get_available_languages", django.templatetags.i18n.do_get_available_languages + 'get_available_languages', django.templatetags.i18n.do_get_available_languages ) -register.tag("get_language_info", django.templatetags.i18n.do_get_language_info) +register.tag('get_language_info', django.templatetags.i18n.do_get_language_info) register.tag( - "get_language_info_list", django.templatetags.i18n.do_get_language_info_list + 'get_language_info_list', django.templatetags.i18n.do_get_language_info_list ) -register.tag("get_current_language", django.templatetags.i18n.do_get_current_language) +register.tag('get_current_language', django.templatetags.i18n.do_get_current_language) register.tag( - "get_current_language_bidi", django.templatetags.i18n.do_get_current_language_bidi + 'get_current_language_bidi', django.templatetags.i18n.do_get_current_language_bidi ) -register.filter("language_name", django.templatetags.i18n.language_name) +register.filter('language_name', django.templatetags.i18n.language_name) register.filter( - "language_name_translated", django.templatetags.i18n.language_name_translated + 'language_name_translated', django.templatetags.i18n.language_name_translated ) -register.filter("language_name_local", django.templatetags.i18n.language_name_local) -register.filter("language_bidi", django.templatetags.i18n.language_bidi) +register.filter('language_name_local', django.templatetags.i18n.language_name_local) +register.filter('language_bidi', django.templatetags.i18n.language_bidi) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 14cc7e7a81..198d10ccfa 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -65,7 +65,7 @@ def render_date(context, date_object): try: date_object = date.fromisoformat(date_object) except ValueError: - logger.warning("Tried to convert invalid date string: %s", date_object) + logger.warning('Tried to convert invalid date string: %s', date_object) return None # We may have already pre-cached the date format by calling this already! @@ -220,7 +220,7 @@ def python_version(*args, **kwargs): def inventree_version(shortstring=False, *args, **kwargs): """Return InvenTree version string.""" if shortstring: - return _(f"{version.inventreeInstanceTitle()} v{version.inventreeVersion()}") + return _(f'{version.inventreeInstanceTitle()} v{version.inventreeVersion()}') return version.inventreeVersion() @@ -645,18 +645,18 @@ def admin_url(user, table, pk): from django.urls import reverse if not djangosettings.INVENTREE_ADMIN_ENABLED: - return "" + return '' if not user.is_staff: - return "" + return '' # Check the user has the correct permission - perm_string = f"{app}.change_{model}" + perm_string = f'{app}.change_{model}' if not user.has_perm(perm_string): return '' # Fallback URL - url = reverse(f"admin:{app}_{model}_changelist") + url = reverse(f'admin:{app}_{model}_changelist') if pk: try: diff --git a/InvenTree/part/test_api.py b/InvenTree/part/test_api.py index 7baa291496..1b32f5f914 100644 --- a/InvenTree/part/test_api.py +++ b/InvenTree/part/test_api.py @@ -178,14 +178,14 @@ class PartCategoryAPITest(InvenTreeAPITestCase): # Create child categories for ii in range(10): child = PartCategory.objects.create( - name=f"Child cat {ii}", description="A child category", parent=cat + name=f'Child cat {ii}', description='A child category', parent=cat ) # Create parts in this category for jj in range(10): Part.objects.create( - name=f"Part xyz {jj}_{ii}", - description="A test part with a description", + name=f'Part xyz {jj}_{ii}', + description='A test part with a description', category=child, ) @@ -351,8 +351,8 @@ class PartCategoryAPITest(InvenTreeAPITestCase): for jj in range(3): parts.append( Part.objects.create( - name=f"Part xyz {i}_{jj}", - description="Child part of the deleted category", + name=f'Part xyz {i}_{jj}', + description='Child part of the deleted category', category=cat_to_delete, ) ) @@ -362,8 +362,8 @@ class PartCategoryAPITest(InvenTreeAPITestCase): # Create child categories under the category to be deleted for ii in range(3): child = PartCategory.objects.create( - name=f"Child parent_cat {i}_{ii}", - description="A child category of the deleted category", + name=f'Child parent_cat {i}_{ii}', + description='A child category of the deleted category', parent=cat_to_delete, ) child_categories.append(child) @@ -372,8 +372,8 @@ class PartCategoryAPITest(InvenTreeAPITestCase): for jj in range(3): child_categories_parts.append( Part.objects.create( - name=f"Part xyz {i}_{jj}_{ii}", - description="Child part in the child category of the deleted category", + name=f'Part xyz {i}_{jj}_{ii}', + description='Child part in the child category of the deleted category', category=child, ) ) @@ -438,8 +438,8 @@ class PartCategoryAPITest(InvenTreeAPITestCase): # Make sure that we get an error if we try to create part in the structural category with self.assertRaises(ValidationError): part = Part.objects.create( - name="-", - description="Part which shall not be created", + name='-', + description='Part which shall not be created', category=structural_category, ) @@ -456,8 +456,8 @@ class PartCategoryAPITest(InvenTreeAPITestCase): # Create the test part assigned to a non-structural category part = Part.objects.create( - name="-", - description="Part which category will be changed to structural", + name='-', + description='Part which category will be changed to structural', category=non_structural_category, ) @@ -752,8 +752,8 @@ class PartAPITest(PartAPITestBase): for color in ['Red', 'Green', 'Blue', 'Yellow', 'Pink', 'Black']: variants.append( Part.objects.create( - name=f"{color} Variant", - description="Variant part with a specific color", + name=f'{color} Variant', + description='Variant part with a specific color', variant_of=master_part, category=category, ) @@ -839,7 +839,7 @@ class PartAPITest(PartAPITestBase): # Try to post a new test with the same name (should fail) response = self.post( url, - data={'part': 10004, 'test_name': " newtest", 'description': 'dafsdf'}, + data={'part': 10004, 'test_name': ' newtest', 'description': 'dafsdf'}, ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -971,8 +971,8 @@ class PartAPITest(PartAPITestBase): for i in range(10): gcv = Part.objects.create( - name=f"GC Var {i}", - description="Green chair variant", + name=f'GC Var {i}', + description='Green chair variant', variant_of=green_chair, ) @@ -1237,10 +1237,10 @@ class PartCreationTests(PartAPITestBase): """Test that non-standard ASCII chars are accepted.""" url = reverse('api-part-list') - name = "Kaltgerätestecker" - description = "Gerät Kaltgerätestecker strange chars should get through" + name = 'Kaltgerätestecker' + description = 'Gerät Kaltgerätestecker strange chars should get through' - data = {"name": name, "description": description, "category": 2} + data = {'name': name, 'description': description, 'category': 2} response = self.post(url, data, expected_code=201) @@ -1284,7 +1284,7 @@ class PartCreationTests(PartAPITestBase): PartCategoryParameterTemplate.objects.create( parameter_template=PartParameterTemplate.objects.get(pk=pk), category=cat, - default_value=f"Value {pk}", + default_value=f'Value {pk}', ) self.assertEqual(cat.parameter_templates.count(), 3) @@ -1630,8 +1630,8 @@ class PartListTests(PartAPITestBase): for ii in range(100): parts.append( Part( - name=f"Extra part {ii}", - description="A new part which will appear via the API", + name=f'Extra part {ii}', + description='A new part which will appear via the API', level=0, tree_id=0, lft=0, @@ -1975,15 +1975,15 @@ class PartAPIAggregationTest(InvenTreeAPITestCase): # First, create some parts paint = PartCategory.objects.create( - parent=None, name="Paint", description="Paints and such" + parent=None, name='Paint', description='Paints and such' ) for color in ['Red', 'Green', 'Blue', 'Orange', 'Yellow']: p = Part.objects.create( category=paint, units='litres', - name=f"{color} Paint", - description=f"Paint which is {color} in color", + name=f'{color} Paint', + description=f'Paint which is {color} in color', ) # Create multiple supplier parts in different sizes @@ -1991,7 +1991,7 @@ class PartAPIAggregationTest(InvenTreeAPITestCase): sp = SupplierPart.objects.create( part=p, supplier=supplier, - SKU=f"PNT-{color}-{pk_sz}L", + SKU=f'PNT-{color}-{pk_sz}L', pack_quantity=str(pk_sz), ) @@ -2137,7 +2137,7 @@ class BomItemTest(InvenTreeAPITestCase): url = reverse('api-bom-list') # Order by increasing quantity - response = self.get(f"{url}?ordering=+quantity", expected_code=200) + response = self.get(f'{url}?ordering=+quantity', expected_code=200) self.assertEqual(len(response.data), 6) @@ -2147,7 +2147,7 @@ class BomItemTest(InvenTreeAPITestCase): self.assertTrue(q1 < q2) # Order by decreasing quantity - response = self.get(f"{url}?ordering=-quantity", expected_code=200) + response = self.get(f'{url}?ordering=-quantity', expected_code=200) self.assertEqual(q1, response.data[-1]['quantity']) self.assertEqual(q2, response.data[0]['quantity']) @@ -2247,8 +2247,8 @@ class BomItemTest(InvenTreeAPITestCase): for ii in range(5): # Create a variant part! variant = Part.objects.create( - name=f"Variant_{ii}", - description="A variant part, with a description", + name=f'Variant_{ii}', + description='A variant part, with a description', component=True, variant_of=sub_part, ) @@ -2295,7 +2295,7 @@ class BomItemTest(InvenTreeAPITestCase): bom_item = BomItem.objects.get(pk=1) # Filter stock items which can be assigned against this stock item - response = self.get(stock_url, {"bom_item": bom_item.pk}, expected_code=200) + response = self.get(stock_url, {'bom_item': bom_item.pk}, expected_code=200) n_items = len(response.data) @@ -2304,8 +2304,8 @@ class BomItemTest(InvenTreeAPITestCase): # Let's make some! for ii in range(5): sub_part = Part.objects.create( - name=f"Substitute {ii}", - description="A substitute part", + name=f'Substitute {ii}', + description='A substitute part', component=True, is_template=False, assembly=False, @@ -2322,7 +2322,7 @@ class BomItemTest(InvenTreeAPITestCase): self.assertEqual(len(response.data), 1) # We should also have more stock available to allocate against this BOM item! - response = self.get(stock_url, {"bom_item": bom_item.pk}, expected_code=200) + response = self.get(stock_url, {'bom_item': bom_item.pk}, expected_code=200) self.assertEqual(len(response.data), n_items + ii + 1) @@ -2355,8 +2355,8 @@ class BomItemTest(InvenTreeAPITestCase): for i in range(5): assy = Part.objects.create( - name=f"Assy_{i}", - description="An assembly made of other parts", + name=f'Assy_{i}', + description='An assembly made of other parts', active=True, assembly=True, ) @@ -2368,8 +2368,8 @@ class BomItemTest(InvenTreeAPITestCase): # Create some sub-components for i in range(5): cmp = Part.objects.create( - name=f"Component_{i}", - description="A sub component", + name=f'Component_{i}', + description='A sub component', active=True, component=True, ) @@ -2403,8 +2403,8 @@ class BomItemTest(InvenTreeAPITestCase): for i in range(10): # Create a variant part vp = Part.objects.create( - name=f"Var {i}", - description="Variant part description field", + name=f'Var {i}', + description='Variant part description field', variant_of=bom_item.sub_part, ) @@ -2523,7 +2523,7 @@ class PartInternalPriceBreakTest(InvenTreeAPITestCase): p.active = False p.save() - response = self.delete(reverse("api-part-detail", kwargs={"pk": 1})) + response = self.delete(reverse('api-part-detail', kwargs={'pk': 1})) self.assertEqual(response.status_code, 204) with self.assertRaises(Part.DoesNotExist): @@ -2588,7 +2588,7 @@ class PartStocktakeTest(InvenTreeAPITestCase): # Initially no stocktake information available self.assertIsNone(p.latest_stocktake) - note = f"Note {p.pk}" + note = f'Note {p.pk}' quantity = p.pk + 5 self.post( diff --git a/InvenTree/part/test_bom_import.py b/InvenTree/part/test_bom_import.py index 0a4a85fc21..2a1c03d78c 100644 --- a/InvenTree/part/test_bom_import.py +++ b/InvenTree/part/test_bom_import.py @@ -33,9 +33,9 @@ class BomUploadTest(InvenTreeAPITestCase): for i in range(10): parts.append( Part( - name=f"Component {i}", - IPN=f"CMP_{i}", - description="A subcomponent that can be used in a BOM", + name=f'Component {i}', + IPN=f'CMP_{i}', + description='A subcomponent that can be used in a BOM', component=True, assembly=False, lft=0, diff --git a/InvenTree/part/test_bom_item.py b/InvenTree/part/test_bom_item.py index 543c576357..4c2a7b7bb5 100644 --- a/InvenTree/part/test_bom_item.py +++ b/InvenTree/part/test_bom_item.py @@ -70,7 +70,7 @@ class BomItemTest(TestCase): def test_integer_quantity(self): """Test integer validation for BomItem.""" p = Part.objects.create( - name="test", description="part description", component=True, trackable=True + name='test', description='part description', component=True, trackable=True ) # Creation of a BOMItem with a non-integer quantity of a trackable Part should fail @@ -157,8 +157,8 @@ class BomItemTest(TestCase): for ii in range(5): # Create a new part sub_part = Part.objects.create( - name=f"Orphan {ii}", - description="A substitute part for the orphan part", + name=f'Orphan {ii}', + description='A substitute part for the orphan part', component=True, is_template=False, assembly=False, @@ -196,7 +196,7 @@ class BomItemTest(TestCase): """Tests for the 'consumable' BomItem field""" # Create an assembly part assembly = Part.objects.create( - name="An assembly", description="Made with parts", assembly=True + name='An assembly', description='Made with parts', assembly=True ) # No BOM information initially @@ -204,16 +204,16 @@ class BomItemTest(TestCase): # Create some component items c1 = Part.objects.create( - name="C1", description="Part C1 - this is just the part description" + name='C1', description='Part C1 - this is just the part description' ) c2 = Part.objects.create( - name="C2", description="Part C2 - this is just the part description" + name='C2', description='Part C2 - this is just the part description' ) c3 = Part.objects.create( - name="C3", description="Part C3 - this is just the part description" + name='C3', description='Part C3 - this is just the part description' ) c4 = Part.objects.create( - name="C4", description="Part C4 - this is just the part description" + name='C4', description='Part C4 - this is just the part description' ) for p in [c1, c2, c3, c4]: @@ -261,20 +261,20 @@ class BomItemTest(TestCase): # Second test: A recursive BOM part_a = Part.objects.create( name='Part A', - description="A part which is called A", + description='A part which is called A', assembly=True, is_template=True, component=True, ) part_b = Part.objects.create( name='Part B', - description="A part which is called B", + description='A part which is called B', assembly=True, component=True, ) part_c = Part.objects.create( name='Part C', - description="A part which is called C", + description='A part which is called C', assembly=True, component=True, ) diff --git a/InvenTree/part/test_category.py b/InvenTree/part/test_category.py index be29cae943..bef67c71e2 100644 --- a/InvenTree/part/test_category.py +++ b/InvenTree/part/test_category.py @@ -106,7 +106,7 @@ class CategoryTest(TestCase): letter = chr(ord('A') + idx) child = PartCategory.objects.create( - name=letter * 10, description=f"Subcategory {letter}", parent=parent + name=letter * 10, description=f'Subcategory {letter}', parent=parent ) parent = child @@ -114,7 +114,7 @@ class CategoryTest(TestCase): self.assertTrue(len(child.path), 26) self.assertEqual( child.pathstring, - "Cat/AAAAAAAAAA/BBBBBBBBBB/CCCCCCCCCC/DDDDDDDDDD/EEEEEEEEEE/FFFFFFFFFF/GGGGGGGGGG/HHHHHHHHHH/IIIIIIIIII/JJJJJJJJJJ/KKKKKKKKK...OO/PPPPPPPPPP/QQQQQQQQQQ/RRRRRRRRRR/SSSSSSSSSS/TTTTTTTTTT/UUUUUUUUUU/VVVVVVVVVV/WWWWWWWWWW/XXXXXXXXXX/YYYYYYYYYY/ZZZZZZZZZZ", + 'Cat/AAAAAAAAAA/BBBBBBBBBB/CCCCCCCCCC/DDDDDDDDDD/EEEEEEEEEE/FFFFFFFFFF/GGGGGGGGGG/HHHHHHHHHH/IIIIIIIIII/JJJJJJJJJJ/KKKKKKKKK...OO/PPPPPPPPPP/QQQQQQQQQQ/RRRRRRRRRR/SSSSSSSSSS/TTTTTTTTTT/UUUUUUUUUU/VVVVVVVVVV/WWWWWWWWWW/XXXXXXXXXX/YYYYYYYYYY/ZZZZZZZZZZ', ) self.assertTrue(len(child.pathstring) <= 250) diff --git a/InvenTree/part/test_migrations.py b/InvenTree/part/test_migrations.py index 3bfb3087b6..cbff8e28f9 100644 --- a/InvenTree/part/test_migrations.py +++ b/InvenTree/part/test_migrations.py @@ -45,7 +45,7 @@ class TestForwardMigrations(MigratorTestCase): for name in ['A', 'C', 'E']: part = Part.objects.get(name=name) - self.assertEqual(part.description, f"My part {name}") + self.assertEqual(part.description, f'My part {name}') class TestBomItemMigrations(MigratorTestCase): diff --git a/InvenTree/part/test_param.py b/InvenTree/part/test_param.py index 528dfda299..c6f6639353 100644 --- a/InvenTree/part/test_param.py +++ b/InvenTree/part/test_param.py @@ -181,7 +181,7 @@ class ParameterTests(TestCase): template2 = PartParameterTemplate.objects.create( name='My Template 2', units='%' ) - for value in ["1", "1%", "1 percent"]: + for value in ['1', '1%', '1 percent']: param = PartParameter(part=prt, template=template2, data=value) param.full_clean() diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index e74d2d08f5..30da66c7f5 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -180,7 +180,7 @@ class PartTest(TestCase): def test_str(self): """Test string representation of a Part""" p = Part.objects.get(pk=100) - self.assertEqual(str(p), "BOB | Bob | A2 - Can we build it? Yes we can!") + self.assertEqual(str(p), 'BOB | Bob | A2 - Can we build it? Yes we can!') def test_duplicate(self): """Test that we cannot create a "duplicate" Part.""" diff --git a/InvenTree/part/test_pricing.py b/InvenTree/part/test_pricing.py index 5d0aac2bf4..a434c3c233 100644 --- a/InvenTree/part/test_pricing.py +++ b/InvenTree/part/test_pricing.py @@ -258,8 +258,8 @@ class PartPricingTests(InvenTreeTestCase): for ii in range(10): # Create a new part for the BOM sub_part = part.models.Part.objects.create( - name=f"Sub Part {ii}", - description="A sub part for use in a BOM", + name=f'Sub Part {ii}', + description='A sub part for use in a BOM', component=True, assembly=False, ) @@ -403,7 +403,7 @@ class PartPricingTests(InvenTreeTestCase): # Create some parts for ii in range(100): part.models.Part.objects.create( - name=f"Part_{ii}", description="A test part" + name=f'Part_{ii}', description='A test part' ) # Ensure there is no pricing data @@ -424,7 +424,7 @@ class PartPricingTests(InvenTreeTestCase): but it pointed to a Part instance which was slated to be deleted inside an atomic transaction. """ p = part.models.Part.objects.create( - name="my part", description="my part description", active=False + name='my part', description='my part description', active=False ) # Create some stock items diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index fb116eb6d5..44993db71b 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -105,9 +105,9 @@ class PartImport(FileManagementFormView): 'part/import_wizard/match_references.html', ] form_steps_description = [ - _("Upload File"), - _("Match Fields"), - _("Match References"), + _('Upload File'), + _('Match Fields'), + _('Match References'), ] form_field_map = { @@ -540,8 +540,8 @@ class PartPricing(AjaxView): """View for inspecting part pricing information.""" model = Part - ajax_template_name = "part/part_pricing.html" - ajax_form_title = _("Part Pricing") + ajax_template_name = 'part/part_pricing.html' + ajax_form_title = _('Part Pricing') form_class = part_forms.PartPriceForm role_required = ['sales_order.view', 'part.view'] diff --git a/InvenTree/plugin/admin.py b/InvenTree/plugin/admin.py index 23bee2fcc5..102a599da9 100644 --- a/InvenTree/plugin/admin.py +++ b/InvenTree/plugin/admin.py @@ -49,7 +49,7 @@ class PluginSettingInline(admin.TabularInline): class PluginConfigAdmin(admin.ModelAdmin): """Custom admin with restricted id fields.""" - readonly_fields = ["key", "name"] + readonly_fields = ['key', 'name'] list_display = [ 'name', 'key', diff --git a/InvenTree/plugin/api.py b/InvenTree/plugin/api.py index e2fbf99a45..8f5d7de655 100644 --- a/InvenTree/plugin/api.py +++ b/InvenTree/plugin/api.py @@ -227,7 +227,7 @@ def check_plugin(plugin_slug: str, plugin_pk: int) -> InvenTreePlugin: """ # Make sure that a plugin reference is specified if plugin_slug is None and plugin_pk is None: - raise NotFound(detail="Plugin not specified") + raise NotFound(detail='Plugin not specified') # Define filter filter = {} @@ -342,13 +342,13 @@ class RegistryStatusView(APIView): for error_detail in errors: for name, message in error_detail.items(): error_list.append({ - "stage": stage, - "name": name, - "message": message, + 'stage': stage, + 'name': name, + 'message': message, }) result = PluginSerializers.PluginRegistryStatusSerializer({ - "registry_errors": error_list + 'registry_errors': error_list }).data return Response(result) @@ -382,7 +382,7 @@ plugin_api_urls = [ r'/', include([ re_path( - r"^settings/", + r'^settings/', include([ re_path( r'^(?P\w+)/', @@ -390,9 +390,9 @@ plugin_api_urls = [ name='api-plugin-setting-detail-pk', ), re_path( - r"^.*$", + r'^.*$', PluginAllSettingList.as_view(), - name="api-plugin-settings", + name='api-plugin-settings', ), ]), ), @@ -419,9 +419,9 @@ plugin_api_urls = [ ), # Registry status re_path( - r"^status/", + r'^status/', RegistryStatusView.as_view(), - name="api-plugin-registry-status", + name='api-plugin-registry-status', ), # Anything else re_path(r'^.*$', PluginList.as_view(), name='api-plugin-list'), diff --git a/InvenTree/plugin/apps.py b/InvenTree/plugin/apps.py index 24bc34ab1f..b617dcd1fe 100644 --- a/InvenTree/plugin/apps.py +++ b/InvenTree/plugin/apps.py @@ -28,7 +28,7 @@ class PluginAppConfig(AppConfig): return if not canAppAccessDatabase(allow_test=True, allow_plugins=True): - logger.info("Skipping plugin loading sequence") # pragma: no cover + logger.info('Skipping plugin loading sequence') # pragma: no cover else: logger.info('Loading InvenTree plugins') diff --git a/InvenTree/plugin/base/action/api.py b/InvenTree/plugin/base/action/api.py index d2ee877fff..37dabd9eda 100644 --- a/InvenTree/plugin/base/action/api.py +++ b/InvenTree/plugin/base/action/api.py @@ -21,7 +21,7 @@ class ActionPluginView(APIView): data = request.data.get('data', None) if action is None: - return Response({'error': _("No action specified")}) + return Response({'error': _('No action specified')}) action_plugins = registry.with_mixin('action') for plugin in action_plugins: @@ -30,4 +30,4 @@ class ActionPluginView(APIView): return Response(plugin.get_response(request.user, data=data)) # If we got to here, no matching action was found - return Response({'error': _("No matching action found"), "action": action}) + return Response({'error': _('No matching action found'), 'action': action}) diff --git a/InvenTree/plugin/base/action/mixins.py b/InvenTree/plugin/base/action/mixins.py index b978a69189..e4931c1012 100644 --- a/InvenTree/plugin/base/action/mixins.py +++ b/InvenTree/plugin/base/action/mixins.py @@ -4,7 +4,7 @@ class ActionMixin: """Mixin that enables custom actions.""" - ACTION_NAME = "" + ACTION_NAME = '' class MixinMeta: """Meta options for this mixin.""" @@ -47,7 +47,7 @@ class ActionMixin: Default implementation is a simple response which can be overridden. """ return { - "action": self.action_name(), - "result": self.get_result(user, data), - "info": self.get_info(user, data), + 'action': self.action_name(), + 'result': self.get_result(user, data), + 'info': self.get_info(user, data), } diff --git a/InvenTree/plugin/base/action/test_action.py b/InvenTree/plugin/base/action/test_action.py index 322779cf8b..c2bcf6e3ec 100644 --- a/InvenTree/plugin/base/action/test_action.py +++ b/InvenTree/plugin/base/action/test_action.py @@ -57,7 +57,7 @@ class ActionMixinTests(TestCase): self.assertEqual(self.plugin.get_result(), False) self.assertIsNone(self.plugin.get_info()) self.assertEqual( - self.plugin.get_response(), {"action": '', "result": False, "info": None} + self.plugin.get_response(), {'action': '', 'result': False, 'info': None} ) # overridden functions @@ -69,9 +69,9 @@ class ActionMixinTests(TestCase): self.assertEqual( self.action_plugin.get_response(), { - "action": 'abc123', - "result": self.ACTION_RETURN + 'result', - "info": self.ACTION_RETURN + 'info', + 'action': 'abc123', + 'result': self.ACTION_RETURN + 'result', + 'info': self.ACTION_RETURN + 'info', }, ) @@ -87,7 +87,7 @@ class APITests(InvenTreeTestCase): self.assertEqual(response.data, {'error': 'No action specified'}) # Test non-exsisting action - response = self.client.post('/api/action/', data={'action': "nonexsisting"}) + response = self.client.post('/api/action/', data={'action': 'nonexsisting'}) self.assertEqual(response.status_code, 200) self.assertEqual( response.data, diff --git a/InvenTree/plugin/base/barcodes/api.py b/InvenTree/plugin/base/barcodes/api.py index e6021b7ebf..3a829b9752 100644 --- a/InvenTree/plugin/base/barcodes/api.py +++ b/InvenTree/plugin/base/barcodes/api.py @@ -58,7 +58,7 @@ class BarcodeView(CreateAPIView): Any custom fields passed by the specific serializer """ raise NotImplementedError( - f"handle_barcode not implemented for {self.__class__}" + f'handle_barcode not implemented for {self.__class__}' ) def scan_barcode(self, barcode: str, request, **kwargs): @@ -79,11 +79,11 @@ class BarcodeView(CreateAPIView): if result is None: continue - if "error" in result: + if 'error' in result: logger.info( - "%s.scan(...) returned an error: %s", + '%s.scan(...) returned an error: %s', current_plugin.__class__.__name__, - result["error"], + result['error'], ) if not response: plugin = current_plugin @@ -155,9 +155,9 @@ class BarcodeAssign(BarcodeView): result = plugin.scan(barcode) if result is not None: - result["error"] = _("Barcode matches existing item") - result["plugin"] = plugin.name - result["barcode_data"] = barcode + result['error'] = _('Barcode matches existing item') + result['plugin'] = plugin.name + result['barcode_data'] = barcode raise ValidationError(result) @@ -174,20 +174,20 @@ class BarcodeAssign(BarcodeView): app_label = model._meta.app_label model_name = model._meta.model_name - table = f"{app_label}_{model_name}" + table = f'{app_label}_{model_name}' - if not RuleSet.check_table_permission(request.user, table, "change"): + if not RuleSet.check_table_permission(request.user, table, 'change'): raise PermissionDenied({ - "error": f"You do not have the required permissions for {table}" + 'error': f'You do not have the required permissions for {table}' }) instance.assign_barcode(barcode_data=barcode, barcode_hash=barcode_hash) return Response({ - 'success': f"Assigned barcode to {label} instance", + 'success': f'Assigned barcode to {label} instance', label: {'pk': instance.pk}, - "barcode_data": barcode, - "barcode_hash": barcode_hash, + 'barcode_data': barcode, + 'barcode_hash': barcode_hash, }) # If we got here, it means that no valid model types were provided @@ -238,11 +238,11 @@ class BarcodeUnassign(BarcodeView): app_label = model._meta.app_label model_name = model._meta.model_name - table = f"{app_label}_{model_name}" + table = f'{app_label}_{model_name}' - if not RuleSet.check_table_permission(request.user, table, "change"): + if not RuleSet.check_table_permission(request.user, table, 'change'): raise PermissionDenied({ - "error": f"You do not have the required permissions for {table}" + 'error': f'You do not have the required permissions for {table}' }) # Unassign the barcode data from the model instance @@ -313,11 +313,11 @@ class BarcodePOAllocate(BarcodeView): ) if supplier_parts.count() == 0: - raise ValidationError({"error": _("No matching supplier parts found")}) + raise ValidationError({'error': _('No matching supplier parts found')}) if supplier_parts.count() > 1: raise ValidationError({ - "error": _("Multiple matching supplier parts found") + 'error': _('Multiple matching supplier parts found') }) # At this stage, we have a single matching supplier part @@ -342,7 +342,7 @@ class BarcodePOAllocate(BarcodeView): manufacturer_part=result.get('manufacturerpart', None), ) - result['success'] = _("Matched supplier part") + result['success'] = _('Matched supplier part') result['supplierpart'] = supplier_part.format_matched_response() # TODO: Determine the 'quantity to order' for the supplier part @@ -379,24 +379,24 @@ class BarcodePOReceive(BarcodeView): purchase_order = kwargs.get('purchase_order', None) location = kwargs.get('location', None) - plugins = registry.with_mixin("barcode") + plugins = registry.with_mixin('barcode') # Look for a barcode plugin which knows how to deal with this barcode plugin = None - response = {"barcode_data": barcode, "barcode_hash": hash_barcode(barcode)} + response = {'barcode_data': barcode, 'barcode_hash': hash_barcode(barcode)} internal_barcode_plugin = next( - filter(lambda plugin: plugin.name == "InvenTreeBarcode", plugins) + filter(lambda plugin: plugin.name == 'InvenTreeBarcode', plugins) ) if result := internal_barcode_plugin.scan(barcode): if 'stockitem' in result: - response["error"] = _("Item has already been received") + response['error'] = _('Item has already been received') raise ValidationError(response) # Now, look just for "supplier-barcode" plugins - plugins = registry.with_mixin("supplier-barcode") + plugins = registry.with_mixin('supplier-barcode') plugin_response = None @@ -408,11 +408,11 @@ class BarcodePOReceive(BarcodeView): if result is None: continue - if "error" in result: + if 'error' in result: logger.info( - "%s.scan_receive_item(...) returned an error: %s", + '%s.scan_receive_item(...) returned an error: %s', current_plugin.__class__.__name__, - result["error"], + result['error'], ) if not plugin_response: plugin = current_plugin @@ -429,9 +429,9 @@ class BarcodePOReceive(BarcodeView): # A plugin has not been found! if plugin is None: - response["error"] = _("No match for supplier barcode") + response['error'] = _('No match for supplier barcode') raise ValidationError(response) - elif "error" in response: + elif 'error' in response: raise ValidationError(response) else: return Response(response) @@ -583,11 +583,11 @@ barcode_api_urls = [ # Unlink a third-party barcode from an item path('unlink/', BarcodeUnassign.as_view(), name='api-barcode-unlink'), # Receive a purchase order item by scanning its barcode - path("po-receive/", BarcodePOReceive.as_view(), name="api-barcode-po-receive"), + path('po-receive/', BarcodePOReceive.as_view(), name='api-barcode-po-receive'), # Allocate parts to a purchase order by scanning their barcode - path("po-allocate/", BarcodePOAllocate.as_view(), name="api-barcode-po-allocate"), + path('po-allocate/', BarcodePOAllocate.as_view(), name='api-barcode-po-allocate'), # Allocate stock to a sales order by scanning barcode - path("so-allocate/", BarcodeSOAllocate.as_view(), name="api-barcode-so-allocate"), + path('so-allocate/', BarcodeSOAllocate.as_view(), name='api-barcode-so-allocate'), # Catch-all performs barcode 'scan' re_path(r'^.*$', BarcodeScan.as_view(), name='api-barcode-scan'), ] diff --git a/InvenTree/plugin/base/barcodes/mixins.py b/InvenTree/plugin/base/barcodes/mixins.py index 7574d86abd..904b40c9f0 100644 --- a/InvenTree/plugin/base/barcodes/mixins.py +++ b/InvenTree/plugin/base/barcodes/mixins.py @@ -23,7 +23,7 @@ class BarcodeMixin: Custom barcode plugins should use and extend this mixin as necessary. """ - ACTION_NAME = "" + ACTION_NAME = '' class MixinMeta: """Meta options for this mixin.""" @@ -62,19 +62,19 @@ class SupplierBarcodeMixin(BarcodeMixin): """ # Set of standard field names which can be extracted from the barcode - CUSTOMER_ORDER_NUMBER = "customer_order_number" - SUPPLIER_ORDER_NUMBER = "supplier_order_number" - PACKING_LIST_NUMBER = "packing_list_number" - SHIP_DATE = "ship_date" - CUSTOMER_PART_NUMBER = "customer_part_number" - SUPPLIER_PART_NUMBER = "supplier_part_number" - PURCHASE_ORDER_LINE = "purchase_order_line" - QUANTITY = "quantity" - DATE_CODE = "date_code" - LOT_CODE = "lot_code" - COUNTRY_OF_ORIGIN = "country_of_origin" - MANUFACTURER = "manufacturer" - MANUFACTURER_PART_NUMBER = "manufacturer_part_number" + CUSTOMER_ORDER_NUMBER = 'customer_order_number' + SUPPLIER_ORDER_NUMBER = 'supplier_order_number' + PACKING_LIST_NUMBER = 'packing_list_number' + SHIP_DATE = 'ship_date' + CUSTOMER_PART_NUMBER = 'customer_part_number' + SUPPLIER_PART_NUMBER = 'supplier_part_number' + PURCHASE_ORDER_LINE = 'purchase_order_line' + QUANTITY = 'quantity' + DATE_CODE = 'date_code' + LOT_CODE = 'lot_code' + COUNTRY_OF_ORIGIN = 'country_of_origin' + MANUFACTURER = 'manufacturer' + MANUFACTURER_PART_NUMBER = 'manufacturer_part_number' def __init__(self): """Register mixin.""" @@ -83,7 +83,7 @@ class SupplierBarcodeMixin(BarcodeMixin): def get_field_value(self, key, backup_value=None): """Return the value of a barcode field.""" - fields = getattr(self, "barcode_fields", None) or {} + fields = getattr(self, 'barcode_fields', None) or {} return fields.get(key, backup_value) @@ -125,7 +125,7 @@ class SupplierBarcodeMixin(BarcodeMixin): """ raise NotImplementedError( - "extract_barcode_fields must be implemented by each plugin" + 'extract_barcode_fields must be implemented by each plugin' ) def scan(self, barcode_data): @@ -145,16 +145,16 @@ class SupplierBarcodeMixin(BarcodeMixin): ) if len(supplier_parts) > 1: - return {"error": _("Found multiple matching supplier parts for barcode")} + return {'error': _('Found multiple matching supplier parts for barcode')} elif not supplier_parts: return None supplier_part = supplier_parts[0] data = { - "pk": supplier_part.pk, - "api_url": f"{SupplierPart.get_api_url()}{supplier_part.pk}/", - "web_url": supplier_part.get_absolute_url(), + 'pk': supplier_part.pk, + 'api_url': f'{SupplierPart.get_api_url()}{supplier_part.pk}/', + 'web_url': supplier_part.get_absolute_url(), } return {SupplierPart.barcode_model_type(): data} @@ -178,7 +178,7 @@ class SupplierBarcodeMixin(BarcodeMixin): ) if len(supplier_parts) > 1: - return {"error": _("Found multiple matching supplier parts for barcode")} + return {'error': _('Found multiple matching supplier parts for barcode')} elif not supplier_parts: return None @@ -196,17 +196,17 @@ class SupplierBarcodeMixin(BarcodeMixin): if len(matching_orders) > 1: return { - "error": _(f"Found multiple purchase orders matching '{order}'") + 'error': _(f"Found multiple purchase orders matching '{order}'") } if len(matching_orders) == 0: - return {"error": _(f"No matching purchase order for '{order}'")} + return {'error': _(f"No matching purchase order for '{order}'")} purchase_order = matching_orders.first() if supplier and purchase_order: if purchase_order.supplier != supplier: - return {"error": _("Purchase order does not match supplier")} + return {'error': _('Purchase order does not match supplier')} return self.receive_purchase_order_item( supplier_part, @@ -226,17 +226,17 @@ class SupplierBarcodeMixin(BarcodeMixin): if not isinstance(self, SettingsMixin): return None - if supplier_pk := self.get_setting("SUPPLIER_ID"): + if supplier_pk := self.get_setting('SUPPLIER_ID'): if supplier := Company.objects.get(pk=supplier_pk): return supplier else: logger.error( - "No company with pk %d (set \"SUPPLIER_ID\" setting to a valid value)", + 'No company with pk %d (set "SUPPLIER_ID" setting to a valid value)', supplier_pk, ) return None - if not (supplier_name := getattr(self, "DEFAULT_SUPPLIER_NAME", None)): + if not (supplier_name := getattr(self, 'DEFAULT_SUPPLIER_NAME', None)): return None suppliers = Company.objects.filter( @@ -246,7 +246,7 @@ class SupplierBarcodeMixin(BarcodeMixin): if len(suppliers) != 1: return None - self.set_setting("SUPPLIER_ID", suppliers.first().pk) + self.set_setting('SUPPLIER_ID', suppliers.first().pk) return suppliers.first() @@ -260,21 +260,21 @@ class SupplierBarcodeMixin(BarcodeMixin): if it does not use the standard field names. """ return { - "K": cls.CUSTOMER_ORDER_NUMBER, - "1K": cls.SUPPLIER_ORDER_NUMBER, - "11K": cls.PACKING_LIST_NUMBER, - "6D": cls.SHIP_DATE, - "9D": cls.DATE_CODE, - "10D": cls.DATE_CODE, - "4K": cls.PURCHASE_ORDER_LINE, - "14K": cls.PURCHASE_ORDER_LINE, - "P": cls.SUPPLIER_PART_NUMBER, - "1P": cls.MANUFACTURER_PART_NUMBER, - "30P": cls.SUPPLIER_PART_NUMBER, - "1T": cls.LOT_CODE, - "4L": cls.COUNTRY_OF_ORIGIN, - "1V": cls.MANUFACTURER, - "Q": cls.QUANTITY, + 'K': cls.CUSTOMER_ORDER_NUMBER, + '1K': cls.SUPPLIER_ORDER_NUMBER, + '11K': cls.PACKING_LIST_NUMBER, + '6D': cls.SHIP_DATE, + '9D': cls.DATE_CODE, + '10D': cls.DATE_CODE, + '4K': cls.PURCHASE_ORDER_LINE, + '14K': cls.PURCHASE_ORDER_LINE, + 'P': cls.SUPPLIER_PART_NUMBER, + '1P': cls.MANUFACTURER_PART_NUMBER, + '30P': cls.SUPPLIER_PART_NUMBER, + '1T': cls.LOT_CODE, + '4L': cls.COUNTRY_OF_ORIGIN, + '1V': cls.MANUFACTURER, + 'Q': cls.QUANTITY, } @classmethod @@ -324,10 +324,10 @@ class SupplierBarcodeMixin(BarcodeMixin): def parse_isoiec_15434_barcode2d(barcode_data: str) -> list[str]: """Parse a ISO/IEC 15434 barcode, returning the split data section.""" - OLD_MOUSER_HEADER = ">[)>06\x1d" - HEADER = "[)>\x1e06\x1d" - TRAILER = "\x1e\x04" - DELIMITER = "\x1d" + OLD_MOUSER_HEADER = '>[)>06\x1d' + HEADER = '[)>\x1e06\x1d' + TRAILER = '\x1e\x04' + DELIMITER = '\x1d' # Some old mouser barcodes start with this messed up header if barcode_data.startswith(OLD_MOUSER_HEADER): @@ -419,7 +419,7 @@ class SupplierBarcodeMixin(BarcodeMixin): # find incomplete line_items that match the supplier_part line_items = purchase_order.lines.filter( - part=supplier_part.pk, quantity__gt=F("received") + part=supplier_part.pk, quantity__gt=F('received') ) if len(line_items) == 1 or not quantity: line_item = line_items[0] @@ -439,7 +439,7 @@ class SupplierBarcodeMixin(BarcodeMixin): line_item = line_items.first() if not line_item: - return {"error": _("Failed to find pending line item for supplier part")} + return {'error': _('Failed to find pending line item for supplier part')} no_stock_locations = False if not location: @@ -457,20 +457,20 @@ class SupplierBarcodeMixin(BarcodeMixin): no_stock_locations = True response = { - "lineitem": {"pk": line_item.pk, "purchase_order": purchase_order.pk} + 'lineitem': {'pk': line_item.pk, 'purchase_order': purchase_order.pk} } if quantity: - response["lineitem"]["quantity"] = quantity + response['lineitem']['quantity'] = quantity if location: - response["lineitem"]["location"] = location.pk + response['lineitem']['location'] = location.pk # if either the quantity is missing or no location is defined/found # -> return the line_item found, so the client can gather the missing # information and complete the action with an 'api-po-receive' call if not quantity or (not location and not no_stock_locations): - response["action_required"] = _( - "Further information required to receive line item" + response['action_required'] = _( + 'Further information required to receive line item' ) return response @@ -478,5 +478,5 @@ class SupplierBarcodeMixin(BarcodeMixin): line_item, location, quantity, user, barcode=barcode ) - response["success"] = _("Received purchase order line item") + response['success'] = _('Received purchase order line item') return response diff --git a/InvenTree/plugin/base/barcodes/serializers.py b/InvenTree/plugin/base/barcodes/serializers.py index 52c4f83ecb..9482969545 100644 --- a/InvenTree/plugin/base/barcodes/serializers.py +++ b/InvenTree/plugin/base/barcodes/serializers.py @@ -86,7 +86,7 @@ class BarcodePOAllocateSerializer(BarcodeSerializer): """Validate the provided order""" if order.status != PurchaseOrderStatus.PENDING.value: - raise ValidationError(_("Purchase order is not pending")) + raise ValidationError(_('Purchase order is not pending')) return order @@ -111,7 +111,7 @@ class BarcodePOReceiveSerializer(BarcodeSerializer): """Validate the provided order""" if order and order.status != PurchaseOrderStatus.PLACED.value: - raise ValidationError(_("Purchase order has not been placed")) + raise ValidationError(_('Purchase order has not been placed')) return order @@ -126,7 +126,7 @@ class BarcodePOReceiveSerializer(BarcodeSerializer): """Validate the provided location""" if location and location.structural: - raise ValidationError(_("Cannot select a structural location")) + raise ValidationError(_('Cannot select a structural location')) return location @@ -147,7 +147,7 @@ class BarcodeSOAllocateSerializer(BarcodeSerializer): """Validate the provided order""" if order and order.status != SalesOrderStatus.PENDING.value: - raise ValidationError(_("Sales order is not pending")) + raise ValidationError(_('Sales order is not pending')) return order @@ -169,7 +169,7 @@ class BarcodeSOAllocateSerializer(BarcodeSerializer): """Validate the provided shipment""" if shipment and shipment.is_delivered(): - raise ValidationError(_("Shipment has already been delivered")) + raise ValidationError(_('Shipment has already been delivered')) return shipment diff --git a/InvenTree/plugin/base/barcodes/test_barcode.py b/InvenTree/plugin/base/barcodes/test_barcode.py index c75229d279..3b5cedba6f 100644 --- a/InvenTree/plugin/base/barcodes/test_barcode.py +++ b/InvenTree/plugin/base/barcodes/test_barcode.py @@ -213,7 +213,7 @@ class BarcodeAPITest(InvenTreeAPITestCase): for k in invalid_keys: response = self.post(self.unassign_url, {k: 123}, expected_code=400) - self.assertIn("Missing data: Provide one of", str(response.data['error'])) + self.assertIn('Missing data: Provide one of', str(response.data['error'])) valid_keys = ['build', 'salesorder', 'part'] @@ -221,7 +221,7 @@ class BarcodeAPITest(InvenTreeAPITestCase): for k in valid_keys: response = self.post(self.unassign_url, {k: 999999999}, expected_code=400) - self.assertIn("object does not exist", str(response.data[k])) + self.assertIn('object does not exist', str(response.data[k])) class SOAllocateTest(InvenTreeAPITestCase): diff --git a/InvenTree/plugin/base/integration/APICallMixin.py b/InvenTree/plugin/base/integration/APICallMixin.py index 11bead3327..8be6218a32 100644 --- a/InvenTree/plugin/base/integration/APICallMixin.py +++ b/InvenTree/plugin/base/integration/APICallMixin.py @@ -76,9 +76,9 @@ class APICallMixin: def has_api_call(self): """Is the mixin ready to call external APIs?""" if not bool(self.API_URL_SETTING): - raise MixinNotImplementedError("API_URL_SETTING must be defined") + raise MixinNotImplementedError('API_URL_SETTING must be defined') if not bool(self.API_TOKEN_SETTING): - raise MixinNotImplementedError("API_TOKEN_SETTING must be defined") + raise MixinNotImplementedError('API_TOKEN_SETTING must be defined') return True @property @@ -99,7 +99,7 @@ class APICallMixin: if token: headers[self.API_TOKEN] = token - headers['Authorization'] = f"{self.API_TOKEN} {token}" + headers['Authorization'] = f'{self.API_TOKEN} {token}' return headers diff --git a/InvenTree/plugin/base/integration/CurrencyExchangeMixin.py b/InvenTree/plugin/base/integration/CurrencyExchangeMixin.py index 613b54d8ad..670e2efeb1 100644 --- a/InvenTree/plugin/base/integration/CurrencyExchangeMixin.py +++ b/InvenTree/plugin/base/integration/CurrencyExchangeMixin.py @@ -16,7 +16,7 @@ class CurrencyExchangeMixin: class MixinMeta: """Meta options for this mixin class""" - MIXIN_NAME = "CurrentExchange" + MIXIN_NAME = 'CurrentExchange' def __init__(self): """Register the mixin""" @@ -39,5 +39,5 @@ class CurrencyExchangeMixin: Can raise any exception if the update fails """ raise MixinNotImplementedError( - "Plugin must implement update_exchange_rates method" + 'Plugin must implement update_exchange_rates method' ) diff --git a/InvenTree/plugin/base/integration/ScheduleMixin.py b/InvenTree/plugin/base/integration/ScheduleMixin.py index edde7a2e4e..3b06c88315 100644 --- a/InvenTree/plugin/base/integration/ScheduleMixin.py +++ b/InvenTree/plugin/base/integration/ScheduleMixin.py @@ -76,7 +76,7 @@ class ScheduleMixin: task_keys += plugin.get_task_names() if len(task_keys) > 0: - logger.info("Activated %s scheduled tasks", len(task_keys)) + logger.info('Activated %s scheduled tasks', len(task_keys)) # Remove any scheduled tasks which do not match # This stops 'old' plugin tasks from accumulating @@ -84,7 +84,7 @@ class ScheduleMixin: from django_q.models import Schedule scheduled_plugin_tasks = Schedule.objects.filter( - name__istartswith="plugin." + name__istartswith='plugin.' ) deleted_count = 0 @@ -96,11 +96,11 @@ class ScheduleMixin: if deleted_count > 0: logger.info( - "Removed %s old scheduled tasks", deleted_count + 'Removed %s old scheduled tasks', deleted_count ) # pragma: no cover except (ProgrammingError, OperationalError): # Database might not yet be ready - logger.warning("activate_integration_schedule failed, database not ready") + logger.warning('activate_integration_schedule failed, database not ready') def get_scheduled_tasks(self): """Returns `SCHEDULED_TASKS` context. @@ -117,7 +117,7 @@ class ScheduleMixin: def validate_scheduled_tasks(self): """Check that the provided scheduled tasks are valid.""" if not self.has_scheduled_tasks: - raise MixinImplementationError("SCHEDULED_TASKS not defined") + raise MixinImplementationError('SCHEDULED_TASKS not defined') for key, task in self.scheduled_tasks.items(): if 'func' not in task: @@ -147,7 +147,7 @@ class ScheduleMixin: """Task name for key.""" # Generate a 'unique' task name slug = self.plugin_slug() - return f"plugin.{slug}.{key}" + return f'plugin.{slug}.{key}' def get_task_names(self): """All defined task names.""" @@ -194,7 +194,7 @@ class ScheduleMixin: except (ProgrammingError, OperationalError): # pragma: no cover # Database might not yet be ready - logger.warning("register_tasks failed, database not ready") + logger.warning('register_tasks failed, database not ready') def unregister_tasks(self): """Deregister the tasks with the database.""" @@ -211,4 +211,4 @@ class ScheduleMixin: pass except (ProgrammingError, OperationalError): # pragma: no cover # Database might not yet be ready - logger.warning("unregister_tasks failed, database not ready") + logger.warning('unregister_tasks failed, database not ready') diff --git a/InvenTree/plugin/base/integration/ValidationMixin.py b/InvenTree/plugin/base/integration/ValidationMixin.py index 0376984ace..cee5030b70 100644 --- a/InvenTree/plugin/base/integration/ValidationMixin.py +++ b/InvenTree/plugin/base/integration/ValidationMixin.py @@ -33,7 +33,7 @@ class ValidationMixin: class MixinMeta: """Metaclass for this mixin""" - MIXIN_NAME = "Validation" + MIXIN_NAME = 'Validation' def __init__(self): """Register the mixin""" diff --git a/InvenTree/plugin/base/integration/mixins.py b/InvenTree/plugin/base/integration/mixins.py index 53e234637e..0421472483 100644 --- a/InvenTree/plugin/base/integration/mixins.py +++ b/InvenTree/plugin/base/integration/mixins.py @@ -12,7 +12,7 @@ class NavigationMixin: """Mixin that enables custom navigation links with the plugin.""" NAVIGATION_TAB_NAME = None - NAVIGATION_TAB_ICON = "fas fa-question" + NAVIGATION_TAB_ICON = 'fas fa-question' class MixinMeta: """Meta options for this mixin.""" @@ -51,7 +51,7 @@ class NavigationMixin: @property def navigation_icon(self): """Icon-name for navigation tab.""" - return getattr(self, 'NAVIGATION_TAB_ICON', "fas fa-question") + return getattr(self, 'NAVIGATION_TAB_ICON', 'fas fa-question') class PanelMixin: @@ -175,7 +175,7 @@ class PanelMixin: if any(key not in panel for key in required_keys): logger.warning( - "Custom panel for plugin %s is missing a required parameter", + 'Custom panel for plugin %s is missing a required parameter', __class__, ) continue diff --git a/InvenTree/plugin/base/integration/test_mixins.py b/InvenTree/plugin/base/integration/test_mixins.py index 70bd7d41b3..e512fb9cea 100644 --- a/InvenTree/plugin/base/integration/test_mixins.py +++ b/InvenTree/plugin/base/integration/test_mixins.py @@ -206,7 +206,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): """Setup for all tests.""" class MixinCls(APICallMixin, SettingsMixin, InvenTreePlugin): - NAME = "Sample API Caller" + NAME = 'Sample API Caller' SETTINGS = { 'API_TOKEN': {'name': 'API Token', 'protected': True}, @@ -223,7 +223,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): @property def api_url(self): """Override API URL for this test""" - return "https://api.github.com" + return 'https://api.github.com' def get_external_url(self, simple: bool = True): """Returns data from the sample endpoint.""" @@ -289,7 +289,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): # api_call with post and data result = self.mixin.api_call( 'https://reqres.in/api/users/', - json={"name": "morpheus", "job": "leader"}, + json={'name': 'morpheus', 'job': 'leader'}, method='POST', endpoint_is_url=True, ) @@ -321,13 +321,13 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): # Too many data arguments with self.assertRaises(ValueError): self.mixin.api_call( - 'https://reqres.in/api/users/', json={"a": 1}, data={"a": 1} + 'https://reqres.in/api/users/', json={'a': 1}, data={'a': 1} ) # Sending a request with a wrong data format should result in 40 result = self.mixin.api_call( 'https://reqres.in/api/users/', - data={"name": "morpheus", "job": "leader"}, + data={'name': 'morpheus', 'job': 'leader'}, method='POST', endpoint_is_url=True, simple_response=False, diff --git a/InvenTree/plugin/base/label/mixins.py b/InvenTree/plugin/base/label/mixins.py index 4462526816..8c62b0b09f 100644 --- a/InvenTree/plugin/base/label/mixins.py +++ b/InvenTree/plugin/base/label/mixins.py @@ -186,7 +186,7 @@ class LabelPrintingMixin: A class instance of a DRF serializer class, by default this an instance of self.PrintingOptionsSerializer using the *args, **kwargs if existing for this plugin """ - serializer = getattr(self, "PrintingOptionsSerializer", None) + serializer = getattr(self, 'PrintingOptionsSerializer', None) if not serializer: return None diff --git a/InvenTree/plugin/base/label/test_label_mixin.py b/InvenTree/plugin/base/label/test_label_mixin.py index 17ae265fcd..ea9a3eacb6 100644 --- a/InvenTree/plugin/base/label/test_label_mixin.py +++ b/InvenTree/plugin/base/label/test_label_mixin.py @@ -46,7 +46,7 @@ class LabelMixinTests(InvenTreeAPITestCase): # Construct URL kwargs = {} if label: - kwargs["pk"] = label.pk + kwargs['pk'] = label.pk url = reverse(url_name, kwargs=kwargs) @@ -133,13 +133,13 @@ class LabelMixinTests(InvenTreeAPITestCase): # Non-exsisting plugin response = self.get(f'{url}123', expected_code=404) self.assertIn( - f'Plugin \'{plugin_ref}123\' not found', str(response.content, 'utf8') + f"Plugin '{plugin_ref}123' not found", str(response.content, 'utf8') ) # Inactive plugin response = self.get(url, expected_code=400) self.assertIn( - f'Plugin \'{plugin_ref}\' is not enabled', str(response.content, 'utf8') + f"Plugin '{plugin_ref}' is not enabled", str(response.content, 'utf8') ) # Active plugin @@ -194,27 +194,27 @@ class LabelMixinTests(InvenTreeAPITestCase): options = self.options( self.do_url(parts, plugin_ref, label), expected_code=200 ).json() - self.assertTrue("amount" in options["actions"]["POST"]) + self.assertTrue('amount' in options['actions']['POST']) plg = registry.get_plugin(plugin_ref) - with mock.patch.object(plg, "print_label") as print_label: + with mock.patch.object(plg, 'print_label') as print_label: # wrong value type res = self.post( self.do_url(parts, plugin_ref, label), - data={"amount": "-no-valid-int-"}, + data={'amount': '-no-valid-int-'}, expected_code=400, ).json() - self.assertTrue("amount" in res) + self.assertTrue('amount' in res) print_label.assert_not_called() # correct value type self.post( self.do_url(parts, plugin_ref, label), - data={"amount": 13}, + data={'amount': 13}, expected_code=200, ).json() self.assertEqual( - print_label.call_args.kwargs["printing_options"], {"amount": 13} + print_label.call_args.kwargs['printing_options'], {'amount': 13} ) def test_printing_endpoints(self): diff --git a/InvenTree/plugin/base/locate/api.py b/InvenTree/plugin/base/locate/api.py index 560d35e295..c3f921d010 100644 --- a/InvenTree/plugin/base/locate/api.py +++ b/InvenTree/plugin/base/locate/api.py @@ -37,7 +37,7 @@ class LocatePluginView(APIView): # StockLocation to identify location_pk = request.data.get('location', None) - data = {"success": "Identification plugin activated", "plugin": plugin} + data = {'success': 'Identification plugin activated', 'plugin': plugin} # StockItem takes priority if item_pk: diff --git a/InvenTree/plugin/base/locate/mixins.py b/InvenTree/plugin/base/locate/mixins.py index f04a8afd75..005e9a8fcd 100644 --- a/InvenTree/plugin/base/locate/mixins.py +++ b/InvenTree/plugin/base/locate/mixins.py @@ -26,7 +26,7 @@ class LocateMixin: class MixinMeta: """Meta for mixin.""" - MIXIN_NAME = "Locate" + MIXIN_NAME = 'Locate' def __init__(self): """Register the mixin.""" @@ -46,7 +46,7 @@ class LocateMixin: Note: A custom implementation could always change this behaviour """ - logger.info("LocateMixin: Attempting to locate StockItem pk=%s", item_pk) + logger.info('LocateMixin: Attempting to locate StockItem pk=%s', item_pk) from stock.models import StockItem @@ -57,7 +57,7 @@ class LocateMixin: self.locate_stock_location(item.location.pk) except StockItem.DoesNotExist: # pragma: no cover - logger.warning("LocateMixin: StockItem pk={item_pk} not found") + logger.warning('LocateMixin: StockItem pk={item_pk} not found') pass def locate_stock_location(self, location_pk): diff --git a/InvenTree/plugin/builtin/barcodes/inventree_barcode.py b/InvenTree/plugin/builtin/barcodes/inventree_barcode.py index 7b0e7a4f7f..83c7b48f17 100644 --- a/InvenTree/plugin/builtin/barcodes/inventree_barcode.py +++ b/InvenTree/plugin/builtin/barcodes/inventree_barcode.py @@ -21,11 +21,11 @@ from plugin.mixins import BarcodeMixin class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin): """Builtin BarcodePlugin for matching and generating internal barcodes.""" - NAME = "InvenTreeBarcode" - TITLE = _("InvenTree Barcodes") - DESCRIPTION = _("Provides native support for barcodes") - VERSION = "2.0.0" - AUTHOR = _("InvenTree contributors") + NAME = 'InvenTreeBarcode' + TITLE = _('InvenTree Barcodes') + DESCRIPTION = _('Provides native support for barcodes') + VERSION = '2.0.0' + AUTHOR = _('InvenTree contributors') @staticmethod def get_supported_barcode_models(): diff --git a/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py b/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py index eead5d6f36..388376f002 100644 --- a/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py +++ b/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py @@ -118,7 +118,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase): si = stock.models.StockItem.objects.get(pk=521) self.assertEqual(si.barcode_data, bc_data) - self.assertEqual(si.barcode_hash, "2f5dba5c83a360599ba7665b2a4131c6") + self.assertEqual(si.barcode_hash, '2f5dba5c83a360599ba7665b2a4131c6') # Now test that we cannot assign this barcode to something else response = self.assign( diff --git a/InvenTree/plugin/builtin/integration/core_notifications.py b/InvenTree/plugin/builtin/integration/core_notifications.py index b2e76f9bcd..e882562ba2 100644 --- a/InvenTree/plugin/builtin/integration/core_notifications.py +++ b/InvenTree/plugin/builtin/integration/core_notifications.py @@ -30,11 +30,11 @@ class InvenTreeCoreNotificationsPlugin( ): """Core notification methods for InvenTree.""" - NAME = "InvenTreeCoreNotificationsPlugin" - TITLE = _("InvenTree Notifications") + NAME = 'InvenTreeCoreNotificationsPlugin' + TITLE = _('InvenTree Notifications') AUTHOR = _('InvenTree contributors') DESCRIPTION = _('Integrated outgoing notification methods') - VERSION = "1.0.0" + VERSION = '1.0.0' SETTINGS = { 'ENABLE_NOTIFICATION_EMAILS': { @@ -145,28 +145,28 @@ class InvenTreeCoreNotificationsPlugin( 'text': str(self.context['message']), 'blocks': [ { - "type": "section", - "text": { - "type": "plain_text", - "text": str(self.context['name']), + 'type': 'section', + 'text': { + 'type': 'plain_text', + 'text': str(self.context['name']), }, }, { - "type": "section", - "text": { - "type": "mrkdwn", - "text": str(self.context['message']), + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': str(self.context['message']), }, - "accessory": { - "type": "button", - "text": { - "type": "plain_text", - "text": str(_("Open link")), - "emoji": True, + 'accessory': { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': str(_('Open link')), + 'emoji': True, }, - "value": f'{self.category}_{self.obj.pk}', - "url": self.context['link'], - "action_id": "button-action", + 'value': f'{self.category}_{self.obj.pk}', + 'url': self.context['link'], + 'action_id': 'button-action', }, }, ], diff --git a/InvenTree/plugin/builtin/integration/currency_exchange.py b/InvenTree/plugin/builtin/integration/currency_exchange.py index 383bc87566..7a57371ce4 100644 --- a/InvenTree/plugin/builtin/integration/currency_exchange.py +++ b/InvenTree/plugin/builtin/integration/currency_exchange.py @@ -16,12 +16,12 @@ class InvenTreeCurrencyExchange(APICallMixin, CurrencyExchangeMixin, InvenTreePl Fetches exchange rate information from frankfurter.app """ - NAME = "InvenTreeCurrencyExchange" - SLUG = "inventreecurrencyexchange" + NAME = 'InvenTreeCurrencyExchange' + SLUG = 'inventreecurrencyexchange' AUTHOR = _('InvenTree contributors') - TITLE = _("InvenTree Currency Exchange") - DESCRIPTION = _("Default currency exchange integration") - VERSION = "1.0.0" + TITLE = _('InvenTree Currency Exchange') + DESCRIPTION = _('Default currency exchange integration') + VERSION = '1.0.0' def update_exchange_rates(self, base_currency: str, symbols: list[str]) -> dict: """Request exchange rate data from external API""" @@ -37,7 +37,7 @@ class InvenTreeCurrencyExchange(APICallMixin, CurrencyExchangeMixin, InvenTreePl return rates logger.warning( - "Failed to update exchange rates from %s: Server returned status %s", + 'Failed to update exchange rates from %s: Server returned status %s', self.api_url, response.status_code, ) diff --git a/InvenTree/plugin/builtin/labels/inventree_label.py b/InvenTree/plugin/builtin/labels/inventree_label.py index 5ad9178b81..f4d18c0a73 100644 --- a/InvenTree/plugin/builtin/labels/inventree_label.py +++ b/InvenTree/plugin/builtin/labels/inventree_label.py @@ -16,11 +16,11 @@ class InvenTreeLabelPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlugin): which is made available for download. """ - NAME = "InvenTreeLabel" - TITLE = _("InvenTree PDF label printer") - DESCRIPTION = _("Provides native support for printing PDF labels") - VERSION = "1.0.0" - AUTHOR = _("InvenTree contributors") + NAME = 'InvenTreeLabel' + TITLE = _('InvenTree PDF label printer') + DESCRIPTION = _('Provides native support for printing PDF labels') + VERSION = '1.0.0' + AUTHOR = _('InvenTree contributors') BLOCKING_PRINT = True diff --git a/InvenTree/plugin/builtin/labels/label_sheet.py b/InvenTree/plugin/builtin/labels/label_sheet.py index c81065e9b0..1d16aca1b9 100644 --- a/InvenTree/plugin/builtin/labels/label_sheet.py +++ b/InvenTree/plugin/builtin/labels/label_sheet.py @@ -56,11 +56,11 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug and returns the resulting PDF file. """ - NAME = "InvenTreeLabelSheet" - TITLE = _("InvenTree Label Sheet Printer") - DESCRIPTION = _("Arrays multiple labels onto a single sheet") - VERSION = "1.0.0" - AUTHOR = _("InvenTree contributors") + NAME = 'InvenTreeLabelSheet' + TITLE = _('InvenTree Label Sheet Printer') + DESCRIPTION = _('Arrays multiple labels onto a single sheet') + VERSION = '1.0.0' + AUTHOR = _('InvenTree contributors') BLOCKING_PRINT = True @@ -92,7 +92,7 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug n_cells = n_cols * n_rows if n_cells == 0: - raise ValidationError(_("Label is too large for page size")) + raise ValidationError(_('Label is too large for page size')) # Prepend the required number of skipped null labels items = [None] * skip + list(items) @@ -101,16 +101,16 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug # Data to pass through to each page document_data = { - "border": border, - "landscape": landscape, - "page_width": page_width, - "page_height": page_height, - "label_width": label.width, - "label_height": label.height, - "n_labels": n_labels, - "n_pages": math.ceil(n_labels / n_cells), - "n_cols": n_cols, - "n_rows": n_rows, + 'border': border, + 'landscape': landscape, + 'page_width': page_width, + 'page_height': page_height, + 'label_width': label.width, + 'label_height': label.height, + 'n_labels': n_labels, + 'n_pages': math.ceil(n_labels / n_cells), + 'n_cols': n_cols, + 'n_rows': n_rows, } pages = [] @@ -126,7 +126,7 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug idx += n_cells if len(pages) == 0: - raise ValidationError(_("No labels were generated")) + raise ValidationError(_('No labels were generated')) # Render to a single HTML document html_data = self.wrap_pages(pages, **document_data) @@ -191,16 +191,16 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug ) html += cell except Exception as exc: - logger.exception("Error rendering label: %s", str(exc)) + logger.exception('Error rendering label: %s', str(exc)) html += """
""" - html += "" + html += '' - html += "" + html += '' - html += "" + html += '' return html @@ -241,7 +241,7 @@ class InvenTreeLabelSheetPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlug """ ) - cell_styles = "\n".join(cell_styles) + cell_styles = '\n'.join(cell_styles) return f""" diff --git a/InvenTree/plugin/builtin/suppliers/digikey.py b/InvenTree/plugin/builtin/suppliers/digikey.py index bf8da46dd1..c441f9a9d9 100644 --- a/InvenTree/plugin/builtin/suppliers/digikey.py +++ b/InvenTree/plugin/builtin/suppliers/digikey.py @@ -12,19 +12,19 @@ from plugin.mixins import SettingsMixin, SupplierBarcodeMixin class DigiKeyPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): """Plugin to integrate the DigiKey API into Inventree.""" - NAME = "DigiKeyPlugin" - TITLE = _("Supplier Integration - DigiKey") - DESCRIPTION = _("Provides support for scanning DigiKey barcodes") - VERSION = "1.0.0" - AUTHOR = _("InvenTree contributors") + NAME = 'DigiKeyPlugin' + TITLE = _('Supplier Integration - DigiKey') + DESCRIPTION = _('Provides support for scanning DigiKey barcodes') + VERSION = '1.0.0' + AUTHOR = _('InvenTree contributors') - DEFAULT_SUPPLIER_NAME = "DigiKey" + DEFAULT_SUPPLIER_NAME = 'DigiKey' SETTINGS = { - "SUPPLIER_ID": { - "name": _("Supplier"), - "description": _("The Supplier which acts as 'DigiKey'"), - "model": "company.company", + 'SUPPLIER_ID': { + 'name': _('Supplier'), + 'description': _("The Supplier which acts as 'DigiKey'"), + 'model': 'company.company', } } diff --git a/InvenTree/plugin/builtin/suppliers/lcsc.py b/InvenTree/plugin/builtin/suppliers/lcsc.py index f47e7ac54b..ec4d688820 100644 --- a/InvenTree/plugin/builtin/suppliers/lcsc.py +++ b/InvenTree/plugin/builtin/suppliers/lcsc.py @@ -14,29 +14,29 @@ from plugin.mixins import SettingsMixin, SupplierBarcodeMixin class LCSCPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): """Plugin to integrate the LCSC API into Inventree.""" - NAME = "LCSCPlugin" - TITLE = _("Supplier Integration - LCSC") - DESCRIPTION = _("Provides support for scanning LCSC barcodes") - VERSION = "1.0.0" - AUTHOR = _("InvenTree contributors") + NAME = 'LCSCPlugin' + TITLE = _('Supplier Integration - LCSC') + DESCRIPTION = _('Provides support for scanning LCSC barcodes') + VERSION = '1.0.0' + AUTHOR = _('InvenTree contributors') - DEFAULT_SUPPLIER_NAME = "LCSC" + DEFAULT_SUPPLIER_NAME = 'LCSC' SETTINGS = { - "SUPPLIER_ID": { - "name": _("Supplier"), - "description": _("The Supplier which acts as 'LCSC'"), - "model": "company.company", + 'SUPPLIER_ID': { + 'name': _('Supplier'), + 'description': _("The Supplier which acts as 'LCSC'"), + 'model': 'company.company', } } - LCSC_BARCODE_REGEX = re.compile(r"^{((?:[^:,]+:[^:,]*,)*(?:[^:,]+:[^:,]*))}$") + LCSC_BARCODE_REGEX = re.compile(r'^{((?:[^:,]+:[^:,]*,)*(?:[^:,]+:[^:,]*))}$') # Custom field mapping for LCSC barcodes LCSC_FIELDS = { - "pm": SupplierBarcodeMixin.MANUFACTURER_PART_NUMBER, - "pc": SupplierBarcodeMixin.SUPPLIER_PART_NUMBER, - "qty": SupplierBarcodeMixin.QUANTITY, - "on": SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER, + 'pm': SupplierBarcodeMixin.MANUFACTURER_PART_NUMBER, + 'pc': SupplierBarcodeMixin.SUPPLIER_PART_NUMBER, + 'qty': SupplierBarcodeMixin.QUANTITY, + 'on': SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER, } def extract_barcode_fields(self, barcode_data: str) -> dict[str, str]: @@ -53,7 +53,7 @@ class LCSCPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): barcode_data, delimiter=',', header='{', trailer='}' ) - fields = dict(pair.split(":") for pair in fields) + fields = dict(pair.split(':') for pair in fields) barcode_fields = {} diff --git a/InvenTree/plugin/builtin/suppliers/mouser.py b/InvenTree/plugin/builtin/suppliers/mouser.py index 6b75f85535..fda6421ac8 100644 --- a/InvenTree/plugin/builtin/suppliers/mouser.py +++ b/InvenTree/plugin/builtin/suppliers/mouser.py @@ -12,18 +12,18 @@ from plugin.mixins import SettingsMixin, SupplierBarcodeMixin class MouserPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): """Plugin to integrate the Mouser API into Inventree.""" - NAME = "MouserPlugin" - TITLE = _("Supplier Integration - Mouser") - DESCRIPTION = _("Provides support for scanning Mouser barcodes") - VERSION = "1.0.0" - AUTHOR = _("InvenTree contributors") + NAME = 'MouserPlugin' + TITLE = _('Supplier Integration - Mouser') + DESCRIPTION = _('Provides support for scanning Mouser barcodes') + VERSION = '1.0.0' + AUTHOR = _('InvenTree contributors') - DEFAULT_SUPPLIER_NAME = "Mouser" + DEFAULT_SUPPLIER_NAME = 'Mouser' SETTINGS = { - "SUPPLIER_ID": { - "name": _("Supplier"), - "description": _("The Supplier which acts as 'Mouser'"), - "model": "company.company", + 'SUPPLIER_ID': { + 'name': _('Supplier'), + 'description': _("The Supplier which acts as 'Mouser'"), + 'model': 'company.company', } } diff --git a/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py b/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py index f6491eec6b..afd1dea435 100644 --- a/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py +++ b/InvenTree/plugin/builtin/suppliers/test_supplier_barcodes.py @@ -12,35 +12,35 @@ from stock.models import StockItem, StockLocation class SupplierBarcodeTests(InvenTreeAPITestCase): """Tests barcode parsing for all suppliers.""" - SCAN_URL = reverse("api-barcode-scan") + SCAN_URL = reverse('api-barcode-scan') @classmethod def setUpTestData(cls): """Create supplier parts for barcodes.""" super().setUpTestData() - part = Part.objects.create(name="Test Part", description="Test Part") + part = Part.objects.create(name='Test Part', description='Test Part') manufacturer = Company.objects.create( - name="Test Manufacturer", is_manufacturer=True + name='Test Manufacturer', is_manufacturer=True ) mpart1 = ManufacturerPart.objects.create( - part=part, manufacturer=manufacturer, MPN="MC34063ADR" + part=part, manufacturer=manufacturer, MPN='MC34063ADR' ) mpart2 = ManufacturerPart.objects.create( - part=part, manufacturer=manufacturer, MPN="LDK320ADU33R" + part=part, manufacturer=manufacturer, MPN='LDK320ADU33R' ) - supplier = Company.objects.create(name="Supplier", is_supplier=True) - mouser = Company.objects.create(name="Mouser Test", is_supplier=True) + supplier = Company.objects.create(name='Supplier', is_supplier=True) + mouser = Company.objects.create(name='Mouser Test', is_supplier=True) supplier_parts = [ - SupplierPart(SKU="296-LM358BIDDFRCT-ND", part=part, supplier=supplier), - SupplierPart(SKU="1", part=part, manufacturer_part=mpart1, supplier=mouser), - SupplierPart(SKU="2", part=part, manufacturer_part=mpart2, supplier=mouser), - SupplierPart(SKU="C312270", part=part, supplier=supplier), - SupplierPart(SKU="WBP-302", part=part, supplier=supplier), + SupplierPart(SKU='296-LM358BIDDFRCT-ND', part=part, supplier=supplier), + SupplierPart(SKU='1', part=part, manufacturer_part=mpart1, supplier=mouser), + SupplierPart(SKU='2', part=part, manufacturer_part=mpart2, supplier=mouser), + SupplierPart(SKU='C312270', part=part, supplier=supplier), + SupplierPart(SKU='WBP-302', part=part, supplier=supplier), ] SupplierPart.objects.bulk_create(supplier_parts) @@ -49,100 +49,100 @@ class SupplierBarcodeTests(InvenTreeAPITestCase): """Test digikey barcode""" result = self.post( - self.SCAN_URL, data={"barcode": DIGIKEY_BARCODE}, expected_code=200 + self.SCAN_URL, data={'barcode': DIGIKEY_BARCODE}, expected_code=200 ) self.assertEqual(result.data['plugin'], 'DigiKeyPlugin') - supplier_part_data = result.data.get("supplierpart") + supplier_part_data = result.data.get('supplierpart') self.assertIn('pk', supplier_part_data) - supplier_part = SupplierPart.objects.get(pk=supplier_part_data["pk"]) - self.assertEqual(supplier_part.SKU, "296-LM358BIDDFRCT-ND") + supplier_part = SupplierPart.objects.get(pk=supplier_part_data['pk']) + self.assertEqual(supplier_part.SKU, '296-LM358BIDDFRCT-ND') def test_digikey_2_barcode(self): """Test digikey barcode which uses 30P instead of P""" result = self.post( - self.SCAN_URL, data={"barcode": DIGIKEY_BARCODE_2}, expected_code=200 + self.SCAN_URL, data={'barcode': DIGIKEY_BARCODE_2}, expected_code=200 ) self.assertEqual(result.data['plugin'], 'DigiKeyPlugin') - supplier_part_data = result.data.get("supplierpart") + supplier_part_data = result.data.get('supplierpart') self.assertIn('pk', supplier_part_data) - supplier_part = SupplierPart.objects.get(pk=supplier_part_data["pk"]) - self.assertEqual(supplier_part.SKU, "296-LM358BIDDFRCT-ND") + supplier_part = SupplierPart.objects.get(pk=supplier_part_data['pk']) + self.assertEqual(supplier_part.SKU, '296-LM358BIDDFRCT-ND') def test_digikey_3_barcode(self): """Test digikey barcode which is invalid""" - self.post(self.SCAN_URL, data={"barcode": DIGIKEY_BARCODE_3}, expected_code=400) + self.post(self.SCAN_URL, data={'barcode': DIGIKEY_BARCODE_3}, expected_code=400) def test_mouser_barcode(self): """Test mouser barcode with custom order number.""" result = self.post( - self.SCAN_URL, data={"barcode": MOUSER_BARCODE}, expected_code=200 + self.SCAN_URL, data={'barcode': MOUSER_BARCODE}, expected_code=200 ) - supplier_part_data = result.data.get("supplierpart") + supplier_part_data = result.data.get('supplierpart') self.assertIn('pk', supplier_part_data) - supplier_part = SupplierPart.objects.get(pk=supplier_part_data["pk"]) + supplier_part = SupplierPart.objects.get(pk=supplier_part_data['pk']) self.assertEqual(supplier_part.SKU, '1') def test_old_mouser_barcode(self): """Test old mouser barcode with messed up header.""" result = self.post( - self.SCAN_URL, data={"barcode": MOUSER_BARCODE_OLD}, expected_code=200 + self.SCAN_URL, data={'barcode': MOUSER_BARCODE_OLD}, expected_code=200 ) - supplier_part_data = result.data.get("supplierpart") + supplier_part_data = result.data.get('supplierpart') self.assertIn('pk', supplier_part_data) - supplier_part = SupplierPart.objects.get(pk=supplier_part_data["pk"]) + supplier_part = SupplierPart.objects.get(pk=supplier_part_data['pk']) self.assertEqual(supplier_part.SKU, '2') def test_lcsc_barcode(self): """Test LCSC barcode.""" result = self.post( - self.SCAN_URL, data={"barcode": LCSC_BARCODE}, expected_code=200 + self.SCAN_URL, data={'barcode': LCSC_BARCODE}, expected_code=200 ) self.assertEqual(result.data['plugin'], 'LCSCPlugin') - supplier_part_data = result.data.get("supplierpart") + supplier_part_data = result.data.get('supplierpart') self.assertIn('pk', supplier_part_data) - supplier_part = SupplierPart.objects.get(pk=supplier_part_data["pk"]) + supplier_part = SupplierPart.objects.get(pk=supplier_part_data['pk']) self.assertEqual(supplier_part.SKU, 'C312270') def test_tme_qrcode(self): """Test TME QR-Code.""" result = self.post( - self.SCAN_URL, data={"barcode": TME_QRCODE}, expected_code=200 + self.SCAN_URL, data={'barcode': TME_QRCODE}, expected_code=200 ) self.assertEqual(result.data['plugin'], 'TMEPlugin') - supplier_part_data = result.data.get("supplierpart") + supplier_part_data = result.data.get('supplierpart') self.assertIn('pk', supplier_part_data) - supplier_part = SupplierPart.objects.get(pk=supplier_part_data["pk"]) + supplier_part = SupplierPart.objects.get(pk=supplier_part_data['pk']) self.assertEqual(supplier_part.SKU, 'WBP-302') def test_tme_barcode2d(self): """Test TME DataMatrix-Code.""" result = self.post( - self.SCAN_URL, data={"barcode": TME_DATAMATRIX_CODE}, expected_code=200 + self.SCAN_URL, data={'barcode': TME_DATAMATRIX_CODE}, expected_code=200 ) self.assertEqual(result.data['plugin'], 'TMEPlugin') - supplier_part_data = result.data.get("supplierpart") + supplier_part_data = result.data.get('supplierpart') self.assertIn('pk', supplier_part_data) - supplier_part = SupplierPart.objects.get(pk=supplier_part_data["pk"]) + supplier_part = SupplierPart.objects.get(pk=supplier_part_data['pk']) self.assertEqual(supplier_part.SKU, 'WBP-302') @@ -153,27 +153,27 @@ class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase): """Create supplier part and purchase_order.""" super().setUp() - part = Part.objects.create(name="Test Part", description="Test Part") - supplier = Company.objects.create(name="Supplier", is_supplier=True) + part = Part.objects.create(name='Test Part', description='Test Part') + supplier = Company.objects.create(name='Supplier', is_supplier=True) manufacturer = Company.objects.create( - name="Test Manufacturer", is_manufacturer=True + name='Test Manufacturer', is_manufacturer=True ) - mouser = Company.objects.create(name="Mouser Test", is_supplier=True) + mouser = Company.objects.create(name='Mouser Test', is_supplier=True) mpart = ManufacturerPart.objects.create( - part=part, manufacturer=manufacturer, MPN="MC34063ADR" + part=part, manufacturer=manufacturer, MPN='MC34063ADR' ) self.purchase_order1 = PurchaseOrder.objects.create( - supplier_reference="72991337", supplier=supplier + supplier_reference='72991337', supplier=supplier ) supplier_parts1 = [ - SupplierPart(SKU=f"1_{i}", part=part, supplier=supplier) for i in range(6) + SupplierPart(SKU=f'1_{i}', part=part, supplier=supplier) for i in range(6) ] supplier_parts1.insert( - 2, SupplierPart(SKU="296-LM358BIDDFRCT-ND", part=part, supplier=supplier) + 2, SupplierPart(SKU='296-LM358BIDDFRCT-ND', part=part, supplier=supplier) ) for supplier_part in supplier_parts1: @@ -181,17 +181,17 @@ class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase): self.purchase_order1.add_line_item(supplier_part, 8) self.purchase_order2 = PurchaseOrder.objects.create( - reference="P0-1337", supplier=mouser + reference='P0-1337', supplier=mouser ) self.purchase_order2.place_order() supplier_parts2 = [ - SupplierPart(SKU=f"2_{i}", part=part, supplier=mouser) for i in range(6) + SupplierPart(SKU=f'2_{i}', part=part, supplier=mouser) for i in range(6) ] supplier_parts2.insert( 3, - SupplierPart(SKU="42", part=part, manufacturer_part=mpart, supplier=mouser), + SupplierPart(SKU='42', part=part, manufacturer_part=mpart, supplier=mouser), ) for supplier_part in supplier_parts2: @@ -201,185 +201,185 @@ class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase): def test_receive(self): """Test receiving an item from a barcode.""" - url = reverse("api-barcode-po-receive") + url = reverse('api-barcode-po-receive') - result1 = self.post(url, data={"barcode": DIGIKEY_BARCODE}, expected_code=400) + result1 = self.post(url, data={'barcode': DIGIKEY_BARCODE}, expected_code=400) - assert result1.data["error"].startswith("No matching purchase order") + assert result1.data['error'].startswith('No matching purchase order') self.purchase_order1.place_order() - result2 = self.post(url, data={"barcode": DIGIKEY_BARCODE}, expected_code=200) - self.assertIn("success", result2.data) + result2 = self.post(url, data={'barcode': DIGIKEY_BARCODE}, expected_code=200) + self.assertIn('success', result2.data) - result3 = self.post(url, data={"barcode": DIGIKEY_BARCODE}, expected_code=400) - self.assertEqual(result3.data['error'], "Item has already been received") + result3 = self.post(url, data={'barcode': DIGIKEY_BARCODE}, expected_code=400) + self.assertEqual(result3.data['error'], 'Item has already been received') result4 = self.post( - url, data={"barcode": DIGIKEY_BARCODE[:-1]}, expected_code=400 + url, data={'barcode': DIGIKEY_BARCODE[:-1]}, expected_code=400 ) - assert result4.data["error"].startswith( - "Failed to find pending line item for supplier part" + assert result4.data['error'].startswith( + 'Failed to find pending line item for supplier part' ) result5 = self.post( - reverse("api-barcode-scan"), - data={"barcode": DIGIKEY_BARCODE}, + reverse('api-barcode-scan'), + data={'barcode': DIGIKEY_BARCODE}, expected_code=200, ) - stock_item = StockItem.objects.get(pk=result5.data["stockitem"]["pk"]) - assert stock_item.supplier_part.SKU == "296-LM358BIDDFRCT-ND" + stock_item = StockItem.objects.get(pk=result5.data['stockitem']['pk']) + assert stock_item.supplier_part.SKU == '296-LM358BIDDFRCT-ND' assert stock_item.quantity == 10 assert stock_item.location is None def test_receive_custom_order_number(self): """Test receiving an item from a barcode with a custom order number.""" - url = reverse("api-barcode-po-receive") - result1 = self.post(url, data={"barcode": MOUSER_BARCODE}) - assert "success" in result1.data + url = reverse('api-barcode-po-receive') + result1 = self.post(url, data={'barcode': MOUSER_BARCODE}) + assert 'success' in result1.data result2 = self.post( - reverse("api-barcode-scan"), data={"barcode": MOUSER_BARCODE} + reverse('api-barcode-scan'), data={'barcode': MOUSER_BARCODE} ) - stock_item = StockItem.objects.get(pk=result2.data["stockitem"]["pk"]) - assert stock_item.supplier_part.SKU == "42" - assert stock_item.supplier_part.manufacturer_part.MPN == "MC34063ADR" + stock_item = StockItem.objects.get(pk=result2.data['stockitem']['pk']) + assert stock_item.supplier_part.SKU == '42' + assert stock_item.supplier_part.manufacturer_part.MPN == 'MC34063ADR' assert stock_item.quantity == 3 assert stock_item.location is None def test_receive_one_stock_location(self): """Test receiving an item when only one stock location exists""" - stock_location = StockLocation.objects.create(name="Test Location") + stock_location = StockLocation.objects.create(name='Test Location') - url = reverse("api-barcode-po-receive") - result1 = self.post(url, data={"barcode": MOUSER_BARCODE}) - assert "success" in result1.data + url = reverse('api-barcode-po-receive') + result1 = self.post(url, data={'barcode': MOUSER_BARCODE}) + assert 'success' in result1.data result2 = self.post( - reverse("api-barcode-scan"), data={"barcode": MOUSER_BARCODE} + reverse('api-barcode-scan'), data={'barcode': MOUSER_BARCODE} ) - stock_item = StockItem.objects.get(pk=result2.data["stockitem"]["pk"]) + stock_item = StockItem.objects.get(pk=result2.data['stockitem']['pk']) assert stock_item.location == stock_location def test_receive_default_line_item_location(self): """Test receiving an item into the default line_item location""" - StockLocation.objects.create(name="Test Location 1") - stock_location2 = StockLocation.objects.create(name="Test Location 2") + StockLocation.objects.create(name='Test Location 1') + stock_location2 = StockLocation.objects.create(name='Test Location 2') - line_item = PurchaseOrderLineItem.objects.filter(part__SKU="42")[0] + line_item = PurchaseOrderLineItem.objects.filter(part__SKU='42')[0] line_item.destination = stock_location2 line_item.save() - url = reverse("api-barcode-po-receive") - result1 = self.post(url, data={"barcode": MOUSER_BARCODE}) - assert "success" in result1.data + url = reverse('api-barcode-po-receive') + result1 = self.post(url, data={'barcode': MOUSER_BARCODE}) + assert 'success' in result1.data result2 = self.post( - reverse("api-barcode-scan"), data={"barcode": MOUSER_BARCODE} + reverse('api-barcode-scan'), data={'barcode': MOUSER_BARCODE} ) - stock_item = StockItem.objects.get(pk=result2.data["stockitem"]["pk"]) + stock_item = StockItem.objects.get(pk=result2.data['stockitem']['pk']) assert stock_item.location == stock_location2 def test_receive_default_part_location(self): """Test receiving an item into the default part location""" - StockLocation.objects.create(name="Test Location 1") - stock_location2 = StockLocation.objects.create(name="Test Location 2") + StockLocation.objects.create(name='Test Location 1') + stock_location2 = StockLocation.objects.create(name='Test Location 2') part = Part.objects.all()[0] part.default_location = stock_location2 part.save() - url = reverse("api-barcode-po-receive") - result1 = self.post(url, data={"barcode": MOUSER_BARCODE}) - assert "success" in result1.data + url = reverse('api-barcode-po-receive') + result1 = self.post(url, data={'barcode': MOUSER_BARCODE}) + assert 'success' in result1.data result2 = self.post( - reverse("api-barcode-scan"), data={"barcode": MOUSER_BARCODE} + reverse('api-barcode-scan'), data={'barcode': MOUSER_BARCODE} ) - stock_item = StockItem.objects.get(pk=result2.data["stockitem"]["pk"]) + stock_item = StockItem.objects.get(pk=result2.data['stockitem']['pk']) assert stock_item.location == stock_location2 def test_receive_specific_order_and_location(self): """Test receiving an item from a specific order into a specific location""" - StockLocation.objects.create(name="Test Location 1") - stock_location2 = StockLocation.objects.create(name="Test Location 2") + StockLocation.objects.create(name='Test Location 1') + stock_location2 = StockLocation.objects.create(name='Test Location 2') - url = reverse("api-barcode-po-receive") - barcode = MOUSER_BARCODE.replace("\x1dKP0-1337", "") + url = reverse('api-barcode-po-receive') + barcode = MOUSER_BARCODE.replace('\x1dKP0-1337', '') result1 = self.post( url, data={ - "barcode": barcode, - "purchase_order": self.purchase_order2.pk, - "location": stock_location2.pk, + 'barcode': barcode, + 'purchase_order': self.purchase_order2.pk, + 'location': stock_location2.pk, }, ) - assert "success" in result1.data + assert 'success' in result1.data - result2 = self.post(reverse("api-barcode-scan"), data={"barcode": barcode}) - stock_item = StockItem.objects.get(pk=result2.data["stockitem"]["pk"]) + result2 = self.post(reverse('api-barcode-scan'), data={'barcode': barcode}) + stock_item = StockItem.objects.get(pk=result2.data['stockitem']['pk']) assert stock_item.location == stock_location2 def test_receive_missing_quantity(self): """Test receiving an with missing quantity information""" - url = reverse("api-barcode-po-receive") - barcode = MOUSER_BARCODE.replace("\x1dQ3", "") - response = self.post(url, data={"barcode": barcode}, expected_code=200) + url = reverse('api-barcode-po-receive') + barcode = MOUSER_BARCODE.replace('\x1dQ3', '') + response = self.post(url, data={'barcode': barcode}, expected_code=200) - assert "lineitem" in response.data - assert "quantity" not in response.data["lineitem"] + assert 'lineitem' in response.data + assert 'quantity' not in response.data['lineitem'] DIGIKEY_BARCODE = ( - "[)>\x1e06\x1dP296-LM358BIDDFRCT-ND\x1d1PLM358BIDDFR\x1dK\x1d1K72991337\x1d" - "10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK\x1d12Z15221337\x1d13Z361337" - "\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000" - "00000000000000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000" + '[)>\x1e06\x1dP296-LM358BIDDFRCT-ND\x1d1PLM358BIDDFR\x1dK\x1d1K72991337\x1d' + '10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK\x1d12Z15221337\x1d13Z361337' + '\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000' + '00000000000000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000' ) # Uses 30P instead of P DIGIKEY_BARCODE_2 = ( - "[)>\x1e06\x1d30P296-LM358BIDDFRCT-ND\x1dK\x1d1K72991337\x1d" - "10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK\x1d12Z15221337\x1d13Z361337" - "\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000" - "00000000000000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000" + '[)>\x1e06\x1d30P296-LM358BIDDFRCT-ND\x1dK\x1d1K72991337\x1d' + '10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK\x1d12Z15221337\x1d13Z361337' + '\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000' + '00000000000000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000' ) # Invalid code DIGIKEY_BARCODE_3 = ( - "[)>\x1e06\x1dPnonsense\x1d30Pnonsense\x1d1Pnonsense\x1dK\x1d1K72991337\x1d" - "10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK\x1d12Z15221337\x1d13Z361337" - "\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000" - "00000000000000000000000000000000000000000000000000000000000000000000000000" - "0000000000000000000000000000000000" + '[)>\x1e06\x1dPnonsense\x1d30Pnonsense\x1d1Pnonsense\x1dK\x1d1K72991337\x1d' + '10K85781337\x1d11K1\x1d4LPH\x1dQ10\x1d11ZPICK\x1d12Z15221337\x1d13Z361337' + '\x1d20Z0000000000000000000000000000000000000000000000000000000000000000000' + '00000000000000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000' ) MOUSER_BARCODE = ( - "[)>\x1e06\x1dKP0-1337\x1d14K011\x1d1PMC34063ADR\x1dQ3\x1d11K073121337\x1d4" - "LMX\x1d1VTI\x1e\x04" + '[)>\x1e06\x1dKP0-1337\x1d14K011\x1d1PMC34063ADR\x1dQ3\x1d11K073121337\x1d4' + 'LMX\x1d1VTI\x1e\x04' ) MOUSER_BARCODE_OLD = ( - ">[)>06\x1dK21421337\x1d14K033\x1d1PLDK320ADU33R\x1dQ32\x1d11K060931337\x1d" - "4LCN\x1d1VSTMicro" + '>[)>06\x1dK21421337\x1d14K033\x1d1PLDK320ADU33R\x1dQ32\x1d11K060931337\x1d' + '4LCN\x1d1VSTMicro' ) LCSC_BARCODE = ( - "{pbn:PICK2009291337,on:SO2009291337,pc:C312270,pm:ST-1-102-A01-T000-RS,qty" - ":2,mc:,cc:1,pdi:34421807}" + '{pbn:PICK2009291337,on:SO2009291337,pc:C312270,pm:ST-1-102-A01-T000-RS,qty' + ':2,mc:,cc:1,pdi:34421807}' ) TME_QRCODE = ( - "QTY:1 PN:WBP-302 PO:19361337/1 CPO:PO-2023-06-08-001337 MFR:WISHERENTERPRI" - "SE MPN:WBP-302 RoHS https://www.tme.eu/details/WBP-302" + 'QTY:1 PN:WBP-302 PO:19361337/1 CPO:PO-2023-06-08-001337 MFR:WISHERENTERPRI' + 'SE MPN:WBP-302 RoHS https://www.tme.eu/details/WBP-302' ) -TME_DATAMATRIX_CODE = "PWBP-302 1PMPNWBP-302 Q1 K19361337/1" +TME_DATAMATRIX_CODE = 'PWBP-302 1PMPNWBP-302 Q1 K19361337/1' diff --git a/InvenTree/plugin/builtin/suppliers/tme.py b/InvenTree/plugin/builtin/suppliers/tme.py index 1be052274d..d0eb19503f 100644 --- a/InvenTree/plugin/builtin/suppliers/tme.py +++ b/InvenTree/plugin/builtin/suppliers/tme.py @@ -14,30 +14,30 @@ from plugin.mixins import SettingsMixin, SupplierBarcodeMixin class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): """Plugin to integrate the TME API into Inventree.""" - NAME = "TMEPlugin" - TITLE = _("Supplier Integration - TME") - DESCRIPTION = _("Provides support for scanning TME barcodes") - VERSION = "1.0.0" - AUTHOR = _("InvenTree contributors") + NAME = 'TMEPlugin' + TITLE = _('Supplier Integration - TME') + DESCRIPTION = _('Provides support for scanning TME barcodes') + VERSION = '1.0.0' + AUTHOR = _('InvenTree contributors') - DEFAULT_SUPPLIER_NAME = "TME" + DEFAULT_SUPPLIER_NAME = 'TME' SETTINGS = { - "SUPPLIER_ID": { - "name": _("Supplier"), - "description": _("The Supplier which acts as 'TME'"), - "model": "company.company", + 'SUPPLIER_ID': { + 'name': _('Supplier'), + 'description': _("The Supplier which acts as 'TME'"), + 'model': 'company.company', } } - TME_IS_QRCODE_REGEX = re.compile(r"([^\s:]+:[^\s:]+\s+)+(\S+(\s|$)+)+") - TME_IS_BARCODE2D_REGEX = re.compile(r"(([^\s]+)(\s+|$))+") + TME_IS_QRCODE_REGEX = re.compile(r'([^\s:]+:[^\s:]+\s+)+(\S+(\s|$)+)+') + TME_IS_BARCODE2D_REGEX = re.compile(r'(([^\s]+)(\s+|$))+') # Custom field mapping TME_QRCODE_FIELDS = { - "PN": SupplierBarcodeMixin.SUPPLIER_PART_NUMBER, - "PO": SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER, - "MPN": SupplierBarcodeMixin.MANUFACTURER_PART_NUMBER, - "QTY": SupplierBarcodeMixin.QUANTITY, + 'PN': SupplierBarcodeMixin.SUPPLIER_PART_NUMBER, + 'PO': SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER, + 'MPN': SupplierBarcodeMixin.MANUFACTURER_PART_NUMBER, + 'QTY': SupplierBarcodeMixin.QUANTITY, } def extract_barcode_fields(self, barcode_data: str) -> dict[str, str]: @@ -47,9 +47,9 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): if self.TME_IS_QRCODE_REGEX.fullmatch(barcode_data): # Custom QR Code format e.g. "QTY: 1 PN:12345" - for item in barcode_data.split(" "): - if ":" in item: - key, value = item.split(":") + for item in barcode_data.split(' '): + if ':' in item: + key, value = item.split(':') if key in self.TME_QRCODE_FIELDS: barcode_fields[self.TME_QRCODE_FIELDS[key]] = value @@ -57,7 +57,7 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): elif self.TME_IS_BARCODE2D_REGEX.fullmatch(barcode_data): # 2D Barcode format e.g. "PWBP-302 1PMPNWBP-302 Q1 K19361337/1" - for item in barcode_data.split(" "): + for item in barcode_data.split(' '): for k, v in self.ecia_field_map().items(): if item.startswith(k): barcode_fields[v] = item[len(k) :] @@ -67,7 +67,7 @@ class TMEPlugin(SupplierBarcodeMixin, SettingsMixin, InvenTreePlugin): # Custom handling for order number if SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER in barcode_fields: order_number = barcode_fields[SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER] - order_number = order_number.split("/")[0] + order_number = order_number.split('/')[0] barcode_fields[SupplierBarcodeMixin.CUSTOMER_ORDER_NUMBER] = order_number return barcode_fields diff --git a/InvenTree/plugin/helpers.py b/InvenTree/plugin/helpers.py index 0dc3239512..211641514a 100644 --- a/InvenTree/plugin/helpers.py +++ b/InvenTree/plugin/helpers.py @@ -66,7 +66,7 @@ def log_error(error, reference: str = 'general'): def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: str = ''): """Handles an error and casts it as an IntegrationPluginError.""" package_path = traceback.extract_tb(error.__traceback__)[-1].filename - install_path = sysconfig.get_paths()["purelib"] + install_path = sysconfig.get_paths()['purelib'] try: package_name = pathlib.Path(package_path).relative_to(install_path).parts[0] diff --git a/InvenTree/plugin/installer.py b/InvenTree/plugin/installer.py index 113bcf014c..72b7ad4aa1 100644 --- a/InvenTree/plugin/installer.py +++ b/InvenTree/plugin/installer.py @@ -25,7 +25,7 @@ def pip_command(*args): command = [str(x) for x in command] - logger.info("Running pip command: %s", ' '.join(command)) + logger.info('Running pip command: %s', ' '.join(command)) return subprocess.check_output( command, cwd=settings.BASE_DIR.parent, stderr=subprocess.STDOUT @@ -38,7 +38,7 @@ def check_package_path(packagename: str): - If installed, return the installation path - If not installed, return False """ - logger.debug("check_package_path: %s", packagename) + logger.debug('check_package_path: %s', packagename) # Remove version information for c in '<>=! ': @@ -47,7 +47,7 @@ def check_package_path(packagename: str): try: result = pip_command('show', packagename) - output = result.decode('utf-8').split("\n") + output = result.decode('utf-8').split('\n') for line in output: # Check if line matches pattern "Location: ..." @@ -58,7 +58,7 @@ def check_package_path(packagename: str): except subprocess.CalledProcessError as error: output = error.output.decode('utf-8') - logger.exception("Plugin lookup failed: %s", str(output)) + logger.exception('Plugin lookup failed: %s', str(output)) return False # If we get here, the package is not installed @@ -67,22 +67,22 @@ def check_package_path(packagename: str): def install_plugins_file(): """Install plugins from the plugins file""" - logger.info("Installing plugins from plugins file") + logger.info('Installing plugins from plugins file') pf = settings.PLUGIN_FILE if not pf or not pf.exists(): - logger.warning("Plugin file %s does not exist", str(pf)) + logger.warning('Plugin file %s does not exist', str(pf)) return try: pip_command('install', '-r', str(pf)) except subprocess.CalledProcessError as error: output = error.output.decode('utf-8') - logger.exception("Plugin file installation failed: %s", str(output)) + logger.exception('Plugin file installation failed: %s', str(output)) return False except Exception as exc: - logger.exception("Plugin file installation failed: %s", exc) + logger.exception('Plugin file installation failed: %s', exc) return False # At this point, the plugins file has been installed @@ -91,12 +91,12 @@ def install_plugins_file(): def add_plugin_to_file(install_name): """Add a plugin to the plugins file""" - logger.info("Adding plugin to plugins file: %s", install_name) + logger.info('Adding plugin to plugins file: %s', install_name) pf = settings.PLUGIN_FILE if not pf or not pf.exists(): - logger.warning("Plugin file %s does not exist", str(pf)) + logger.warning('Plugin file %s does not exist', str(pf)) return # First, read in existing plugin file @@ -104,13 +104,13 @@ def add_plugin_to_file(install_name): with pf.open(mode='r') as f: lines = f.readlines() except Exception as exc: - logger.exception("Failed to read plugins file: %s", str(exc)) + logger.exception('Failed to read plugins file: %s', str(exc)) return # Check if plugin is already in file for line in lines: if line.strip() == install_name: - logger.debug("Plugin already exists in file") + logger.debug('Plugin already exists in file') return # Append plugin to file @@ -125,7 +125,7 @@ def add_plugin_to_file(install_name): if not line.endswith('\n'): f.write('\n') except Exception as exc: - logger.exception("Failed to add plugin to plugins file: %s", str(exc)) + logger.exception('Failed to add plugin to plugins file: %s', str(exc)) def install_plugin(url=None, packagename=None, user=None): @@ -136,17 +136,17 @@ def install_plugin(url=None, packagename=None, user=None): """ if user and not user.is_staff: raise ValidationError( - _("Permission denied: only staff users can install plugins") + _('Permission denied: only staff users can install plugins') ) - logger.debug("install_plugin: %s, %s", url, packagename) + logger.debug('install_plugin: %s, %s', url, packagename) # Check if we are running in a virtual environment # For now, just log a warning in_venv = sys.prefix != sys.base_prefix if not in_venv: - logger.warning("InvenTree is not running in a virtual environment") + logger.warning('InvenTree is not running in a virtual environment') # build up the command install_name = ['install', '-U'] @@ -185,23 +185,23 @@ def install_plugin(url=None, packagename=None, user=None): try: result = pip_command(*install_name) - ret['result'] = ret['success'] = _("Installed plugin successfully") + ret['result'] = ret['success'] = _('Installed plugin successfully') ret['output'] = str(result, 'utf-8') if packagename: if path := check_package_path(packagename): # Override result information - ret['result'] = _(f"Installed plugin into {path}") + ret['result'] = _(f'Installed plugin into {path}') except subprocess.CalledProcessError as error: # If an error was thrown, we need to parse the output output = error.output.decode('utf-8') - logger.exception("Plugin installation failed: %s", str(output)) + logger.exception('Plugin installation failed: %s', str(output)) - errors = [_("Plugin installation failed")] + errors = [_('Plugin installation failed')] - for msg in output.split("\n"): + for msg in output.split('\n'): msg = msg.strip() if msg: diff --git a/InvenTree/plugin/mock/simple.py b/InvenTree/plugin/mock/simple.py index 8130aa7eed..3160fed4b8 100644 --- a/InvenTree/plugin/mock/simple.py +++ b/InvenTree/plugin/mock/simple.py @@ -7,4 +7,4 @@ class SimplePlugin(InvenTreePlugin): """A very simple plugin.""" NAME = 'SimplePlugin' - SLUG = "simple" + SLUG = 'simple' diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py index fa3183e395..c8df8987c9 100644 --- a/InvenTree/plugin/models.py +++ b/InvenTree/plugin/models.py @@ -26,8 +26,8 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model): class Meta: """Meta for PluginConfig.""" - verbose_name = _("Plugin Configuration") - verbose_name_plural = _("Plugin Configurations") + verbose_name = _('Plugin Configuration') + verbose_name_plural = _('Plugin Configurations') key = models.CharField( unique=True, max_length=255, verbose_name=_('Key'), help_text=_('Key of plugin') @@ -114,7 +114,7 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model): """Customize pickling behavior.""" state = super().__getstate__() state.pop( - "plugin", None + 'plugin', None ) # plugin cannot be pickled in some circumstances when used with drf views, remove it (#5408) return state diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py index 1cf5b28c9d..54fc1ff8d0 100644 --- a/InvenTree/plugin/plugin.py +++ b/InvenTree/plugin/plugin.py @@ -16,7 +16,7 @@ from django.utils.translation import gettext_lazy as _ from plugin.helpers import get_git_log -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class MetaBase: diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 5fa890bf34..2b3dc354b8 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -281,7 +281,7 @@ class PluginsRegistry: """ # Do not reload when currently loading if self.is_loading: - logger.debug("Skipping reload - plugin registry is currently loading") + logger.debug('Skipping reload - plugin registry is currently loading') return if self.loading_lock.acquire(blocking=False): @@ -347,7 +347,7 @@ class PluginsRegistry: if not init_filename.exists(): try: - init_filename.write_text("# InvenTree plugin directory\n") + init_filename.write_text('# InvenTree plugin directory\n') except Exception: # pragma: no cover logger.exception( "Could not create file '%s'", init_filename @@ -415,7 +415,7 @@ class PluginsRegistry: # Log collected plugins logger.info('Collected %s plugins', len(collected_plugins)) - logger.debug(", ".join([a.__module__ for a in collected_plugins])) + logger.debug(', '.join([a.__module__ for a in collected_plugins])) return collected_plugins @@ -497,7 +497,7 @@ class PluginsRegistry: raise error # pragma: no cover plg_db = None except IntegrityError as error: # pragma: no cover - logger.exception("Error initializing plugin `%s`: %s", plg_name, error) + logger.exception('Error initializing plugin `%s`: %s', plg_name, error) handle_error(error, log_name='init') # Append reference to plugin @@ -533,7 +533,7 @@ class PluginsRegistry: handle_error( error, log_name='init' ) # log error and raise it -> disable plugin - logger.warning("Plugin `%s` could not be loaded", plg_name) + logger.warning('Plugin `%s` could not be loaded', plg_name) # Safe extra attributes plg_i.is_package = getattr(plg_i, 'is_package', False) @@ -694,25 +694,25 @@ class PluginsRegistry: try: old_hash = InvenTreeSetting.get_setting( - "_PLUGIN_REGISTRY_HASH", "", create=False, cache=False + '_PLUGIN_REGISTRY_HASH', '', create=False, cache=False ) except Exception: - old_hash = "" + old_hash = '' if old_hash != self.registry_hash: try: logger.debug( - "Updating plugin registry hash: %s", str(self.registry_hash) + 'Updating plugin registry hash: %s', str(self.registry_hash) ) InvenTreeSetting.set_setting( - "_PLUGIN_REGISTRY_HASH", self.registry_hash, change_user=None + '_PLUGIN_REGISTRY_HASH', self.registry_hash, change_user=None ) except (OperationalError, ProgrammingError): # Exception if the database has not been migrated yet, or is not ready pass except Exception as exc: # Some other exception, we want to know about it - logger.exception("Failed to update plugin registry hash: %s", str(exc)) + logger.exception('Failed to update plugin registry hash: %s', str(exc)) def calculate_plugin_hash(self): """Calculate a 'hash' value for the current registry @@ -767,7 +767,7 @@ class PluginsRegistry: # Skip check if database cannot be accessed return - logger.debug("Checking plugin registry hash") + logger.debug('Checking plugin registry hash') # If not already cached, calculate the hash if not self.registry_hash: @@ -775,14 +775,14 @@ class PluginsRegistry: try: reg_hash = InvenTreeSetting.get_setting( - "_PLUGIN_REGISTRY_HASH", "", create=False, cache=False + '_PLUGIN_REGISTRY_HASH', '', create=False, cache=False ) except Exception as exc: - logger.exception("Failed to retrieve plugin registry hash: %s", str(exc)) + logger.exception('Failed to retrieve plugin registry hash: %s', str(exc)) return if reg_hash and reg_hash != self.registry_hash: - logger.info("Plugin registry hash has changed - reloading") + logger.info('Plugin registry hash has changed - reloading') self.reload_plugins(full_reload=True, force_reload=True, collect=True) # endregion diff --git a/InvenTree/plugin/samples/event/event_sample.py b/InvenTree/plugin/samples/event/event_sample.py index 2ae21dcbb5..1b2bf59615 100644 --- a/InvenTree/plugin/samples/event/event_sample.py +++ b/InvenTree/plugin/samples/event/event_sample.py @@ -13,15 +13,15 @@ logger = logging.getLogger('inventree') class EventPluginSample(EventMixin, InvenTreePlugin): """A sample plugin which provides supports for triggered events.""" - NAME = "EventPlugin" - SLUG = "sampleevent" - TITLE = "Triggered Events" + NAME = 'EventPlugin' + SLUG = 'sampleevent' + TITLE = 'Triggered Events' def process_event(self, event, *args, **kwargs): """Custom event processing.""" print(f"Processing triggered event: '{event}'") - print("args:", str(args)) - print("kwargs:", str(kwargs)) + print('args:', str(args)) + print('kwargs:', str(kwargs)) # Issue warning that we can test for if settings.PLUGIN_TESTING: diff --git a/InvenTree/plugin/samples/event/filtered_event_sample.py b/InvenTree/plugin/samples/event/filtered_event_sample.py index 4267f3be1b..7380d0622a 100644 --- a/InvenTree/plugin/samples/event/filtered_event_sample.py +++ b/InvenTree/plugin/samples/event/filtered_event_sample.py @@ -13,19 +13,19 @@ logger = logging.getLogger('inventree') class FilteredEventPluginSample(EventMixin, InvenTreePlugin): """A sample plugin which provides supports for triggered events.""" - NAME = "FilteredEventPlugin" - SLUG = "filteredsampleevent" - TITLE = "Triggered by test.event only" + NAME = 'FilteredEventPlugin' + SLUG = 'filteredsampleevent' + TITLE = 'Triggered by test.event only' def wants_process_event(self, event): """Return whether given event should be processed or not.""" - return event == "test.event" + return event == 'test.event' def process_event(self, event, *args, **kwargs): """Custom event processing.""" print(f"Processing triggered event: '{event}'") - print("args:", str(args)) - print("kwargs:", str(kwargs)) + print('args:', str(args)) + print('kwargs:', str(kwargs)) # Issue warning that we can test for if settings.PLUGIN_TESTING: diff --git a/InvenTree/plugin/samples/event/test_event_sample.py b/InvenTree/plugin/samples/event/test_event_sample.py index f1a8d103f0..0e5eb9b86d 100644 --- a/InvenTree/plugin/samples/event/test_event_sample.py +++ b/InvenTree/plugin/samples/event/test_event_sample.py @@ -27,7 +27,7 @@ class EventPluginSampleTests(TestCase): # Enable event testing settings.PLUGIN_TESTING_EVENTS = True # Check that an event is issued - with self.assertLogs(logger=logger, level="DEBUG") as cm: + with self.assertLogs(logger=logger, level='DEBUG') as cm: trigger_event('test.event') self.assertIn( 'DEBUG:inventree:Event `test.event` triggered in sample plugin', cm[1] diff --git a/InvenTree/plugin/samples/event/test_filtered_event_sample.py b/InvenTree/plugin/samples/event/test_filtered_event_sample.py index fce2254785..1da5a3b499 100644 --- a/InvenTree/plugin/samples/event/test_filtered_event_sample.py +++ b/InvenTree/plugin/samples/event/test_filtered_event_sample.py @@ -25,7 +25,7 @@ class FilteredEventPluginSampleTests(TestCase): # Enable event testing settings.PLUGIN_TESTING_EVENTS = True # Check that an event is issued - with self.assertLogs(logger=logger, level="DEBUG") as cm: + with self.assertLogs(logger=logger, level='DEBUG') as cm: trigger_event('test.event') self.assertIn( 'DEBUG:inventree:Event `test.event` triggered in sample plugin', cm[1] @@ -46,7 +46,7 @@ class FilteredEventPluginSampleTests(TestCase): # Enable event testing settings.PLUGIN_TESTING_EVENTS = True # Check that an event is issued - with self.assertLogs(logger=logger, level="DEBUG") as cm: + with self.assertLogs(logger=logger, level='DEBUG') as cm: trigger_event('test.some.other.event') self.assertNotIn( 'DEBUG:inventree:Event `test.some.other.event` triggered in sample plugin', diff --git a/InvenTree/plugin/samples/integration/another_sample.py b/InvenTree/plugin/samples/integration/another_sample.py index 9891584ab2..95cff15d2f 100644 --- a/InvenTree/plugin/samples/integration/another_sample.py +++ b/InvenTree/plugin/samples/integration/another_sample.py @@ -7,10 +7,10 @@ from plugin.mixins import UrlsMixin class NoIntegrationPlugin(InvenTreePlugin): """A basic plugin.""" - NAME = "NoIntegrationPlugin" + NAME = 'NoIntegrationPlugin' class WrongIntegrationPlugin(UrlsMixin, InvenTreePlugin): """A basic wrong plugin with urls.""" - NAME = "WrongIntegrationPlugin" + NAME = 'WrongIntegrationPlugin' diff --git a/InvenTree/plugin/samples/integration/api_caller.py b/InvenTree/plugin/samples/integration/api_caller.py index ca704a839e..ea78f175c5 100644 --- a/InvenTree/plugin/samples/integration/api_caller.py +++ b/InvenTree/plugin/samples/integration/api_caller.py @@ -7,7 +7,7 @@ from plugin.mixins import APICallMixin, SettingsMixin class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin): """A small api call sample.""" - NAME = "Sample API Caller" + NAME = 'Sample API Caller' SETTINGS = { 'API_TOKEN': {'name': 'API Token', 'protected': True}, diff --git a/InvenTree/plugin/samples/integration/custom_panel_sample.py b/InvenTree/plugin/samples/integration/custom_panel_sample.py index 695816ce47..9f6ccabb8c 100644 --- a/InvenTree/plugin/samples/integration/custom_panel_sample.py +++ b/InvenTree/plugin/samples/integration/custom_panel_sample.py @@ -9,11 +9,11 @@ from stock.views import StockLocationDetail class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin): """A sample plugin which renders some custom panels.""" - NAME = "CustomPanelExample" - SLUG = "samplepanel" - TITLE = "Custom Panel Example" - DESCRIPTION = "An example plugin demonstrating how custom panels can be added to the user interface" - VERSION = "0.1" + NAME = 'CustomPanelExample' + SLUG = 'samplepanel' + TITLE = 'Custom Panel Example' + DESCRIPTION = 'An example plugin demonstrating how custom panels can be added to the user interface' + VERSION = '0.1' SETTINGS = { 'ENABLE_HELLO_WORLD': { diff --git a/InvenTree/plugin/samples/integration/label_sample.py b/InvenTree/plugin/samples/integration/label_sample.py index dbf5a65700..5ac88f532b 100644 --- a/InvenTree/plugin/samples/integration/label_sample.py +++ b/InvenTree/plugin/samples/integration/label_sample.py @@ -12,12 +12,12 @@ from plugin.mixins import LabelPrintingMixin class SampleLabelPrinter(LabelPrintingMixin, InvenTreePlugin): """Sample plugin which provides a 'fake' label printer endpoint.""" - NAME = "Sample Label Printer" - SLUG = "samplelabelprinter" - TITLE = "Sample Label Printer" - DESCRIPTION = "A sample plugin which provides a (fake) label printer interface" - AUTHOR = "InvenTree contributors" - VERSION = "0.3.0" + NAME = 'Sample Label Printer' + SLUG = 'samplelabelprinter' + TITLE = 'Sample Label Printer' + DESCRIPTION = 'A sample plugin which provides a (fake) label printer interface' + AUTHOR = 'InvenTree contributors' + VERSION = '0.3.0' class PrintingOptionsSerializer(serializers.Serializer): """Serializer to return printing options.""" diff --git a/InvenTree/plugin/samples/integration/report_plugin_sample.py b/InvenTree/plugin/samples/integration/report_plugin_sample.py index 7c8dbaa908..e14cadbfda 100644 --- a/InvenTree/plugin/samples/integration/report_plugin_sample.py +++ b/InvenTree/plugin/samples/integration/report_plugin_sample.py @@ -10,11 +10,11 @@ from report.models import PurchaseOrderReport class SampleReportPlugin(ReportMixin, InvenTreePlugin): """Sample plugin which provides extra context data to a report""" - NAME = "Sample Report Plugin" - SLUG = "samplereport" - TITLE = "Sample Report Plugin" - DESCRIPTION = "A sample plugin which provides extra context data to a report" - VERSION = "1.0" + NAME = 'Sample Report Plugin' + SLUG = 'samplereport' + TITLE = 'Sample Report Plugin' + DESCRIPTION = 'A sample plugin which provides extra context data to a report' + VERSION = '1.0' def some_custom_function(self): """Some custom function which is not required for the plugin to function""" diff --git a/InvenTree/plugin/samples/integration/sample.py b/InvenTree/plugin/samples/integration/sample.py index efdc342b52..894d5f51e3 100644 --- a/InvenTree/plugin/samples/integration/sample.py +++ b/InvenTree/plugin/samples/integration/sample.py @@ -24,11 +24,11 @@ class SampleIntegrationPlugin( ): """A full plugin example.""" - NAME = "SampleIntegrationPlugin" - SLUG = "sample" - TITLE = "Sample Plugin" + NAME = 'SampleIntegrationPlugin' + SLUG = 'sample' + TITLE = 'Sample Plugin' - NAVIGATION_TAB_NAME = "Sample Nav" + NAVIGATION_TAB_NAME = 'Sample Nav' NAVIGATION_TAB_ICON = 'fas fa-plus' def view_test(self, request): @@ -66,7 +66,7 @@ class SampleIntegrationPlugin( 'default': 123, }, 'CHOICE_SETTING': { - 'name': _("Choice Setting"), + 'name': _('Choice Setting'), 'description': _('A setting with multiple choices'), 'choices': [('A', 'Anaconda'), ('B', 'Bat'), ('C', 'Cat'), ('D', 'Dog')], 'default': 'A', diff --git a/InvenTree/plugin/samples/integration/sample_currency_exchange.py b/InvenTree/plugin/samples/integration/sample_currency_exchange.py index b26a438c58..a60a2eb11c 100644 --- a/InvenTree/plugin/samples/integration/sample_currency_exchange.py +++ b/InvenTree/plugin/samples/integration/sample_currency_exchange.py @@ -11,11 +11,11 @@ from plugin.mixins import CurrencyExchangeMixin class SampleCurrencyExchangePlugin(CurrencyExchangeMixin, InvenTreePlugin): """Dummy currency exchange plugin which provides fake exchange rates""" - NAME = "Sample Exchange" - DESCRIPTION = _("Sample currency exchange plugin") - SLUG = "samplecurrencyexchange" - VERSION = "0.1.0" - AUTHOR = _("InvenTree Contributors") + NAME = 'Sample Exchange' + DESCRIPTION = _('Sample currency exchange plugin') + SLUG = 'samplecurrencyexchange' + VERSION = '0.1.0' + AUTHOR = _('InvenTree Contributors') def update_exchange_rates(self, base_currency: str, symbols: list[str]) -> dict: """Return dummy data for some currencies""" diff --git a/InvenTree/plugin/samples/integration/scheduled_task.py b/InvenTree/plugin/samples/integration/scheduled_task.py index d2e4240877..3e8a8f574a 100644 --- a/InvenTree/plugin/samples/integration/scheduled_task.py +++ b/InvenTree/plugin/samples/integration/scheduled_task.py @@ -10,7 +10,7 @@ def print_hello(): Contents do not matter - therefore no coverage. """ - print("Hello") # pragma: no cover + print('Hello') # pragma: no cover def print_world(): @@ -18,16 +18,16 @@ def print_world(): Contents do not matter - therefore no coverage. """ - print("World") # pragma: no cover + print('World') # pragma: no cover class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin): """A sample plugin which provides support for scheduled tasks.""" - NAME = "ScheduledTasksPlugin" - SLUG = "schedule" - TITLE = "Scheduled Tasks" - VERSION = "0.2.0" + NAME = 'ScheduledTasksPlugin' + SLUG = 'schedule' + TITLE = 'Scheduled Tasks' + VERSION = '0.2.0' SCHEDULED_TASKS = { 'member': {'func': 'member_func', 'schedule': 'I', 'minutes': 30}, @@ -55,5 +55,5 @@ class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin): """A simple member function to demonstrate functionality.""" t_or_f = self.get_setting('T_OR_F') - print(f"Called member_func - value is {t_or_f}") + print(f'Called member_func - value is {t_or_f}') return t_or_f diff --git a/InvenTree/plugin/samples/integration/simpleactionplugin.py b/InvenTree/plugin/samples/integration/simpleactionplugin.py index 4e4b82f13d..bf892ba577 100644 --- a/InvenTree/plugin/samples/integration/simpleactionplugin.py +++ b/InvenTree/plugin/samples/integration/simpleactionplugin.py @@ -7,16 +7,16 @@ from plugin.mixins import ActionMixin class SimpleActionPlugin(ActionMixin, InvenTreePlugin): """An EXTREMELY simple action plugin which demonstrates the capability of the ActionMixin class.""" - NAME = "SimpleActionPlugin" - ACTION_NAME = "simple" + NAME = 'SimpleActionPlugin' + ACTION_NAME = 'simple' def perform_action(self, user=None, data=None): """Sample method.""" - print("Action plugin in action!") + print('Action plugin in action!') def get_info(self, user, data=None): """Sample method.""" - return {"user": user.username, "hello": "world"} + return {'user': user.username, 'hello': 'world'} def get_result(self, user=None, data=None): """Sample method.""" diff --git a/InvenTree/plugin/samples/integration/test_sample.py b/InvenTree/plugin/samples/integration/test_sample.py index 18e15f8721..0d7ac58893 100644 --- a/InvenTree/plugin/samples/integration/test_sample.py +++ b/InvenTree/plugin/samples/integration/test_sample.py @@ -46,7 +46,7 @@ class SampleIntegrationPluginTests(InvenTreeTestCase): # check settings self.assertEqual(plugin.check_settings(), (False, ['API_KEY'])) - plugin.set_setting('API_KEY', "dsfiodsfjsfdjsf") + plugin.set_setting('API_KEY', 'dsfiodsfjsfdjsf') self.assertEqual(plugin.check_settings(), (True, [])) # validator diff --git a/InvenTree/plugin/samples/integration/test_scheduled_task.py b/InvenTree/plugin/samples/integration/test_scheduled_task.py index 6a9518a917..c6513a7aa4 100644 --- a/InvenTree/plugin/samples/integration/test_scheduled_task.py +++ b/InvenTree/plugin/samples/integration/test_scheduled_task.py @@ -36,7 +36,7 @@ class ExampleScheduledTaskPluginTests(TestCase): # check that schedule was registers from django_q.models import Schedule - scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith="plugin.") + scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith='plugin.') self.assertEqual(len(scheduled_plugin_tasks), 3) # test updating the schedule @@ -48,7 +48,7 @@ class ExampleScheduledTaskPluginTests(TestCase): # Check that the schedule was updated hello_schedule = Schedule.objects.get(name='plugin.schedule.hello') - scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith="plugin.") + scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith='plugin.') self.assertEqual(hello_schedule.minutes, 15) self.assertEqual(len(scheduled_plugin_tasks), 3) @@ -56,12 +56,12 @@ class ExampleScheduledTaskPluginTests(TestCase): # this is to check the system also deals with disappearing tasks scheduled_plugin_tasks[1].delete() # there should be one less now - scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith="plugin.") + scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith='plugin.') self.assertEqual(len(scheduled_plugin_tasks), 2) # test unregistering plg.unregister_tasks() - scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith="plugin.") + scheduled_plugin_tasks = Schedule.objects.filter(name__istartswith='plugin.') self.assertEqual(len(scheduled_plugin_tasks), 0) def test_calling(self): diff --git a/InvenTree/plugin/samples/integration/test_simpleactionplugin.py b/InvenTree/plugin/samples/integration/test_simpleactionplugin.py index c6205be7e4..d1542c4c2d 100644 --- a/InvenTree/plugin/samples/integration/test_simpleactionplugin.py +++ b/InvenTree/plugin/samples/integration/test_simpleactionplugin.py @@ -15,21 +15,21 @@ class SimpleActionPluginTests(InvenTreeTestCase): def test_name(self): """Check plugn names.""" - self.assertEqual(self.plugin.plugin_name(), "SimpleActionPlugin") - self.assertEqual(self.plugin.action_name(), "simple") + self.assertEqual(self.plugin.plugin_name(), 'SimpleActionPlugin') + self.assertEqual(self.plugin.action_name(), 'simple') def test_function(self): """Check if functions work.""" # test functions response = self.client.post( - '/api/action/', data={'action': "simple", 'data': {'foo': "bar"}} + '/api/action/', data={'action': 'simple', 'data': {'foo': 'bar'}} ) self.assertEqual(response.status_code, 200) self.assertJSONEqual( str(response.content, encoding='utf8'), { - "action": 'simple', - "result": True, - "info": {"user": self.username, "hello": "world"}, + 'action': 'simple', + 'result': True, + 'info': {'user': self.username, 'hello': 'world'}, }, ) diff --git a/InvenTree/plugin/samples/integration/transition.py b/InvenTree/plugin/samples/integration/transition.py index bed1a91a5a..97166506bd 100644 --- a/InvenTree/plugin/samples/integration/transition.py +++ b/InvenTree/plugin/samples/integration/transition.py @@ -10,7 +10,7 @@ from plugin import InvenTreePlugin class SampleTransitionPlugin(InvenTreePlugin): """A sample plugin which shows how state transitions might be implemented.""" - NAME = "SampleTransitionPlugin" + NAME = 'SampleTransitionPlugin' class ReturnChangeHandler(TransitionMethod): """Transition method for PurchaseOrder objects.""" @@ -32,7 +32,7 @@ class SampleTransitionPlugin(InvenTreePlugin): 'sampel_123_456', targets=[instance.created_by], context={ - 'message': "Return order without responsible owner can not be completed!" + 'message': 'Return order without responsible owner can not be completed!' }, ) return True # True means nothing will happen diff --git a/InvenTree/plugin/samples/integration/validation_sample.py b/InvenTree/plugin/samples/integration/validation_sample.py index 67b84b636b..585ab59ed6 100644 --- a/InvenTree/plugin/samples/integration/validation_sample.py +++ b/InvenTree/plugin/samples/integration/validation_sample.py @@ -14,11 +14,11 @@ class CustomValidationMixin(SettingsMixin, ValidationMixin, InvenTreePlugin): Simple of examples of custom validator code. """ - NAME = "CustomValidator" - SLUG = "validator" - TITLE = "Custom Validator Plugin" - DESCRIPTION = "A sample plugin for demonstrating custom validation functionality" - VERSION = "0.3.0" + NAME = 'CustomValidator' + SLUG = 'validator' + TITLE = 'Custom Validator Plugin' + DESCRIPTION = 'A sample plugin for demonstrating custom validation functionality' + VERSION = '0.3.0' SETTINGS = { 'ILLEGAL_PART_CHARS': { @@ -60,7 +60,7 @@ class CustomValidationMixin(SettingsMixin, ValidationMixin, InvenTreePlugin): These examples are silly, but serve to demonstrate how the feature could be used """ if len(part.description) < len(name): - raise ValidationError("Part description cannot be shorter than the name") + raise ValidationError('Part description cannot be shorter than the name') illegal_chars = self.get_setting('ILLEGAL_PART_CHARS') @@ -84,7 +84,7 @@ class CustomValidationMixin(SettingsMixin, ValidationMixin, InvenTreePlugin): if parameter.template.name.lower() in ['length', 'width']: d = int(data) if d >= 100: - raise ValidationError("Value must be less than 100") + raise ValidationError('Value must be less than 100') def validate_serial_number(self, serial: str, part): """Validate serial number for a given StockItem @@ -93,13 +93,13 @@ class CustomValidationMixin(SettingsMixin, ValidationMixin, InvenTreePlugin): """ if self.get_setting('SERIAL_MUST_BE_PALINDROME'): if serial != serial[::-1]: - raise ValidationError("Serial must be a palindrome") + raise ValidationError('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]: raise ValidationError( - "Serial number must start with same letter as part" + 'Serial number must start with same letter as part' ) def validate_batch_code(self, batch_code: str, item): @@ -116,4 +116,4 @@ class CustomValidationMixin(SettingsMixin, ValidationMixin, InvenTreePlugin): def generate_batch_code(self): """Generate a new batch code.""" now = datetime.now() - return f"BATCH-{now.year}:{now.month}:{now.day}" + return f'BATCH-{now.year}:{now.month}:{now.day}' diff --git a/InvenTree/plugin/samples/integration/version.py b/InvenTree/plugin/samples/integration/version.py index 78f9ccec73..eda28a5431 100644 --- a/InvenTree/plugin/samples/integration/version.py +++ b/InvenTree/plugin/samples/integration/version.py @@ -6,8 +6,8 @@ from plugin import InvenTreePlugin class VersionPlugin(InvenTreePlugin): """A small version sample.""" - SLUG = "sampleversion" - NAME = "Sample Version Plugin" - DESCRIPTION = "A simple plugin which shows how to use the version limits" + SLUG = 'sampleversion' + NAME = 'Sample Version Plugin' + DESCRIPTION = 'A simple plugin which shows how to use the version limits' MIN_VERSION = '0.1.0' MAX_VERSION = '1.0.0' diff --git a/InvenTree/plugin/samples/locate/locate_sample.py b/InvenTree/plugin/samples/locate/locate_sample.py index 2a53bf79f6..50f3263f98 100644 --- a/InvenTree/plugin/samples/locate/locate_sample.py +++ b/InvenTree/plugin/samples/locate/locate_sample.py @@ -17,11 +17,11 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin): This plugin class simply prints location information to the logger. """ - NAME = "SampleLocatePlugin" - SLUG = "samplelocate" - TITLE = "Sample plugin for locating items" + NAME = 'SampleLocatePlugin' + SLUG = 'samplelocate' + TITLE = 'Sample plugin for locating items' - VERSION = "0.2" + VERSION = '0.2' def locate_stock_item(self, item_pk): """Locate a StockItem. @@ -31,17 +31,17 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin): """ from stock.models import StockItem - logger.info("SampleLocatePlugin attempting to locate item ID %s", item_pk) + logger.info('SampleLocatePlugin attempting to locate item ID %s', item_pk) try: item = StockItem.objects.get(pk=item_pk) - logger.info("StockItem %s located!", item_pk) + logger.info('StockItem %s located!', item_pk) # Tag metadata item.set_metadata('located', True) except (ValueError, StockItem.DoesNotExist): # pragma: no cover - logger.exception("StockItem ID %s does not exist!", item_pk) + logger.exception('StockItem ID %s does not exist!', item_pk) def locate_stock_location(self, location_pk): """Locate a StockLocation. @@ -52,7 +52,7 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin): from stock.models import StockLocation logger.info( - "SampleLocatePlugin attempting to locate location ID %s", location_pk + 'SampleLocatePlugin attempting to locate location ID %s', location_pk ) try: @@ -63,4 +63,4 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin): location.set_metadata('located', True) except (ValueError, StockLocation.DoesNotExist): # pragma: no cover - logger.exception("Location ID %s does not exist!", location_pk) + logger.exception('Location ID %s does not exist!', location_pk) diff --git a/InvenTree/plugin/serializers.py b/InvenTree/plugin/serializers.py index 57d17e6dd0..9d123ee952 100644 --- a/InvenTree/plugin/serializers.py +++ b/InvenTree/plugin/serializers.py @@ -135,24 +135,24 @@ class PluginReloadSerializer(serializers.Serializer): full_reload = serializers.BooleanField( required=False, default=False, - label=_("Full reload"), - help_text=_("Perform a full reload of the plugin registry"), + label=_('Full reload'), + help_text=_('Perform a full reload of the plugin registry'), ) force_reload = serializers.BooleanField( required=False, default=False, - label=_("Force reload"), + label=_('Force reload'), help_text=_( - "Force a reload of the plugin registry, even if it is already loaded" + 'Force a reload of the plugin registry, even if it is already loaded' ), ) collect_plugins = serializers.BooleanField( required=False, default=False, - label=_("Collect plugins"), - help_text=_("Collect plugins and add them to the registry"), + label=_('Collect plugins'), + help_text=_('Collect plugins and add them to the registry'), ) def save(self): diff --git a/InvenTree/plugin/test_plugin.py b/InvenTree/plugin/test_plugin.py index 9dc6d7f63f..0612cb70bb 100644 --- a/InvenTree/plugin/test_plugin.py +++ b/InvenTree/plugin/test_plugin.py @@ -98,7 +98,7 @@ class InvenTreePluginTests(TestCase): NAME = 'Aplugin' SLUG = 'a' TITLE = 'a title' - PUBLISH_DATE = "1111-11-11" + PUBLISH_DATE = '1111-11-11' AUTHOR = 'AA BB' DESCRIPTION = 'A description' VERSION = '1.2.3a' diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index 83e983fe96..8a5a6cbd10 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -73,7 +73,7 @@ class ReportFilterMixin: """Return a list of database objects from query parameters""" if not self.ITEM_MODEL: raise NotImplementedError( - f"ITEM_MODEL attribute not defined for {__class__}" + f'ITEM_MODEL attribute not defined for {__class__}' ) ids = [] @@ -184,7 +184,7 @@ class ReportPrintMixin: ) # Start with a default report name - report_name = "report.pdf" + report_name = 'report.pdf' try: # Merge one or more PDF files into a single download @@ -223,7 +223,7 @@ class ReportPrintMixin: if debug_mode: """Concatenate all rendered templates into a single HTML string, and return the string as a HTML response.""" - html = "\n".join(outputs) + html = '\n'.join(outputs) return HttpResponse(html) else: @@ -320,7 +320,7 @@ class StockItemTestReportPrint(StockItemTestReportMixin, ReportPrintMixin, Retri # Construct a PDF file object try: pdf = report.get_document().write_pdf() - pdf_content = ContentFile(pdf, "test_report.pdf") + pdf_content = ContentFile(pdf, 'test_report.pdf') except TemplateDoesNotExist: return @@ -328,7 +328,7 @@ class StockItemTestReportPrint(StockItemTestReportMixin, ReportPrintMixin, Retri attachment=pdf_content, stock_item=item, user=request.user, - comment=_("Test report"), + comment=_('Test report'), ) diff --git a/InvenTree/report/apps.py b/InvenTree/report/apps.py index 4362f5f4f4..19a7e9e284 100644 --- a/InvenTree/report/apps.py +++ b/InvenTree/report/apps.py @@ -11,7 +11,7 @@ from django.conf import settings from django.core.exceptions import AppRegistryNotReady from django.db.utils import IntegrityError, OperationalError, ProgrammingError -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') class ReportConfig(AppConfig): diff --git a/InvenTree/report/helpers.py b/InvenTree/report/helpers.py index 2088913db5..d3594300e3 100644 --- a/InvenTree/report/helpers.py +++ b/InvenTree/report/helpers.py @@ -46,7 +46,7 @@ def report_page_size_default(): try: page_size = InvenTreeSetting.get_setting('REPORT_DEFAULT_PAGE_SIZE', 'A4') except Exception as exc: - logger.exception("Error getting default page size: %s", str(exc)) + logger.exception('Error getting default page size: %s', str(exc)) page_size = 'A4' return page_size @@ -70,4 +70,4 @@ def encode_image_base64(image, format: str = 'PNG'): img_str = base64.b64encode(buffered.getvalue()) - return f"data:image/{fmt};charset=utf-8;base64," + img_str.decode() + return f'data:image/{fmt};charset=utf-8;base64,' + img_str.decode() diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index f301ece04d..e64ae4abda 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -28,12 +28,12 @@ from plugin.registry import registry try: from django_weasyprint import WeasyTemplateResponseMixin except OSError as err: # pragma: no cover - print(f"OSError: {err}") - print("You may require some further system packages to be installed.") + print(f'OSError: {err}') + print('You may require some further system packages to be installed.') sys.exit(1) -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') def rename_template(instance, filename): @@ -118,7 +118,7 @@ class ReportBase(models.Model): def __str__(self): """Format a string representation of a report instance""" - return f"{self.name} - {self.description}" + return f'{self.name} - {self.description}' @classmethod def getSubdir(cls): @@ -176,20 +176,20 @@ class ReportBase(models.Model): template = models.FileField( upload_to=rename_template, verbose_name=_('Template'), - help_text=_("Report template file"), + help_text=_('Report template file'), validators=[FileExtensionValidator(allowed_extensions=['html', 'htm'])], ) description = models.CharField( max_length=250, verbose_name=_('Description'), - help_text=_("Report template description"), + help_text=_('Report template description'), ) revision = models.PositiveIntegerField( default=1, - verbose_name=_("Revision"), - help_text=_("Report revision number (auto-increments)"), + verbose_name=_('Revision'), + help_text=_('Report revision number (auto-increments)'), editable=False, ) @@ -295,7 +295,7 @@ class ReportTemplateBase(MetadataMixin, ReportBase): wp = WeasyprintReportMixin( request, self.template_name, - base_url=request.build_absolute_uri("/"), + base_url=request.build_absolute_uri('/'), presentational_hints=True, filename=self.generate_filename(request), **kwargs, @@ -304,7 +304,7 @@ class ReportTemplateBase(MetadataMixin, ReportBase): return wp.render_to_response(self.context(request), **kwargs) filename_pattern = models.CharField( - default="report.pdf", + default='report.pdf', verbose_name=_('Filename Pattern'), help_text=_('Pattern for generating report filenames'), max_length=100, @@ -335,7 +335,7 @@ class TestReport(ReportTemplateBase): max_length=250, verbose_name=_('Filters'), help_text=_( - "StockItem query filters (comma-separated list of key=value pairs)" + 'StockItem query filters (comma-separated list of key=value pairs)' ), validators=[validate_stock_item_report_filters], ) @@ -612,7 +612,7 @@ class ReportSnippet(models.Model): description = models.CharField( max_length=250, verbose_name=_('Description'), - help_text=_("Snippet file description"), + help_text=_('Snippet file description'), ) @@ -650,14 +650,14 @@ class ReportAsset(models.Model): asset = models.FileField( upload_to=rename_asset, verbose_name=_('Asset'), - help_text=_("Report asset file"), + help_text=_('Report asset file'), ) # Asset description (user facing string, not used internally) description = models.CharField( max_length=250, verbose_name=_('Description'), - help_text=_("Asset file description"), + help_text=_('Asset file description'), ) @@ -679,7 +679,7 @@ class StockLocationReport(ReportTemplateBase): max_length=250, verbose_name=_('Filters'), help_text=_( - "stock location query filters (comma-separated list of key=value pairs)" + 'stock location query filters (comma-separated list of key=value pairs)' ), validators=[validate_stock_location_report_filters], ) diff --git a/InvenTree/report/templatetags/barcode.py b/InvenTree/report/templatetags/barcode.py index 268f00e504..451136eb0f 100644 --- a/InvenTree/report/templatetags/barcode.py +++ b/InvenTree/report/templatetags/barcode.py @@ -35,7 +35,7 @@ def qrcode(data, **kwargs): """ # Construct "default" values - params = {"box_size": 20, "border": 1, "version": 1} + params = {'box_size': 20, 'border': 1, 'version': 1} fill_color = kwargs.pop('fill_color', 'black') back_color = kwargs.pop('back_color', 'white') diff --git a/InvenTree/report/templatetags/report.py b/InvenTree/report/templatetags/report.py index de1cb0544d..c2a096136f 100644 --- a/InvenTree/report/templatetags/report.py +++ b/InvenTree/report/templatetags/report.py @@ -63,7 +63,7 @@ def getkey(container: dict, key): key: The 'key' to be found within the dict """ if type(container) is not dict: - logger.warning("getkey() called with non-dict object") + logger.warning('getkey() called with non-dict object') return None if key in container: @@ -92,11 +92,11 @@ def asset(filename): full_path = settings.MEDIA_ROOT.joinpath('report', 'assets', filename).resolve() if not full_path.exists() or not full_path.is_file(): - raise FileNotFoundError(_("Asset file does not exist") + f": '{filename}'") + raise FileNotFoundError(_('Asset file does not exist') + f": '{filename}'") if debug_mode: return os.path.join(settings.MEDIA_URL, 'report', 'assets', filename) - return f"file://{full_path}" + return f'file://{full_path}' @register.simple_tag() @@ -147,7 +147,7 @@ def uploaded_image( exists = False if not exists and not replace_missing: - raise FileNotFoundError(_("Image file not found") + f": '{filename}'") + raise FileNotFoundError(_('Image file not found') + f": '{filename}'") if debug_mode: # In debug mode, return a web path (rather than an encoded image blob) @@ -212,14 +212,14 @@ def encode_svg_image(filename): exists = False if not exists: - raise FileNotFoundError(_("Image file not found") + f": '{filename}'") + raise FileNotFoundError(_('Image file not found') + f": '{filename}'") # Read the file data with open(full_path, 'rb') as f: data = f.read() # Return the base64-encoded data - return "data:image/svg+xml;charset=utf-8;base64," + base64.b64encode(data).decode( + return 'data:image/svg+xml;charset=utf-8;base64,' + base64.b64encode(data).decode( 'utf-8' ) @@ -235,7 +235,7 @@ def part_image(part: Part, preview=False, thumbnail=False, **kwargs): TypeError if provided part is not a Part instance """ if type(part) is not Part: - raise TypeError(_("part_image tag requires a Part instance")) + raise TypeError(_('part_image tag requires a Part instance')) if preview: img = part.image.preview.name @@ -274,7 +274,7 @@ def company_image(company, preview=False, thumbnail=False, **kwargs): TypeError if provided company is not a Company instance """ if type(company) is not Company: - raise TypeError(_("company_image tag requires a Company instance")) + raise TypeError(_('company_image tag requires a Company instance')) if preview: img = company.image.preview.name diff --git a/InvenTree/report/tests.py b/InvenTree/report/tests.py index aa6c4fcb81..96f64a22a2 100644 --- a/InvenTree/report/tests.py +++ b/InvenTree/report/tests.py @@ -56,14 +56,14 @@ class ReportTagTest(TestCase): self.debug_mode(b) with self.assertRaises(FileNotFoundError): - report_tags.asset("bad_file.txt") + report_tags.asset('bad_file.txt') # Create an asset file asset_dir = settings.MEDIA_ROOT.joinpath('report', 'assets') asset_dir.mkdir(parents=True, exist_ok=True) asset_path = asset_dir.joinpath('test.txt') - asset_path.write_text("dummy data") + asset_path.write_text('dummy data') self.debug_mode(True) asset = report_tags.asset('test.txt') @@ -101,7 +101,7 @@ class ReportTagTest(TestCase): img_file = img_path.joinpath('test.jpg') img_path.mkdir(parents=True, exist_ok=True) - img_file.write_text("dummy data") + img_file.write_text('dummy data') # Test in debug mode. Returns blank image as dummy file is not a valid image self.debug_mode(True) @@ -158,7 +158,7 @@ class BarcodeTagTest(TestCase): def test_barcode(self): """Test the barcode generation tag""" - barcode = barcode_tags.barcode("12345") + barcode = barcode_tags.barcode('12345') self.assertTrue(isinstance(barcode, str)) self.assertTrue(barcode.startswith('data:image/png;')) @@ -171,14 +171,14 @@ class BarcodeTagTest(TestCase): def test_qrcode(self): """Test the qrcode generation tag""" # Test with default settings - qrcode = barcode_tags.qrcode("hello world") + qrcode = barcode_tags.qrcode('hello world') self.assertTrue(isinstance(qrcode, str)) self.assertTrue(qrcode.startswith('data:image/png;')) self.assertEqual(len(qrcode), 700) # Generate a much larger qrcode qrcode = barcode_tags.qrcode( - "hello_world", version=2, box_size=50, format='BMP' + 'hello_world', version=2, box_size=50, format='BMP' ) self.assertTrue(isinstance(qrcode, str)) self.assertTrue(qrcode.startswith('data:image/bmp;')) diff --git a/InvenTree/script/translation_stats.py b/InvenTree/script/translation_stats.py index 2e197ae6aa..f7a848f042 100644 --- a/InvenTree/script/translation_stats.py +++ b/InvenTree/script/translation_stats.py @@ -15,10 +15,10 @@ def calculate_coverage(filename): lines_uncovered = 0 for line in lines: - if line.startswith("msgid "): + if line.startswith('msgid '): lines_count += 1 - elif line.startswith("msgstr"): + elif line.startswith('msgstr'): if line.startswith('msgstr ""') or line.startswith("msgstr ''"): lines_uncovered += 1 else: @@ -49,7 +49,7 @@ if __name__ == '__main__': locales[locale] = locale_file if verbose: - print("-" * 16) + print('-' * 16) percentages = [] @@ -72,7 +72,7 @@ if __name__ == '__main__': percentages.append(percentage) if verbose: - print("-" * 16) + print('-' * 16) # write locale stats with open(STAT_FILE, 'w') as target: @@ -83,4 +83,4 @@ if __name__ == '__main__': else: avg = 0 - print(f"InvenTree translation coverage: {avg}%") + print(f'InvenTree translation coverage: {avg}%') diff --git a/InvenTree/stock/admin.py b/InvenTree/stock/admin.py index 45a74f533e..d31bf9e1a4 100644 --- a/InvenTree/stock/admin.py +++ b/InvenTree/stock/admin.py @@ -110,7 +110,7 @@ class LocationTypeAdmin(admin.ModelAdmin): return ( super() .get_queryset(request) - .annotate(location_count=Count("stock_locations")) + .annotate(location_count=Count('stock_locations')) ) def location_count(self, obj): diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index d4b7ade93e..a01e4e9fbf 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -283,7 +283,7 @@ class StockLocationList(APIDownloadMixin, ListCreateAPI): """Download the filtered queryset as a data file""" dataset = LocationResource().export(queryset=queryset) filedata = dataset.export(export_format) - filename = f"InvenTree_Locations.{export_format}" + filename = f'InvenTree_Locations.{export_format}' return DownloadFile(filedata, filename) @@ -389,11 +389,11 @@ class StockLocationTypeList(ListCreateAPI): filter_backends = SEARCH_ORDER_FILTER - ordering_fields = ["name", "location_count", "icon"] + ordering_fields = ['name', 'location_count', 'icon'] - ordering = ["-location_count"] + ordering = ['-location_count'] - search_fields = ["name"] + search_fields = ['name'] def get_queryset(self): """Override the queryset method to include location count.""" @@ -491,9 +491,9 @@ class StockFilter(rest_filters.FilterSet): ) # Part attribute filters - assembly = rest_filters.BooleanFilter(label="Assembly", field_name='part__assembly') - active = rest_filters.BooleanFilter(label="Active", field_name='part__active') - salable = rest_filters.BooleanFilter(label="Salable", field_name='part__salable') + assembly = rest_filters.BooleanFilter(label='Assembly', field_name='part__assembly') + active = rest_filters.BooleanFilter(label='Active', field_name='part__active') + salable = rest_filters.BooleanFilter(label='Salable', field_name='part__salable') min_stock = rest_filters.NumberFilter( label='Minimum stock', field_name='quantity', lookup_expr='gte' @@ -570,14 +570,14 @@ class StockFilter(rest_filters.FilterSet): return queryset.filter(Q(quantity__lte=F('allocated'))) batch = rest_filters.CharFilter( - label="Batch code filter (case insensitive)", lookup_expr='iexact' + label='Batch code filter (case insensitive)', lookup_expr='iexact' ) batch_regex = rest_filters.CharFilter( - label="Batch code filter (regex)", field_name='batch', lookup_expr='iregex' + label='Batch code filter (regex)', field_name='batch', lookup_expr='iregex' ) - is_building = rest_filters.BooleanFilter(label="In production") + is_building = rest_filters.BooleanFilter(label='In production') # Serial number filtering serial_gte = rest_filters.NumberFilter( @@ -741,7 +741,7 @@ class StockFilter(rest_filters.FilterSet): # Stock "expiry" filters expiry_date_lte = InvenTreeDateFilter( - label=_("Expiry date before"), field_name='expiry_date', lookup_expr='lte' + label=_('Expiry date before'), field_name='expiry_date', lookup_expr='lte' ) expiry_date_gte = InvenTreeDateFilter( @@ -913,7 +913,7 @@ class StockList(APIDownloadMixin, ListCreateDestroyAPIView): if not part.trackable: raise ValidationError({ 'serial_numbers': [ - _("Serial numbers cannot be supplied for a non-trackable part") + _('Serial numbers cannot be supplied for a non-trackable part') ] }) @@ -939,9 +939,9 @@ class StockList(APIDownloadMixin, ListCreateDestroyAPIView): errors.append(exc.message) if len(errors) > 0: - msg = _("The following serial numbers already exist or are invalid") - msg += " : " - msg += ",".join([str(e) for e in invalid]) + msg = _('The following serial numbers already exist or are invalid') + msg += ' : ' + msg += ','.join([str(e) for e in invalid]) raise ValidationError({'serial_numbers': errors + [msg]}) @@ -1098,7 +1098,7 @@ class StockList(APIDownloadMixin, ListCreateDestroyAPIView): queryset = queryset.filter(part=part) except (ValueError, Part.DoesNotExist): - raise ValidationError({"part": "Invalid Part ID specified"}) + raise ValidationError({'part': 'Invalid Part ID specified'}) # Does the client wish to filter by stock location? loc_id = params.get('location', None) @@ -1531,7 +1531,7 @@ stock_api_urls = [ ]), ), re_path( - r'^.*$', StockLocationTypeList.as_view(), name="api-location-type-list" + r'^.*$', StockLocationTypeList.as_view(), name='api-location-type-list' ), ]), ), diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index b0fd9f4b28..af29e74fac 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -62,8 +62,8 @@ class StockLocationType(MetadataMixin, models.Model): class Meta: """Metaclass defines extra model properties.""" - verbose_name = _("Stock Location type") - verbose_name_plural = _("Stock Location types") + verbose_name = _('Stock Location type') + verbose_name_plural = _('Stock Location types') @staticmethod def get_api_url(): @@ -75,21 +75,21 @@ class StockLocationType(MetadataMixin, models.Model): return self.name name = models.CharField( - blank=False, max_length=100, verbose_name=_("Name"), help_text=_("Name") + blank=False, max_length=100, verbose_name=_('Name'), help_text=_('Name') ) description = models.CharField( blank=True, max_length=250, - verbose_name=_("Description"), - help_text=_("Description (optional)"), + verbose_name=_('Description'), + help_text=_('Description (optional)'), ) icon = models.CharField( blank=True, max_length=100, - verbose_name=_("Icon"), - help_text=_("Default icon for all locations that have no icon set (optional)"), + verbose_name=_('Icon'), + help_text=_('Default icon for all locations that have no icon set (optional)'), ) @@ -104,7 +104,7 @@ class StockLocationManager(TreeManager): - Joins the StockLocationType by default for speedier icon access """ - return super().get_queryset().select_related("location_type") + return super().get_queryset().select_related('location_type') class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree): @@ -145,9 +145,9 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree): custom_icon = models.CharField( blank=True, max_length=100, - verbose_name=_("Icon"), - help_text=_("Icon (optional)"), - db_column="icon", + verbose_name=_('Icon'), + help_text=_('Icon (optional)'), + db_column='icon', ) owner = models.ForeignKey( @@ -178,11 +178,11 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree): location_type = models.ForeignKey( StockLocationType, on_delete=models.SET_NULL, - verbose_name=_("Location type"), - related_name="stock_locations", + verbose_name=_('Location type'), + related_name='stock_locations', null=True, blank=True, - help_text=_("Stock location type of this location"), + help_text=_('Stock location type of this location'), ) @property @@ -197,7 +197,7 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree): if self.location_type: return self.location_type.icon - return "" + return '' @icon.setter def icon(self, value): @@ -250,8 +250,8 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree): if self.pk and self.structural and self.stock_item_count(False) > 0: raise ValidationError( _( - "You cannot make this stock location structural because some stock items " - "are already located into it!" + 'You cannot make this stock location structural because some stock items ' + 'are already located into it!' ) ) super().clean() @@ -614,7 +614,7 @@ class StockItem( if self.location is not None and self.location.structural: raise ValidationError({ 'location': _( - "Stock items cannot be located into structural stock locations!" + 'Stock items cannot be located into structural stock locations!' ) }) @@ -644,7 +644,7 @@ class StockItem( # Virtual parts cannot have stock items created against them if self.part.virtual: raise ValidationError({ - 'part': _("Stock item cannot be created for virtual parts") + 'part': _('Stock item cannot be created for virtual parts') }) except PartModels.Part.DoesNotExist: # For some reason the 'clean' process sometimes throws errors because self.part does not exist @@ -703,7 +703,7 @@ class StockItem( # If the item is marked as "is_building", it must point to a build! if self.is_building and not self.build: raise ValidationError({ - 'build': _("Item must have a build reference if is_building=True") + 'build': _('Item must have a build reference if is_building=True') }) # If the item points to a build, check that the Part references match @@ -716,7 +716,7 @@ class StockItem( pass else: raise ValidationError({ - 'build': _("Build reference does not point to the same part object") + 'build': _('Build reference does not point to the same part object') }) def get_absolute_url(self): @@ -793,8 +793,8 @@ class StockItem( blank=True, limit_choices_to={'is_customer': True}, related_name='assigned_stock', - help_text=_("Customer"), - verbose_name=_("Customer"), + help_text=_('Customer'), + verbose_name=_('Customer'), ) serial = models.CharField( @@ -808,7 +808,7 @@ class StockItem( serial_int = models.IntegerField(default=0) link = InvenTreeURLField( - verbose_name=_('External Link'), blank=True, help_text=_("Link to external URL") + verbose_name=_('External Link'), blank=True, help_text=_('Link to external URL') ) batch = models.CharField( @@ -821,7 +821,7 @@ class StockItem( ) quantity = models.DecimalField( - verbose_name=_("Stock Quantity"), + verbose_name=_('Stock Quantity'), max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], @@ -863,7 +863,7 @@ class StockItem( sales_order = models.ForeignKey( 'order.SalesOrder', on_delete=models.SET_NULL, - verbose_name=_("Destination Sales Order"), + verbose_name=_('Destination Sales Order'), related_name='stock_items', null=True, blank=True, @@ -1454,32 +1454,32 @@ class StockItem( return if not self.part.trackable: - raise ValidationError({"part": _("Part is not set as trackable")}) + raise ValidationError({'part': _('Part is not set as trackable')}) # Quantity must be a valid integer value try: quantity = int(quantity) except ValueError: - raise ValidationError({"quantity": _("Quantity must be integer")}) + raise ValidationError({'quantity': _('Quantity must be integer')}) if quantity <= 0: - raise ValidationError({"quantity": _("Quantity must be greater than zero")}) + raise ValidationError({'quantity': _('Quantity must be greater than zero')}) if quantity > self.quantity: raise ValidationError({ - "quantity": _( - f"Quantity must not exceed available stock quantity ({self.quantity})" + 'quantity': _( + f'Quantity must not exceed available stock quantity ({self.quantity})' ) }) if type(serials) not in [list, tuple]: raise ValidationError({ - "serial_numbers": _("Serial numbers must be a list of integers") + 'serial_numbers': _('Serial numbers must be a list of integers') }) if quantity != len(serials): raise ValidationError({ - "quantity": _("Quantity does not match serial numbers") + 'quantity': _('Quantity does not match serial numbers') }) # Test if each of the serial numbers are valid @@ -1487,8 +1487,8 @@ class StockItem( if len(existing) > 0: exists = ','.join([str(x) for x in existing]) - msg = _("Serial numbers already exist") + f": {exists}" - raise ValidationError({"serial_numbers": msg}) + msg = _('Serial numbers already exist') + f': {exists}' + raise ValidationError({'serial_numbers': msg}) # Create a new stock item for each unique serial number for serial in serials: @@ -1570,7 +1570,7 @@ class StockItem( raise ValidationError(_('Stock item is currently in production')) if self.serialized: - raise ValidationError(_("Serialized stock cannot be merged")) + raise ValidationError(_('Serialized stock cannot be merged')) if other: # Specific checks (rely on the 'other' part) @@ -1581,7 +1581,7 @@ class StockItem( # Base part must match if self.part != other.part: - raise ValidationError(_("Stock items must refer to the same part")) + raise ValidationError(_('Stock items must refer to the same part')) # Check if supplier part references match if ( @@ -1589,12 +1589,12 @@ class StockItem( and not allow_mismatched_suppliers ): raise ValidationError( - _("Stock items must refer to the same supplier part") + _('Stock items must refer to the same supplier part') ) # Check if stock status codes match if self.status != other.status and not allow_mismatched_status: - raise ValidationError(_("Stock status codes must match")) + raise ValidationError(_('Stock status codes must match')) except ValidationError as e: if raise_error: @@ -1781,7 +1781,7 @@ class StockItem( return False if not self.in_stock: - raise ValidationError(_("StockItem cannot be moved as it is not in stock")) + raise ValidationError(_('StockItem cannot be moved as it is not in stock')) if quantity <= 0: return False @@ -1957,7 +1957,7 @@ class StockItem( s += f' @ {self.location.name}' if self.purchase_order: - s += f" ({self.purchase_order})" + s += f' ({self.purchase_order})' return s @@ -2182,7 +2182,7 @@ class StockItemAttachment(InvenTreeAttachment): def getSubdir(self): """Override attachment location.""" - return os.path.join("stock_files", str(self.stock_item.id)) + return os.path.join('stock_files', str(self.stock_item.id)) stock_item = models.ForeignKey( StockItem, on_delete=models.CASCADE, related_name='attachments' @@ -2297,13 +2297,13 @@ class StockItemTestResult(MetadataMixin, models.Model): if template.requires_value: if not self.value: raise ValidationError({ - "value": _("Value must be provided for this test") + 'value': _('Value must be provided for this test') }) if template.requires_attachment: if not self.attachment: raise ValidationError({ - "attachment": _("Attachment must be uploaded for this test") + 'attachment': _('Attachment must be uploaded for this test') }) break @@ -2341,7 +2341,7 @@ class StockItemTestResult(MetadataMixin, models.Model): ) notes = models.CharField( - blank=True, max_length=500, verbose_name=_('Notes'), help_text=_("Test notes") + blank=True, max_length=500, verbose_name=_('Notes'), help_text=_('Test notes') ) user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index 952b7b63ef..f01b71f954 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -115,7 +115,7 @@ class StockItemSerializerBrief(InvenTree.serializers.InvenTreeModelSerializer): def validate_serial(self, value): """Make sure serial is not to big.""" if abs(extract_int(value)) > 0x7FFFFFFF: - raise serializers.ValidationError(_("Serial number is too large")) + raise serializers.ValidationError(_('Serial number is too large')) return value @@ -196,8 +196,8 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer): queryset=part_models.Part.objects.all(), many=False, allow_null=False, - help_text=_("Base Part"), - label=_("Part"), + help_text=_('Base Part'), + label=_('Part'), ) location_path = serializers.ListField( @@ -212,15 +212,15 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer): required=False, allow_null=True, help_text=_( - "Use pack size when adding: the quantity defined is the number of packs" + 'Use pack size when adding: the quantity defined is the number of packs' ), - label=("Use pack size"), + label=('Use pack size'), ) def validate_part(self, part): """Ensure the provided Part instance is valid""" if part.virtual: - raise ValidationError(_("Stock item cannot be created for virtual parts")) + raise ValidationError(_('Stock item cannot be created for virtual parts')) return part @@ -391,12 +391,12 @@ class SerializeStockItemSerializer(serializers.Serializer): item = self.context['item'] if quantity < 0: - raise ValidationError(_("Quantity must be greater than zero")) + raise ValidationError(_('Quantity must be greater than zero')) if quantity > item.quantity: q = item.quantity raise ValidationError( - _(f"Quantity must not exceed available stock quantity ({q})") + _(f'Quantity must not exceed available stock quantity ({q})') ) return quantity @@ -420,8 +420,8 @@ class SerializeStockItemSerializer(serializers.Serializer): notes = serializers.CharField( required=False, allow_blank=True, - label=_("Notes"), - help_text=_("Optional note field"), + label=_('Notes'), + help_text=_('Optional note field'), ) def validate(self, data): @@ -431,7 +431,7 @@ class SerializeStockItemSerializer(serializers.Serializer): item = self.context['item'] if not item.part.trackable: - raise ValidationError(_("Serial numbers cannot be assigned to this part")) + raise ValidationError(_('Serial numbers cannot be assigned to this part')) # Ensure the serial numbers are valid! quantity = data['quantity'] @@ -448,7 +448,7 @@ class SerializeStockItemSerializer(serializers.Serializer): if len(existing) > 0: exists = ','.join([str(x) for x in existing]) - error = _('Serial numbers already exist') + ": " + exists + error = _('Serial numbers already exist') + ': ' + exists raise ValidationError({'serial_numbers': error}) @@ -508,7 +508,7 @@ class InstallStockItemSerializer(serializers.Serializer): """Validate the quantity value.""" if quantity < 1: - raise ValidationError(_("Quantity to install must be at least 1")) + raise ValidationError(_('Quantity to install must be at least 1')) return quantity @@ -516,14 +516,14 @@ class InstallStockItemSerializer(serializers.Serializer): """Validate the selected stock item.""" if not stock_item.in_stock: # StockItem must be in stock to be "installed" - raise ValidationError(_("Stock item is unavailable")) + raise ValidationError(_('Stock item is unavailable')) 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 not parent_part.check_if_part_in_bom(stock_item.part): - raise ValidationError(_("Selected part is not in the Bill of Materials")) + raise ValidationError(_('Selected part is not in the Bill of Materials')) return stock_item @@ -536,7 +536,7 @@ class InstallStockItemSerializer(serializers.Serializer): if quantity > stock_item.quantity: raise ValidationError( - _("Quantity to install must not exceed available quantity") + _('Quantity to install must not exceed available quantity') ) return data @@ -619,7 +619,7 @@ class ConvertStockItemSerializer(serializers.Serializer): if part not in valid_options: raise ValidationError( - _("Selected part is not a valid option for conversion") + _('Selected part is not a valid option for conversion') ) return part @@ -635,7 +635,7 @@ class ConvertStockItemSerializer(serializers.Serializer): if stock_item.supplier_part is not None: raise ValidationError( - _("Cannot convert stock item with assigned SupplierPart") + _('Cannot convert stock item with assigned SupplierPart') ) return data @@ -709,7 +709,7 @@ class StockChangeStatusSerializer(serializers.Serializer): def validate_items(self, items): """Validate the selected stock items""" if len(items) == 0: - raise ValidationError(_("No stock items selected")) + raise ValidationError(_('No stock items selected')) return items @@ -784,16 +784,16 @@ class StockLocationTypeSerializer(InvenTree.serializers.InvenTreeModelSerializer """Serializer metaclass.""" model = StockLocationType - fields = ["pk", "name", "description", "icon", "location_count"] + fields = ['pk', 'name', 'description', 'icon', 'location_count'] - read_only_fields = ["location_count"] + read_only_fields = ['location_count'] location_count = serializers.IntegerField(read_only=True) @staticmethod def annotate_queryset(queryset): """Add location count to each location type.""" - return queryset.annotate(location_count=Count("stock_locations")) + return queryset.annotate(location_count=Count('stock_locations')) class LocationTreeSerializer(InvenTree.serializers.InvenTreeModelSerializer): @@ -870,7 +870,7 @@ class LocationSerializer(InvenTree.serializers.InvenTreeTagModelSerializer): # Detail for location type location_type_detail = StockLocationTypeSerializer( - source="location_type", read_only=True, many=False + source='location_type', read_only=True, many=False ) @@ -967,19 +967,19 @@ class StockAssignmentItemSerializer(serializers.Serializer): """ # The item must currently be "in stock" if not item.in_stock: - raise ValidationError(_("Item must be in stock")) + raise ValidationError(_('Item must be in stock')) # The base part must be "salable" if not item.part.salable: - raise ValidationError(_("Part must be salable")) + raise ValidationError(_('Part must be salable')) # The item must not be allocated to a sales order if item.sales_order_allocations.count() > 0: - raise ValidationError(_("Item is allocated to a sales order")) + raise ValidationError(_('Item is allocated to a sales order')) # The item must not be allocated to a build order if item.allocations.count() > 0: - raise ValidationError(_("Item is allocated to a build order")) + raise ValidationError(_('Item is allocated to a build order')) return item @@ -1027,7 +1027,7 @@ class StockAssignmentSerializer(serializers.Serializer): items = data.get('items', []) if len(items) == 0: - raise ValidationError(_("A list of stock items must be provided")) + raise ValidationError(_('A list of stock items must be provided')) return data @@ -1262,8 +1262,8 @@ class StockAdjustmentSerializer(serializers.Serializer): notes = serializers.CharField( required=False, allow_blank=True, - label=_("Notes"), - help_text=_("Stock transaction notes"), + label=_('Notes'), + help_text=_('Stock transaction notes'), ) def validate(self, data): @@ -1273,7 +1273,7 @@ class StockAdjustmentSerializer(serializers.Serializer): items = data.get('items', []) if len(items) == 0: - raise ValidationError(_("A list of stock items must be provided")) + raise ValidationError(_('A list of stock items must be provided')) return data diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 6070ff4ab9..349e4574e9 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -222,7 +222,7 @@ class StockLocationTest(StockAPITestCase): for jj in range(3): stock_items.append( StockItem.objects.create( - batch=f"Batch xyz {jj}", + batch=f'Batch xyz {jj}', location=stock_location_to_delete, part=part, ) @@ -233,8 +233,8 @@ class StockLocationTest(StockAPITestCase): # Create sub location under the stock location to be deleted for ii in range(3): child = StockLocation.objects.create( - name=f"Sub-location {ii}", - description="A sub-location of the deleted stock location", + name=f'Sub-location {ii}', + description='A sub-location of the deleted stock location', parent=stock_location_to_delete, ) child_stock_locations.append(child) @@ -243,7 +243,7 @@ class StockLocationTest(StockAPITestCase): for jj in range(3): child_stock_locations_items.append( StockItem.objects.create( - batch=f"B xyz {jj}", part=part, location=child + batch=f'B xyz {jj}', part=part, location=child ) ) @@ -314,7 +314,7 @@ class StockLocationTest(StockAPITestCase): # Make sure that we get an error if we try to create a stock item in the structural location with self.assertRaises(ValidationError): item = StockItem.objects.create( - batch="Stock item which shall not be created", + batch='Stock item which shall not be created', location=structural_location, ) @@ -339,7 +339,7 @@ class StockLocationTest(StockAPITestCase): # Create the test stock item located to a non-structural category item = StockItem.objects.create( - batch="BBB", location=non_structural_location, part=part + batch='BBB', location=non_structural_location, part=part ) # Try to relocate it to a structural location @@ -358,99 +358,99 @@ class StockLocationTest(StockAPITestCase): def test_stock_location_icon(self): """Test stock location icon inheritance from StockLocationType.""" - parent_location = StockLocation.objects.create(name="Parent location") + parent_location = StockLocation.objects.create(name='Parent location') location_type = StockLocationType.objects.create( - name="Box", description="This is a very cool type of box", icon="fas fa-box" + name='Box', description='This is a very cool type of box', icon='fas fa-box' ) location = StockLocation.objects.create( - name="Test location", - custom_icon="fas fa-microscope", + name='Test location', + custom_icon='fas fa-microscope', location_type=location_type, parent=parent_location, ) res = self.get( - self.list_url, {"parent": str(parent_location.pk)}, expected_code=200 + self.list_url, {'parent': str(parent_location.pk)}, expected_code=200 ).json() self.assertEqual( - res[0]["icon"], - "fas fa-microscope", - "Custom icon from location should be returned", + res[0]['icon'], + 'fas fa-microscope', + 'Custom icon from location should be returned', ) - location.custom_icon = "" + location.custom_icon = '' location.save() res = self.get( - self.list_url, {"parent": str(parent_location.pk)}, expected_code=200 + self.list_url, {'parent': str(parent_location.pk)}, expected_code=200 ).json() self.assertEqual( - res[0]["icon"], - "fas fa-box", - "Custom icon is None, therefore it should inherit the location type icon", + res[0]['icon'], + 'fas fa-box', + 'Custom icon is None, therefore it should inherit the location type icon', ) - location_type.icon = "" + location_type.icon = '' location_type.save() res = self.get( - self.list_url, {"parent": str(parent_location.pk)}, expected_code=200 + self.list_url, {'parent': str(parent_location.pk)}, expected_code=200 ).json() self.assertEqual( - res[0]["icon"], - "", - "Custom icon and location type icon is None, None should be returned", + res[0]['icon'], + '', + 'Custom icon and location type icon is None, None should be returned', ) def test_stock_location_list_filter(self): """Test stock location list filters.""" - parent_location = StockLocation.objects.create(name="Parent location") + parent_location = StockLocation.objects.create(name='Parent location') location_type = StockLocationType.objects.create( - name="Box", description="This is a very cool type of box", icon="fas fa-box" + name='Box', description='This is a very cool type of box', icon='fas fa-box' ) location_type2 = StockLocationType.objects.create( - name="Shelf", - description="This is a very cool type of shelf", - icon="fas fa-shapes", + name='Shelf', + description='This is a very cool type of shelf', + icon='fas fa-shapes', ) StockLocation.objects.create( - name="Test location w. type", + name='Test location w. type', location_type=location_type, parent=parent_location, ) StockLocation.objects.create( - name="Test location w. type 2", + name='Test location w. type 2', parent=parent_location, location_type=location_type2, ) StockLocation.objects.create( - name="Test location wo type", parent=parent_location + name='Test location wo type', parent=parent_location ) res = self.get( self.list_url, - {"parent": str(parent_location.pk), "has_location_type": "1"}, + {'parent': str(parent_location.pk), 'has_location_type': '1'}, expected_code=200, ).json() self.assertEqual(len(res), 2) - self.assertEqual(res[0]["name"], "Test location w. type") - self.assertEqual(res[1]["name"], "Test location w. type 2") + self.assertEqual(res[0]['name'], 'Test location w. type') + self.assertEqual(res[1]['name'], 'Test location w. type 2') res = self.get( self.list_url, - {"parent": str(parent_location.pk), "location_type": str(location_type.pk)}, + {'parent': str(parent_location.pk), 'location_type': str(location_type.pk)}, expected_code=200, ).json() self.assertEqual(len(res), 1) - self.assertEqual(res[0]["name"], "Test location w. type") + self.assertEqual(res[0]['name'], 'Test location w. type') res = self.get( self.list_url, - {"parent": str(parent_location.pk), "has_location_type": "0"}, + {'parent': str(parent_location.pk), 'has_location_type': '0'}, expected_code=200, ).json() self.assertEqual(len(res), 1) - self.assertEqual(res[0]["name"], "Test location wo type") + self.assertEqual(res[0]['name'], 'Test location wo type') class StockLocationTypeTest(StockAPITestCase): @@ -462,31 +462,31 @@ class StockLocationTypeTest(StockAPITestCase): """Test that the list endpoint works as expected.""" location_types = [ StockLocationType.objects.create( - name="Type 1", description="Type 1 desc", icon="fas fa-box" + name='Type 1', description='Type 1 desc', icon='fas fa-box' ), StockLocationType.objects.create( - name="Type 2", description="Type 2 desc", icon="fas fa-box" + name='Type 2', description='Type 2 desc', icon='fas fa-box' ), StockLocationType.objects.create( - name="Type 3", description="Type 3 desc", icon="fas fa-box" + name='Type 3', description='Type 3 desc', icon='fas fa-box' ), ] - StockLocation.objects.create(name="Loc 1", location_type=location_types[0]) - StockLocation.objects.create(name="Loc 2", location_type=location_types[0]) - StockLocation.objects.create(name="Loc 3", location_type=location_types[1]) + StockLocation.objects.create(name='Loc 1', location_type=location_types[0]) + StockLocation.objects.create(name='Loc 2', location_type=location_types[0]) + StockLocation.objects.create(name='Loc 3', location_type=location_types[1]) res = self.get(self.list_url, expected_code=200).json() self.assertEqual(len(res), 3) - self.assertCountEqual([r["location_count"] for r in res], [2, 1, 0]) + self.assertCountEqual([r['location_count'] for r in res], [2, 1, 0]) def test_delete(self): """Test that we can delete a location type via API.""" location_type = StockLocationType.objects.create( - name="Type 1", description="Type 1 desc", icon="fas fa-box" + name='Type 1', description='Type 1 desc', icon='fas fa-box' ) self.delete( - reverse('api-location-type-detail', kwargs={"pk": location_type.pk}), + reverse('api-location-type-detail', kwargs={'pk': location_type.pk}), expected_code=204, ) self.assertEqual(StockLocationType.objects.count(), 0) @@ -495,24 +495,24 @@ class StockLocationTypeTest(StockAPITestCase): """Test that we can create a location type via API.""" self.post( self.list_url, - {"name": "Test Type 1", "description": "Test desc 1", "icon": "fas fa-box"}, + {'name': 'Test Type 1', 'description': 'Test desc 1', 'icon': 'fas fa-box'}, expected_code=201, ) self.assertIsNotNone( - StockLocationType.objects.filter(name="Test Type 1").first() + StockLocationType.objects.filter(name='Test Type 1').first() ) def test_update(self): """Test that we can update a location type via API.""" location_type = StockLocationType.objects.create( - name="Type 1", description="Type 1 desc", icon="fas fa-box" + name='Type 1', description='Type 1 desc', icon='fas fa-box' ) res = self.patch( - reverse('api-location-type-detail', kwargs={"pk": location_type.pk}), - {"icon": "fas fa-shapes"}, + reverse('api-location-type-detail', kwargs={'pk': location_type.pk}), + {'icon': 'fas fa-shapes'}, expected_code=200, ).json() - self.assertEqual(res["icon"], "fas fa-shapes") + self.assertEqual(res['icon'], 'fas fa-shapes') class StockItemListTest(StockAPITestCase): @@ -573,7 +573,7 @@ class StockItemListTest(StockAPITestCase): def test_filter_by_ipn(self): """Filter StockItem by IPN reference.""" - response = self.get_stock(IPN="R.CH") + response = self.get_stock(IPN='R.CH') self.assertEqual(len(response), 3) def test_filter_by_location(self): @@ -850,30 +850,30 @@ class StockItemListTest(StockAPITestCase): # 3 items when just filtering by part response = self.get( - url, {"part": component.pk, "in_stock": True}, expected_code=200 + url, {'part': component.pk, 'in_stock': True}, expected_code=200 ) self.assertEqual(len(response.data), 3) # 1 item when filtering by "not allocated" response = self.get( url, - {"part": component.pk, "in_stock": True, "allocated": False}, + {'part': component.pk, 'in_stock': True, 'allocated': False}, expected_code=200, ) self.assertEqual(len(response.data), 1) - self.assertEqual(response.data[0]["pk"], stock_3.pk) + self.assertEqual(response.data[0]['pk'], stock_3.pk) # 2 items when filtering by "allocated" response = self.get( url, - {"part": component.pk, "in_stock": True, "allocated": True}, + {'part': component.pk, 'in_stock': True, 'allocated': True}, expected_code=200, ) self.assertEqual(len(response.data), 2) - ids = [item["pk"] for item in response.data] + ids = [item['pk'] for item in response.data] self.assertIn(stock_1.pk, ids) self.assertIn(stock_2.pk, ids) @@ -1253,7 +1253,7 @@ class StockItemTest(StockAPITestCase): # Now, try to install an item which *is* in the BOM for the parent part response = self.post( url, - {'stock_item': sub_item.pk, 'note': "This time, it should be good!"}, + {'stock_item': sub_item.pk, 'note': 'This time, it should be good!'}, expected_code=201, ) @@ -1333,8 +1333,8 @@ class StockItemTest(StockAPITestCase): for color in ['Red', 'Green', 'Blue', 'Yellow', 'Pink', 'Black']: variants.append( part.models.Part.objects.create( - name=f"{color} Variant", - description="Variant part with a specific color", + name=f'{color} Variant', + description='Variant part with a specific color', variant_of=master_part, category=category, ) @@ -1416,7 +1416,7 @@ class StocktakeTest(StockAPITestCase): # POST with a valid action response = self.post(url, data) - self.assertIn("This field is required", str(response.data["items"])) + self.assertIn('This field is required', str(response.data['items'])) data['items'] = [{'no': 'aa'}] @@ -1456,7 +1456,7 @@ class StocktakeTest(StockAPITestCase): status_code=status.HTTP_400_BAD_REQUEST, ) - data['items'] = [{'pk': 1234, 'quantity': "-1.234"}] + data['items'] = [{'pk': 1234, 'quantity': '-1.234'}] response = self.post(url, data) self.assertContains( @@ -1470,7 +1470,7 @@ class StocktakeTest(StockAPITestCase): data = { 'items': [{'pk': 1234, 'quantity': 10}], 'location': 1, - 'notes': "Moving to a new location", + 'notes': 'Moving to a new location', } url = reverse('api-stock-transfer') @@ -1602,7 +1602,7 @@ class StockTestResultTest(StockAPITestCase): 'result': False, 'value': '150kPa', 'notes': 'I guess there was just too much pressure?', - "attachment": bitmap, + 'attachment': bitmap, } response = self.client.post(self.get_url(), data) @@ -1625,7 +1625,7 @@ class StockTestResultTest(StockAPITestCase): url, { 'stock_item': 1, - 'test': f"Some test {_ii}", + 'test': f'Some test {_ii}', 'result': True, 'value': 'Test result value', }, diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index b88ec9679d..8b44c9774d 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -54,10 +54,10 @@ class StockTest(StockTestBase): def test_pathstring(self): """Check that pathstring updates occur as expected""" - a = StockLocation.objects.create(name="A") - b = StockLocation.objects.create(name="B", parent=a) - c = StockLocation.objects.create(name="C", parent=b) - d = StockLocation.objects.create(name="D", parent=c) + a = StockLocation.objects.create(name='A') + b = StockLocation.objects.create(name='B', parent=a) + c = StockLocation.objects.create(name='C', parent=b) + d = StockLocation.objects.create(name='D', parent=c) def refresh(): a.refresh_from_db() @@ -66,56 +66,56 @@ class StockTest(StockTestBase): d.refresh_from_db() # Initial checks - self.assertEqual(a.pathstring, "A") - self.assertEqual(b.pathstring, "A/B") - self.assertEqual(c.pathstring, "A/B/C") - self.assertEqual(d.pathstring, "A/B/C/D") + self.assertEqual(a.pathstring, 'A') + self.assertEqual(b.pathstring, 'A/B') + self.assertEqual(c.pathstring, 'A/B/C') + self.assertEqual(d.pathstring, 'A/B/C/D') - c.name = "Cc" + c.name = 'Cc' c.save() refresh() - self.assertEqual(a.pathstring, "A") - self.assertEqual(b.pathstring, "A/B") - self.assertEqual(c.pathstring, "A/B/Cc") - self.assertEqual(d.pathstring, "A/B/Cc/D") + self.assertEqual(a.pathstring, 'A') + self.assertEqual(b.pathstring, 'A/B') + self.assertEqual(c.pathstring, 'A/B/Cc') + self.assertEqual(d.pathstring, 'A/B/Cc/D') - b.name = "Bb" + b.name = 'Bb' b.save() refresh() - self.assertEqual(a.pathstring, "A") - self.assertEqual(b.pathstring, "A/Bb") - self.assertEqual(c.pathstring, "A/Bb/Cc") - self.assertEqual(d.pathstring, "A/Bb/Cc/D") + self.assertEqual(a.pathstring, 'A') + self.assertEqual(b.pathstring, 'A/Bb') + self.assertEqual(c.pathstring, 'A/Bb/Cc') + self.assertEqual(d.pathstring, 'A/Bb/Cc/D') - a.name = "Aa" + a.name = 'Aa' a.save() refresh() - self.assertEqual(a.pathstring, "Aa") - self.assertEqual(b.pathstring, "Aa/Bb") - self.assertEqual(c.pathstring, "Aa/Bb/Cc") - self.assertEqual(d.pathstring, "Aa/Bb/Cc/D") + self.assertEqual(a.pathstring, 'Aa') + self.assertEqual(b.pathstring, 'Aa/Bb') + self.assertEqual(c.pathstring, 'Aa/Bb/Cc') + self.assertEqual(d.pathstring, 'Aa/Bb/Cc/D') - d.name = "Dd" + d.name = 'Dd' d.save() refresh() - self.assertEqual(a.pathstring, "Aa") - self.assertEqual(b.pathstring, "Aa/Bb") - self.assertEqual(c.pathstring, "Aa/Bb/Cc") - self.assertEqual(d.pathstring, "Aa/Bb/Cc/Dd") + self.assertEqual(a.pathstring, 'Aa') + self.assertEqual(b.pathstring, 'Aa/Bb') + self.assertEqual(c.pathstring, 'Aa/Bb/Cc') + self.assertEqual(d.pathstring, 'Aa/Bb/Cc/Dd') # Test a really long name # (it will be clipped to < 250 characters) - a.name = "A" * 100 + a.name = 'A' * 100 a.save() - b.name = "B" * 100 + b.name = 'B' * 100 b.save() - c.name = "C" * 100 + c.name = 'C' * 100 c.save() - d.name = "D" * 100 + d.name = 'D' * 100 d.save() refresh() @@ -124,8 +124,8 @@ class StockTest(StockTestBase): self.assertEqual(len(c.pathstring), 249) self.assertEqual(len(d.pathstring), 249) - self.assertTrue(d.pathstring.startswith("AAAAAAAA")) - self.assertTrue(d.pathstring.endswith("DDDDDDDD")) + self.assertTrue(d.pathstring.startswith('AAAAAAAA')) + self.assertTrue(d.pathstring.endswith('DDDDDDDD')) def test_link(self): """Test the link URL field validation""" @@ -460,8 +460,8 @@ class StockTest(StockTestBase): it = StockItem.objects.get(pk=2) n = it.quantity an = n - 10 - customer = Company.objects.create(name="MyTestCompany") - order = SalesOrder.objects.create(description="Test order") + customer = Company.objects.create(name='MyTestCompany') + order = SalesOrder.objects.create(description='Test order') ait = it.allocateToCustomer( customer, quantity=an, order=order, user=None, notes='Allocated some stock' ) @@ -491,18 +491,18 @@ class StockTest(StockTestBase): # First establish total stock for this part allstock_before = StockItem.objects.filter(part=it.part).aggregate( - Sum("quantity") - )["quantity__sum"] + Sum('quantity') + )['quantity__sum'] n = it.quantity an = n - 10 - customer = Company.objects.create(name="MyTestCompany") - order = SalesOrder.objects.create(description="Test order") + customer = Company.objects.create(name='MyTestCompany') + order = SalesOrder.objects.create(description='Test order') ait = it.allocateToCustomer( customer, quantity=an, order=order, user=None, notes='Allocated some stock' ) - ait.return_from_customer(it.location, None, notes="Stock removed from customer") + ait.return_from_customer(it.location, None, notes='Stock removed from customer') # When returned stock is returned to its original (parent) location, check that the parent has correct quantity self.assertEqual(it.quantity, n) @@ -511,7 +511,7 @@ class StockTest(StockTestBase): customer, quantity=an, order=order, user=None, notes='Allocated some stock' ) ait.return_from_customer( - self.drawer3, None, notes="Stock removed from customer" + self.drawer3, None, notes='Stock removed from customer' ) # Check correct assignment of the new location @@ -533,8 +533,8 @@ class StockTest(StockTestBase): # Establish total stock for the part after remove from customer to check that we still have the correct quantity in stock allstock_after = StockItem.objects.filter(part=it.part).aggregate( - Sum("quantity") - )["quantity__sum"] + Sum('quantity') + )['quantity__sum'] self.assertEqual(allstock_before, allstock_after) def test_take_stock(self): @@ -621,7 +621,7 @@ class StockTest(StockTestBase): self.assertEqual(item.serial_int, 12345) - item.serial = "-123" + item.serial = '-123' item.save() # Negative number should map to positive value @@ -685,14 +685,14 @@ class StockTest(StockTestBase): # Try an invalid quantity with self.assertRaises(ValidationError): - item.serializeStock("k", [], self.user) + item.serializeStock('k', [], self.user) with self.assertRaises(ValidationError): item.serializeStock(-1, [], self.user) # Not enough serial numbers for all stock items. with self.assertRaises(ValidationError): - item.serializeStock(3, "hello", self.user) + item.serializeStock(3, 'hello', self.user) def test_serialize_stock_valid(self): """Perform valid stock serializations.""" @@ -996,7 +996,7 @@ class TestResultTest(StockTestBase): tests = item.test_results self.assertEqual(tests.count(), 4) - results = item.getTestResults(test="Temperature Test") + results = item.getTestResults(test='Temperature Test') self.assertEqual(results.count(), 2) # Passing tests @@ -1057,25 +1057,25 @@ class TestResultTest(StockTestBase): item.quantity = 50 # Try with an invalid batch code (according to sample validatoin plugin) - item.batch = "X234" + item.batch = 'X234' with self.assertRaises(ValidationError): item.save() - item.batch = "B123" + item.batch = 'B123' item.save() # Do some tests! StockItemTestResult.objects.create( - stock_item=item, test="Firmware", result=True + stock_item=item, test='Firmware', result=True ) StockItemTestResult.objects.create( - stock_item=item, test="Paint Color", result=True, value="Red" + stock_item=item, test='Paint Color', result=True, value='Red' ) StockItemTestResult.objects.create( - stock_item=item, test="Applied Sticker", result=False + stock_item=item, test='Applied Sticker', result=False ) self.assertEqual(item.test_results.count(), 3) diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index 67629b381e..f7758206a9 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -247,9 +247,9 @@ class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover # If any, display warning message when group is saved if len(multiple_group_users) > 0: msg = ( - _("The following users are members of multiple groups") - + ": " - + ", ".join(multiple_group_users) + _('The following users are members of multiple groups') + + ': ' + + ', '.join(multiple_group_users) ) messages.add_message(request, messages.WARNING, msg) diff --git a/InvenTree/users/apps.py b/InvenTree/users/apps.py index 0719369218..facdf2f15a 100644 --- a/InvenTree/users/apps.py +++ b/InvenTree/users/apps.py @@ -43,7 +43,7 @@ class UsersConfig(AppConfig): if ( rule.name not in RuleSet.RULESET_NAMES ): # pragma: no cover # can not change ORM without the app being loaded - logger.info("Deleting outdated ruleset: %s", rule.name) + logger.info('Deleting outdated ruleset: %s', rule.name) rule.delete() # Update group permission assignments for all groups diff --git a/InvenTree/users/authentication.py b/InvenTree/users/authentication.py index 659fafcc48..aad1cfdd5b 100644 --- a/InvenTree/users/authentication.py +++ b/InvenTree/users/authentication.py @@ -26,10 +26,10 @@ class ApiTokenAuthentication(TokenAuthentication): (user, token) = super().authenticate_credentials(key) if token.revoked: - raise exceptions.AuthenticationFailed(_("Token has been revoked")) + raise exceptions.AuthenticationFailed(_('Token has been revoked')) if token.expired: - raise exceptions.AuthenticationFailed(_("Token has expired")) + raise exceptions.AuthenticationFailed(_('Token has expired')) if token.last_seen != datetime.date.today(): # Update the last-seen date diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 1d8753ef4d..8b40192915 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -26,7 +26,7 @@ import InvenTree.helpers import InvenTree.models from InvenTree.ready import canAppAccessDatabase -logger = logging.getLogger("inventree") +logger = logging.getLogger('inventree') # OVERRIDE START @@ -41,7 +41,7 @@ def user_model_str(self): return self.username -User.add_to_class("__str__", user_model_str) # Overriding User.__str__ +User.add_to_class('__str__', user_model_str) # Overriding User.__str__ # OVERRIDE END @@ -445,7 +445,7 @@ class RuleSet(models.Model): """Construct the correctly formatted permission string, given the app_model name, and the permission type.""" model, app = split_model(model) - return f"{app}.{permission}_{model}" + return f'{app}.{permission}_{model}' def __str__(self, debug=False): # pragma: no cover """Ruleset string representation.""" @@ -525,7 +525,7 @@ def update_group_roles(group, debug=False): # and create a simplified permission key string for p in group.permissions.all().prefetch_related('content_type'): (permission, app, model) = p.natural_key() - permission_string = f"{app}.{permission}" + permission_string = f'{app}.{permission}' group_permissions.add(permission_string) # List of permissions which must be added to the group @@ -543,7 +543,7 @@ def update_group_roles(group, debug=False): allowed: Whether or not the action is allowed """ if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover - raise ValueError(f"Action {action} is invalid") + raise ValueError(f'Action {action} is invalid') permission_string = RuleSet.get_model_permission_string(model, action) @@ -624,7 +624,7 @@ def update_group_roles(group, debug=False): group.permissions.add(permission) if debug: # pragma: no cover - logger.debug("Adding permission %s to group %s", perm, group.name) + logger.debug('Adding permission %s to group %s', perm, group.name) # Remove any extra permissions from the group for perm in permissions_to_delete: @@ -638,7 +638,7 @@ def update_group_roles(group, debug=False): group.permissions.remove(permission) if debug: # pragma: no cover - logger.debug("Removing permission %s from group %s", perm, group.name) + logger.debug('Removing permission %s from group %s', perm, group.name) # Enable all action permissions for certain children models # if parent model has 'change' permission @@ -661,7 +661,7 @@ def update_group_roles(group, debug=False): if permission: group.permissions.add(permission) logger.debug( - "Adding permission %s to group %s", child_perm, group.name + 'Adding permission %s to group %s', child_perm, group.name ) @@ -675,7 +675,7 @@ def clear_user_role_cache(user): """ for role in RuleSet.RULESET_MODELS.keys(): for perm in ['add', 'change', 'view', 'delete']: - key = f"role_{user}_{role}_{perm}" + key = f'role_{user}_{role}_{perm}' cache.delete(key) @@ -707,7 +707,7 @@ def check_user_role(user, role, permission): return True # First, check the cache - key = f"role_{user}_{role}_{permission}" + key = f'role_{user}_{role}_{permission}' result = cache.get(key) diff --git a/InvenTree/users/tests.py b/InvenTree/users/tests.py index 21ff21805b..76eee1c0f6 100644 --- a/InvenTree/users/tests.py +++ b/InvenTree/users/tests.py @@ -21,27 +21,27 @@ class RuleSetModelTest(TestCase): missing = [name for name in RuleSet.RULESET_NAMES if name not in keys] if len(missing) > 0: # pragma: no cover - print("The following rulesets do not have models assigned:") + print('The following rulesets do not have models assigned:') for m in missing: - print("-", m) + print('-', m) # Check if models have been defined for a ruleset which is incorrect extra = [name for name in keys if name not in RuleSet.RULESET_NAMES] if len(extra) > 0: # pragma: no cover print( - "The following rulesets have been improperly added to RULESET_MODELS:" + 'The following rulesets have been improperly added to RULESET_MODELS:' ) for e in extra: - print("-", e) + print('-', e) # Check that each ruleset has models assigned empty = [key for key in keys if len(RuleSet.RULESET_MODELS[key]) == 0] if len(empty) > 0: # pragma: no cover - print("The following rulesets have empty entries in RULESET_MODELS:") + print('The following rulesets have empty entries in RULESET_MODELS:') for e in empty: - print("-", e) + print('-', e) self.assertEqual(len(missing), 0) self.assertEqual(len(extra), 0) @@ -78,10 +78,10 @@ class RuleSetModelTest(TestCase): if len(missing_models) > 0: # pragma: no cover print( - "The following database models are not covered by the defined RuleSet permissions:" + 'The following database models are not covered by the defined RuleSet permissions:' ) for m in missing_models: - print("-", m) + print('-', m) extra_models = set() @@ -98,9 +98,9 @@ class RuleSetModelTest(TestCase): extra_models.add(model) if len(extra_models) > 0: # pragma: no cover - print("The following RuleSet permissions do not match a database model:") + print('The following RuleSet permissions do not match a database model:') for m in extra_models: - print("-", m) + print('-', m) self.assertEqual(len(missing_models), 0) self.assertEqual(len(extra_models), 0) @@ -108,7 +108,7 @@ class RuleSetModelTest(TestCase): def test_permission_assign(self): """Test that the permission assigning works!""" # Create a new group - group = Group.objects.create(name="Test group") + group = Group.objects.create(name='Test group') rulesets = group.rule_sets.all() diff --git a/InvenTree/web/templatetags/spa_helper.py b/InvenTree/web/templatetags/spa_helper.py index 9b2e09be45..003682402a 100644 --- a/InvenTree/web/templatetags/spa_helper.py +++ b/InvenTree/web/templatetags/spa_helper.py @@ -9,7 +9,7 @@ from django import template from django.conf import settings from django.utils.safestring import mark_safe -logger = getLogger("InvenTree") +logger = getLogger('InvenTree') register = template.Library() FRONTEND_SETTINGS = json.dumps(settings.FRONTEND_SETTINGS) @@ -21,36 +21,36 @@ def spa_bundle(manifest_path: Union[str, Path] = '', app: str = 'web'): def get_url(file: str) -> str: """Get static url for file.""" - return f"{settings.STATIC_URL}{app}/{file}" + return f'{settings.STATIC_URL}{app}/{file}' if manifest_path == '': manifest_path = Path(__file__).parent.parent.joinpath( - "static/web/manifest.json" + 'static/web/manifest.json' ) manifest = Path(manifest_path) if not manifest.exists(): - logger.error("Manifest file not found") + logger.error('Manifest file not found') return try: manifest_data = json.load(manifest.open()) except (TypeError, json.decoder.JSONDecodeError): - logger.exception("Failed to parse manifest file") + logger.exception('Failed to parse manifest file') return - return_string = "" + return_string = '' # CSS (based on index.css file as entrypoint) - css_index = manifest_data.get("index.css") + css_index = manifest_data.get('index.css') if css_index: return_string += ( f'' ) # JS (based on index.html file as entrypoint) - index = manifest_data.get("index.html") - dynamic_files = index.get("dynamicImports", []) - imports_files = "".join([ + index = manifest_data.get('index.html') + dynamic_files = index.get('dynamicImports', []) + imports_files = ''.join([ f'' for file in dynamic_files ]) diff --git a/InvenTree/web/tests.py b/InvenTree/web/tests.py index 0419741564..1975bfd3f2 100644 --- a/InvenTree/web/tests.py +++ b/InvenTree/web/tests.py @@ -31,7 +31,7 @@ class TemplateTagTest(InvenTreeTestCase): self.assertTrue(len(shipped_js) > 0) self.assertTrue(len(shipped_js) == 3) - manifest_file = Path(__file__).parent.joinpath("static/web/manifest.json") + manifest_file = Path(__file__).parent.joinpath('static/web/manifest.json') # Try with removed manifest file manifest_file.rename(manifest_file.with_suffix('.json.bak')) # Rename resp = resp = spa_helper.spa_bundle() diff --git a/InvenTree/web/urls.py b/InvenTree/web/urls.py index 02d24a8033..16cdb57f40 100644 --- a/InvenTree/web/urls.py +++ b/InvenTree/web/urls.py @@ -17,23 +17,23 @@ class RedirectAssetView(TemplateView): ) -spa_view = ensure_csrf_cookie(TemplateView.as_view(template_name="web/index.html")) -assets_path = path("assets/", RedirectAssetView.as_view()) +spa_view = ensure_csrf_cookie(TemplateView.as_view(template_name='web/index.html')) +assets_path = path('assets/', RedirectAssetView.as_view()) urlpatterns = [ path( - f"{settings.FRONTEND_URL_BASE}/", + f'{settings.FRONTEND_URL_BASE}/', include([ assets_path, path( - "set-password?uid=&token=", + 'set-password?uid=&token=', spa_view, - name="password_reset_confirm", + name='password_reset_confirm', ), - re_path(".*", spa_view), + re_path('.*', spa_view), ]), ), assets_path, - path(settings.FRONTEND_URL_BASE, spa_view, name="platform"), + path(settings.FRONTEND_URL_BASE, spa_view, name='platform'), ] diff --git a/ci/check_api_endpoint.py b/ci/check_api_endpoint.py index ef263ee9f1..b216fce32f 100644 --- a/ci/check_api_endpoint.py +++ b/ci/check_api_endpoint.py @@ -5,15 +5,15 @@ import json import requests # We expect the server to be running on the local host -url = "http://localhost:8000/api/" +url = 'http://localhost:8000/api/' -print("Testing InvenTree API endpoint") +print('Testing InvenTree API endpoint') response = requests.get(url) assert response.status_code == 200 -print("- Response 200 OK") +print('- Response 200 OK') data = json.loads(response.text) @@ -26,6 +26,6 @@ for key in required_keys: # Check that the worker is running assert data['worker_running'] -print("- Background worker is operational") +print('- Background worker is operational') -print("API Endpoint Tests Passed OK") +print('API Endpoint Tests Passed OK') diff --git a/ci/check_js_templates.py b/ci/check_js_templates.py index f51a89a243..3c3cee8e48 100644 --- a/ci/check_js_templates.py +++ b/ci/check_js_templates.py @@ -18,14 +18,14 @@ js_dynamic_dir = os.path.join(template_dir, 'js', 'dynamic') errors = 0 -print("=================================") -print("Checking static javascript files:") -print("=================================") +print('=================================') +print('Checking static javascript files:') +print('=================================') def check_invalid_tag(data): """Check for invalid tags.""" - pattern = r"{%(\w+)" + pattern = r'{%(\w+)' err_count = 0 @@ -35,7 +35,7 @@ def check_invalid_tag(data): for result in results: err_count += 1 - print(f" - Error on line {idx+1}: %{{{result[0]}") + print(f' - Error on line {idx+1}: %{{{result[0]}') return err_count @@ -55,7 +55,7 @@ def check_prohibited_tags(data): 'url', ] - pattern = r"{% (\w+)\s" + pattern = r'{% (\w+)\s' err_count = 0 @@ -94,9 +94,9 @@ for filename in pathlib.Path(js_dynamic_dir).rglob('*.js'): if len(results) > 0: errors += 1 - print(f" > prohibited {{% trans %}} tag found at line {idx + 1}") + print(f' > prohibited {{% trans %}} tag found at line {idx + 1}') if errors > 0: - print(f"Found {errors} incorrect template tags") + print(f'Found {errors} incorrect template tags') sys.exit(errors) diff --git a/ci/check_locale_files.py b/ci/check_locale_files.py index 3bfbd66943..d5e2b89fbe 100644 --- a/ci/check_locale_files.py +++ b/ci/check_locale_files.py @@ -3,7 +3,7 @@ import subprocess import sys -print("Checking for uncommitted locale files...") +print('Checking for uncommitted locale files...') cmd = ['git', 'status'] @@ -19,9 +19,9 @@ for line in str(out.decode()).split('\n'): locales.append(line) if len(locales) > 0: - print("There are {n} unstaged locale files:".format(n=len(locales))) + print('There are {n} unstaged locale files:'.format(n=len(locales))) for lang in locales: - print(" - {l}".format(l=lang)) + print(' - {l}'.format(l=lang)) sys.exit(len(locales)) diff --git a/ci/check_migration_files.py b/ci/check_migration_files.py index 24668350be..d224848f02 100644 --- a/ci/check_migration_files.py +++ b/ci/check_migration_files.py @@ -3,7 +3,7 @@ import subprocess import sys -print("Checking for unstaged migration files...") +print('Checking for unstaged migration files...') cmd = ['git', 'ls-files', '--exclude-standard', '--others'] @@ -20,9 +20,9 @@ for line in str(out.decode()).split('\n'): if len(migrations) == 0: sys.exit(0) -print("There are {n} unstaged migration files:".format(n=len(migrations))) +print('There are {n} unstaged migration files:'.format(n=len(migrations))) for m in migrations: - print(" - {m}".format(m=m)) + print(' - {m}'.format(m=m)) sys.exit(len(migrations)) diff --git a/ci/version_check.py b/ci/version_check.py index 39aa52bbe0..2c18da0880 100644 --- a/ci/version_check.py +++ b/ci/version_check.py @@ -26,7 +26,7 @@ def get_existing_release_tags(): headers = None if token: - headers = {"Authorization": f"Bearer {token}"} + headers = {'Authorization': f'Bearer {token}'} response = requests.get( 'https://api.github.com/repos/inventree/inventree/releases', headers=headers @@ -44,7 +44,7 @@ def get_existing_release_tags(): for release in data: tag = release['tag_name'].strip() - match = re.match(r"^.*(\d+)\.(\d+)\.(\d+).*$", tag) + match = re.match(r'^.*(\d+)\.(\d+)\.(\d+).*$', tag) if len(match.groups()) != 3: print(f"Version '{tag}' did not match expected pattern") @@ -64,7 +64,7 @@ def check_version_number(version_string, allow_duplicate=False): print(f"Checking version '{version_string}'") # Check that the version string matches the required format - match = re.match(r"^(\d+)\.(\d+)\.(\d+)(?: dev)?$", version_string) + match = re.match(r'^(\d+)\.(\d+)\.(\d+)(?: dev)?$', version_string) if not match or len(match.groups()) != 3: raise ValueError( @@ -85,7 +85,7 @@ def check_version_number(version_string, allow_duplicate=False): if release > version_tuple: highest_release = False - print(f"Found newer release: {str(release)}") + print(f'Found newer release: {str(release)}') return highest_release @@ -104,10 +104,10 @@ if __name__ == '__main__': GITHUB_BASE_REF = os.environ['GITHUB_BASE_REF'] # Print out version information, makes debugging actions *much* easier! - print(f"GITHUB_REF: {GITHUB_REF}") - print(f"GITHUB_REF_NAME: {GITHUB_REF_NAME}") - print(f"GITHUB_REF_TYPE: {GITHUB_REF_TYPE}") - print(f"GITHUB_BASE_REF: {GITHUB_BASE_REF}") + print(f'GITHUB_REF: {GITHUB_REF}') + print(f'GITHUB_REF_NAME: {GITHUB_REF_NAME}') + print(f'GITHUB_REF_TYPE: {GITHUB_REF_TYPE}') + print(f'GITHUB_BASE_REF: {GITHUB_BASE_REF}') version_file = os.path.join(here, '..', 'InvenTree', 'InvenTree', 'version.py') @@ -120,7 +120,7 @@ if __name__ == '__main__': results = re.findall(r'INVENTREE_SW_VERSION = "(.*)"', text) if len(results) != 1: - print(f"Could not find INVENTREE_SW_VERSION in {version_file}") + print(f'Could not find INVENTREE_SW_VERSION in {version_file}') sys.exit(1) version = results[0] @@ -164,15 +164,15 @@ if __name__ == '__main__': docker_tags = ['latest'] else: - print("Unsupported branch / version combination:") - print(f"InvenTree Version: {version}") - print("GITHUB_REF_TYPE:", GITHUB_REF_TYPE) - print("GITHUB_BASE_REF:", GITHUB_BASE_REF) - print("GITHUB_REF:", GITHUB_REF) + print('Unsupported branch / version combination:') + print(f'InvenTree Version: {version}') + print('GITHUB_REF_TYPE:', GITHUB_REF_TYPE) + print('GITHUB_BASE_REF:', GITHUB_BASE_REF) + print('GITHUB_REF:', GITHUB_REF) sys.exit(1) if docker_tags is None: - print("Docker tags could not be determined") + print('Docker tags could not be determined') sys.exit(1) print(f"Version check passed for '{version}'!") @@ -181,9 +181,9 @@ if __name__ == '__main__': # Ref: https://getridbug.com/python/how-to-set-environment-variables-in-github-actions-using-python/ with open(os.getenv('GITHUB_ENV'), 'a') as env_file: # Construct tag string - tags = ",".join([f"inventree/inventree:{tag}" for tag in docker_tags]) + tags = ','.join([f'inventree/inventree:{tag}' for tag in docker_tags]) - env_file.write(f"docker_tags={tags}\n") + env_file.write(f'docker_tags={tags}\n') if GITHUB_REF_TYPE == 'tag' and highest_release: - env_file.write("stable_release=true\n") + env_file.write('stable_release=true\n') diff --git a/docker/gunicorn.conf.py b/docker/gunicorn.conf.py index 256bd5c0b7..6d36a74269 100644 --- a/docker/gunicorn.conf.py +++ b/docker/gunicorn.conf.py @@ -33,7 +33,7 @@ if workers is not None: if workers is None: workers = multiprocessing.cpu_count() * 2 + 1 -logger.info("Starting gunicorn server with %s workers", workers) +logger.info('Starting gunicorn server with %s workers', workers) max_requests = 1000 max_requests_jitter = 50 diff --git a/docs/docs/hooks.py b/docs/docs/hooks.py index 9e4a5c4d97..6b33829c20 100644 --- a/docs/docs/hooks.py +++ b/docs/docs/hooks.py @@ -11,7 +11,7 @@ import requests def fetch_rtd_versions(): """Get a list of RTD docs versions to build the version selector""" - print("Fetching documentation versions from ReadTheDocs") + print('Fetching documentation versions from ReadTheDocs') versions = [] @@ -20,7 +20,7 @@ def fetch_rtd_versions(): response = requests.get(url, headers=headers) if response.status_code != 200: - print(f"Error fetching RTD versions: {response.status_code}") + print(f'Error fetching RTD versions: {response.status_code}') return data = json.loads(response.text) @@ -48,10 +48,10 @@ def fetch_rtd_versions(): token = os.environ.get('RTD_TOKEN', None) if token: headers = {'Authorization': f'Token {token}'} - url = "https://readthedocs.org/api/v3/projects/inventree/versions/?active=true&limit=50" + url = 'https://readthedocs.org/api/v3/projects/inventree/versions/?active=true&limit=50' make_request(url, headers) else: - print("No RTD token found - skipping RTD version fetch") + print('No RTD token found - skipping RTD version fetch') # Sort versions by version number versions = sorted(versions, key=lambda x: StrictVersion(x['version']), reverse=True) @@ -79,7 +79,7 @@ def fetch_rtd_versions(): output_filename = os.path.join(os.path.dirname(__file__), 'versions.json') - print("Discovered the following versions:") + print('Discovered the following versions:') print(versions) with open(output_filename, 'w') as file: @@ -104,14 +104,14 @@ def get_release_data(): return json.loads(f.read()) # Download release information via the GitHub API - print("Fetching InvenTree release information from api.github.com:") + print('Fetching InvenTree release information from api.github.com:') releases = [] # Keep making API requests until we run out of results page = 1 while 1: - url = f"https://api.github.com/repos/inventree/inventree/releases?page={page}&per_page=150" + url = f'https://api.github.com/repos/inventree/inventree/releases?page={page}&per_page=150' response = requests.get(url, timeout=30) assert response.status_code == 200 @@ -163,12 +163,12 @@ def on_config(config, *args, **kwargs): rtd_version = os.environ['READTHEDOCS_VERSION'] rtd_language = os.environ['READTHEDOCS_LANGUAGE'] - site_url = f"https://docs.inventree.org/{rtd_language}/{rtd_version}" - assets_dir = f"/{rtd_language}/{rtd_version}/assets" + site_url = f'https://docs.inventree.org/{rtd_language}/{rtd_version}' + assets_dir = f'/{rtd_language}/{rtd_version}/assets' - print("Building within READTHEDOCS environment!") - print(f" - Version: {rtd_version}") - print(f" - Language: {rtd_language}") + print('Building within READTHEDOCS environment!') + print(f' - Version: {rtd_version}') + print(f' - Language: {rtd_language}') # Add *all* readthedocs related keys readthedocs = {} @@ -187,7 +187,7 @@ def on_config(config, *args, **kwargs): else: print("'READTHEDOCS' environment variable not found") - print("Building for localhost configuration!") + print('Building for localhost configuration!') assets_dir = '/assets' site_url = config['site_url'] @@ -215,7 +215,7 @@ def on_config(config, *args, **kwargs): re.match(r'^\d+\.\d+\.\d+$', tag) if not re.match: - print(f"Found badly formatted release: {tag}") + print(f'Found badly formatted release: {tag}') continue # Check if there is a local file with release information @@ -238,7 +238,7 @@ def on_config(config, *args, **kwargs): releases.append(item) - print(f"- found {len(releases)} releases.") + print(f'- found {len(releases)} releases.') # Sort releases by descending date config['releases'] = sorted(releases, key=lambda it: it['date'], reverse=True) diff --git a/pyproject.toml b/pyproject.toml index f3807470d3..7bc74bf2b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,7 +69,7 @@ known-first-party = ["src", "plugin", "InvenTree", "common"] "django" = ["django"] [tool.ruff.format] -quote-style = "preserve" +quote-style = "single" indent-style = "space" skip-magic-trailing-comma = true line-ending = "auto" diff --git a/tasks.py b/tasks.py index 63ad43ee04..b964397aea 100644 --- a/tasks.py +++ b/tasks.py @@ -22,7 +22,7 @@ def checkPythonVersion(): REQ_MAJOR = 3 REQ_MINOR = 9 - version = sys.version.split(" ")[0] + version = sys.version.split(' ')[0] valid = True @@ -33,8 +33,8 @@ def checkPythonVersion(): valid = False if not valid: - print(f"The installed python version ({version}) is not supported!") - print(f"InvenTree requires Python {REQ_MAJOR}.{REQ_MINOR} or above") + print(f'The installed python version ({version}) is not supported!') + print(f'InvenTree requires Python {REQ_MAJOR}.{REQ_MINOR} or above') sys.exit(1) @@ -62,26 +62,26 @@ def apps(): def content_excludes(): """Returns a list of content types to exclude from import/export.""" excludes = [ - "contenttypes", - "auth.permission", - "users.apitoken", - "error_report.error", - "admin.logentry", - "django_q.schedule", - "django_q.task", - "django_q.ormq", - "users.owner", - "exchange.rate", - "exchange.exchangebackend", - "common.notificationentry", - "common.notificationmessage", - "user_sessions.session", + 'contenttypes', + 'auth.permission', + 'users.apitoken', + 'error_report.error', + 'admin.logentry', + 'django_q.schedule', + 'django_q.task', + 'django_q.ormq', + 'users.owner', + 'exchange.rate', + 'exchange.exchangebackend', + 'common.notificationentry', + 'common.notificationmessage', + 'user_sessions.session', ] - output = "" + output = '' for e in excludes: - output += f"--exclude {e} " + output += f'--exclude {e} ' return output @@ -175,12 +175,12 @@ def check_file_existance(filename: str, overwrite: bool = False): """ if Path(filename).is_file() and overwrite is False: response = input( - "Warning: file already exists. Do you want to overwrite? [y/N]: " + 'Warning: file already exists. Do you want to overwrite? [y/N]: ' ) response = str(response).strip().lower() if response not in ['y', 'yes']: - print("Cancelled export operation") + print('Cancelled export operation') sys.exit(1) @@ -220,12 +220,12 @@ def setup_dev(c, tests=False): c.run('pip3 install -U -r requirements-dev.txt') # Install pre-commit hook - print("Installing pre-commit for checks before git commits...") + print('Installing pre-commit for checks before git commits...') c.run('pre-commit install') # Update all the hooks c.run('pre-commit autoupdate') - print("pre-commit set up is done...") + print('pre-commit set up is done...') # Set up test-data if flag is set if tests: @@ -242,19 +242,19 @@ def superuser(c): @task def rebuild_models(c): """Rebuild database models with MPTT structures.""" - manage(c, "rebuild_models", pty=True) + manage(c, 'rebuild_models', pty=True) @task def rebuild_thumbnails(c): """Rebuild missing image thumbnails.""" - manage(c, "rebuild_thumbnails", pty=True) + manage(c, 'rebuild_thumbnails', pty=True) @task def clean_settings(c): """Clean the setting tables of old settings.""" - manage(c, "clean_settings") + manage(c, 'clean_settings') @task(help={'mail': "mail of the user who's MFA should be disabled"}) @@ -263,16 +263,16 @@ def remove_mfa(c, mail=''): if not mail: print('You must provide a users mail') - manage(c, f"remove_mfa {mail}") + manage(c, f'remove_mfa {mail}') @task(help={'frontend': 'Build the frontend'}) def static(c, frontend=False): """Copies required static files to the STATIC_ROOT directory, as per Django requirements.""" - manage(c, "prerender") + manage(c, 'prerender') if frontend and node_available(): frontend_build(c) - manage(c, "collectstatic --no-input") + manage(c, 'collectstatic --no-input') @task @@ -286,7 +286,7 @@ def translate_stats(c): try: manage(c, 'compilemessages', pty=True) except Exception: - print("WARNING: Translation files could not be compiled:") + print('WARNING: Translation files could not be compiled:') path = Path('InvenTree', 'script', 'translation_stats.py') c.run(f'python3 {path}') @@ -300,8 +300,8 @@ def translate(c): it is performed as part of the InvenTree translation toolchain. """ # Translate applicable .py / .html / .js / .tsx files - manage(c, "makemessages --all -e py,html,js --no-wrap") - manage(c, "compilemessages") + manage(c, 'makemessages --all -e py,html,js --no-wrap') + manage(c, 'compilemessages') if node_available(): frontend_install(c) @@ -315,19 +315,19 @@ def translate(c): @task def backup(c): """Backup the database and media files.""" - print("Backing up InvenTree database...") - manage(c, "dbbackup --noinput --clean --compress") - print("Backing up InvenTree media files...") - manage(c, "mediabackup --noinput --clean --compress") + print('Backing up InvenTree database...') + manage(c, 'dbbackup --noinput --clean --compress') + print('Backing up InvenTree media files...') + manage(c, 'mediabackup --noinput --clean --compress') @task def restore(c): """Restore the database and media files.""" - print("Restoring InvenTree database...") - manage(c, "dbrestore --noinput --uncompress") - print("Restoring InvenTree media files...") - manage(c, "mediarestore --noinput --uncompress") + print('Restoring InvenTree database...') + manage(c, 'dbrestore --noinput --uncompress') + print('Restoring InvenTree media files...') + manage(c, 'mediarestore --noinput --uncompress') @task(post=[rebuild_models, rebuild_thumbnails]) @@ -336,16 +336,16 @@ def migrate(c): This is a critical step if the database schema have been altered! """ - print("Running InvenTree database migrations...") - print("========================================") + print('Running InvenTree database migrations...') + print('========================================') - manage(c, "makemigrations") - manage(c, "migrate --noinput") - manage(c, "migrate --run-syncdb") - manage(c, "check") + manage(c, 'makemigrations') + manage(c, 'migrate --noinput') + manage(c, 'migrate --run-syncdb') + manage(c, 'check') - print("========================================") - print("InvenTree database migrations completed!") + print('========================================') + print('InvenTree database migrations completed!') @task( @@ -399,9 +399,9 @@ def update(c, skip_backup=False, frontend: bool = False): @task( help={ 'filename': "Output filename (default = 'data.json')", - 'overwrite': "Overwrite existing files without asking first (default = off/False)", - 'include_permissions': "Include user and group permissions in the output file (filename) (default = off/False)", - 'delete_temp': "Delete temporary files (containing permissions) at end of run. Note that this will delete temporary files from previous runs as well. (default = off/False)", + 'overwrite': 'Overwrite existing files without asking first (default = off/False)', + 'include_permissions': 'Include user and group permissions in the output file (filename) (default = off/False)', + 'delete_temp': 'Delete temporary files (containing permissions) at end of run. Note that this will delete temporary files from previous runs as well. (default = off/False)', } ) def export_records( @@ -436,38 +436,38 @@ def export_records( check_file_existance(filename, overwrite) - tmpfile = f"{filename}.tmp" + tmpfile = f'{filename}.tmp' cmd = f"dumpdata --indent 2 --output '{tmpfile}' {content_excludes()}" # Dump data to temporary file manage(c, cmd, pty=True) - print("Running data post-processing step...") + print('Running data post-processing step...') # Post-process the file, to remove any "permissions" specified for a user or group - with open(tmpfile, "r") as f_in: + with open(tmpfile, 'r') as f_in: data = json.loads(f_in.read()) if include_permissions is False: for entry in data: - if "model" in entry: + if 'model' in entry: # Clear out any permissions specified for a group - if entry["model"] == "auth.group": - entry["fields"]["permissions"] = [] + if entry['model'] == 'auth.group': + entry['fields']['permissions'] = [] # Clear out any permissions specified for a user - if entry["model"] == "auth.user": - entry["fields"]["user_permissions"] = [] + if entry['model'] == 'auth.user': + entry['fields']['user_permissions'] = [] # Write the processed data to file - with open(filename, "w") as f_out: + with open(filename, 'w') as f_out: f_out.write(json.dumps(data, indent=2)) - print("Data export completed") + print('Data export completed') if delete_temp is True: - print("Removing temporary file") + print('Removing temporary file') os.remove(tmpfile) @@ -491,30 +491,30 @@ def import_records(c, filename='data.json', clear=False): print(f"Importing database records from '{filename}'") # Pre-process the data, to remove any "permissions" specified for a user or group - tmpfile = f"{filename}.tmp.json" + tmpfile = f'{filename}.tmp.json' - with open(filename, "r") as f_in: + with open(filename, 'r') as f_in: data = json.loads(f_in.read()) for entry in data: - if "model" in entry: + if 'model' in entry: # Clear out any permissions specified for a group - if entry["model"] == "auth.group": - entry["fields"]["permissions"] = [] + if entry['model'] == 'auth.group': + entry['fields']['permissions'] = [] # Clear out any permissions specified for a user - if entry["model"] == "auth.user": - entry["fields"]["user_permissions"] = [] + if entry['model'] == 'auth.user': + entry['fields']['user_permissions'] = [] # Write the processed data to the tmp file - with open(tmpfile, "w") as f_out: + with open(tmpfile, 'w') as f_out: f_out.write(json.dumps(data, indent=2)) cmd = f"loaddata '{tmpfile}' -i {content_excludes()}" manage(c, cmd, pty=True) - print("Data import completed") + print('Data import completed') @task @@ -523,7 +523,7 @@ def delete_data(c, force=False): Warning: This will REALLY delete all records in the database!! """ - print("Deleting all data from InvenTree database...") + print('Deleting all data from InvenTree database...') if force: manage(c, 'flush --noinput') @@ -576,16 +576,16 @@ def import_fixtures(c): @task def wait(c): """Wait until the database connection is ready.""" - return manage(c, "wait_for_db") + return manage(c, 'wait_for_db') @task(pre=[wait], help={'address': 'Server address:port (default=127.0.0.1:8000)'}) -def server(c, address="127.0.0.1:8000"): +def server(c, address='127.0.0.1:8000'): """Launch a (development) server using Django's in-built webserver. Note: This is *not* sufficient for a production installation. """ - manage(c, "runserver {address}".format(address=address), pty=True) + manage(c, 'runserver {address}'.format(address=address), pty=True) @task(pre=[wait]) @@ -598,7 +598,7 @@ def worker(c): @task def render_js_files(c): """Render templated javascript files (used for static testing).""" - manage(c, "test InvenTree.ci_render_js") + manage(c, 'test InvenTree.ci_render_js') @task(post=[translate_stats, static, server]) @@ -616,34 +616,34 @@ def test_translations(c): django.setup() # Add language - print("Add dummy language...") - print("========================================") - manage(c, "makemessages -e py,html,js --no-wrap -l xx") + print('Add dummy language...') + print('========================================') + manage(c, 'makemessages -e py,html,js --no-wrap -l xx') # change translation - print("Fill in dummy translations...") - print("========================================") + print('Fill in dummy translations...') + print('========================================') file_path = pathlib.Path(settings.LOCALE_PATHS[0], 'xx', 'LC_MESSAGES', 'django.po') new_file_path = str(file_path) + '_new' # compile regex reg = re.compile( - r"[a-zA-Z0-9]{1}" # match any single letter and number # noqa: W504 - + r"(?![^{\(\<]*[}\)\>])" # that is not inside curly brackets, brackets or a tag # noqa: W504 - + r"(?])' # that is not inside curly brackets, brackets or a tag # noqa: W504 + + r'(? replace regex matches with x in the read in (multi)string file_new.write(f'msgstr "{reg.sub("x", last_string[7:-2])}"\n') - last_string = "" # reset (multi)string + last_string = '' # reset (multi)string elif line.startswith('msgid "'): last_string = ( last_string + line @@ -661,9 +661,9 @@ def test_translations(c): new_file_path.rename(file_path) # compile languages - print("Compile languages ...") - print("========================================") - manage(c, "compilemessages") + print('Compile languages ...') + print('========================================') + manage(c, 'compilemessages') # reset cwd os.chdir(base_path) @@ -728,7 +728,7 @@ def test( @task(help={'dev': 'Set up development environment at the end'}) -def setup_test(c, ignore_update=False, dev=False, path="inventree-demo-dataset"): +def setup_test(c, ignore_update=False, dev=False, path='inventree-demo-dataset'): """Setup a testing environment.""" from InvenTree.InvenTree.config import get_media_dir @@ -737,31 +737,31 @@ def setup_test(c, ignore_update=False, dev=False, path="inventree-demo-dataset") # Remove old data directory if os.path.exists(path): - print("Removing old data ...") + print('Removing old data ...') c.run(f'rm {path} -r') # Get test data - print("Cloning demo dataset ...") + print('Cloning demo dataset ...') c.run(f'git clone https://github.com/inventree/demo-dataset {path} -v --depth=1') - print("========================================") + print('========================================') # Make sure migrations are done - might have just deleted sqlite database if not ignore_update: migrate(c) # Load data - print("Loading database records ...") + print('Loading database records ...') import_records(c, filename=f'{path}/inventree_data.json', clear=True) # Copy media files - print("Copying media files ...") + print('Copying media files ...') src = Path(path).joinpath('media').resolve() dst = get_media_dir() shutil.copytree(src, dst, dirs_exist_ok=True) - print("Done setting up test environment...") - print("========================================") + print('Done setting up test environment...') + print('========================================') # Set up development setup if flag is set if dev: @@ -771,7 +771,7 @@ def setup_test(c, ignore_update=False, dev=False, path="inventree-demo-dataset") @task( help={ 'filename': "Output filename (default = 'schema.yml')", - 'overwrite': "Overwrite existing files without asking first (default = off/False)", + 'overwrite': 'Overwrite existing files without asking first (default = off/False)', } ) def schema(c, filename='schema.yml', overwrite=False): @@ -850,8 +850,8 @@ def frontend_install(c): Args: c: Context variable """ - print("Installing frontend dependencies") - yarn(c, "yarn install") + print('Installing frontend dependencies') + yarn(c, 'yarn install') @task @@ -861,9 +861,9 @@ def frontend_trans(c): Args: c: Context variable """ - print("Compiling frontend translations") - yarn(c, "yarn run extract") - yarn(c, "yarn run compile") + print('Compiling frontend translations') + yarn(c, 'yarn run extract') + yarn(c, 'yarn run compile') @task @@ -873,8 +873,8 @@ def frontend_build(c): Args: c: Context variable """ - print("Building frontend") - yarn(c, "yarn run build --emptyOutDir") + print('Building frontend') + yarn(c, 'yarn run build --emptyOutDir') @task @@ -884,18 +884,18 @@ def frontend_dev(c): Args: c: Context variable """ - print("Starting frontend development server") - yarn(c, "yarn run dev") + print('Starting frontend development server') + yarn(c, 'yarn run dev') @task( help={ - 'ref': "git ref, default: current git ref", - 'tag': "git tag to look for release", - 'file': "destination to frontend-build.zip file", - 'repo': "GitHub repository, default: InvenTree/inventree", - 'extract': "Also extract and place at the correct destination, default: True", - 'clean': "Delete old files from InvenTree/web/static/web first, default: True", + 'ref': 'git ref, default: current git ref', + 'tag': 'git tag to look for release', + 'file': 'destination to frontend-build.zip file', + 'repo': 'GitHub repository, default: InvenTree/inventree', + 'extract': 'Also extract and place at the correct destination, default: True', + 'clean': 'Delete old files from InvenTree/web/static/web first, default: True', } ) def frontend_download( @@ -903,7 +903,7 @@ def frontend_download( ref=None, tag=None, file=None, - repo="InvenTree/inventree", + repo='InvenTree/inventree', extract=True, clean=True, ): @@ -928,7 +928,7 @@ def frontend_download( import requests # globals - default_headers = {"Accept": "application/vnd.github.v3+json"} + default_headers = {'Accept': 'application/vnd.github.v3+json'} # helper functions def find_resource(resource, key, value): @@ -942,34 +942,34 @@ def frontend_download( if not extract: return - dest_path = Path(__file__).parent / "InvenTree/web/static/web" + dest_path = Path(__file__).parent / 'InvenTree/web/static/web' # if clean, delete static/web directory if clean: shutil.rmtree(dest_path, ignore_errors=True) os.makedirs(dest_path) - print(f"Cleaned directory: {dest_path}") + print(f'Cleaned directory: {dest_path}') # unzip build to static folder - with ZipFile(file, "r") as zip_ref: + with ZipFile(file, 'r') as zip_ref: zip_ref.extractall(dest_path) - print(f"Unzipped downloaded frontend build to: {dest_path}") + print(f'Unzipped downloaded frontend build to: {dest_path}') def handle_download(url): # download frontend-build.zip to temporary file with requests.get( url, headers=default_headers, stream=True, allow_redirects=True - ) as response, NamedTemporaryFile(suffix=".zip") as dst: + ) as response, NamedTemporaryFile(suffix='.zip') as dst: response.raise_for_status() # auto decode the gzipped raw data response.raw.read = functools.partial( response.raw.read, decode_content=True ) - with open(dst.name, "wb") as f: + with open(dst.name, 'wb') as f: shutil.copyfileobj(response.raw, f) - print(f"Downloaded frontend build to temporary file: {dst.name}") + print(f'Downloaded frontend build to temporary file: {dst.name}') handle_extract(dst.name) @@ -980,26 +980,26 @@ def frontend_download( # check arguments if ref is not None and tag is not None: - print("[ERROR] Do not set ref and tag.") + print('[ERROR] Do not set ref and tag.') return if ref is None and tag is None: try: ref = subprocess.check_output( - ["git", "rev-parse", "HEAD"], encoding="utf-8" + ['git', 'rev-parse', 'HEAD'], encoding='utf-8' ).strip() except Exception: print("[ERROR] Cannot get current ref via 'git rev-parse HEAD'") return if ref is None and tag is None: - print("[ERROR] Either ref or tag needs to be set.") + print('[ERROR] Either ref or tag needs to be set.') if tag: - tag = tag.lstrip("v") + tag = tag.lstrip('v') try: handle_download( - f"https://github.com/{repo}/releases/download/{tag}/frontend-build.zip" + f'https://github.com/{repo}/releases/download/{tag}/frontend-build.zip' ) except Exception as e: if not isinstance(e, requests.HTTPError): @@ -1015,12 +1015,12 @@ Then try continuing by running: invoke frontend-download --file