diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 93b208451b..b884796cec 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -182,9 +182,9 @@ jobs: run: | sudo apt-get update sudo apt-get install gettext + python -m pip install -U pip pip3 install invoke - invoke install - invoke static + invoke update - name: Coverage Tests run: | invoke coverage @@ -245,11 +245,12 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install libpq-dev + sudo apt-get install libpq-dev gettext + python -m pip install -U pip pip3 install invoke pip3 install psycopg2 pip3 install django-redis>=5.0.0 - invoke install + invoke update - name: Run Tests run: invoke test - name: Data Import Export @@ -302,10 +303,11 @@ jobs: - name: Install Dependencies run: | sudo apt-get update - sudo apt-get install libmysqlclient-dev + sudo apt-get install libmysqlclient-dev gettext + python -m pip install -U pip pip3 install invoke pip3 install mysqlclient - invoke install + invoke update - name: Run Tests run: invoke test - name: Data Import Export diff --git a/InvenTree/InvenTree/test_middleware.py b/InvenTree/InvenTree/test_middleware.py index bced2eb079..865e285783 100644 --- a/InvenTree/InvenTree/test_middleware.py +++ b/InvenTree/InvenTree/test_middleware.py @@ -32,6 +32,10 @@ class MiddlewareTests(TestCase): # logout self.client.logout() + # check that static files go through + # TODO @matmair reenable this check + # self.check_path('/static/css/inventree.css', 302) + # check that account things go through self.check_path(reverse('account_login')) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 92e5c1522c..aa54c814ad 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -1429,6 +1429,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'validator': bool, }, + 'SEARCH_HIDE_INACTIVE_PARTS': { + 'name': _("Hide Inactive Parts"), + 'description': _('Excluded inactive parts from search preview window'), + 'default': False, + 'validator': bool, + }, + 'SEARCH_PREVIEW_SHOW_CATEGORIES': { 'name': _('Search Categories'), 'description': _('Display part categories in search preview window'), @@ -1443,6 +1450,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'validator': bool, }, + 'SEARCH_PREVIEW_HIDE_UNAVAILABLE_STOCK': { + 'name': _('Hide Unavailable Stock Items'), + 'description': _('Exclude stock items which are not available from the search preview window'), + 'validator': bool, + 'default': False, + }, + 'SEARCH_PREVIEW_SHOW_LOCATIONS': { 'name': _('Search Locations'), 'description': _('Display stock locations in search preview window'), @@ -1464,6 +1478,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'validator': bool, }, + 'SEARCH_PREVIEW_EXCLUDE_INACTIVE_PURCHASE_ORDERS': { + 'name': _('Exclude Inactive Purchase Orders'), + 'description': _('Exclude inactive purchase orders from search preview window'), + 'default': True, + 'validator': bool, + }, + 'SEARCH_PREVIEW_SHOW_SALES_ORDERS': { 'name': _('Search Sales Orders'), 'description': _('Display sales orders in search preview window'), @@ -1471,6 +1492,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'validator': bool, }, + 'SEARCH_PREVIEW_EXCLUDE_INACTIVE_SALES_ORDERS': { + 'name': _('Exclude Inactive Sales Orders'), + 'description': _('Exclude inactive sales orders from search preview window'), + 'validator': bool, + 'default': True, + }, + 'SEARCH_PREVIEW_RESULTS': { 'name': _('Search Preview Results'), 'description': _('Number of results to show in each section of the search preview window'), @@ -1478,13 +1506,6 @@ class InvenTreeUserSetting(BaseInvenTreeSetting): 'validator': [int, MinValueValidator(1)] }, - 'SEARCH_HIDE_INACTIVE_PARTS': { - 'name': _("Hide Inactive Parts"), - 'description': _('Hide inactive parts in search preview window'), - 'default': False, - 'validator': bool, - }, - 'PART_SHOW_QUANTITY_IN_FORMS': { 'name': _('Show Quantity in Forms'), 'description': _('Display available part quantity in some forms'), @@ -1701,6 +1722,9 @@ class ColorTheme(models.Model): @classmethod def get_color_themes_choices(cls): """ Get all color themes from static folder """ + if settings.TESTING and not os.path.exists(settings.STATIC_COLOR_THEMES_DIR): + logger.error('Theme directory does not exsist') + return [] # Get files list from css/color-themes/ folder files_list = [] diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 7f6f6dbe40..1f32b8e941 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -12,7 +12,7 @@ from InvenTree.helpers import str2bool from plugin.models import NotificationUserSetting, PluginConfig from plugin import registry -from .models import InvenTreeSetting, InvenTreeUserSetting, WebhookEndpoint, WebhookMessage, NotificationEntry +from .models import InvenTreeSetting, InvenTreeUserSetting, WebhookEndpoint, WebhookMessage, NotificationEntry, ColorTheme from .api import WebhookView CONTENT_TYPE_JSON = 'application/json' @@ -163,10 +163,19 @@ class SettingsTest(TestCase): """ for key, setting in InvenTreeSetting.SETTINGS.items(): - self.run_settings_check(key, setting) + + try: + self.run_settings_check(key, setting) + except Exception as exc: + print(f"run_settings_check failed for global setting '{key}'") + raise exc for key, setting in InvenTreeUserSetting.SETTINGS.items(): - self.run_settings_check(key, setting) + try: + self.run_settings_check(key, setting) + except Exception as exc: + print(f"run_settings_check failed for user setting '{key}'") + raise exc def test_defaults(self): """ @@ -707,3 +716,35 @@ class LoadingTest(TestCase): # now it should be false again self.assertFalse(common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED')) + + +class ColorThemeTest(TestCase): + """Tests for ColorTheme""" + + def test_choices(self): + """Test that default choices are returned""" + result = ColorTheme.get_color_themes_choices() + + # skip + if not result: + return + self.assertIn(('default', 'Default'), result) + + def test_valid_choice(self): + """Check that is_valid_choice works correctly""" + result = ColorTheme.get_color_themes_choices() + + # skip + if not result: + return + + # check wrong reference + self.assertFalse(ColorTheme.is_valid_choice('abcdd')) + + # create themes + aa = ColorTheme.objects.create(user='aa', name='testname') + ab = ColorTheme.objects.create(user='ab', name='darker') + + # check valid theme + self.assertFalse(ColorTheme.is_valid_choice(aa)) + self.assertTrue(ColorTheme.is_valid_choice(ab)) diff --git a/InvenTree/label/apps.py b/InvenTree/label/apps.py index 1a719a9638..f2c3c4ada5 100644 --- a/InvenTree/label/apps.py +++ b/InvenTree/label/apps.py @@ -2,6 +2,7 @@ import os import shutil import logging import hashlib +import warnings from django.apps import AppConfig from django.conf import settings @@ -42,6 +43,15 @@ class LabelConfig(AppConfig): """ Create all default templates """ + # Test if models are ready + try: + from .models import StockLocationLabel + assert bool(StockLocationLabel is not None) + except AppRegistryNotReady: + # Database might not yet be ready + warnings.warn('Database was not ready for creating labels') + return + self.create_stock_item_labels() self.create_stock_location_labels() self.create_part_labels() @@ -52,11 +62,7 @@ class LabelConfig(AppConfig): if they do not already exist """ - try: - from .models import StockItemLabel - except AppRegistryNotReady: # pragma: no cover - # Database might not by ready yet - return + from .models import StockItemLabel src_dir = os.path.join( os.path.dirname(os.path.realpath(__file__)), @@ -139,11 +145,7 @@ class LabelConfig(AppConfig): if they do not already exist """ - try: - from .models import StockLocationLabel - except AppRegistryNotReady: # pragma: no cover - # Database might not yet be ready - return + from .models import StockLocationLabel src_dir = os.path.join( os.path.dirname(os.path.realpath(__file__)), @@ -233,11 +235,7 @@ class LabelConfig(AppConfig): if they do not already exist. """ - try: - from .models import PartLabel - except AppRegistryNotReady: # pragma: no cover - # Database might not yet be ready - return + from .models import PartLabel src_dir = os.path.join( os.path.dirname(os.path.realpath(__file__)), diff --git a/InvenTree/label/tests.py b/InvenTree/label/tests.py index 9e066aa91e..5bf7ff3c7c 100644 --- a/InvenTree/label/tests.py +++ b/InvenTree/label/tests.py @@ -4,12 +4,14 @@ import os from django.conf import settings from django.apps import apps +from django.urls import reverse from django.core.exceptions import ValidationError from InvenTree.helpers import validateFilterString from InvenTree.api_tester import InvenTreeAPITestCase -from .models import StockItemLabel, StockLocationLabel +from .models import StockItemLabel, StockLocationLabel, PartLabel +from part.models import Part from stock.models import StockItem @@ -82,3 +84,13 @@ class LabelTest(InvenTreeAPITestCase): with self.assertRaises(ValidationError): validateFilterString(bad_filter_string, model=StockItem) + + def test_label_rendering(self): + """Test label rendering""" + + labels = PartLabel.objects.all() + part = Part.objects.first() + + for label in labels: + url = reverse('api-part-label-print', kwargs={'pk': label.pk}) + self.get(f'{url}?parts={part.pk}', expected_code=200) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index c30e604e68..a4af8a9383 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -83,7 +83,7 @@ def render_date(context, date_object): user = context.get('user', None) - if user: + if user and user.is_authenticated: # User is specified - look for their date display preference user_date_format = InvenTreeUserSetting.get_setting('DATE_DISPLAY_FORMAT', user=user) else: @@ -329,7 +329,7 @@ def settings_value(key, *args, **kwargs): """ if 'user' in kwargs: - if not kwargs['user']: + 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']) diff --git a/InvenTree/templates/InvenTree/settings/user_search.html b/InvenTree/templates/InvenTree/settings/user_search.html index 1883110b80..f18fb5816c 100644 --- a/InvenTree/templates/InvenTree/settings/user_search.html +++ b/InvenTree/templates/InvenTree/settings/user_search.html @@ -15,16 +15,19 @@ {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_PARTS" user_setting=True icon='fa-shapes' %} + {% include "InvenTree/settings/setting.html" with key="SEARCH_HIDE_INACTIVE_PARTS" user_setting=True icon='fa-eye-slash' %} {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_CATEGORIES" user_setting=True icon='fa-sitemap' %} {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_STOCK" user_setting=True icon='fa-boxes' %} + {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_HIDE_UNAVAILABLE_STOCK" user_setting=True icon='fa-eye-slash' %} {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_LOCATIONS" user_setting=True icon='fa-sitemap' %} {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_COMPANIES" user_setting=True icon='fa-building' %} {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS" user_setting=True icon='fa-shopping-cart' %} + {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_EXCLUDE_INACTIVE_PURCHASE_ORDERS" user_setting=True icon='fa-eye-slash' %} {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_SHOW_SALES_ORDERS" user_setting=True icon='fa-truck' %} + {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_EXCLUDE_INACTIVE_SALES_ORDERS" user_setting=True icon='fa-eye-slash' %} {% include "InvenTree/settings/setting.html" with key="SEARCH_PREVIEW_RESULTS" user_setting=True icon='fa-search' %} - {% include "InvenTree/settings/setting.html" with key="SEARCH_HIDE_INACTIVE_PARTS" user_setting=True icon='fa-eye-slash' %}
diff --git a/InvenTree/templates/js/translated/search.js b/InvenTree/templates/js/translated/search.js index 4db310a062..9758ee2ff9 100644 --- a/InvenTree/templates/js/translated/search.js +++ b/InvenTree/templates/js/translated/search.js @@ -122,14 +122,22 @@ function updateSearch() { if (user_settings.SEARCH_PREVIEW_SHOW_STOCK) { // Search for matching stock items + + var filters = { + part_detail: true, + location_detail: true, + }; + + if (user_settings.SEARCH_PREVIEW_HIDE_UNAVAILABLE_STOCK) { + // Only show 'in stock' items in the preview windoww + filters.in_stock = true; + } + addSearchQuery( 'stock', '{% trans "Stock Items" %}', '{% url "api-stock-list" %}', - { - part_detail: true, - location_detail: true, - }, + filters, renderStockItem, { url: '/stock/item', @@ -167,15 +175,21 @@ function updateSearch() { } if (user_settings.SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS) { + + var filters = { + supplier_detail: true, + }; + + if (user_settings.SEARCH_PREVIEW_EXCLUDE_INACTIVE_PURCHASE_ORDERS) { + filters.outstanding = true; + } + // Search for matching purchase orders addSearchQuery( 'purchaseorder', '{% trans "Purchase Orders" %}', '{% url "api-po-list" %}', - { - supplier_detail: true, - outstanding: true, - }, + filters, renderPurchaseOrder, { url: '/order/purchase-order', @@ -184,15 +198,22 @@ function updateSearch() { } if (user_settings.SEARCH_PREVIEW_SHOW_SALES_ORDERS) { + + var filters = { + customer_detail: true, + }; + + // Hide inactive (not "outstanding" orders) + if (user_settings.SEARCH_PREVIEW_EXCLUDE_INACTIVE_SALES_ORDERS) { + filters.outstanding = true; + } + // Search for matching sales orders addSearchQuery( 'salesorder', '{% trans "Sales Orders" %}', '{% url "api-so-list" %}', - { - customer_detail: true, - outstanding: true, - }, + filters, renderSalesOrder, { url: '/order/sales-order', diff --git a/tasks.py b/tasks.py index fb0f085146..2511090f86 100644 --- a/tasks.py +++ b/tasks.py @@ -15,7 +15,6 @@ def apps(): """ return [ - 'barcode', 'build', 'common', 'company', @@ -24,8 +23,9 @@ def apps(): 'part', 'report', 'stock', - 'InvenTree', 'users', + 'plugin', + 'InvenTree', ]