diff --git a/InvenTree/InvenTree/apps.py b/InvenTree/InvenTree/apps.py index 80389bda95..18df00030a 100644 --- a/InvenTree/InvenTree/apps.py +++ b/InvenTree/InvenTree/apps.py @@ -28,7 +28,7 @@ class InvenTreeConfig(AppConfig): self.start_background_tasks() - if not isInTestMode(): + if not isInTestMode(): # pragma: no cover self.update_exchange_rates() if canAppAccessDatabase() or settings.TESTING_ENV: @@ -98,7 +98,7 @@ class InvenTreeConfig(AppConfig): schedule_type=Schedule.DAILY, ) - def update_exchange_rates(self): + def update_exchange_rates(self): # pragma: no cover """ Update exchange rates each time the server is started, *if*: diff --git a/InvenTree/InvenTree/ci_render_js.py b/InvenTree/InvenTree/ci_render_js.py index e747f1a3c0..94530db096 100644 --- a/InvenTree/InvenTree/ci_render_js.py +++ b/InvenTree/InvenTree/ci_render_js.py @@ -3,14 +3,14 @@ Pull rendered copies of the templated only used for testing the js files! - This file is omited from coverage """ -from django.test import TestCase -from django.contrib.auth import get_user_model +from django.test import TestCase # pragma: no cover +from django.contrib.auth import get_user_model # pragma: no cover -import os -import pathlib +import os # pragma: no cover +import pathlib # pragma: no cover -class RenderJavascriptFiles(TestCase): +class RenderJavascriptFiles(TestCase): # pragma: no cover """ A unit test to "render" javascript files. diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index 94665f9e07..7d65cb4231 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -95,7 +95,7 @@ def user_roles(request): } if user.is_superuser: - for ruleset in RuleSet.RULESET_MODELS.keys(): + for ruleset in RuleSet.RULESET_MODELS.keys(): # pragma: no cover roles[ruleset] = { 'view': True, 'add': True, diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 9d4039d4b1..c156d421ab 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -68,7 +68,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): import importlib from InvenTree.status import is_worker_running - if is_worker_running() and not force_sync: + if is_worker_running() and not force_sync: # pragma: no cover # Running as asynchronous task try: task = AsyncTask(taskname, *args, **kwargs) @@ -94,13 +94,13 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): # Retrieve function try: _func = getattr(_mod, func) - except AttributeError: + except AttributeError: # pragma: no cover # getattr does not work for local import _func = None try: if not _func: - _func = eval(func) + _func = eval(func) # pragma: no cover except NameError: logger.warning(f"WARNING: '{taskname}' not started - No function named '{func}'") return @@ -248,7 +248,7 @@ def update_exchange_rates(): # Apps not yet loaded! logger.info("Could not perform 'update_exchange_rates' - App registry not ready") return - except: + except: # pragma: no cover # Other error? return @@ -257,7 +257,7 @@ def update_exchange_rates(): backend = ExchangeBackend.objects.get(name='InvenTreeExchange') except ExchangeBackend.DoesNotExist: pass - except: + except: # pragma: no cover # Some other error logger.warning("update_exchange_rates: Database not ready") return @@ -274,7 +274,7 @@ def update_exchange_rates(): # Remove any exchange rates which are not in the provided currencies Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete() - except Exception as e: + except Exception as e: # pragma: no cover logger.error(f"Error updating exchange rates: {e}") diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index f89a8b073d..669628bdea 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -17,6 +17,7 @@ from . import helpers from . import version from . import status from . import ready +from . import config from decimal import Decimal @@ -24,6 +25,7 @@ import InvenTree.tasks from stock.models import StockLocation from common.settings import currency_codes +from common.models import InvenTreeSetting class ValidatorTest(TestCase): @@ -453,3 +455,55 @@ class TestSettings(TestCase): # make sure to clean up settings.TESTING_ENV = False + + def test_helpers_cfg_file(self): + # normal run - not configured + self.assertIn('InvenTree/InvenTree/config.yaml', config.get_config_file()) + + # with env set + with self.env: + self.env.set('INVENTREE_CONFIG_FILE', 'my_special_conf.yaml') + self.assertIn('InvenTree/InvenTree/my_special_conf.yaml', config.get_config_file()) + + def test_helpers_plugin_file(self): + # normal run - not configured + self.assertIn('InvenTree/InvenTree/plugins.txt', config.get_plugin_file()) + + # with env set + with self.env: + self.env.set('INVENTREE_PLUGIN_FILE', 'my_special_plugins.txt') + self.assertIn('my_special_plugins.txt', config.get_plugin_file()) + + def test_helpers_setting(self): + TEST_ENV_NAME = '123TEST' + # check that default gets returned if not present + self.assertEqual(config.get_setting(TEST_ENV_NAME, None, '123!'), '123!') + + # with env set + with self.env: + self.env.set(TEST_ENV_NAME, '321') + self.assertEqual(config.get_setting(TEST_ENV_NAME, None), '321') + + +class TestInstanceName(TestCase): + """ + Unit tests for instance name + """ + + def setUp(self): + # Create a user for auth + user = get_user_model() + self.user = user.objects.create_superuser('testuser', 'test@testing.com', 'password') + + self.client.login(username='testuser', password='password') + + def test_instance_name(self): + + # default setting + 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) + + self.assertEqual(version.inventreeInstanceTitle(), 'Testing title') diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 19373e930c..85dc275690 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -12,11 +12,14 @@ import common.models INVENTREE_SW_VERSION = "0.7.0 dev" # InvenTree API version -INVENTREE_API_VERSION = 30 +INVENTREE_API_VERSION = 31 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v31 -> 2022-03-14 + - Adds "updated" field to SupplierPriceBreakList and SupplierPriceBreakDetail API endpoints + v30 -> 2022-03-09 - Adds "exclude_location" field to BuildAutoAllocation API endpoint - Allows BuildItem API endpoint to be filtered by BomItem relation @@ -171,7 +174,7 @@ def inventreeDocsVersion(): if isInvenTreeDevelopmentVersion(): return "latest" else: - return INVENTREE_SW_VERSION + return INVENTREE_SW_VERSION # pragma: no cover def isInvenTreeUpToDate(): @@ -189,10 +192,10 @@ def isInvenTreeUpToDate(): return True # Extract "tuple" version (Python can directly compare version tuples) - latest_version = inventreeVersionTuple(latest) - inventree_version = inventreeVersionTuple() + latest_version = inventreeVersionTuple(latest) # pragma: no cover + inventree_version = inventreeVersionTuple() # pragma: no cover - return inventree_version >= latest_version + return inventree_version >= latest_version # pragma: no cover def inventreeApiVersion(): @@ -209,7 +212,7 @@ def inventreeCommitHash(): try: return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip() - except: + except: # pragma: no cover return None @@ -219,5 +222,5 @@ def inventreeCommitDate(): try: d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip() return d.split(' ')[0] - except: + except: # pragma: no cover return None diff --git a/InvenTree/InvenTree/wsgi.py b/InvenTree/InvenTree/wsgi.py index c6bef4d663..9c4d72edbc 100644 --- a/InvenTree/InvenTree/wsgi.py +++ b/InvenTree/InvenTree/wsgi.py @@ -7,10 +7,10 @@ For more information on this file, see https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ """ -import os +import os # pragma: no cover -from django.core.wsgi import get_wsgi_application +from django.core.wsgi import get_wsgi_application # pragma: no cover -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "InvenTree.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "InvenTree.settings") # pragma: no cover -application = get_wsgi_application() +application = get_wsgi_application() # pragma: no cover diff --git a/InvenTree/barcodes/plugins/inventree_barcode.py b/InvenTree/barcodes/plugins/inventree_barcode.py index 1b451f0286..5df71cb776 100644 --- a/InvenTree/barcodes/plugins/inventree_barcode.py +++ b/InvenTree/barcodes/plugins/inventree_barcode.py @@ -47,7 +47,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): except json.JSONDecodeError: return False else: - return False + return False # pragma: no cover # If any of the following keys are in the JSON data, # let's go ahead and assume that the code is a valid InvenTree one... @@ -70,10 +70,10 @@ class InvenTreeBarcodePlugin(BarcodePlugin): # Initially try casting to an integer try: pk = int(data) - except (TypeError, ValueError): + except (TypeError, ValueError): # pragma: no cover pk = None - if pk is None: + if pk is None: # pragma: no cover try: pk = self.data[k]['id'] except (AttributeError, KeyError): @@ -82,7 +82,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): try: item = StockItem.objects.get(pk=pk) return item - except (ValueError, StockItem.DoesNotExist): + except (ValueError, StockItem.DoesNotExist): # pragma: no cover raise ValidationError({k, "Stock item does not exist"}) return None @@ -97,10 +97,10 @@ class InvenTreeBarcodePlugin(BarcodePlugin): # First try simple integer lookup try: pk = int(self.data[k]) - except (TypeError, ValueError): + except (TypeError, ValueError): # pragma: no cover pk = None - if pk is None: + if pk is None: # pragma: no cover # Lookup by 'id' field try: pk = self.data[k]['id'] @@ -110,7 +110,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): try: loc = StockLocation.objects.get(pk=pk) return loc - except (ValueError, StockLocation.DoesNotExist): + except (ValueError, StockLocation.DoesNotExist): # pragma: no cover raise ValidationError({k, "Stock location does not exist"}) return None @@ -125,10 +125,10 @@ class InvenTreeBarcodePlugin(BarcodePlugin): # Try integer lookup first try: pk = int(self.data[k]) - except (TypeError, ValueError): + except (TypeError, ValueError): # pragma: no cover pk = None - if pk is None: + if pk is None: # pragma: no cover try: pk = self.data[k]['id'] except (AttributeError, KeyError): @@ -137,7 +137,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): try: part = Part.objects.get(pk=pk) return part - except (ValueError, Part.DoesNotExist): + except (ValueError, Part.DoesNotExist): # pragma: no cover raise ValidationError({k, 'Part does not exist'}) return None diff --git a/InvenTree/barcodes/tests.py b/InvenTree/barcodes/tests.py index c9a063e8f0..18d4e77d20 100644 --- a/InvenTree/barcodes/tests.py +++ b/InvenTree/barcodes/tests.py @@ -38,8 +38,18 @@ class BarcodeAPITest(APITestCase): def test_invalid(self): + # test scan url response = self.client.post(self.scan_url, format='json', data={}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + # test wrong assign urls + response = self.client.post(self.assign_url, format='json', data={}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + response = self.client.post(self.assign_url, format='json', data={'barcode': '123'}) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + response = self.client.post(self.assign_url, format='json', data={'barcode': '123', 'stockitem': '123'}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test_empty(self): @@ -204,3 +214,57 @@ class BarcodeAPITest(APITestCase): self.assertIn('error', data) self.assertNotIn('success', data) + + +class TestInvenTreeBarcode(APITestCase): + + fixtures = [ + 'category', + 'part', + 'location', + 'stock' + ] + + def setUp(self): + # Create a user for auth + user = get_user_model() + user.objects.create_user('testuser', 'test@testing.com', 'password') + + self.client.login(username='testuser', password='password') + + def test_errors(self): + """ + Test all possible error cases for assigment action + """ + + def test_assert_error(barcode_data): + response = self.client.post( + reverse('api-barcode-link'), format='json', + data={ + 'barcode': barcode_data, + 'stockitem': 521 + } + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('error', response.data) + + # test with already existing stock + test_assert_error('{"stockitem": 521}') + + # test with already existing stock location + test_assert_error('{"stocklocation": 7}') + + # test with already existing part location + test_assert_error('{"part": 10004}') + + # test with hash + test_assert_error('{"blbla": 10004}') + + def test_scan(self): + """ + Test that a barcode can be scanned + """ + + response = self.client.post(reverse('api-barcode-scan'), format='json', data={'barcode': 'blbla=10004'}) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('success', response.data) diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index 3ec0e32da1..91c872a642 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -12,7 +12,7 @@ class SettingsAdmin(ImportExportModelAdmin): list_display = ('key', 'value') - def get_readonly_fields(self, request, obj=None): + def get_readonly_fields(self, request, obj=None): # pragma: no cover """ Prevent the 'key' field being edited once the setting is created """ @@ -27,7 +27,7 @@ class UserSettingsAdmin(ImportExportModelAdmin): list_display = ('key', 'value', 'user', ) - def get_readonly_fields(self, request, obj=None): + def get_readonly_fields(self, request, obj=None): # pragma: no cover """ Prevent the 'key' field being edited once the setting is created """ diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 2b84918e82..22afb80664 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -570,7 +570,7 @@ class BaseInvenTreeSetting(models.Model): try: value = int(self.value) except (ValueError, TypeError): - value = self.default_value() + value = self.default_value return value diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py index 80f80b886f..6973f9ecd3 100644 --- a/InvenTree/common/settings.py +++ b/InvenTree/common/settings.py @@ -18,12 +18,12 @@ def currency_code_default(): try: code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') - except ProgrammingError: + except ProgrammingError: # pragma: no cover # database is not initialized yet code = '' if code not in CURRENCIES: - code = 'USD' + code = 'USD' # pragma: no cover return code diff --git a/InvenTree/common/test_tasks.py b/InvenTree/common/test_tasks.py new file mode 100644 index 0000000000..4a3da9c028 --- /dev/null +++ b/InvenTree/common/test_tasks.py @@ -0,0 +1,18 @@ + +# -*- coding: utf-8 -*- +from django.test import TestCase + +from common.models import NotificationEntry +from InvenTree.tasks import offload_task + + +class TaskTest(TestCase): + """ + Tests for common tasks + """ + + def test_delete(self): + + # check empty run + self.assertEqual(NotificationEntry.objects.all().count(), 0) + offload_task('common.tasks.delete_old_notifications',) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 1dd5bc03bc..77a4f1a12d 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -6,7 +6,9 @@ from datetime import timedelta from django.test import TestCase, Client from django.contrib.auth import get_user_model +from django.urls import reverse +from InvenTree.api_tester import InvenTreeAPITestCase from .models import InvenTreeSetting, WebhookEndpoint, WebhookMessage, NotificationEntry from .api import WebhookView @@ -46,6 +48,67 @@ class SettingsTest(TestCase): # Check object lookup (case insensitive) self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 1) + def test_settings_functions(self): + """ + Test settings functions and properties + """ + # define settings to check + instance_ref = 'INVENTREE_INSTANCE' + instance_obj = InvenTreeSetting.get_setting_object(instance_ref) + + stale_ref = 'STOCK_STALE_DAYS' + stale_days = InvenTreeSetting.get_setting_object(stale_ref) + + report_size_obj = InvenTreeSetting.get_setting_object('REPORT_DEFAULT_PAGE_SIZE') + report_test_obj = InvenTreeSetting.get_setting_object('REPORT_ENABLE_TEST_REPORT') + + # check settings base fields + self.assertEqual(instance_obj.name, 'InvenTree Instance Name') + self.assertEqual(instance_obj.get_setting_name(instance_ref), 'InvenTree Instance Name') + self.assertEqual(instance_obj.description, 'String descriptor for the server instance') + self.assertEqual(instance_obj.get_setting_description(instance_ref), 'String descriptor for the server instance') + + # check units + self.assertEqual(instance_obj.units, '') + self.assertEqual(instance_obj.get_setting_units(instance_ref), '') + self.assertEqual(instance_obj.get_setting_units(stale_ref), 'days') + + # check as_choice + self.assertEqual(instance_obj.as_choice(), 'My very first InvenTree Instance') + self.assertEqual(report_size_obj.as_choice(), 'A4') + + # check is_choice + self.assertEqual(instance_obj.is_choice(), False) + self.assertEqual(report_size_obj.is_choice(), True) + + # check setting_type + self.assertEqual(instance_obj.setting_type(), 'string') + self.assertEqual(report_test_obj.setting_type(), 'boolean') + self.assertEqual(stale_days.setting_type(), 'integer') + + # check as_int + self.assertEqual(stale_days.as_int(), 0) + self.assertEqual(instance_obj.as_int(), 'InvenTree server') # not an int -> return default + + # check as_bool + self.assertEqual(report_test_obj.as_bool(), True) + + # check to_native_value + self.assertEqual(stale_days.to_native_value(), 0) + + def test_allValues(self): + """ + Make sure that the allValues functions returns correctly + """ + # define testing settings + + # check a few keys + result = InvenTreeSetting.allValues() + self.assertIn('INVENTREE_INSTANCE', result) + self.assertIn('PART_COPY_TESTS', result) + self.assertIn('STOCK_OWNERSHIP_CONTROL', result) + self.assertIn('SIGNUP_GROUP', result) + def test_required_values(self): """ - Ensure that every global setting has a name. @@ -93,6 +156,14 @@ class SettingsTest(TestCase): raise ValueError(f'Non-boolean default value specified for {key}') # pragma: no cover +class SettingsApiTest(InvenTreeAPITestCase): + + def test_settings_api(self): + # test setting with choice + url = reverse('api-user-setting-list') + self.get(url, expected_code=200) + + class WebhookMessageTests(TestCase): def setUp(self): self.endpoint_def = WebhookEndpoint.objects.create() @@ -223,3 +294,26 @@ class NotificationTest(TestCase): self.assertFalse(NotificationEntry.check_recent('test.notification2', 1, delta)) self.assertTrue(NotificationEntry.check_recent('test.notification', 1, delta)) + + +class LoadingTest(TestCase): + """ + Tests for the common config + """ + + def test_restart_flag(self): + """ + Test that the restart flag is reset on start + """ + + import common.models + from plugin import registry + + # set flag true + common.models.InvenTreeSetting.set_setting('SERVER_RESTART_REQUIRED', False, None) + + # reload the app + registry.reload_plugins() + + # now it should be false again + self.assertFalse(common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED')) diff --git a/InvenTree/company/migrations/0042_supplierpricebreak_updated.py b/InvenTree/company/migrations/0042_supplierpricebreak_updated.py new file mode 100644 index 0000000000..cf0788fe4e --- /dev/null +++ b/InvenTree/company/migrations/0042_supplierpricebreak_updated.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-03-14 22:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0041_alter_company_options'), + ] + + operations = [ + migrations.AddField( + model_name='supplierpricebreak', + name='updated', + field=models.DateTimeField(auto_now=True, null=True, verbose_name='last updated'), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index ef3c8aad2e..f72668f9f0 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -693,6 +693,7 @@ class SupplierPriceBreak(common.models.PriceBreak): Attributes: part: Link to a SupplierPart object that this price break applies to + updated: Automatic DateTime field that shows last time the price break was updated quantity: Quantity required for price break cost: Cost at specified quantity currency: Reference to the currency of this pricebreak (leave empty for base currency) @@ -704,6 +705,8 @@ class SupplierPriceBreak(common.models.PriceBreak): part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='pricebreaks', verbose_name=_('Part'),) + updated = models.DateTimeField(auto_now=True, null=True, verbose_name=_('last updated')) + class Meta: unique_together = ("part", "quantity") diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index 6efebce6a0..736e379c8a 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -278,4 +278,5 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer): 'quantity', 'price', 'price_currency', + 'updated', ] diff --git a/InvenTree/company/templates/company/supplier_part.html b/InvenTree/company/templates/company/supplier_part.html index 44e6756845..67902dc6f6 100644 --- a/InvenTree/company/templates/company/supplier_part.html +++ b/InvenTree/company/templates/company/supplier_part.html @@ -268,6 +268,14 @@ $('#price-break-table').inventreeTable({ return html; } }, + { + field: 'updated', + title: '{% trans "Last updated" %}', + sortable: true, + formatter: function(value) { + return renderDate(value); + } + }, ] }); @@ -349,4 +357,4 @@ $('#delete-part').click(function() { enableSidebar('supplierpart'); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/InvenTree/plugin/plugin.py b/InvenTree/plugin/plugin.py index 9abbcc041e..b83297dcfd 100644 --- a/InvenTree/plugin/plugin.py +++ b/InvenTree/plugin/plugin.py @@ -53,7 +53,7 @@ class InvenTreePluginBase(): else: return self.plugin_name() - def plugin_config(self, raise_error=False): + def plugin_config(self): """ Return the PluginConfig object associated with this plugin """ @@ -65,12 +65,9 @@ class InvenTreePluginBase(): key=self.plugin_slug(), name=self.plugin_name(), ) - except (OperationalError, ProgrammingError) as error: + except (OperationalError, ProgrammingError): cfg = None - if raise_error: - raise error - return cfg def is_active(self): @@ -91,6 +88,6 @@ class InvenTreePlugin(InvenTreePluginBase): """ This is here for leagcy reasons and will be removed in the next major release """ - def __init__(self): + def __init__(self): # pragma: no cover warnings.warn("Using the InvenTreePlugin is depreceated", DeprecationWarning) super().__init__() diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 40a4a4e2d9..928f238f58 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -21,7 +21,7 @@ from django.utils.text import slugify try: from importlib import metadata -except: +except: # pragma: no cover import importlib_metadata as metadata # TODO remove when python minimum is 3.8 @@ -84,7 +84,7 @@ class PluginsRegistry: """ if not settings.PLUGINS_ENABLED: # Plugins not enabled, do nothing - return + return # pragma: no cover logger.info('Start loading plugins') @@ -120,7 +120,7 @@ class PluginsRegistry: # We do not want to end in an endless loop retry_counter -= 1 - if retry_counter <= 0: + if retry_counter <= 0: # pragma: no cover if settings.PLUGIN_TESTING: print('[PLUGIN] Max retries, breaking loading') # TODO error for server status @@ -143,14 +143,14 @@ class PluginsRegistry: if not settings.PLUGINS_ENABLED: # Plugins not enabled, do nothing - return + return # pragma: no cover logger.info('Start unloading plugins') # Set maintanace mode _maintenance = bool(get_maintenance_mode()) if not _maintenance: - set_maintenance_mode(True) + set_maintenance_mode(True) # pragma: no cover # remove all plugins from registry self._clean_registry() @@ -160,7 +160,7 @@ class PluginsRegistry: # remove maintenance if not _maintenance: - set_maintenance_mode(False) + set_maintenance_mode(False) # pragma: no cover logger.info('Finished unloading plugins') def reload_plugins(self): @@ -170,7 +170,7 @@ class PluginsRegistry: # Do not reload whe currently loading if self.is_loading: - return + return # pragma: no cover logger.info('Start reloading plugins') @@ -187,7 +187,7 @@ class PluginsRegistry: if not settings.PLUGINS_ENABLED: # Plugins not enabled, do nothing - return + return # pragma: no cover self.plugin_modules = [] # clear @@ -200,7 +200,7 @@ class PluginsRegistry: # Check if not running in testing mode and apps should be loaded from hooks if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP): # Collect plugins from setup entry points - for entry in metadata.entry_points().get('inventree_plugins', []): + for entry in metadata.entry_points().get('inventree_plugins', []): # pragma: no cover try: plugin = entry.load() plugin.is_package = True @@ -257,7 +257,7 @@ class PluginsRegistry: except (OperationalError, ProgrammingError) as error: # Exception if the database has not been migrated yet - check if test are running - raise if not if not settings.PLUGIN_TESTING: - raise error + raise error # pragma: no cover plugin_db_setting = None # Always activate if testing @@ -267,7 +267,7 @@ class PluginsRegistry: # option1: package, option2: file-based if (plugin.__name__ == disabled) or (plugin.__module__ == disabled): # Errors are bad so disable the plugin in the database - if not settings.PLUGIN_TESTING: + if not settings.PLUGIN_TESTING: # pragma: no cover plugin_db_setting.active = False # TODO save the error to the plugin plugin_db_setting.save(no_reload=True) @@ -445,7 +445,7 @@ class PluginsRegistry: try: app_name = plugin_path.split('.')[-1] app_config = apps.get_app_config(app_name) - except LookupError: + except LookupError: # pragma: no cover # the plugin was never loaded correctly logger.debug(f'{app_name} App was not found during deregistering') break @@ -499,7 +499,7 @@ class PluginsRegistry: # remove model from admin site admin.site.unregister(model) models += [model._meta.model_name] - except LookupError: + except LookupError: # pragma: no cover # if an error occurs the app was never loaded right -> so nothing to do anymore logger.debug(f'{app_name} App was not found during deregistering') break @@ -572,7 +572,7 @@ class PluginsRegistry: try: cmd(*args, **kwargs) return True, [] - except Exception as error: + except Exception as error: # pragma: no cover handle_error(error) # endregion diff --git a/InvenTree/plugins/__init__.py b/InvenTree/plugins/__init__.py index e69de29bb2..ea758ff8c5 100644 --- a/InvenTree/plugins/__init__.py +++ b/InvenTree/plugins/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals # pragma: no cover diff --git a/InvenTree/stock/fixtures/stock.yaml b/InvenTree/stock/fixtures/stock.yaml index 23012c3cd3..0f44828d8e 100644 --- a/InvenTree/stock/fixtures/stock.yaml +++ b/InvenTree/stock/fixtures/stock.yaml @@ -222,6 +222,7 @@ lft: 0 rght: 0 expiry_date: "1990-10-10" + uid: 9e5ae7fc20568ed4814c10967bba8b65 - model: stock.stockitem pk: 521 @@ -235,6 +236,7 @@ lft: 0 rght: 0 status: 60 + uid: 1be0dfa925825c5c6c79301449e50c2d - model: stock.stockitem pk: 522 @@ -248,4 +250,4 @@ lft: 0 rght: 0 expiry_date: "1990-10-10" - status: 70 \ No newline at end of file + status: 70 diff --git a/InvenTree/users/admin.py b/InvenTree/users/admin.py index 91fed49830..39a7b1c9ee 100644 --- a/InvenTree/users/admin.py +++ b/InvenTree/users/admin.py @@ -49,7 +49,7 @@ class InvenTreeGroupAdminForm(forms.ModelForm): 'users', ] - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs): # pragma: no cover super().__init__(*args, **kwargs) if self.instance.pk: @@ -65,12 +65,12 @@ class InvenTreeGroupAdminForm(forms.ModelForm): help_text=_('Select which users are assigned to this group') ) - def save_m2m(self): + def save_m2m(self): # pragma: no cover # Add the users to the Group. self.instance.user_set.set(self.cleaned_data['users']) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs): # pragma: no cover # Default save instance = super().save() # Save many-to-many data @@ -78,7 +78,7 @@ class InvenTreeGroupAdminForm(forms.ModelForm): return instance -class RoleGroupAdmin(admin.ModelAdmin): +class RoleGroupAdmin(admin.ModelAdmin): # pragma: no cover """ Custom admin interface for the Group model """ diff --git a/InvenTree/users/api.py b/InvenTree/users/api.py index f8be3067ae..0e62af4dbe 100644 --- a/InvenTree/users/api.py +++ b/InvenTree/users/api.py @@ -100,7 +100,7 @@ class RoleDetails(APIView): if len(permissions) > 0: roles[role] = permissions else: - roles[role] = None + roles[role] = None # pragma: no cover data = { 'user': user.pk, diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index e2410b544d..3a4ac52b37 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -267,9 +267,9 @@ class RuleSet(models.Model): model=model ) - def __str__(self, debug=False): + def __str__(self, debug=False): # pragma: no cover """ Ruleset string representation """ - if debug: # pragma: no cover + if debug: # Makes debugging easier return f'{str(self.group).ljust(15)}: {self.name.title().ljust(15)} | ' \ f'v: {str(self.can_view).ljust(5)} | a: {str(self.can_add).ljust(5)} | ' \ @@ -319,7 +319,7 @@ def split_permission(app, perm): """split permission string into permission and model""" permission_name, *model = perm.split('_') # handle models that have underscores - if len(model) > 1: + if len(model) > 1: # pragma: no cover app += '_' + '_'.join(model[:-1]) perm = permission_name + '_' + model[-1:][0] model = model[-1:][0] @@ -375,7 +375,7 @@ def update_group_roles(group, debug=False): allowed - Whether or not the action is allowed """ - if action not in ['view', 'add', 'change', 'delete']: + if action not in ['view', 'add', 'change', 'delete']: # pragma: no cover raise ValueError("Action {a} is invalid".format(a=action)) permission_string = RuleSet.get_model_permission_string(model, action) @@ -576,7 +576,7 @@ class Owner(models.Model): return owners @staticmethod - def get_api_url(): + def get_api_url(): # pragma: no cover return reverse('api-owner-list') class Meta: diff --git a/setup.cfg b/setup.cfg index 41975d6985..b4b0af8836 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,7 +20,3 @@ max-complexity = 20 [coverage:run] source = ./InvenTree -[coverage:report] -omit= - InvenTree/wsgi.py - InvenTree/ci_render_js.py