Пример #1
0
    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'))
Пример #2
0
    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()
Пример #3
0
    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'))
Пример #4
0
    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",
        ])
Пример #5
0
    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)
Пример #6
0
    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))
Пример #7
0
    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')
Пример #8
0
    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')
Пример #9
0
    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')
Пример #10
0
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)
Пример #11
0
    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'])
Пример #12
0
    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))
Пример #13
0
    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)
Пример #14
0
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
Пример #15
0
        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
Пример #16
0
    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
Пример #17
0
    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
Пример #18
0
    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
Пример #19
0
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
Пример #20
0
    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')
Пример #21
0
 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)
Пример #22
0
    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)
Пример #23
0
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>')
Пример #24
0
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}"
Пример #25
0
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)
Пример #26
0
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')
Пример #27
0
    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
Пример #28
0
 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
Пример #29
0
 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
Пример #30
0
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