def test_binary_values(self): """ Test for binary value """ setting = InvenTreeSetting.get_setting_object('PART_COMPONENT') self.assertTrue(setting.as_bool()) url = self.get_url(setting.pk) setting.value = True setting.save() # Try posting some invalid values # The value should be "cleaned" and stay the same for value in ['', 'abc', 'cat', 'TRUETRUETRUE']: self.post(url, {'value': value}, valid=True) # Try posting some valid (True) values for value in [True, 'True', '1', 'yes']: self.post(url, {'value': value}, valid=True) self.assertTrue(InvenTreeSetting.get_setting('PART_COMPONENT')) # Try posting some valid (False) values for value in [False, 'False']: self.post(url, {'value': value}, valid=True) self.assertFalse(InvenTreeSetting.get_setting('PART_COMPONENT'))
def test_duplicate_ipn(self): """ Test the setting which controls duplicate IPN values """ # Create a part Part.objects.create(name='Hello', description='A thing', IPN='IPN123', revision='A') # Attempt to create a duplicate item (should fail) with self.assertRaises(ValidationError): part = Part(name='Hello', description='A thing', IPN='IPN123', revision='A') part.validate_unique() # Attempt to create item with duplicate IPN (should be allowed by default) Part.objects.create(name='Hello', description='A thing', IPN='IPN123', revision='B') # And attempt again with the same values (should fail) with self.assertRaises(ValidationError): part = Part(name='Hello', description='A thing', IPN='IPN123', revision='B') part.validate_unique() # Now update the settings so duplicate IPN values are *not* allowed InvenTreeSetting.set_setting('PART_ALLOW_DUPLICATE_IPN', False, self.user) with self.assertRaises(ValidationError): part = Part(name='Hello', description='A thing', IPN='IPN123', revision='C') part.full_clean()
def enable_ownership(self): # Enable stock location ownership InvenTreeSetting.set_setting('STOCK_OWNERSHIP_CONTROL', True, self.user) self.assertEqual( True, InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL'))
def __init__(self, *args, **kwargs): kwargs['email_required'] = InvenTreeSetting.get_setting( 'LOGIN_MAIL_REQUIRED') super().__init__(*args, **kwargs) # check for two mail fields if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'): self.fields["email2"] = forms.EmailField( label=_("Email (again)"), widget=forms.TextInput( attrs={ "type": "email", "placeholder": _("Email address confirmation"), }), ) # check for two password fields if not InvenTreeSetting.get_setting('LOGIN_SIGNUP_PWD_TWICE'): self.fields.pop("password2") # reorder fields set_form_field_order(self, [ "username", "email", "email2", "password1", "password2", ])
def test_default_shipment(self): """Test sales order default shipment creation""" # Default setting value should be False self.assertEqual( False, InvenTreeSetting.get_setting('SALESORDER_DEFAULT_SHIPMENT')) # Create an order order_1 = SalesOrder.objects.create(customer=self.customer, reference='1235', customer_reference='ABC 55556') # Order should have no shipments when setting is False self.assertEqual(0, order_1.shipment_count) # Update setting to True InvenTreeSetting.set_setting('SALESORDER_DEFAULT_SHIPMENT', True, None) self.assertEqual( True, InvenTreeSetting.get_setting('SALESORDER_DEFAULT_SHIPMENT')) # Create a second order order_2 = SalesOrder.objects.create(customer=self.customer, reference='1236', customer_reference='ABC 55557') # Order should have one shipment self.assertEqual(1, order_2.shipment_count) self.assertEqual(1, order_2.pending_shipments().count()) # Shipment should have default reference of '1' self.assertEqual('1', order_2.pending_shipments()[0].reference)
def test_create_stock_with_expiry(self): """ Test creation of stock item of a part with an expiry date. The initial value for the "expiry_date" field should be pre-filled, and should be in the future! """ # First, ensure that the expiry date feature is enabled! InvenTreeSetting.set_setting('STOCK_ENABLE_EXPIRY', True, self.user) url = reverse('stock-item-create') response = self.client.get(url, {'part': 25}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') self.assertEqual(response.status_code, 200) # We are expecting 10 days in the future expiry = datetime.now().date() + timedelta(10) expected = f'name=\\\\"expiry_date\\\\" value=\\\\"{expiry.isoformat()}\\\\"' self.assertIn(expected, str(response.content)) # Now check with a part which does *not* have a default expiry period response = self.client.get(url, {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest') expected = 'name=\\\\"expiry_date\\\\" placeholder=\\\\"\\\\"' self.assertIn(expected, str(response.content))
def test_printing_process(self): """Test that a label can be printed.""" # Ensure the labels were created apps.get_app_config('label').create_labels() # Lookup references part = Part.objects.first() plugin_ref = 'samplelabel' label = PartLabel.objects.first() url = self.do_url([part], plugin_ref, label) # Non-exsisting plugin response = self.get(f'{url}123', expected_code=404) self.assertIn(f'Plugin \'{plugin_ref}123\' not found', str(response.content, 'utf8')) # Inactive plugin response = self.get(url, expected_code=400) self.assertIn(f'Plugin \'{plugin_ref}\' is not enabled', str(response.content, 'utf8')) # Active plugin self.do_activate_plugin() # Print one part self.get(url, expected_code=200) # Print multiple parts self.get(self.do_url(Part.objects.all()[:2], plugin_ref, label), expected_code=200) # Print multiple parts without a plugin self.get(self.do_url(Part.objects.all()[:2], None, label), expected_code=200) # Print multiple parts without a plugin in debug mode InvenTreeSetting.set_setting('REPORT_DEBUG_MODE', True, None) response = self.get(self.do_url(Part.objects.all()[:2], None, label), expected_code=200) self.assertIn('@page', str(response.content)) # Print no part self.get(self.do_url(None, plugin_ref, label), expected_code=400) # Test that the labels have been printed # The sample labelling plugin simply prints to file self.assertTrue(os.path.exists('label.pdf')) # Read the raw .pdf data - ensure it contains some sensible information with open('label.pdf', 'rb') as f: pdf_data = str(f.read()) self.assertIn('WeasyPrint', pdf_data) # Check that the .png file has already been created self.assertTrue(os.path.exists('label.png')) # And that it is a valid image file Image.open('label.png')
def test_instance_url(self): """Test instance url settings.""" # Set up required setting InvenTreeSetting.set_setting("INVENTREE_BASE_URL", "http://127.1.2.3", self.user) # The site should also be changed site_obj = Site.objects.all().order_by('id').first() self.assertEqual(site_obj.domain, 'http://127.1.2.3')
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')
def set_default_currency(apps, schema_editor): """ migrate the currency setting from config.yml to db """ # get value from settings-file base_currency = get_setting('INVENTREE_BASE_CURRENCY', CONFIG.get('base_currency', 'USD')) # write to database InvenTreeSetting.set_setting('INVENTREE_DEFAULT_CURRENCY', base_currency, None, create=True)
def test_default_values(self): """ Tests for 'default' values: Ensure that unspecified fields revert to "default" values (as specified in the model field definition) """ url = reverse('api-part-list') response = self.client.post(url, { 'name': 'all defaults', 'description': 'my test part', 'category': 1, }) data = response.data # Check that the un-specified fields have used correct default values self.assertTrue(data['active']) self.assertFalse(data['virtual']) # By default, parts are purchaseable self.assertTrue(data['purchaseable']) # Set the default 'purchaseable' status to True InvenTreeSetting.set_setting( 'PART_PURCHASEABLE', True, self.user ) response = self.client.post(url, { 'name': 'all defaults', 'description': 'my test part 2', 'category': 1, }) # Part should now be purchaseable by default self.assertTrue(response.data['purchaseable']) # "default" values should not be used if the value is specified response = self.client.post(url, { 'name': 'all defaults', 'description': 'my test part 2', 'category': 1, 'active': False, 'purchaseable': False, }) self.assertFalse(response.data['active']) self.assertFalse(response.data['purchaseable'])
def test_task_check_for_updates(self): """Test the task check_for_updates.""" # Check that setting should be empty self.assertEqual( InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION'), '') # Get new version InvenTree.tasks.offload_task(InvenTree.tasks.check_for_updates) # Check that setting is not empty response = InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION') self.assertNotEqual(response, '') self.assertTrue(bool(response))
def test_initial_install(self): """Test if install of plugins on startup works""" from plugin import registry # Check an install run response = registry.install_plugin_file() self.assertEqual(response, 'first_run') # Set dynamic setting to True and rerun to launch install InvenTreeSetting.set_setting('PLUGIN_ON_STARTUP', True, self.user) registry.reload_plugins() # Check that there was anotehr run response = registry.install_plugin_file() self.assertEqual(response, True)
def construct_absolute_url(*arg): """ Construct (or attempt to construct) an absolute URL from a relative URL. This is useful when (for example) sending an email to a user with a link to something in the InvenTree web framework. This requires the BASE_URL configuration option to be set! """ base = str(InvenTreeSetting.get_setting('INVENTREE_BASE_URL')) url = '/'.join(arg) if not base: return url # Strip trailing slash from base url if base.endswith('/'): base = base[:-1] if url.startswith('/'): url = url[1:] url = f"{base}/{url}" return url
def convert_price(price, currency, decimal_places=4): """ Convert price field, returns Money field """ price_adjusted = None # Get default currency from settings default_currency = InvenTreeSetting.get_setting( 'INVENTREE_DEFAULT_CURRENCY') if price: if currency and default_currency: try: # Get adjusted price price_adjusted = convert_money(Money(price, currency), default_currency) except MissingRate: # No conversion rate set price_adjusted = Money(price, currency) else: # Currency exists if currency: price_adjusted = Money(price, currency) # Default currency exists if default_currency: price_adjusted = Money(price, default_currency) if price_adjusted and decimal_places: price_adjusted.decimal_places = decimal_places return price_adjusted
def get_form(self): """ Disable owner field when: - creating child location - and stock ownership control is enable """ form = super().get_form() # Is ownership control enabled? stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') if not stock_ownership_control: # Hide owner field form.fields['owner'].widget = HiddenInput() else: # If user did not selected owner: automatically match to parent's owner if not form['owner'].data: try: parent_id = form['parent'].value() parent = StockLocation.objects.get(pk=parent_id) if parent: form.fields['owner'].initial = parent.owner if not self.request.user.is_superuser: form.fields['owner'].disabled = True except StockLocation.DoesNotExist: pass except ValueError: pass return form
def validate(self, item, form): """ Check that owner is set if stock ownership control is enabled """ parent = form.cleaned_data.get('parent', None) owner = form.cleaned_data.get('owner', None) # Is ownership control enabled? stock_ownership_control = InvenTreeSetting.get_setting( 'STOCK_OWNERSHIP_CONTROL') if stock_ownership_control: if not owner and not self.request.user.is_superuser: form.add_error( 'owner', _('Owner is required (ownership control is enabled)')) else: try: if parent.owner: if parent.owner != owner: error = f'Owner requires to be equivalent to parent\'s owner ({parent.owner})' form.add_error('owner', error) except AttributeError: # No parent pass
def ready(self): if settings.PLUGINS_ENABLED: if not canAppAccessDatabase(allow_test=True): logger.info("Skipping plugin loading sequence") # pragma: no cover else: logger.info('Loading InvenTree plugins') if not registry.is_loading: # this is the first startup try: from common.models import InvenTreeSetting if InvenTreeSetting.get_setting('PLUGIN_ON_STARTUP', create=False): # make sure all plugins are installed registry.install_plugin_file() except: # pragma: no cover pass # get plugins and init them registry.collect_plugins() registry.load_plugins() # drop out of maintenance # makes sure we did not have an error in reloading and maintenance is still active set_maintenance_mode(False) # check git version registry.git_is_modern = check_git_version() if not registry.git_is_modern: # pragma: no cover # simulating old git seems not worth it for coverage log_error(_('Your enviroment has an outdated git version. This prevents InvenTree from loading plugin details.'), 'load') else: logger.info("Plugins not enabled - skipping loading sequence") # pragma: no cover
def navigation_enabled(*args, **kwargs): """ Is plugin navigation enabled? """ if djangosettings.PLUGIN_TESTING: return True return InvenTreeSetting.get_setting('ENABLE_PLUGINS_NAVIGATION') # pragma: no cover
def test_instance_name(self): """Test instance name settings.""" # 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') # The site should also be changed site_obj = Site.objects.all().order_by('id').first() self.assertEqual(site_obj.name, 'Testing title')
def set_globalsetting(self, key, value, user): """ set plugin global setting by key """ from common.models import InvenTreeSetting return InvenTreeSetting.set_setting(self._globalsetting_name(key), value, user)
def get_special_field(self, col_guess, row, file_manager): """ Set special fields """ # set quantity field if 'quantity' in col_guess.lower(): return forms.CharField( required=False, widget=forms.NumberInput(attrs={ 'name': 'quantity' + str(row['index']), 'class': 'numberinput', 'type': 'number', 'min': '0', 'step': 'any', 'value': clean_decimal(row.get('quantity', '')), }) ) # set price field elif 'price' in col_guess.lower(): return MoneyField( label=_(col_guess), default_currency=InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY'), decimal_places=5, max_digits=19, required=False, default_amount=clean_decimal(row.get('purchase_price', '')), ) # return default return super().get_special_field(col_guess, row, file_manager)
def internal_link(link, text): """ Make a <a></a> href which points to an InvenTree URL. Important Note: This only works if the INVENTREE_BASE_URL parameter is set! If the INVENTREE_BASE_URL parameter is not configured, the text will be returned (unlinked) """ text = str(text) base_url = InvenTreeSetting.get_setting('INVENTREE_BASE_URL') # If the base URL is not set, just return the text if not base_url: return text if not base_url.endswith('/'): base_url += '/' if base_url.endswith('/') and link.startswith('/'): link = link[1:] url = f"{base_url}{link}" return mark_safe(f'<a href="{url}">{text}</a>')
def part_image(part): """ Return a fully-qualified path for a part image """ # If in debug mode, return URL to the image, not a local file debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE') if type(part) is Part: img = part.image.name elif type(part) is StockItem: img = part.part.image.name else: img = '' if debug_mode: if img: return os.path.join(settings.MEDIA_URL, img) else: return os.path.join(settings.STATIC_URL, 'img', 'blank_image.png') else: path = os.path.join(settings.MEDIA_ROOT, img) path = os.path.abspath(path) if not os.path.exists(path) or not os.path.isfile(path): # Image does not exist # Return the 'blank' image path = os.path.join(settings.STATIC_ROOT, 'img', 'blank_image.png') path = os.path.abspath(path) return f"file://{path}"
def register_event(event, *args, **kwargs): """Register the event with any interested plugins. Note: This function is processed by the background worker, as it performs multiple database access operations. """ from common.models import InvenTreeSetting logger.debug(f"Registering triggered event: '{event}'") # Determine if there are any plugins which are interested in responding if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting( 'ENABLE_PLUGINS_EVENTS'): with transaction.atomic(): for slug, plugin in registry.plugins.items(): if plugin.mixin_enabled('events'): config = plugin.plugin_config() if config and config.active: logger.debug( f"Registering callback for plugin '{slug}'") # Offload a separate task for each plugin offload_task(process_event, slug, event, *args, **kwargs)
def stock_expiry_enabled(): """ Returns True if the stock expiry feature is enabled """ from common.models import InvenTreeSetting return InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY')
def validate(self, item, form): """ Extra form validation steps """ data = form.cleaned_data part = data.get('part', None) quantity = data.get('quantity', None) owner = data.get('owner', None) if not part: return if not quantity: return try: quantity = Decimal(quantity) except (ValueError, InvalidOperation): form.add_error('quantity', _('Invalid quantity provided')) return if quantity < 0: form.add_error('quantity', _('Quantity cannot be negative')) # Trackable parts are treated differently if part.trackable: sn = data.get('serial_numbers', '') sn = str(sn).strip() if len(sn) > 0: try: serials = extract_serial_numbers(sn, quantity) except ValidationError as e: serials = None form.add_error('serial_numbers', e.messages) if serials is not None: existing = part.find_conflicting_serial_numbers(serials) if len(existing) > 0: exists = ','.join([str(x) for x in existing]) form.add_error( 'serial_numbers', _('Serial numbers already exist') + ': ' + exists ) # Is ownership control enabled? stock_ownership_control = InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL') if stock_ownership_control: # Check if owner is set if not owner and not self.request.user.is_superuser: form.add_error('owner', _('Owner is required (ownership control is enabled)')) return
def require_2fa(self, request): # Superusers are require to have 2FA. try: if url_matcher.resolve(request.path[1:]): return InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA') except Resolver404: pass return False
def require_2fa(self, request): """Use setting to check if MFA should be enforced for frontend page.""" try: if url_matcher.resolve(request.path[1:]): return InvenTreeSetting.get_setting('LOGIN_ENFORCE_MFA') except Resolver404: pass return False
def setting_object(key, *args, **kwargs): """ Return a setting object speciifed by the given key (Or return None if the setting does not exist) """ setting = InvenTreeSetting.get_setting_object(key) return setting