def test_custom(self): """Update some of the part values and re-test.""" for val in [True, False]: InvenTreeSetting.set_setting('PART_COMPONENT', val, self.user) InvenTreeSetting.set_setting('PART_PURCHASEABLE', val, self.user) InvenTreeSetting.set_setting('PART_SALABLE', val, self.user) InvenTreeSetting.set_setting('PART_TRACKABLE', val, self.user) InvenTreeSetting.set_setting('PART_ASSEMBLY', val, self.user) InvenTreeSetting.set_setting('PART_TEMPLATE', val, self.user) self.assertEqual(val, InvenTreeSetting.get_setting('PART_COMPONENT')) self.assertEqual(val, InvenTreeSetting.get_setting('PART_PURCHASEABLE')) self.assertEqual(val, InvenTreeSetting.get_setting('PART_SALABLE')) self.assertEqual(val, InvenTreeSetting.get_setting('PART_TRACKABLE')) part = self.make_part() self.assertEqual(part.component, val) self.assertEqual(part.purchaseable, val) self.assertEqual(part.salable, val) self.assertEqual(part.trackable, val) self.assertEqual(part.assembly, val) self.assertEqual(part.is_template, val) Part.objects.filter(pk=part.pk).delete()
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_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 __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_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 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 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 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 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 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 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 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 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 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 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 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): """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 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 settings_value(key, *args, **kwargs): """ Return a settings value specified by the given key """ if 'user' in kwargs: return InvenTreeUserSetting.get_setting(key, user=kwargs['user']) return InvenTreeSetting.get_setting(key)
def settings_value(key, *args, **kwargs): """Return a settings value specified by the given key.""" if 'user' in kwargs: if not kwargs['user'] or (kwargs['user'] and kwargs['user'].is_authenticated is False): return InvenTreeUserSetting.get_setting(key) return InvenTreeUserSetting.get_setting(key, user=kwargs['user']) return InvenTreeSetting.get_setting(key)
def get_form(self): """ Customize form data for StockLocation editing. Limit the choices for 'parent' field to those which make sense. If ownership control is enabled and location has parent, disable owner field. """ form = super(AjaxUpdateView, self).get_form() location = self.get_object() # Remove any invalid choices for the 'parent' field parent_choices = StockLocation.objects.all() parent_choices = parent_choices.exclude( id__in=location.getUniqueChildren()) form.fields['parent'].queryset = parent_choices # 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: # Get location's owner location_owner = location.owner if location_owner: if location.parent: try: # If location has parent and owner: automatically select parent's owner parent_owner = location.parent.owner form.fields['owner'].initial = parent_owner except AttributeError: pass else: # If current owner exists: automatically select it form.fields['owner'].initial = location_owner # Update queryset or disable field (only if not admin) if not self.request.user.is_superuser: if type(location_owner.owner) is Group: user_as_owner = Owner.get_owner(self.request.user) queryset = location_owner.get_related_owners( include_group=True) if user_as_owner not in queryset: # Only owners or admin can change current owner form.fields['owner'].disabled = True else: form.fields['owner'].queryset = queryset return form
def validate(self, item, form): """ Check that owner is set if stock ownership control is enabled """ 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)'))
def logo_image(**kwargs): """Return a fully-qualified path for the logo image. - If a custom logo has been provided, return a path to that logo - Otherwise, return a path to the default InvenTree logo """ # If in debug mode, return URL to the image, not a local file debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE') return InvenTree.helpers.getLogoImage(as_file=not debug_mode, **kwargs)
def save_user(self, request, user, form, commit=True): user = super().save_user(request, user, form, commit=commit) start_group = InvenTreeSetting.get_setting('SIGNUP_GROUP') if start_group: try: group = Group.objects.get(id=start_group) user.groups.add(group) except Group.DoesNotExist: logger.error('The setting `SIGNUP_GROUP` contains an non existant group', start_group) user.save() return user
def clean(self): cleaned_data = super().clean() # check for two mail fields if InvenTreeSetting.get_setting('LOGIN_SIGNUP_MAIL_TWICE'): email = cleaned_data.get("email") email2 = cleaned_data.get("email2") if (email and email2) and email != email2: self.add_error("email2", _("You must type the same email each time.")) return cleaned_data
def currency_code_default(): """ Returns the default currency code (or USD if not specified) """ code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') if code not in CURRENCIES: code = 'USD' return code
def get_reference_pattern(cls): """Returns the reference pattern associated with this model. This is defined by a global setting object, specified by the REFERENCE_PATTERN_SETTING attribute """ # By default, we return an empty string if cls.REFERENCE_PATTERN_SETTING is None: return '' return InvenTreeSetting.get_setting(cls.REFERENCE_PATTERN_SETTING, create=False).strip()
def validate_part_ipn(value): """ Validate the Part IPN against regex rule """ pattern = InvenTreeSetting.get_setting('part_ipn_regex') if pattern: match = re.search(pattern, value) if match is None: raise ValidationError( _('IPN must match regex pattern') + " '{pat}'".format(pat=pattern))