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/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..1dc2ba3f70 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: mixed-line-ending +- repo: https://github.com/pycqa/flake8 + rev: '4.0.1' + hooks: + - id: flake8 +- repo: https://github.com/pycqa/isort + rev: '5.10.1' + hooks: + - id: isort diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c36c11b62b..e6a58dba54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,9 @@ Please read the contribution guidelines below, before submitting your first pull request to the InvenTree codebase. +## Setup + +Please run `invoke setup_dev` in the root directory of your InvenTree code base to set up your development setup before starting to contribute. This will install and set up pre-commit to run some checks before each commit and help reduce the style errors. + ## Branches and Versioning InvenTree roughly follow the [GitLab flow](https://docs.gitlab.com/ee/topics/gitlab_flow.html) branching style, to allow simple management of multiple tagged releases, short-lived branches, and development on the main branch. @@ -90,7 +94,8 @@ The various github actions can be found in the `./github/workflows` directory ## Code Style -Sumbitted Python code is automatically checked against PEP style guidelines. Locally you can run `invoke style` to ensure the style checks will pass, before submitting the PR. +Sumbitted Python code is automatically checked against PEP style guidelines. Locally you can run `invoke style` to ensure the style checks will pass, before submitting the PR. +Please write docstrings for each function and class - we follow the [google doc-style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) for python. Docstrings for general javascript code is encouraged! Docstyles are checked by `invoke style`. ## Documentation diff --git a/InvenTree/InvenTree/api_tester.py b/InvenTree/InvenTree/api_tester.py index 34976ffbfe..935252de5b 100644 --- a/InvenTree/InvenTree/api_tester.py +++ b/InvenTree/InvenTree/api_tester.py @@ -6,16 +6,14 @@ import csv import io import re -from django.http.response import StreamingHttpResponse from django.contrib.auth import get_user_model from django.contrib.auth.models import Group +from django.http.response import StreamingHttpResponse + from rest_framework.test import APITestCase -class InvenTreeAPITestCase(APITestCase): - """ - Base class for running InvenTree API tests - """ +class UserMixin: # User information username = 'testuser' @@ -52,37 +50,49 @@ class InvenTreeAPITestCase(APITestCase): self.user.save() - for role in self.roles: - self.assignRole(role) + # Assign all roles if set + if self.roles == 'all': + self.assignRole(assign_all=True) + # else filter the roles + else: + for role in self.roles: + self.assignRole(role) if self.auto_login: self.client.login(username=self.username, password=self.password) - def assignRole(self, role): + def assignRole(self, role=None, assign_all: bool = False): """ Set the user roles for the registered user """ # role is of the format 'rule.permission' e.g. 'part.add' - rule, perm = role.split('.') + if not assign_all and role: + rule, perm = role.split('.') for ruleset in self.group.rule_sets.all(): - if ruleset.name == rule: + if assign_all or ruleset.name == rule: - if perm == 'view': + if assign_all or perm == 'view': ruleset.can_view = True - elif perm == 'change': + elif assign_all or perm == 'change': ruleset.can_change = True - elif perm == 'delete': + elif assign_all or perm == 'delete': ruleset.can_delete = True - elif perm == 'add': + elif assign_all or perm == 'add': ruleset.can_add = True ruleset.save() break + +class InvenTreeAPITestCase(UserMixin, APITestCase): + """ + Base class for running InvenTree API tests + """ + def getActions(self, url): """ Return a dict of the 'actions' available at a given endpoint. diff --git a/InvenTree/InvenTree/ci_render_js.py b/InvenTree/InvenTree/ci_render_js.py index 94530db096..824fe5d7e3 100644 --- a/InvenTree/InvenTree/ci_render_js.py +++ b/InvenTree/InvenTree/ci_render_js.py @@ -3,14 +3,13 @@ Pull rendered copies of the templated only used for testing the js files! - This file is omited from coverage """ -from django.test import TestCase # pragma: no cover -from django.contrib.auth import get_user_model # pragma: no cover - import os # pragma: no cover -import pathlib # pragma: no cover +import pathlib + +from InvenTree.helpers import InvenTreeTestCase # pragma: no cover -class RenderJavascriptFiles(TestCase): # pragma: no cover +class RenderJavascriptFiles(InvenTreeTestCase): # pragma: no cover """ A unit test to "render" javascript files. @@ -18,18 +17,6 @@ class RenderJavascriptFiles(TestCase): # pragma: no cover we need the fully-rendered files for linting and static tests. """ - def setUp(self): - - user = get_user_model() - - self.user = user.objects.create_user( - username='testuser', - password='testpassword', - email='user@gmail.com', - ) - - self.client.login(username='testuser', password='testpassword') - def download_file(self, filename, prefix): url = os.path.join(prefix, filename) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 9e6e24acb8..34ce1439c8 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -14,8 +14,10 @@ from wsgiref.util import FileWrapper from django.http import StreamingHttpResponse from django.core.exceptions import ValidationError, FieldError from django.utils.translation import gettext_lazy as _ +from django.test import TestCase from django.contrib.auth.models import Permission +from .api_tester import UserMixin import InvenTree.version @@ -781,3 +783,7 @@ def inheritors(cls): subcls.add(child) work.append(child) return subcls + + +class InvenTreeTestCase(UserMixin, TestCase): + pass diff --git a/InvenTree/InvenTree/test_api.py b/InvenTree/InvenTree/test_api.py index f55dfbcda2..889ff674b3 100644 --- a/InvenTree/InvenTree/test_api.py +++ b/InvenTree/InvenTree/test_api.py @@ -1,22 +1,17 @@ """ Low level tests for the InvenTree API """ -from rest_framework import status - -from django.test import TestCase - -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group - -from django.urls import reverse - -from InvenTree.api_tester import InvenTreeAPITestCase - -from users.models import RuleSet - from base64 import b64encode +from django.urls import reverse -class HTMLAPITests(TestCase): +from rest_framework import status + +from InvenTree.api_tester import InvenTreeAPITestCase +from InvenTree.helpers import InvenTreeTestCase +from users.models import RuleSet + + +class HTMLAPITests(InvenTreeTestCase): """ Test that we can access the REST API endpoints via the HTML interface. @@ -24,33 +19,7 @@ class HTMLAPITests(TestCase): which raised an AssertionError when using the HTML API interface, while the regular JSON interface continued to work as expected. """ - - def setUp(self): - super().setUp() - - # Create a user - user = get_user_model() - - self.user = user.objects.create_user( - username='username', - email='user@email.com', - password='password' - ) - - # Put the user into a group with the correct permissions - group = Group.objects.create(name='mygroup') - self.user.groups.add(group) - - # Give the group *all* the permissions! - for rule in group.rule_sets.all(): - rule.can_view = True - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - self.client.login(username='username', password='password') + roles = 'all' def test_part_api(self): url = reverse('api-part-list') diff --git a/InvenTree/InvenTree/test_middleware.py b/InvenTree/InvenTree/test_middleware.py index bced2eb079..e9e5e4846f 100644 --- a/InvenTree/InvenTree/test_middleware.py +++ b/InvenTree/InvenTree/test_middleware.py @@ -1,12 +1,11 @@ """Tests for middleware functions""" -from django.test import TestCase - -from django.contrib.auth import get_user_model from django.urls import reverse +from InvenTree.helpers import InvenTreeTestCase -class MiddlewareTests(TestCase): + +class MiddlewareTests(InvenTreeTestCase): """Test for middleware functions""" def check_path(self, url, code=200, **kwargs): @@ -14,15 +13,6 @@ class MiddlewareTests(TestCase): self.assertEqual(response.status_code, code) return response - def setUp(self): - super().setUp() - - # Create a user - user = get_user_model() - - self.user = user.objects.create_user(username='username', email='user@email.com', password='password') - self.client.login(username='username', password='password') - def test_AuthRequiredMiddleware(self): """Test the auth middleware""" @@ -32,6 +22,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/InvenTree/test_views.py b/InvenTree/InvenTree/test_views.py index 0a145ec508..8dac196a1d 100644 --- a/InvenTree/InvenTree/test_views.py +++ b/InvenTree/InvenTree/test_views.py @@ -5,28 +5,17 @@ Unit tests for the main web views import re import os -from django.test import TestCase from django.urls import reverse -from django.contrib.auth import get_user_model + +from InvenTree.helpers import InvenTreeTestCase -class ViewTests(TestCase): +class ViewTests(InvenTreeTestCase): """ Tests for various top-level views """ username = 'test_user' password = 'test_pass' - def setUp(self): - - # Create a user - self.user = get_user_model().objects.create_user(self.username, 'user@email.com', self.password) - self.user.set_password(self.password) - self.user.save() - - result = self.client.login(username=self.username, password=self.password) - - self.assertEqual(result, True) - def test_api_doc(self): """ Test that the api-doc view works """ diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 26b50a0eca..553856d86f 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -7,8 +7,8 @@ from unittest import mock from django.test import TestCase, override_settings import django.core.exceptions as django_exceptions from django.core.exceptions import ValidationError -from django.contrib.auth import get_user_model from django.conf import settings +from django.contrib.auth import get_user_model from djmoney.money import Money from djmoney.contrib.exchange.models import Rate, convert_money @@ -457,18 +457,12 @@ class TestStatus(TestCase): self.assertEqual(ready.isImportingData(), False) -class TestSettings(TestCase): +class TestSettings(helpers.InvenTreeTestCase): """ Unit tests for settings """ - def setUp(self) -> None: - self.user_mdl = get_user_model() - - # Create a user for auth - user = get_user_model() - self.user = user.objects.create_superuser('testuser1', 'test1@testing.com', 'password1') - self.client.login(username='testuser1', password='password1') + superuser = True def in_env_context(self, envs={}): """Patch the env to include the given dict""" @@ -483,8 +477,9 @@ class TestSettings(TestCase): @override_settings(TESTING_ENV=True) def test_set_user_to_few(self): + user_model = get_user_model() # add shortcut - user_count = self.user_mdl.objects.count + user_count = user_model.objects.count # enable testing mode settings.TESTING_ENV = True @@ -506,14 +501,18 @@ class TestSettings(TestCase): }) self.assertEqual(user_count(), 2) + username2 = 'testuser1' + email2 = 'test1@testing.com' + password2 = 'password1' + # create user manually - self.user_mdl.objects.create_user('testuser', 'test@testing.com', 'password') + user_model.objects.create_user(username2, email2, password2) self.assertEqual(user_count(), 3) # check it will not be created again self.run_reload({ - 'INVENTREE_ADMIN_USER': 'testuser', - 'INVENTREE_ADMIN_EMAIL': 'test@testing.com', - 'INVENTREE_ADMIN_PASSWORD': 'password', + 'INVENTREE_ADMIN_USER': username2, + 'INVENTREE_ADMIN_EMAIL': email2, + 'INVENTREE_ADMIN_PASSWORD': password2, }) self.assertEqual(user_count(), 3) @@ -548,7 +547,7 @@ class TestSettings(TestCase): # with env set with self.in_env_context({'INVENTREE_CONFIG_FILE': 'my_special_conf.yaml'}): - self.assertIn('inventree/inventree/my_special_conf.yaml', config.get_config_file().lower()) + self.assertIn('inventree/my_special_conf.yaml', config.get_config_file().lower()) def test_helpers_plugin_file(self): # normal run - not configured @@ -574,18 +573,11 @@ class TestSettings(TestCase): self.assertEqual(config.get_setting(TEST_ENV_NAME, None), '321') -class TestInstanceName(TestCase): +class TestInstanceName(helpers.InvenTreeTestCase): """ 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 diff --git a/InvenTree/build/test_api.py b/InvenTree/build/test_api.py index 4ba54e9c73..1adf38935e 100644 --- a/InvenTree/build/test_api.py +++ b/InvenTree/build/test_api.py @@ -2,10 +2,6 @@ from datetime import datetime, timedelta from django.urls import reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group - -from rest_framework.test import APITestCase from rest_framework import status from part.models import Part @@ -16,7 +12,7 @@ from InvenTree.status_codes import BuildStatus from InvenTree.api_tester import InvenTreeAPITestCase -class TestBuildAPI(APITestCase): +class TestBuildAPI(InvenTreeAPITestCase): """ Series of tests for the Build DRF API - Tests for Build API @@ -30,25 +26,11 @@ class TestBuildAPI(APITestCase): 'build', ] - def setUp(self): - # Create a user for auth - user = get_user_model() - self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') - - g = Group.objects.create(name='builders') - self.user.groups.add(g) - - for rule in g.rule_sets.all(): - if rule.name == 'build': - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - g.save() - - self.client.login(username='testuser', password='password') + roles = [ + 'build.change', + 'build.add', + 'build.delete', + ] def test_get_build_list(self): """ diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index d3c0c41112..8bd7553a52 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -1,18 +1,16 @@ -from django.test import TestCase from django.urls import reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group - from datetime import datetime, timedelta +from InvenTree.helpers import InvenTreeTestCase + from .models import Build from stock.models import StockItem from InvenTree.status_codes import BuildStatus -class BuildTestSimple(TestCase): +class BuildTestSimple(InvenTreeTestCase): fixtures = [ 'category', @@ -21,27 +19,11 @@ class BuildTestSimple(TestCase): 'build', ] - def setUp(self): - # Create a user for auth - user = get_user_model() - user.objects.create_user('testuser', 'test@testing.com', 'password') - - self.user = user.objects.get(username='testuser') - - g = Group.objects.create(name='builders') - self.user.groups.add(g) - - for rule in g.rule_sets.all(): - if rule.name == 'build': - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - g.save() - - self.client.login(username='testuser', password='password') + roles = [ + 'build.change', + 'build.add', + 'build.delete', + ] def test_build_objects(self): # Ensure the Build objects were correctly created @@ -106,7 +88,7 @@ class BuildTestSimple(TestCase): self.assertEqual(build.status, BuildStatus.CANCELLED) -class TestBuildViews(TestCase): +class TestBuildViews(InvenTreeTestCase): """ Tests for Build app views """ fixtures = [ @@ -116,28 +98,15 @@ class TestBuildViews(TestCase): 'build', ] + roles = [ + 'build.change', + 'build.add', + 'build.delete', + ] + def setUp(self): super().setUp() - # Create a user - user = get_user_model() - self.user = user.objects.create_user('username', 'user@email.com', 'password') - - g = Group.objects.create(name='builders') - self.user.groups.add(g) - - for rule in g.rule_sets.all(): - if rule.name == 'build': - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - g.save() - - self.client.login(username='username', password='password') - # Create a build output for build # 1 self.build = Build.objects.get(pk=1) 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 8e3f69c21e..39ea376274 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -4,21 +4,20 @@ import json 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 InvenTree.helpers import str2bool +from InvenTree.helpers import InvenTreeTestCase, 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' -class SettingsTest(TestCase): +class SettingsTest(InvenTreeTestCase): """ Tests for the 'settings' model """ @@ -27,16 +26,6 @@ class SettingsTest(TestCase): 'settings', ] - def setUp(self): - - user = get_user_model() - - self.user = user.objects.create_user('username', 'user@email.com', 'password') - self.user.is_staff = True - self.user.save() - - self.client.login(username='username', password='password') - def test_settings_objects(self): # There should be two settings objects in the database @@ -112,28 +101,70 @@ class SettingsTest(TestCase): self.assertIn('STOCK_OWNERSHIP_CONTROL', result) self.assertIn('SIGNUP_GROUP', result) - def test_required_values(self): + def run_settings_check(self, key, setting): + + self.assertTrue(type(setting) is dict) + + name = setting.get('name', None) + + self.assertIsNotNone(name) + self.assertIn('django.utils.functional.lazy', str(type(name))) + + description = setting.get('description', None) + + self.assertIsNotNone(description) + self.assertIn('django.utils.functional.lazy', str(type(description))) + + if key != key.upper(): + raise ValueError(f"Setting key '{key}' is not uppercase") # pragma: no cover + + # Check that only allowed keys are provided + allowed_keys = [ + 'name', + 'description', + 'default', + 'validator', + 'hidden', + 'choices', + 'units', + 'requires_restart', + ] + + for k in setting.keys(): + self.assertIn(k, allowed_keys) + + # Check default value for boolean settings + validator = setting.get('validator', None) + + if validator is bool: + default = setting.get('default', None) + + # Default value *must* be supplied for boolean setting! + self.assertIsNotNone(default) + + # Default value for boolean must itself be a boolean + self.assertIn(default, [True, False]) + + def test_setting_data(self): """ - - Ensure that every global setting has a name. - - Ensure that every global setting has a description. + - Ensure that every setting has a name, which is translated + - Ensure that every setting has a description, which is translated """ - for key in InvenTreeSetting.SETTINGS.keys(): + for key, setting in InvenTreeSetting.SETTINGS.items(): - setting = InvenTreeSetting.SETTINGS[key] + try: + self.run_settings_check(key, setting) + except Exception as exc: + print(f"run_settings_check failed for global setting '{key}'") + raise exc - name = setting.get('name', None) - - if name is None: - raise ValueError(f'Missing GLOBAL_SETTING name for {key}') # pragma: no cover - - description = setting.get('description', None) - - if description is None: - raise ValueError(f'Missing GLOBAL_SETTING description for {key}') # pragma: no cover - - if key != key.upper(): - raise ValueError(f"SETTINGS key '{key}' is not uppercase") # pragma: no cover + for key, setting in InvenTreeUserSetting.SETTINGS.items(): + 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): """ @@ -674,3 +705,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/company/test_views.py b/InvenTree/company/test_views.py index 29900236bf..a3ecd1651a 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -1,12 +1,11 @@ """ Unit tests for Company views (see views.py) """ -from django.test import TestCase from django.urls import reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group + +from InvenTree.helpers import InvenTreeTestCase -class CompanyViewTestBase(TestCase): +class CompanyViewTestBase(InvenTreeTestCase): fixtures = [ 'category', @@ -17,32 +16,7 @@ class CompanyViewTestBase(TestCase): 'supplier_part', ] - def setUp(self): - super().setUp() - - # Create a user - user = get_user_model() - - self.user = user.objects.create_user( - username='username', - email='user@email.com', - password='password' - ) - - # Put the user into a group with the correct permissions - group = Group.objects.create(name='mygroup') - self.user.groups.add(group) - - # Give the group *all* the permissions! - for rule in group.rule_sets.all(): - rule.can_view = True - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - self.client.login(username='username', password='password') + roles = 'all' class CompanyViewTest(CompanyViewTestBase): 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/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html index 5593918a38..af923ab097 100644 --- a/InvenTree/order/templates/order/sales_order_base.html +++ b/InvenTree/order/templates/order/sales_order_base.html @@ -57,6 +57,7 @@ src="{% static 'img/blank_image.png' %}"
@@ -223,6 +224,16 @@ $("#edit-order").click(function() { }); }); +$("#complete-order-shipments").click(function() { + + completePendingShipments( + {{ order.pk }}, + { + reload: true, + } + ); +}); + $("#cancel-order").click(function() { cancelSalesOrder( diff --git a/InvenTree/order/test_views.py b/InvenTree/order/test_views.py index e38ea7ecef..aad0fed25d 100644 --- a/InvenTree/order/test_views.py +++ b/InvenTree/order/test_views.py @@ -1,12 +1,11 @@ """ Unit tests for Order views (see views.py) """ -from django.test import TestCase from django.urls import reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group + +from InvenTree.helpers import InvenTreeTestCase -class OrderViewTestCase(TestCase): +class OrderViewTestCase(InvenTreeTestCase): fixtures = [ 'category', @@ -19,27 +18,14 @@ class OrderViewTestCase(TestCase): 'order', ] - def setUp(self): - super().setUp() - - # Create a user - user = get_user_model().objects.create_user('username', 'user@email.com', 'password') - - # Ensure that the user has the correct permissions! - g = Group.objects.create(name='orders') - user.groups.add(g) - - for rule in g.rule_sets.all(): - if rule.name in ['purchase_order', 'sales_order']: - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - g.save() - - self.client.login(username='username', password='password') + roles = [ + 'purchase_order.change', + 'purchase_order.add', + 'purchase_order.delete', + 'sales_order.change', + 'sales_order.add', + 'sales_order.delete', + ] class OrderListTest(OrderViewTestCase): 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/part/test_bom_export.py b/InvenTree/part/test_bom_export.py index 4ae0b88269..d78ced4b17 100644 --- a/InvenTree/part/test_bom_export.py +++ b/InvenTree/part/test_bom_export.py @@ -4,14 +4,12 @@ Unit testing for BOM export functionality import csv -from django.test import TestCase - from django.urls import reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group + +from InvenTree.helpers import InvenTreeTestCase -class BomExportTest(TestCase): +class BomExportTest(InvenTreeTestCase): fixtures = [ 'category', @@ -20,33 +18,11 @@ class BomExportTest(TestCase): 'bom', ] + roles = 'all' + def setUp(self): super().setUp() - # Create a user - user = get_user_model() - - self.user = user.objects.create_user( - username='username', - email='user@email.com', - password='password' - ) - - # Put the user into a group with the correct permissions - group = Group.objects.create(name='mygroup') - self.user.groups.add(group) - - # Give the group *all* the permissions! - for rule in group.rule_sets.all(): - rule.can_view = True - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - self.client.login(username='username', password='password') - self.url = reverse('bom-download', kwargs={'pk': 100}) def test_bom_template(self): diff --git a/InvenTree/part/test_part.py b/InvenTree/part/test_part.py index 2df3c10b01..a413159cfe 100644 --- a/InvenTree/part/test_part.py +++ b/InvenTree/part/test_part.py @@ -3,13 +3,14 @@ from allauth.account.models import EmailAddress from django.conf import settings -from django.contrib.auth import get_user_model from django.test import TestCase from django.core.exceptions import ValidationError import os +from InvenTree.helpers import InvenTreeTestCase + from .models import Part, PartCategory, PartCategoryStar, PartStar, PartTestTemplate from .models import rename_part_image from .templatetags import inventree_extras @@ -21,15 +22,9 @@ from common.models import InvenTreeSetting, InvenTreeUserSetting, NotificationEn from common.notifications import storage, UIMessageNotification -class TemplateTagTest(TestCase): +class TemplateTagTest(InvenTreeTestCase): """ Tests for the custom template tag code """ - def setUp(self): - # Create a user for auth - user = get_user_model() - self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') - self.client.login(username='testuser', password='password') - def test_define(self): self.assertEqual(int(inventree_extras.define(3)), 3) @@ -330,24 +325,13 @@ class TestTemplateTest(TestCase): self.assertEqual(variant.getTestTemplates().count(), n + 1) -class PartSettingsTest(TestCase): +class PartSettingsTest(InvenTreeTestCase): """ Tests to ensure that the user-configurable default values work as expected. Some fields for the Part model can have default values specified by the user. """ - def setUp(self): - # Create a user for auth - user = get_user_model() - - self.user = user.objects.create_user( - username='testuser', - email='test@testing.com', - password='password', - is_staff=True - ) - def make_part(self): """ Helper function to create a simple part @@ -461,7 +445,7 @@ class PartSettingsTest(TestCase): Part.objects.create(name='abc', revision='6', description='A part', IPN=' ') -class PartSubscriptionTests(TestCase): +class PartSubscriptionTests(InvenTreeTestCase): fixtures = [ 'location', @@ -470,15 +454,7 @@ class PartSubscriptionTests(TestCase): ] def setUp(self): - # Create a user for auth - user = get_user_model() - - self.user = user.objects.create_user( - username='testuser', - email='test@testing.com', - password='password', - is_staff=True - ) + super().setUp() # electronics / IC / MCU self.category = PartCategory.objects.get(pk=4) @@ -578,7 +554,7 @@ class PartSubscriptionTests(TestCase): self.assertTrue(self.part.is_starred_by(self.user)) -class BaseNotificationIntegrationTest(TestCase): +class BaseNotificationIntegrationTest(InvenTreeTestCase): """ Integration test for notifications """ fixtures = [ @@ -589,15 +565,7 @@ class BaseNotificationIntegrationTest(TestCase): ] def setUp(self): - # Create a user for auth - user = get_user_model() - - self.user = user.objects.create_user( - username='testuser', - email='test@testing.com', - password='password', - is_staff=True - ) + super().setUp() # Add Mailadress EmailAddress.objects.create(user=self.user, email='test@testing.com') diff --git a/InvenTree/part/test_views.py b/InvenTree/part/test_views.py index 2171a09b17..21ec6f7909 100644 --- a/InvenTree/part/test_views.py +++ b/InvenTree/part/test_views.py @@ -1,14 +1,13 @@ """ Unit tests for Part Views (see views.py) """ -from django.test import TestCase from django.urls import reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group + +from InvenTree.helpers import InvenTreeTestCase from .models import Part -class PartViewTestCase(TestCase): +class PartViewTestCase(InvenTreeTestCase): fixtures = [ 'category', @@ -19,33 +18,12 @@ class PartViewTestCase(TestCase): 'supplier_part', ] + roles = 'all' + superuser = True + def setUp(self): super().setUp() - # Create a user - user = get_user_model() - - self.user = user.objects.create_user( - username='username', - email='user@email.com', - password='password' - ) - - # Put the user into a group with the correct permissions - group = Group.objects.create(name='mygroup') - self.user.groups.add(group) - - # Give the group *all* the permissions! - for rule in group.rule_sets.all(): - rule.can_view = True - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - self.client.login(username='username', password='password') - class PartListTest(PartViewTestCase): diff --git a/InvenTree/plugin/base/action/test_action.py b/InvenTree/plugin/base/action/test_action.py index 16790ccd62..5678a0d44e 100644 --- a/InvenTree/plugin/base/action/test_action.py +++ b/InvenTree/plugin/base/action/test_action.py @@ -1,8 +1,8 @@ """ Unit tests for action plugins """ from django.test import TestCase -from django.contrib.auth import get_user_model +from InvenTree.helpers import InvenTreeTestCase from plugin import InvenTreePlugin from plugin.mixins import ActionMixin @@ -65,15 +65,9 @@ class ActionMixinTests(TestCase): }) -class APITests(TestCase): +class APITests(InvenTreeTestCase): """ Tests for action api """ - def setUp(self): - # Create a user for auth - user = get_user_model() - self.test_user = user.objects.create_user('testuser', 'test@testing.com', 'password') - self.client.login(username='testuser', password='password') - def test_post_errors(self): """Check the possible errors with post""" diff --git a/InvenTree/plugin/base/barcodes/test_barcode.py b/InvenTree/plugin/base/barcodes/test_barcode.py index 43a713a57b..4b66f8b208 100644 --- a/InvenTree/plugin/base/barcodes/test_barcode.py +++ b/InvenTree/plugin/base/barcodes/test_barcode.py @@ -4,16 +4,15 @@ Unit tests for Barcode endpoints """ -from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.test import APITestCase from rest_framework import status +from InvenTree.api_tester import InvenTreeAPITestCase from stock.models import StockItem -class BarcodeAPITest(APITestCase): +class BarcodeAPITest(InvenTreeAPITestCase): fixtures = [ 'category', @@ -23,11 +22,7 @@ class BarcodeAPITest(APITestCase): ] 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') + super().setUp() self.scan_url = reverse('api-barcode-scan') self.assign_url = reverse('api-barcode-link') diff --git a/InvenTree/plugin/base/integration/test_mixins.py b/InvenTree/plugin/base/integration/test_mixins.py index c1afa39fc2..9759e0f00d 100644 --- a/InvenTree/plugin/base/integration/test_mixins.py +++ b/InvenTree/plugin/base/integration/test_mixins.py @@ -1,19 +1,18 @@ """ Unit tests for base mixins for plugins """ -from django.test import TestCase from django.conf import settings +from django.test import TestCase from django.urls import include, re_path, reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group from error_report.models import Error +from InvenTree.helpers import InvenTreeTestCase from plugin import InvenTreePlugin -from plugin.mixins import AppMixin, SettingsMixin, UrlsMixin, NavigationMixin, APICallMixin -from plugin.urls import PLUGIN_BASE from plugin.helpers import MixinNotImplementedError - +from plugin.mixins import (APICallMixin, AppMixin, NavigationMixin, + SettingsMixin, UrlsMixin) from plugin.registry import registry +from plugin.urls import PLUGIN_BASE class BaseMixinDefinition: @@ -24,7 +23,7 @@ class BaseMixinDefinition: self.assertIn(self.MIXIN_HUMAN_NAME, [item['human_name'] for item in self.mixin.registered_mixins]) -class SettingsMixinTest(BaseMixinDefinition, TestCase): +class SettingsMixinTest(BaseMixinDefinition, InvenTreeTestCase): MIXIN_HUMAN_NAME = 'Settings' MIXIN_NAME = 'settings' MIXIN_ENABLE_CHECK = 'has_settings' @@ -40,9 +39,7 @@ class SettingsMixinTest(BaseMixinDefinition, TestCase): pass self.mixin_nothing = NoSettingsCls() - user = get_user_model() - self.test_user = user.objects.create_user('testuser', 'test@testing.com', 'password') - self.test_user.is_staff = True + super().setUp() def test_function(self): # settings variable @@ -54,7 +51,7 @@ class SettingsMixinTest(BaseMixinDefinition, TestCase): self.assertEqual(self.mixin_nothing.get_setting('ABCD'), '') # right setting - self.mixin.set_setting('SETTING1', '12345', self.test_user) + self.mixin.set_setting('SETTING1', '12345', self.user) self.assertEqual(self.mixin.get_setting('SETTING1'), '12345') # no setting @@ -251,7 +248,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase): self.mixin_wrong2.has_api_call() -class PanelMixinTests(TestCase): +class PanelMixinTests(InvenTreeTestCase): """Test that the PanelMixin plugin operates correctly""" fixtures = [ @@ -261,32 +258,7 @@ class PanelMixinTests(TestCase): 'stock', ] - def setUp(self): - super().setUp() - - # Create a user which has all the privelages - user = get_user_model() - - self.user = user.objects.create_user( - username='username', - email='user@email.com', - password='password' - ) - - # Put the user into a group with the correct permissions - group = Group.objects.create(name='mygroup') - self.user.groups.add(group) - - # Give the group *all* the permissions! - for rule in group.rule_sets.all(): - rule.can_view = True - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - self.client.login(username='username', password='password') + roles = 'all' def test_installed(self): """Test that the sample panel plugin is installed""" diff --git a/InvenTree/plugin/builtin/action/test_simpleactionplugin.py b/InvenTree/plugin/builtin/action/test_simpleactionplugin.py index 92e1affa67..e645dea85c 100644 --- a/InvenTree/plugin/builtin/action/test_simpleactionplugin.py +++ b/InvenTree/plugin/builtin/action/test_simpleactionplugin.py @@ -1,20 +1,15 @@ """ Unit tests for action plugins """ -from django.test import TestCase -from django.contrib.auth import get_user_model - +from InvenTree.helpers import InvenTreeTestCase from plugin.builtin.action.simpleactionplugin import SimpleActionPlugin -class SimpleActionPluginTests(TestCase): +class SimpleActionPluginTests(InvenTreeTestCase): """ Tests for SampleIntegrationPlugin """ def setUp(self): - # Create a user for auth - user = get_user_model() - self.test_user = user.objects.create_user('testuser', 'test@testing.com', 'password') + super().setUp() - self.client.login(username='testuser', password='password') self.plugin = SimpleActionPlugin() def test_name(self): @@ -33,7 +28,7 @@ class SimpleActionPluginTests(TestCase): "action": 'simple', "result": True, "info": { - "user": "testuser", + "user": self.username, "hello": "world", }, } diff --git a/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py b/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py index 1424d89ff2..0bc855aa24 100644 --- a/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py +++ b/InvenTree/plugin/builtin/barcodes/test_inventree_barcode.py @@ -1,14 +1,14 @@ # -*- coding: utf-8 -*- """Unit tests for InvenTreeBarcodePlugin""" -from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework.test import APITestCase from rest_framework import status +from InvenTree.api_tester import InvenTreeAPITestCase -class TestInvenTreeBarcode(APITestCase): + +class TestInvenTreeBarcode(InvenTreeAPITestCase): fixtures = [ 'category', @@ -17,13 +17,6 @@ class TestInvenTreeBarcode(APITestCase): '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 diff --git a/InvenTree/plugin/samples/integration/test_sample.py b/InvenTree/plugin/samples/integration/test_sample.py index 733e443638..577aa5812e 100644 --- a/InvenTree/plugin/samples/integration/test_sample.py +++ b/InvenTree/plugin/samples/integration/test_sample.py @@ -1,19 +1,11 @@ """ Unit tests for action plugins """ -from django.test import TestCase -from django.contrib.auth import get_user_model +from InvenTree.helpers import InvenTreeTestCase -class SampleIntegrationPluginTests(TestCase): +class SampleIntegrationPluginTests(InvenTreeTestCase): """ Tests for SampleIntegrationPlugin """ - 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_view(self): """check the function of the custom sample plugin """ response = self.client.get('/plugin/sample/ho/he/') diff --git a/InvenTree/stock/test_views.py b/InvenTree/stock/test_views.py index d656201f81..dba39334de 100644 --- a/InvenTree/stock/test_views.py +++ b/InvenTree/stock/test_views.py @@ -1,14 +1,13 @@ """ Unit tests for Stock views (see views.py) """ -from django.test import TestCase from django.urls import reverse -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group + +from InvenTree.helpers import InvenTreeTestCase # from common.models import InvenTreeSetting -class StockViewTestCase(TestCase): +class StockViewTestCase(InvenTreeTestCase): fixtures = [ 'category', @@ -19,35 +18,7 @@ class StockViewTestCase(TestCase): 'stock', ] - def setUp(self): - super().setUp() - - # Create a user - user = get_user_model() - - self.user = user.objects.create_user( - username='username', - email='user@email.com', - password='password' - ) - - self.user.is_staff = True - self.user.save() - - # Put the user into a group with the correct permissions - group = Group.objects.create(name='mygroup') - self.user.groups.add(group) - - # Give the group *all* the permissions! - for rule in group.rule_sets.all(): - rule.can_view = True - rule.can_change = True - rule.can_add = True - rule.can_delete = True - - rule.save() - - self.client.login(username='username', password='password') + roles = 'all' class StockListTest(StockViewTestCase): diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 97639b15bd..95c7646a9d 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -1,9 +1,8 @@ -from django.test import TestCase from django.db.models import Sum -from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError import datetime +from InvenTree.helpers import InvenTreeTestCase from InvenTree.status_codes import StockHistoryCode @@ -14,7 +13,7 @@ from part.models import Part from build.models import Build -class StockTest(TestCase): +class StockTest(InvenTreeTestCase): """ Tests to ensure that the stock location tree functions correcly """ @@ -29,6 +28,8 @@ class StockTest(TestCase): ] def setUp(self): + super().setUp() + # Extract some shortcuts from the fixtures self.home = StockLocation.objects.get(name='Home') self.bathroom = StockLocation.objects.get(name='Bathroom') @@ -39,14 +40,6 @@ class StockTest(TestCase): self.drawer2 = StockLocation.objects.get(name='Drawer_2') self.drawer3 = StockLocation.objects.get(name='Drawer_3') - # Create a user - user = get_user_model() - user.objects.create_user('username', 'user@email.com', 'password') - - self.client.login(username='username', password='password') - - self.user = user.objects.get(username='username') - # Ensure the MPTT objects are correctly rebuild Part.objects.rebuild() StockItem.objects.rebuild() 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 @@