mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'inventree:master' into matmair/issue2694
This commit is contained in:
commit
966eb60dd8
@ -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*:
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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}")
|
||||
|
||||
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
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.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'))
|
||||
|
@ -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:
|
||||
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")
|
||||
|
||||
|
@ -278,4 +278,5 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
|
||||
'quantity',
|
||||
'price',
|
||||
'price_currency',
|
||||
'updated',
|
||||
]
|
||||
|
@ -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 %}
|
||||
{% endblock %}
|
||||
|
@ -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__()
|
||||
|
@ -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
|
||||
|
||||
|
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals # pragma: no cover
|
@ -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
|
||||
status: 70
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user