Merge branch 'inventree:master' into matmair/issue2694

This commit is contained in:
Matthias Mair 2022-03-16 15:16:07 +01:00 committed by GitHub
commit 966eb60dd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 336 additions and 76 deletions

View File

@ -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*:

View File

@ -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.

View File

@ -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,

View File

@ -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}")

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
"""

View File

@ -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

View File

@ -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

View 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',)

View File

@ -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'))

View File

@ -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'),
),
]

View File

@ -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")

View File

@ -278,4 +278,5 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
'quantity',
'price',
'price_currency',
'updated',
]

View File

@ -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 %}

View File

@ -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__()

View File

@ -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

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals # pragma: no cover

View File

@ -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

View File

@ -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
"""

View File

@ -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,

View File

@ -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:

View File

@ -20,7 +20,3 @@ max-complexity = 20
[coverage:run]
source = ./InvenTree
[coverage:report]
omit=
InvenTree/wsgi.py
InvenTree/ci_render_js.py