mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	Merge branch 'inventree:master' into matmair/issue2524
This commit is contained in:
		| @@ -28,7 +28,7 @@ class InvenTreeConfig(AppConfig): | |||||||
|  |  | ||||||
|             self.start_background_tasks() |             self.start_background_tasks() | ||||||
|  |  | ||||||
|             if not isInTestMode(): |             if not isInTestMode():  # pragma: no cover | ||||||
|                 self.update_exchange_rates() |                 self.update_exchange_rates() | ||||||
|  |  | ||||||
|         if canAppAccessDatabase() or settings.TESTING_ENV: |         if canAppAccessDatabase() or settings.TESTING_ENV: | ||||||
| @@ -98,7 +98,7 @@ class InvenTreeConfig(AppConfig): | |||||||
|             schedule_type=Schedule.DAILY, |             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*: |         Update exchange rates each time the server is started, *if*: | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,14 +3,14 @@ Pull rendered copies of the templated | |||||||
| only used for testing the js files! - This file is omited from coverage | only used for testing the js files! - This file is omited from coverage | ||||||
| """ | """ | ||||||
|  |  | ||||||
| from django.test import TestCase | from django.test import TestCase  # pragma: no cover | ||||||
| from django.contrib.auth import get_user_model | from django.contrib.auth import get_user_model  # pragma: no cover | ||||||
|  |  | ||||||
| import os | import os  # pragma: no cover | ||||||
| import pathlib | import pathlib  # pragma: no cover | ||||||
|  |  | ||||||
|  |  | ||||||
| class RenderJavascriptFiles(TestCase): | class RenderJavascriptFiles(TestCase):  # pragma: no cover | ||||||
|     """ |     """ | ||||||
|     A unit test to "render" javascript files. |     A unit test to "render" javascript files. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -95,7 +95,7 @@ def user_roles(request): | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if user.is_superuser: |     if user.is_superuser: | ||||||
|         for ruleset in RuleSet.RULESET_MODELS.keys(): |         for ruleset in RuleSet.RULESET_MODELS.keys():  # pragma: no cover | ||||||
|             roles[ruleset] = { |             roles[ruleset] = { | ||||||
|                 'view': True, |                 'view': True, | ||||||
|                 'add': True, |                 'add': True, | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): | |||||||
|         import importlib |         import importlib | ||||||
|         from InvenTree.status import is_worker_running |         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 |             # Running as asynchronous task | ||||||
|             try: |             try: | ||||||
|                 task = AsyncTask(taskname, *args, **kwargs) |                 task = AsyncTask(taskname, *args, **kwargs) | ||||||
| @@ -94,13 +94,13 @@ def offload_task(taskname, *args, force_sync=False, **kwargs): | |||||||
|             # Retrieve function |             # Retrieve function | ||||||
|             try: |             try: | ||||||
|                 _func = getattr(_mod, func) |                 _func = getattr(_mod, func) | ||||||
|             except AttributeError: |             except AttributeError:  # pragma: no cover | ||||||
|                 # getattr does not work for local import |                 # getattr does not work for local import | ||||||
|                 _func = None |                 _func = None | ||||||
|  |  | ||||||
|             try: |             try: | ||||||
|                 if not _func: |                 if not _func: | ||||||
|                     _func = eval(func) |                     _func = eval(func)  # pragma: no cover | ||||||
|             except NameError: |             except NameError: | ||||||
|                 logger.warning(f"WARNING: '{taskname}' not started - No function named '{func}'") |                 logger.warning(f"WARNING: '{taskname}' not started - No function named '{func}'") | ||||||
|                 return |                 return | ||||||
| @@ -248,7 +248,7 @@ def update_exchange_rates(): | |||||||
|         # Apps not yet loaded! |         # Apps not yet loaded! | ||||||
|         logger.info("Could not perform 'update_exchange_rates' - App registry not ready") |         logger.info("Could not perform 'update_exchange_rates' - App registry not ready") | ||||||
|         return |         return | ||||||
|     except: |     except:  # pragma: no cover | ||||||
|         # Other error? |         # Other error? | ||||||
|         return |         return | ||||||
|  |  | ||||||
| @@ -257,7 +257,7 @@ def update_exchange_rates(): | |||||||
|         backend = ExchangeBackend.objects.get(name='InvenTreeExchange') |         backend = ExchangeBackend.objects.get(name='InvenTreeExchange') | ||||||
|     except ExchangeBackend.DoesNotExist: |     except ExchangeBackend.DoesNotExist: | ||||||
|         pass |         pass | ||||||
|     except: |     except:  # pragma: no cover | ||||||
|         # Some other error |         # Some other error | ||||||
|         logger.warning("update_exchange_rates: Database not ready") |         logger.warning("update_exchange_rates: Database not ready") | ||||||
|         return |         return | ||||||
| @@ -274,7 +274,7 @@ def update_exchange_rates(): | |||||||
|  |  | ||||||
|         # Remove any exchange rates which are not in the provided currencies |         # Remove any exchange rates which are not in the provided currencies | ||||||
|         Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete() |         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}") |         logger.error(f"Error updating exchange rates: {e}") | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ from . import helpers | |||||||
| from . import version | from . import version | ||||||
| from . import status | from . import status | ||||||
| from . import ready | from . import ready | ||||||
|  | from . import config | ||||||
|  |  | ||||||
| from decimal import Decimal | from decimal import Decimal | ||||||
|  |  | ||||||
| @@ -24,6 +25,7 @@ import InvenTree.tasks | |||||||
|  |  | ||||||
| from stock.models import StockLocation | from stock.models import StockLocation | ||||||
| from common.settings import currency_codes | from common.settings import currency_codes | ||||||
|  | from common.models import InvenTreeSetting | ||||||
|  |  | ||||||
|  |  | ||||||
| class ValidatorTest(TestCase): | class ValidatorTest(TestCase): | ||||||
| @@ -453,3 +455,55 @@ class TestSettings(TestCase): | |||||||
|  |  | ||||||
|         # make sure to clean up |         # make sure to clean up | ||||||
|         settings.TESTING_ENV = False |         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') | ||||||
|   | |||||||
| @@ -12,11 +12,14 @@ import common.models | |||||||
| INVENTREE_SW_VERSION = "0.7.0 dev" | INVENTREE_SW_VERSION = "0.7.0 dev" | ||||||
|  |  | ||||||
| # InvenTree API version | # 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 | 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 | v30 -> 2022-03-09 | ||||||
|     - Adds "exclude_location" field to BuildAutoAllocation API endpoint |     - Adds "exclude_location" field to BuildAutoAllocation API endpoint | ||||||
|     - Allows BuildItem API endpoint to be filtered by BomItem relation |     - Allows BuildItem API endpoint to be filtered by BomItem relation | ||||||
| @@ -171,7 +174,7 @@ def inventreeDocsVersion(): | |||||||
|     if isInvenTreeDevelopmentVersion(): |     if isInvenTreeDevelopmentVersion(): | ||||||
|         return "latest" |         return "latest" | ||||||
|     else: |     else: | ||||||
|         return INVENTREE_SW_VERSION |         return INVENTREE_SW_VERSION  # pragma: no cover | ||||||
|  |  | ||||||
|  |  | ||||||
| def isInvenTreeUpToDate(): | def isInvenTreeUpToDate(): | ||||||
| @@ -189,10 +192,10 @@ def isInvenTreeUpToDate(): | |||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     # Extract "tuple" version (Python can directly compare version tuples) |     # Extract "tuple" version (Python can directly compare version tuples) | ||||||
|     latest_version = inventreeVersionTuple(latest) |     latest_version = inventreeVersionTuple(latest)  # pragma: no cover | ||||||
|     inventree_version = inventreeVersionTuple() |     inventree_version = inventreeVersionTuple()  # pragma: no cover | ||||||
|  |  | ||||||
|     return inventree_version >= latest_version |     return inventree_version >= latest_version  # pragma: no cover | ||||||
|  |  | ||||||
|  |  | ||||||
| def inventreeApiVersion(): | def inventreeApiVersion(): | ||||||
| @@ -209,7 +212,7 @@ def inventreeCommitHash(): | |||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip() |         return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip() | ||||||
|     except: |     except:  # pragma: no cover | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -219,5 +222,5 @@ def inventreeCommitDate(): | |||||||
|     try: |     try: | ||||||
|         d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip() |         d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip() | ||||||
|         return d.split(' ')[0] |         return d.split(' ')[0] | ||||||
|     except: |     except:  # pragma: no cover | ||||||
|         return None |         return None | ||||||
|   | |||||||
| @@ -7,10 +7,10 @@ For more information on this file, see | |||||||
| https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ | 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 | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): | |||||||
|             except json.JSONDecodeError: |             except json.JSONDecodeError: | ||||||
|                 return False |                 return False | ||||||
|         else: |         else: | ||||||
|             return False |             return False  # pragma: no cover | ||||||
|  |  | ||||||
|         # If any of the following keys are in the JSON data, |         # 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... |         # 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 |                 # Initially try casting to an integer | ||||||
|                 try: |                 try: | ||||||
|                     pk = int(data) |                     pk = int(data) | ||||||
|                 except (TypeError, ValueError): |                 except (TypeError, ValueError):  # pragma: no cover | ||||||
|                     pk = None |                     pk = None | ||||||
|  |  | ||||||
|                 if pk is None: |                 if pk is None:  # pragma: no cover | ||||||
|                     try: |                     try: | ||||||
|                         pk = self.data[k]['id'] |                         pk = self.data[k]['id'] | ||||||
|                     except (AttributeError, KeyError): |                     except (AttributeError, KeyError): | ||||||
| @@ -82,7 +82,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): | |||||||
|                 try: |                 try: | ||||||
|                     item = StockItem.objects.get(pk=pk) |                     item = StockItem.objects.get(pk=pk) | ||||||
|                     return item |                     return item | ||||||
|                 except (ValueError, StockItem.DoesNotExist): |                 except (ValueError, StockItem.DoesNotExist):  # pragma: no cover | ||||||
|                     raise ValidationError({k, "Stock item does not exist"}) |                     raise ValidationError({k, "Stock item does not exist"}) | ||||||
|  |  | ||||||
|         return None |         return None | ||||||
| @@ -97,10 +97,10 @@ class InvenTreeBarcodePlugin(BarcodePlugin): | |||||||
|                 # First try simple integer lookup |                 # First try simple integer lookup | ||||||
|                 try: |                 try: | ||||||
|                     pk = int(self.data[k]) |                     pk = int(self.data[k]) | ||||||
|                 except (TypeError, ValueError): |                 except (TypeError, ValueError):  # pragma: no cover | ||||||
|                     pk = None |                     pk = None | ||||||
|  |  | ||||||
|                 if pk is None: |                 if pk is None:  # pragma: no cover | ||||||
|                     # Lookup by 'id' field |                     # Lookup by 'id' field | ||||||
|                     try: |                     try: | ||||||
|                         pk = self.data[k]['id'] |                         pk = self.data[k]['id'] | ||||||
| @@ -110,7 +110,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): | |||||||
|                 try: |                 try: | ||||||
|                     loc = StockLocation.objects.get(pk=pk) |                     loc = StockLocation.objects.get(pk=pk) | ||||||
|                     return loc |                     return loc | ||||||
|                 except (ValueError, StockLocation.DoesNotExist): |                 except (ValueError, StockLocation.DoesNotExist):  # pragma: no cover | ||||||
|                     raise ValidationError({k, "Stock location does not exist"}) |                     raise ValidationError({k, "Stock location does not exist"}) | ||||||
|  |  | ||||||
|         return None |         return None | ||||||
| @@ -125,10 +125,10 @@ class InvenTreeBarcodePlugin(BarcodePlugin): | |||||||
|                 # Try integer lookup first |                 # Try integer lookup first | ||||||
|                 try: |                 try: | ||||||
|                     pk = int(self.data[k]) |                     pk = int(self.data[k]) | ||||||
|                 except (TypeError, ValueError): |                 except (TypeError, ValueError):  # pragma: no cover | ||||||
|                     pk = None |                     pk = None | ||||||
|  |  | ||||||
|                 if pk is None: |                 if pk is None:  # pragma: no cover | ||||||
|                     try: |                     try: | ||||||
|                         pk = self.data[k]['id'] |                         pk = self.data[k]['id'] | ||||||
|                     except (AttributeError, KeyError): |                     except (AttributeError, KeyError): | ||||||
| @@ -137,7 +137,7 @@ class InvenTreeBarcodePlugin(BarcodePlugin): | |||||||
|                 try: |                 try: | ||||||
|                     part = Part.objects.get(pk=pk) |                     part = Part.objects.get(pk=pk) | ||||||
|                     return part |                     return part | ||||||
|                 except (ValueError, Part.DoesNotExist): |                 except (ValueError, Part.DoesNotExist):  # pragma: no cover | ||||||
|                     raise ValidationError({k, 'Part does not exist'}) |                     raise ValidationError({k, 'Part does not exist'}) | ||||||
|  |  | ||||||
|         return None |         return None | ||||||
|   | |||||||
| @@ -38,8 +38,18 @@ class BarcodeAPITest(APITestCase): | |||||||
|  |  | ||||||
|     def test_invalid(self): |     def test_invalid(self): | ||||||
|  |  | ||||||
|  |         # test scan url | ||||||
|         response = self.client.post(self.scan_url, format='json', data={}) |         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) |         self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) | ||||||
|  |  | ||||||
|     def test_empty(self): |     def test_empty(self): | ||||||
| @@ -204,3 +214,57 @@ class BarcodeAPITest(APITestCase): | |||||||
|  |  | ||||||
|         self.assertIn('error', data) |         self.assertIn('error', data) | ||||||
|         self.assertNotIn('success', 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) | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ class SettingsAdmin(ImportExportModelAdmin): | |||||||
|  |  | ||||||
|     list_display = ('key', 'value') |     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 |         Prevent the 'key' field being edited once the setting is created | ||||||
|         """ |         """ | ||||||
| @@ -27,7 +27,7 @@ class UserSettingsAdmin(ImportExportModelAdmin): | |||||||
|  |  | ||||||
|     list_display = ('key', 'value', 'user', ) |     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 |         Prevent the 'key' field being edited once the setting is created | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -570,7 +570,7 @@ class BaseInvenTreeSetting(models.Model): | |||||||
|         try: |         try: | ||||||
|             value = int(self.value) |             value = int(self.value) | ||||||
|         except (ValueError, TypeError): |         except (ValueError, TypeError): | ||||||
|             value = self.default_value() |             value = self.default_value | ||||||
|  |  | ||||||
|         return value |         return value | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,12 +18,12 @@ def currency_code_default(): | |||||||
|  |  | ||||||
|     try: |     try: | ||||||
|         code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') |         code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') | ||||||
|     except ProgrammingError: |     except ProgrammingError:  # pragma: no cover | ||||||
|         # database is not initialized yet |         # database is not initialized yet | ||||||
|         code = '' |         code = '' | ||||||
|  |  | ||||||
|     if code not in CURRENCIES: |     if code not in CURRENCIES: | ||||||
|         code = 'USD' |         code = 'USD'  # pragma: no cover | ||||||
|  |  | ||||||
|     return code |     return code | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								InvenTree/common/test_tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								InvenTree/common/test_tasks.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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',) | ||||||
| @@ -6,7 +6,9 @@ from datetime import timedelta | |||||||
|  |  | ||||||
| from django.test import TestCase, Client | from django.test import TestCase, Client | ||||||
| from django.contrib.auth import get_user_model | 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 .models import InvenTreeSetting, WebhookEndpoint, WebhookMessage, NotificationEntry | ||||||
| from .api import WebhookView | from .api import WebhookView | ||||||
|  |  | ||||||
| @@ -46,6 +48,67 @@ class SettingsTest(TestCase): | |||||||
|         # Check object lookup (case insensitive) |         # Check object lookup (case insensitive) | ||||||
|         self.assertEqual(InvenTreeSetting.get_setting_object('iNvEnTrEE_inSTanCE').pk, 1) |         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): |     def test_required_values(self): | ||||||
|         """ |         """ | ||||||
|         - Ensure that every global setting has a name. |         - 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 |                     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): | class WebhookMessageTests(TestCase): | ||||||
|     def setUp(self): |     def setUp(self): | ||||||
|         self.endpoint_def = WebhookEndpoint.objects.create() |         self.endpoint_def = WebhookEndpoint.objects.create() | ||||||
| @@ -223,3 +294,26 @@ class NotificationTest(TestCase): | |||||||
|         self.assertFalse(NotificationEntry.check_recent('test.notification2', 1, delta)) |         self.assertFalse(NotificationEntry.check_recent('test.notification2', 1, delta)) | ||||||
|  |  | ||||||
|         self.assertTrue(NotificationEntry.check_recent('test.notification', 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')) | ||||||
|   | |||||||
| @@ -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'), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @@ -693,6 +693,7 @@ class SupplierPriceBreak(common.models.PriceBreak): | |||||||
|  |  | ||||||
|     Attributes: |     Attributes: | ||||||
|         part: Link to a SupplierPart object that this price break applies to |         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 |         quantity: Quantity required for price break | ||||||
|         cost: Cost at specified quantity |         cost: Cost at specified quantity | ||||||
|         currency: Reference to the currency of this pricebreak (leave empty for base currency) |         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'),) |     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: |     class Meta: | ||||||
|         unique_together = ("part", "quantity") |         unique_together = ("part", "quantity") | ||||||
|  |  | ||||||
|   | |||||||
| @@ -278,4 +278,5 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer): | |||||||
|             'quantity', |             'quantity', | ||||||
|             'price', |             'price', | ||||||
|             'price_currency', |             'price_currency', | ||||||
|  |             'updated', | ||||||
|         ] |         ] | ||||||
|   | |||||||
| @@ -268,6 +268,14 @@ $('#price-break-table').inventreeTable({ | |||||||
|                 return html; |                 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'); | enableSidebar('supplierpart'); | ||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -53,7 +53,7 @@ class InvenTreePluginBase(): | |||||||
|         else: |         else: | ||||||
|             return self.plugin_name() |             return self.plugin_name() | ||||||
|  |  | ||||||
|     def plugin_config(self, raise_error=False): |     def plugin_config(self): | ||||||
|         """ |         """ | ||||||
|         Return the PluginConfig object associated with this plugin |         Return the PluginConfig object associated with this plugin | ||||||
|         """ |         """ | ||||||
| @@ -65,12 +65,9 @@ class InvenTreePluginBase(): | |||||||
|                 key=self.plugin_slug(), |                 key=self.plugin_slug(), | ||||||
|                 name=self.plugin_name(), |                 name=self.plugin_name(), | ||||||
|             ) |             ) | ||||||
|         except (OperationalError, ProgrammingError) as error: |         except (OperationalError, ProgrammingError): | ||||||
|             cfg = None |             cfg = None | ||||||
|  |  | ||||||
|             if raise_error: |  | ||||||
|                 raise error |  | ||||||
|  |  | ||||||
|         return cfg |         return cfg | ||||||
|  |  | ||||||
|     def is_active(self): |     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 |     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) |         warnings.warn("Using the InvenTreePlugin is depreceated", DeprecationWarning) | ||||||
|         super().__init__() |         super().__init__() | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ from django.utils.text import slugify | |||||||
|  |  | ||||||
| try: | try: | ||||||
|     from importlib import metadata |     from importlib import metadata | ||||||
| except: | except:  # pragma: no cover | ||||||
|     import importlib_metadata as metadata |     import importlib_metadata as metadata | ||||||
|     # TODO remove when python minimum is 3.8 |     # TODO remove when python minimum is 3.8 | ||||||
|  |  | ||||||
| @@ -86,7 +86,7 @@ class PluginsRegistry: | |||||||
|         """ |         """ | ||||||
|         if not settings.PLUGINS_ENABLED: |         if not settings.PLUGINS_ENABLED: | ||||||
|             # Plugins not enabled, do nothing |             # Plugins not enabled, do nothing | ||||||
|             return |             return  # pragma: no cover | ||||||
|  |  | ||||||
|         logger.info('Start loading plugins') |         logger.info('Start loading plugins') | ||||||
|  |  | ||||||
| @@ -122,7 +122,7 @@ class PluginsRegistry: | |||||||
|                 # We do not want to end in an endless loop |                 # We do not want to end in an endless loop | ||||||
|                 retry_counter -= 1 |                 retry_counter -= 1 | ||||||
|  |  | ||||||
|                 if retry_counter <= 0: |                 if retry_counter <= 0:  # pragma: no cover | ||||||
|                     if settings.PLUGIN_TESTING: |                     if settings.PLUGIN_TESTING: | ||||||
|                         print('[PLUGIN] Max retries, breaking loading') |                         print('[PLUGIN] Max retries, breaking loading') | ||||||
|                     # TODO error for server status |                     # TODO error for server status | ||||||
| @@ -145,14 +145,14 @@ class PluginsRegistry: | |||||||
|  |  | ||||||
|         if not settings.PLUGINS_ENABLED: |         if not settings.PLUGINS_ENABLED: | ||||||
|             # Plugins not enabled, do nothing |             # Plugins not enabled, do nothing | ||||||
|             return |             return  # pragma: no cover | ||||||
|  |  | ||||||
|         logger.info('Start unloading plugins') |         logger.info('Start unloading plugins') | ||||||
|  |  | ||||||
|         # Set maintanace mode |         # Set maintanace mode | ||||||
|         _maintenance = bool(get_maintenance_mode()) |         _maintenance = bool(get_maintenance_mode()) | ||||||
|         if not _maintenance: |         if not _maintenance: | ||||||
|             set_maintenance_mode(True) |             set_maintenance_mode(True)  # pragma: no cover | ||||||
|  |  | ||||||
|         # remove all plugins from registry |         # remove all plugins from registry | ||||||
|         self._clean_registry() |         self._clean_registry() | ||||||
| @@ -162,7 +162,7 @@ class PluginsRegistry: | |||||||
|  |  | ||||||
|         # remove maintenance |         # remove maintenance | ||||||
|         if not _maintenance: |         if not _maintenance: | ||||||
|             set_maintenance_mode(False) |             set_maintenance_mode(False)  # pragma: no cover | ||||||
|         logger.info('Finished unloading plugins') |         logger.info('Finished unloading plugins') | ||||||
|  |  | ||||||
|     def reload_plugins(self): |     def reload_plugins(self): | ||||||
| @@ -172,7 +172,7 @@ class PluginsRegistry: | |||||||
|  |  | ||||||
|         # Do not reload whe currently loading |         # Do not reload whe currently loading | ||||||
|         if self.is_loading: |         if self.is_loading: | ||||||
|             return |             return  # pragma: no cover | ||||||
|  |  | ||||||
|         logger.info('Start reloading plugins') |         logger.info('Start reloading plugins') | ||||||
|  |  | ||||||
| @@ -189,7 +189,7 @@ class PluginsRegistry: | |||||||
|  |  | ||||||
|         if not settings.PLUGINS_ENABLED: |         if not settings.PLUGINS_ENABLED: | ||||||
|             # Plugins not enabled, do nothing |             # Plugins not enabled, do nothing | ||||||
|             return |             return  # pragma: no cover | ||||||
|  |  | ||||||
|         self.plugin_modules = []  # clear |         self.plugin_modules = []  # clear | ||||||
|  |  | ||||||
| @@ -202,7 +202,7 @@ class PluginsRegistry: | |||||||
|         # Check if not running in testing mode and apps should be loaded from hooks |         # 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): |         if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP): | ||||||
|             # Collect plugins from setup entry points |             # 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: |                 try: | ||||||
|                     plugin = entry.load() |                     plugin = entry.load() | ||||||
|                     plugin.is_package = True |                     plugin.is_package = True | ||||||
| @@ -280,7 +280,7 @@ class PluginsRegistry: | |||||||
|             except (OperationalError, ProgrammingError) as error: |             except (OperationalError, ProgrammingError) as error: | ||||||
|                 # Exception if the database has not been migrated yet - check if test are running - raise if not |                 # Exception if the database has not been migrated yet - check if test are running - raise if not | ||||||
|                 if not settings.PLUGIN_TESTING: |                 if not settings.PLUGIN_TESTING: | ||||||
|                     raise error |                     raise error  # pragma: no cover | ||||||
|                 plugin_db_setting = None |                 plugin_db_setting = None | ||||||
|  |  | ||||||
|             # Always activate if testing |             # Always activate if testing | ||||||
| @@ -290,7 +290,7 @@ class PluginsRegistry: | |||||||
|                     # option1: package, option2: file-based |                     # option1: package, option2: file-based | ||||||
|                     if (plugin.__name__ == disabled) or (plugin.__module__ == disabled): |                     if (plugin.__name__ == disabled) or (plugin.__module__ == disabled): | ||||||
|                         # Errors are bad so disable the plugin in the database |                         # 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 |                             plugin_db_setting.active = False | ||||||
|                             # TODO save the error to the plugin |                             # TODO save the error to the plugin | ||||||
|                             plugin_db_setting.save(no_reload=True) |                             plugin_db_setting.save(no_reload=True) | ||||||
| @@ -468,7 +468,7 @@ class PluginsRegistry: | |||||||
|             try: |             try: | ||||||
|                 app_name = plugin_path.split('.')[-1] |                 app_name = plugin_path.split('.')[-1] | ||||||
|                 app_config = apps.get_app_config(app_name) |                 app_config = apps.get_app_config(app_name) | ||||||
|             except LookupError: |             except LookupError:  # pragma: no cover | ||||||
|                 # the plugin was never loaded correctly |                 # the plugin was never loaded correctly | ||||||
|                 logger.debug(f'{app_name} App was not found during deregistering') |                 logger.debug(f'{app_name} App was not found during deregistering') | ||||||
|                 break |                 break | ||||||
| @@ -522,7 +522,7 @@ class PluginsRegistry: | |||||||
|                     # remove model from admin site |                     # remove model from admin site | ||||||
|                     admin.site.unregister(model) |                     admin.site.unregister(model) | ||||||
|                     models += [model._meta.model_name] |                     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 |                 # 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') |                 logger.debug(f'{app_name} App was not found during deregistering') | ||||||
|                 break |                 break | ||||||
| @@ -595,7 +595,7 @@ class PluginsRegistry: | |||||||
|         try: |         try: | ||||||
|             cmd(*args, **kwargs) |             cmd(*args, **kwargs) | ||||||
|             return True, [] |             return True, [] | ||||||
|         except Exception as error: |         except Exception as error:  # pragma: no cover | ||||||
|             handle_error(error) |             handle_error(error) | ||||||
|     # endregion |     # endregion | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,2 @@ | |||||||
|  | # -*- coding: utf-8 -*- | ||||||
|  | from __future__ import unicode_literals  # pragma: no cover | ||||||
|   | |||||||
| @@ -222,6 +222,7 @@ | |||||||
|     lft: 0 |     lft: 0 | ||||||
|     rght: 0 |     rght: 0 | ||||||
|     expiry_date: "1990-10-10" |     expiry_date: "1990-10-10" | ||||||
|  |     uid: 9e5ae7fc20568ed4814c10967bba8b65 | ||||||
|  |  | ||||||
| - model: stock.stockitem | - model: stock.stockitem | ||||||
|   pk: 521 |   pk: 521 | ||||||
| @@ -235,6 +236,7 @@ | |||||||
|     lft: 0 |     lft: 0 | ||||||
|     rght: 0 |     rght: 0 | ||||||
|     status: 60 |     status: 60 | ||||||
|  |     uid: 1be0dfa925825c5c6c79301449e50c2d | ||||||
|  |  | ||||||
| - model: stock.stockitem | - model: stock.stockitem | ||||||
|   pk: 522 |   pk: 522 | ||||||
| @@ -248,4 +250,4 @@ | |||||||
|     lft: 0 |     lft: 0 | ||||||
|     rght: 0 |     rght: 0 | ||||||
|     expiry_date: "1990-10-10" |     expiry_date: "1990-10-10" | ||||||
|     status: 70 |     status: 70 | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ class InvenTreeGroupAdminForm(forms.ModelForm): | |||||||
|             'users', |             'users', | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs):  # pragma: no cover | ||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|  |  | ||||||
|         if self.instance.pk: |         if self.instance.pk: | ||||||
| @@ -65,12 +65,12 @@ class InvenTreeGroupAdminForm(forms.ModelForm): | |||||||
|         help_text=_('Select which users are assigned to this group') |         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. |         # Add the users to the Group. | ||||||
|  |  | ||||||
|         self.instance.user_set.set(self.cleaned_data['users']) |         self.instance.user_set.set(self.cleaned_data['users']) | ||||||
|  |  | ||||||
|     def save(self, *args, **kwargs): |     def save(self, *args, **kwargs):  # pragma: no cover | ||||||
|         # Default save |         # Default save | ||||||
|         instance = super().save() |         instance = super().save() | ||||||
|         # Save many-to-many data |         # Save many-to-many data | ||||||
| @@ -78,7 +78,7 @@ class InvenTreeGroupAdminForm(forms.ModelForm): | |||||||
|         return instance |         return instance | ||||||
|  |  | ||||||
|  |  | ||||||
| class RoleGroupAdmin(admin.ModelAdmin): | class RoleGroupAdmin(admin.ModelAdmin):  # pragma: no cover | ||||||
|     """ |     """ | ||||||
|     Custom admin interface for the Group model |     Custom admin interface for the Group model | ||||||
|     """ |     """ | ||||||
|   | |||||||
| @@ -100,7 +100,7 @@ class RoleDetails(APIView): | |||||||
|             if len(permissions) > 0: |             if len(permissions) > 0: | ||||||
|                 roles[role] = permissions |                 roles[role] = permissions | ||||||
|             else: |             else: | ||||||
|                 roles[role] = None |                 roles[role] = None  # pragma: no cover | ||||||
|  |  | ||||||
|         data = { |         data = { | ||||||
|             'user': user.pk, |             'user': user.pk, | ||||||
|   | |||||||
| @@ -265,9 +265,9 @@ class RuleSet(models.Model): | |||||||
|             model=model |             model=model | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def __str__(self, debug=False): |     def __str__(self, debug=False):  # pragma: no cover | ||||||
|         """ Ruleset string representation """ |         """ Ruleset string representation """ | ||||||
|         if debug:  # pragma: no cover |         if debug: | ||||||
|             # Makes debugging easier |             # Makes debugging easier | ||||||
|             return f'{str(self.group).ljust(15)}: {self.name.title().ljust(15)} | ' \ |             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)} | ' \ |                    f'v: {str(self.can_view).ljust(5)} | a: {str(self.can_add).ljust(5)} | ' \ | ||||||
| @@ -317,7 +317,7 @@ def split_permission(app, perm): | |||||||
|     """split permission string into permission and model""" |     """split permission string into permission and model""" | ||||||
|     permission_name, *model = perm.split('_') |     permission_name, *model = perm.split('_') | ||||||
|     # handle models that have underscores |     # handle models that have underscores | ||||||
|     if len(model) > 1: |     if len(model) > 1:  # pragma: no cover | ||||||
|         app += '_' + '_'.join(model[:-1]) |         app += '_' + '_'.join(model[:-1]) | ||||||
|         perm = permission_name + '_' + model[-1:][0] |         perm = permission_name + '_' + model[-1:][0] | ||||||
|     model = model[-1:][0] |     model = model[-1:][0] | ||||||
| @@ -373,7 +373,7 @@ def update_group_roles(group, debug=False): | |||||||
|             allowed - Whether or not the action is allowed |             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)) |             raise ValueError("Action {a} is invalid".format(a=action)) | ||||||
|  |  | ||||||
|         permission_string = RuleSet.get_model_permission_string(model, action) |         permission_string = RuleSet.get_model_permission_string(model, action) | ||||||
| @@ -574,7 +574,7 @@ class Owner(models.Model): | |||||||
|         return owners |         return owners | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_api_url(): |     def get_api_url():  # pragma: no cover | ||||||
|         return reverse('api-owner-list') |         return reverse('api-owner-list') | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user