Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2022-05-22 08:42:28 +10:00
commit 0439ab84fd
40 changed files with 564 additions and 571 deletions

View File

@ -182,9 +182,9 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install gettext sudo apt-get install gettext
python -m pip install -U pip
pip3 install invoke pip3 install invoke
invoke install invoke update
invoke static
- name: Coverage Tests - name: Coverage Tests
run: | run: |
invoke coverage invoke coverage
@ -245,11 +245,12 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update 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 invoke
pip3 install psycopg2 pip3 install psycopg2
pip3 install django-redis>=5.0.0 pip3 install django-redis>=5.0.0
invoke install invoke update
- name: Run Tests - name: Run Tests
run: invoke test run: invoke test
- name: Data Import Export - name: Data Import Export
@ -302,10 +303,11 @@ jobs:
- name: Install Dependencies - name: Install Dependencies
run: | run: |
sudo apt-get update 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 invoke
pip3 install mysqlclient pip3 install mysqlclient
invoke install invoke update
- name: Run Tests - name: Run Tests
run: invoke test run: invoke test
- name: Data Import Export - name: Data Import Export

19
.pre-commit-config.yaml Normal file
View File

@ -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

View File

@ -1,5 +1,9 @@
Please read the contribution guidelines below, before submitting your first pull request to the InvenTree codebase. 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 ## 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. 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 ## 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 ## Documentation

View File

@ -6,16 +6,14 @@ import csv
import io import io
import re import re
from django.http.response import StreamingHttpResponse
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.http.response import StreamingHttpResponse
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
class InvenTreeAPITestCase(APITestCase): class UserMixin:
"""
Base class for running InvenTree API tests
"""
# User information # User information
username = 'testuser' username = 'testuser'
@ -52,37 +50,49 @@ class InvenTreeAPITestCase(APITestCase):
self.user.save() self.user.save()
for role in self.roles: # Assign all roles if set
self.assignRole(role) 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: if self.auto_login:
self.client.login(username=self.username, password=self.password) 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 Set the user roles for the registered user
""" """
# role is of the format 'rule.permission' e.g. 'part.add' # 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(): 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 ruleset.can_view = True
elif perm == 'change': elif assign_all or perm == 'change':
ruleset.can_change = True ruleset.can_change = True
elif perm == 'delete': elif assign_all or perm == 'delete':
ruleset.can_delete = True ruleset.can_delete = True
elif perm == 'add': elif assign_all or perm == 'add':
ruleset.can_add = True ruleset.can_add = True
ruleset.save() ruleset.save()
break break
class InvenTreeAPITestCase(UserMixin, APITestCase):
"""
Base class for running InvenTree API tests
"""
def getActions(self, url): def getActions(self, url):
""" """
Return a dict of the 'actions' available at a given endpoint. Return a dict of the 'actions' available at a given endpoint.

View File

@ -3,14 +3,13 @@ Pull rendered copies of the templated
only used for testing the js files! - This file is omited from coverage 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 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. 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. 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): def download_file(self, filename, prefix):
url = os.path.join(prefix, filename) url = os.path.join(prefix, filename)

View File

@ -14,8 +14,10 @@ from wsgiref.util import FileWrapper
from django.http import StreamingHttpResponse from django.http import StreamingHttpResponse
from django.core.exceptions import ValidationError, FieldError from django.core.exceptions import ValidationError, FieldError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.test import TestCase
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from .api_tester import UserMixin
import InvenTree.version import InvenTree.version
@ -781,3 +783,7 @@ def inheritors(cls):
subcls.add(child) subcls.add(child)
work.append(child) work.append(child)
return subcls return subcls
class InvenTreeTestCase(UserMixin, TestCase):
pass

View File

@ -1,22 +1,17 @@
""" Low level tests for the InvenTree API """ """ 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 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. 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, which raised an AssertionError when using the HTML API interface,
while the regular JSON interface continued to work as expected. while the regular JSON interface continued to work as expected.
""" """
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')
def test_part_api(self): def test_part_api(self):
url = reverse('api-part-list') url = reverse('api-part-list')

View File

@ -1,12 +1,11 @@
"""Tests for middleware functions""" """Tests for middleware functions"""
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse from django.urls import reverse
from InvenTree.helpers import InvenTreeTestCase
class MiddlewareTests(TestCase):
class MiddlewareTests(InvenTreeTestCase):
"""Test for middleware functions""" """Test for middleware functions"""
def check_path(self, url, code=200, **kwargs): def check_path(self, url, code=200, **kwargs):
@ -14,15 +13,6 @@ class MiddlewareTests(TestCase):
self.assertEqual(response.status_code, code) self.assertEqual(response.status_code, code)
return response 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): def test_AuthRequiredMiddleware(self):
"""Test the auth middleware""" """Test the auth middleware"""
@ -32,6 +22,10 @@ class MiddlewareTests(TestCase):
# logout # logout
self.client.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 # check that account things go through
self.check_path(reverse('account_login')) self.check_path(reverse('account_login'))

View File

@ -5,28 +5,17 @@ Unit tests for the main web views
import re import re
import os import os
from django.test import TestCase
from django.urls import reverse 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 """ """ Tests for various top-level views """
username = 'test_user' username = 'test_user'
password = 'test_pass' 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): def test_api_doc(self):
""" Test that the api-doc view works """ """ Test that the api-doc view works """

View File

@ -7,8 +7,8 @@ from unittest import mock
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
import django.core.exceptions as django_exceptions import django.core.exceptions as django_exceptions
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from djmoney.money import Money from djmoney.money import Money
from djmoney.contrib.exchange.models import Rate, convert_money from djmoney.contrib.exchange.models import Rate, convert_money
@ -457,18 +457,12 @@ class TestStatus(TestCase):
self.assertEqual(ready.isImportingData(), False) self.assertEqual(ready.isImportingData(), False)
class TestSettings(TestCase): class TestSettings(helpers.InvenTreeTestCase):
""" """
Unit tests for settings Unit tests for settings
""" """
def setUp(self) -> None: superuser = True
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')
def in_env_context(self, envs={}): def in_env_context(self, envs={}):
"""Patch the env to include the given dict""" """Patch the env to include the given dict"""
@ -483,8 +477,9 @@ class TestSettings(TestCase):
@override_settings(TESTING_ENV=True) @override_settings(TESTING_ENV=True)
def test_set_user_to_few(self): def test_set_user_to_few(self):
user_model = get_user_model()
# add shortcut # add shortcut
user_count = self.user_mdl.objects.count user_count = user_model.objects.count
# enable testing mode # enable testing mode
settings.TESTING_ENV = True settings.TESTING_ENV = True
@ -506,14 +501,18 @@ class TestSettings(TestCase):
}) })
self.assertEqual(user_count(), 2) self.assertEqual(user_count(), 2)
username2 = 'testuser1'
email2 = 'test1@testing.com'
password2 = 'password1'
# create user manually # 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) self.assertEqual(user_count(), 3)
# check it will not be created again # check it will not be created again
self.run_reload({ self.run_reload({
'INVENTREE_ADMIN_USER': 'testuser', 'INVENTREE_ADMIN_USER': username2,
'INVENTREE_ADMIN_EMAIL': 'test@testing.com', 'INVENTREE_ADMIN_EMAIL': email2,
'INVENTREE_ADMIN_PASSWORD': 'password', 'INVENTREE_ADMIN_PASSWORD': password2,
}) })
self.assertEqual(user_count(), 3) self.assertEqual(user_count(), 3)
@ -548,7 +547,7 @@ class TestSettings(TestCase):
# with env set # with env set
with self.in_env_context({'INVENTREE_CONFIG_FILE': 'my_special_conf.yaml'}): 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): def test_helpers_plugin_file(self):
# normal run - not configured # normal run - not configured
@ -574,18 +573,11 @@ class TestSettings(TestCase):
self.assertEqual(config.get_setting(TEST_ENV_NAME, None), '321') self.assertEqual(config.get_setting(TEST_ENV_NAME, None), '321')
class TestInstanceName(TestCase): class TestInstanceName(helpers.InvenTreeTestCase):
""" """
Unit tests for instance name 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): def test_instance_name(self):
# default setting # default setting

View File

@ -2,10 +2,6 @@ from datetime import datetime, timedelta
from django.urls import reverse 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 rest_framework import status
from part.models import Part from part.models import Part
@ -16,7 +12,7 @@ from InvenTree.status_codes import BuildStatus
from InvenTree.api_tester import InvenTreeAPITestCase from InvenTree.api_tester import InvenTreeAPITestCase
class TestBuildAPI(APITestCase): class TestBuildAPI(InvenTreeAPITestCase):
""" """
Series of tests for the Build DRF API Series of tests for the Build DRF API
- Tests for Build API - Tests for Build API
@ -30,25 +26,11 @@ class TestBuildAPI(APITestCase):
'build', 'build',
] ]
def setUp(self): roles = [
# Create a user for auth 'build.change',
user = get_user_model() 'build.add',
self.user = user.objects.create_user('testuser', 'test@testing.com', 'password') 'build.delete',
]
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')
def test_get_build_list(self): def test_get_build_list(self):
""" """

View File

@ -1,18 +1,16 @@
from django.test import TestCase
from django.urls import reverse 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 datetime import datetime, timedelta
from InvenTree.helpers import InvenTreeTestCase
from .models import Build from .models import Build
from stock.models import StockItem from stock.models import StockItem
from InvenTree.status_codes import BuildStatus from InvenTree.status_codes import BuildStatus
class BuildTestSimple(TestCase): class BuildTestSimple(InvenTreeTestCase):
fixtures = [ fixtures = [
'category', 'category',
@ -21,27 +19,11 @@ class BuildTestSimple(TestCase):
'build', 'build',
] ]
def setUp(self): roles = [
# Create a user for auth 'build.change',
user = get_user_model() 'build.add',
user.objects.create_user('testuser', 'test@testing.com', 'password') 'build.delete',
]
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')
def test_build_objects(self): def test_build_objects(self):
# Ensure the Build objects were correctly created # Ensure the Build objects were correctly created
@ -106,7 +88,7 @@ class BuildTestSimple(TestCase):
self.assertEqual(build.status, BuildStatus.CANCELLED) self.assertEqual(build.status, BuildStatus.CANCELLED)
class TestBuildViews(TestCase): class TestBuildViews(InvenTreeTestCase):
""" Tests for Build app views """ """ Tests for Build app views """
fixtures = [ fixtures = [
@ -116,28 +98,15 @@ class TestBuildViews(TestCase):
'build', 'build',
] ]
roles = [
'build.change',
'build.add',
'build.delete',
]
def setUp(self): def setUp(self):
super().setUp() 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 # Create a build output for build # 1
self.build = Build.objects.get(pk=1) self.build = Build.objects.get(pk=1)

View File

@ -1429,6 +1429,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
'validator': bool, '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': { 'SEARCH_PREVIEW_SHOW_CATEGORIES': {
'name': _('Search Categories'), 'name': _('Search Categories'),
'description': _('Display part categories in search preview window'), 'description': _('Display part categories in search preview window'),
@ -1443,6 +1450,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
'validator': bool, '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': { 'SEARCH_PREVIEW_SHOW_LOCATIONS': {
'name': _('Search Locations'), 'name': _('Search Locations'),
'description': _('Display stock locations in search preview window'), 'description': _('Display stock locations in search preview window'),
@ -1464,6 +1478,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
'validator': bool, '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': { 'SEARCH_PREVIEW_SHOW_SALES_ORDERS': {
'name': _('Search Sales Orders'), 'name': _('Search Sales Orders'),
'description': _('Display sales orders in search preview window'), 'description': _('Display sales orders in search preview window'),
@ -1471,6 +1492,13 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
'validator': bool, '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': { 'SEARCH_PREVIEW_RESULTS': {
'name': _('Search Preview Results'), 'name': _('Search Preview Results'),
'description': _('Number of results to show in each section of the search preview window'), '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)] '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': { 'PART_SHOW_QUANTITY_IN_FORMS': {
'name': _('Show Quantity in Forms'), 'name': _('Show Quantity in Forms'),
'description': _('Display available part quantity in some forms'), 'description': _('Display available part quantity in some forms'),
@ -1701,6 +1722,9 @@ class ColorTheme(models.Model):
@classmethod @classmethod
def get_color_themes_choices(cls): def get_color_themes_choices(cls):
""" Get all color themes from static folder """ """ 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 # Get files list from css/color-themes/ folder
files_list = [] files_list = []

View File

@ -4,21 +4,20 @@ import json
from datetime import timedelta from datetime import timedelta
from django.test import TestCase, Client from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse from django.urls import reverse
from InvenTree.api_tester import InvenTreeAPITestCase 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.models import NotificationUserSetting, PluginConfig
from plugin import registry 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 from .api import WebhookView
CONTENT_TYPE_JSON = 'application/json' CONTENT_TYPE_JSON = 'application/json'
class SettingsTest(TestCase): class SettingsTest(InvenTreeTestCase):
""" """
Tests for the 'settings' model Tests for the 'settings' model
""" """
@ -27,16 +26,6 @@ class SettingsTest(TestCase):
'settings', '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): def test_settings_objects(self):
# There should be two settings objects in the database # There should be two settings objects in the database
@ -112,28 +101,70 @@ class SettingsTest(TestCase):
self.assertIn('STOCK_OWNERSHIP_CONTROL', result) self.assertIn('STOCK_OWNERSHIP_CONTROL', result)
self.assertIn('SIGNUP_GROUP', 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 setting has a name, which is translated
- Ensure that every global setting has a description. - 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) for key, setting in InvenTreeUserSetting.SETTINGS.items():
try:
if name is None: self.run_settings_check(key, setting)
raise ValueError(f'Missing GLOBAL_SETTING name for {key}') # pragma: no cover except Exception as exc:
print(f"run_settings_check failed for user setting '{key}'")
description = setting.get('description', None) raise exc
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
def test_defaults(self): def test_defaults(self):
""" """
@ -674,3 +705,35 @@ class LoadingTest(TestCase):
# now it should be false again # now it should be false again
self.assertFalse(common.models.InvenTreeSetting.get_setting('SERVER_RESTART_REQUIRED')) 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))

View File

@ -1,12 +1,11 @@
""" Unit tests for Company views (see views.py) """ """ Unit tests for Company views (see views.py) """
from django.test import TestCase
from django.urls import reverse 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 = [ fixtures = [
'category', 'category',
@ -17,32 +16,7 @@ class CompanyViewTestBase(TestCase):
'supplier_part', 'supplier_part',
] ]
def setUp(self): roles = 'all'
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 CompanyViewTest(CompanyViewTestBase): class CompanyViewTest(CompanyViewTestBase):

View File

@ -2,6 +2,7 @@ import os
import shutil import shutil
import logging import logging
import hashlib import hashlib
import warnings
from django.apps import AppConfig from django.apps import AppConfig
from django.conf import settings from django.conf import settings
@ -42,6 +43,15 @@ class LabelConfig(AppConfig):
""" """
Create all default templates 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_item_labels()
self.create_stock_location_labels() self.create_stock_location_labels()
self.create_part_labels() self.create_part_labels()
@ -52,11 +62,7 @@ class LabelConfig(AppConfig):
if they do not already exist if they do not already exist
""" """
try: from .models import StockItemLabel
from .models import StockItemLabel
except AppRegistryNotReady: # pragma: no cover
# Database might not by ready yet
return
src_dir = os.path.join( src_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), os.path.dirname(os.path.realpath(__file__)),
@ -139,11 +145,7 @@ class LabelConfig(AppConfig):
if they do not already exist if they do not already exist
""" """
try: from .models import StockLocationLabel
from .models import StockLocationLabel
except AppRegistryNotReady: # pragma: no cover
# Database might not yet be ready
return
src_dir = os.path.join( src_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), os.path.dirname(os.path.realpath(__file__)),
@ -233,11 +235,7 @@ class LabelConfig(AppConfig):
if they do not already exist. if they do not already exist.
""" """
try: from .models import PartLabel
from .models import PartLabel
except AppRegistryNotReady: # pragma: no cover
# Database might not yet be ready
return
src_dir = os.path.join( src_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), os.path.dirname(os.path.realpath(__file__)),

View File

@ -4,12 +4,14 @@ import os
from django.conf import settings from django.conf import settings
from django.apps import apps from django.apps import apps
from django.urls import reverse
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from InvenTree.helpers import validateFilterString from InvenTree.helpers import validateFilterString
from InvenTree.api_tester import InvenTreeAPITestCase 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 from stock.models import StockItem
@ -82,3 +84,13 @@ class LabelTest(InvenTreeAPITestCase):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
validateFilterString(bad_filter_string, model=StockItem) 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)

View File

@ -57,6 +57,7 @@ src="{% static 'img/blank_image.png' %}"
<ul class='dropdown-menu' role='menu'> <ul class='dropdown-menu' role='menu'>
<li><a class='dropdown-item' href='#' id='edit-order'><span class='fas fa-edit icon-green'></span> {% trans "Edit order" %}</a></li> <li><a class='dropdown-item' href='#' id='edit-order'><span class='fas fa-edit icon-green'></span> {% trans "Edit order" %}</a></li>
{% if order.status == SalesOrderStatus.PENDING %} {% if order.status == SalesOrderStatus.PENDING %}
<li><a class='dropdown-item' href='#' id='complete-order-shipments'><span class='fas fa-truck'></span> {% trans "Complete Shipments" %}</a></li>
<li><a class='dropdown-item' href='#' id='cancel-order'><span class='fas fa-times-circle icon-red'></span> {% trans "Cancel order" %}</a></li> <li><a class='dropdown-item' href='#' id='cancel-order'><span class='fas fa-times-circle icon-red'></span> {% trans "Cancel order" %}</a></li>
{% endif %} {% endif %}
</ul> </ul>
@ -223,6 +224,16 @@ $("#edit-order").click(function() {
}); });
}); });
$("#complete-order-shipments").click(function() {
completePendingShipments(
{{ order.pk }},
{
reload: true,
}
);
});
$("#cancel-order").click(function() { $("#cancel-order").click(function() {
cancelSalesOrder( cancelSalesOrder(

View File

@ -1,12 +1,11 @@
""" Unit tests for Order views (see views.py) """ """ Unit tests for Order views (see views.py) """
from django.test import TestCase
from django.urls import reverse 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 = [ fixtures = [
'category', 'category',
@ -19,27 +18,14 @@ class OrderViewTestCase(TestCase):
'order', 'order',
] ]
def setUp(self): roles = [
super().setUp() 'purchase_order.change',
'purchase_order.add',
# Create a user 'purchase_order.delete',
user = get_user_model().objects.create_user('username', 'user@email.com', 'password') 'sales_order.change',
'sales_order.add',
# Ensure that the user has the correct permissions! 'sales_order.delete',
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')
class OrderListTest(OrderViewTestCase): class OrderListTest(OrderViewTestCase):

View File

@ -83,7 +83,7 @@ def render_date(context, date_object):
user = context.get('user', None) user = context.get('user', None)
if user: if user and user.is_authenticated:
# User is specified - look for their date display preference # User is specified - look for their date display preference
user_date_format = InvenTreeUserSetting.get_setting('DATE_DISPLAY_FORMAT', user=user) user_date_format = InvenTreeUserSetting.get_setting('DATE_DISPLAY_FORMAT', user=user)
else: else:
@ -329,7 +329,7 @@ def settings_value(key, *args, **kwargs):
""" """
if 'user' in 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)
return InvenTreeUserSetting.get_setting(key, user=kwargs['user']) return InvenTreeUserSetting.get_setting(key, user=kwargs['user'])

View File

@ -4,14 +4,12 @@ Unit testing for BOM export functionality
import csv import csv
from django.test import TestCase
from django.urls import reverse 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 = [ fixtures = [
'category', 'category',
@ -20,33 +18,11 @@ class BomExportTest(TestCase):
'bom', 'bom',
] ]
roles = 'all'
def setUp(self): def setUp(self):
super().setUp() 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}) self.url = reverse('bom-download', kwargs={'pk': 100})
def test_bom_template(self): def test_bom_template(self):

View File

@ -3,13 +3,14 @@
from allauth.account.models import EmailAddress from allauth.account.models import EmailAddress
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model
from django.test import TestCase from django.test import TestCase
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
import os import os
from InvenTree.helpers import InvenTreeTestCase
from .models import Part, PartCategory, PartCategoryStar, PartStar, PartTestTemplate from .models import Part, PartCategory, PartCategoryStar, PartStar, PartTestTemplate
from .models import rename_part_image from .models import rename_part_image
from .templatetags import inventree_extras from .templatetags import inventree_extras
@ -21,15 +22,9 @@ from common.models import InvenTreeSetting, InvenTreeUserSetting, NotificationEn
from common.notifications import storage, UIMessageNotification from common.notifications import storage, UIMessageNotification
class TemplateTagTest(TestCase): class TemplateTagTest(InvenTreeTestCase):
""" Tests for the custom template tag code """ """ 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): def test_define(self):
self.assertEqual(int(inventree_extras.define(3)), 3) self.assertEqual(int(inventree_extras.define(3)), 3)
@ -330,24 +325,13 @@ class TestTemplateTest(TestCase):
self.assertEqual(variant.getTestTemplates().count(), n + 1) 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. 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. 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): def make_part(self):
""" """
Helper function to create a simple part 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=' ') Part.objects.create(name='abc', revision='6', description='A part', IPN=' ')
class PartSubscriptionTests(TestCase): class PartSubscriptionTests(InvenTreeTestCase):
fixtures = [ fixtures = [
'location', 'location',
@ -470,15 +454,7 @@ class PartSubscriptionTests(TestCase):
] ]
def setUp(self): def setUp(self):
# Create a user for auth super().setUp()
user = get_user_model()
self.user = user.objects.create_user(
username='testuser',
email='test@testing.com',
password='password',
is_staff=True
)
# electronics / IC / MCU # electronics / IC / MCU
self.category = PartCategory.objects.get(pk=4) self.category = PartCategory.objects.get(pk=4)
@ -578,7 +554,7 @@ class PartSubscriptionTests(TestCase):
self.assertTrue(self.part.is_starred_by(self.user)) self.assertTrue(self.part.is_starred_by(self.user))
class BaseNotificationIntegrationTest(TestCase): class BaseNotificationIntegrationTest(InvenTreeTestCase):
""" Integration test for notifications """ """ Integration test for notifications """
fixtures = [ fixtures = [
@ -589,15 +565,7 @@ class BaseNotificationIntegrationTest(TestCase):
] ]
def setUp(self): def setUp(self):
# Create a user for auth super().setUp()
user = get_user_model()
self.user = user.objects.create_user(
username='testuser',
email='test@testing.com',
password='password',
is_staff=True
)
# Add Mailadress # Add Mailadress
EmailAddress.objects.create(user=self.user, email='test@testing.com') EmailAddress.objects.create(user=self.user, email='test@testing.com')

View File

@ -1,14 +1,13 @@
""" Unit tests for Part Views (see views.py) """ """ Unit tests for Part Views (see views.py) """
from django.test import TestCase
from django.urls import reverse 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 from .models import Part
class PartViewTestCase(TestCase): class PartViewTestCase(InvenTreeTestCase):
fixtures = [ fixtures = [
'category', 'category',
@ -19,33 +18,12 @@ class PartViewTestCase(TestCase):
'supplier_part', 'supplier_part',
] ]
roles = 'all'
superuser = True
def setUp(self): def setUp(self):
super().setUp() 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): class PartListTest(PartViewTestCase):

View File

@ -1,8 +1,8 @@
""" Unit tests for action plugins """ """ Unit tests for action plugins """
from django.test import TestCase from django.test import TestCase
from django.contrib.auth import get_user_model
from InvenTree.helpers import InvenTreeTestCase
from plugin import InvenTreePlugin from plugin import InvenTreePlugin
from plugin.mixins import ActionMixin from plugin.mixins import ActionMixin
@ -65,15 +65,9 @@ class ActionMixinTests(TestCase):
}) })
class APITests(TestCase): class APITests(InvenTreeTestCase):
""" Tests for action api """ """ 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): def test_post_errors(self):
"""Check the possible errors with post""" """Check the possible errors with post"""

View File

@ -4,16 +4,15 @@
Unit tests for Barcode endpoints Unit tests for Barcode endpoints
""" """
from django.contrib.auth import get_user_model
from django.urls import reverse from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status from rest_framework import status
from InvenTree.api_tester import InvenTreeAPITestCase
from stock.models import StockItem from stock.models import StockItem
class BarcodeAPITest(APITestCase): class BarcodeAPITest(InvenTreeAPITestCase):
fixtures = [ fixtures = [
'category', 'category',
@ -23,11 +22,7 @@ class BarcodeAPITest(APITestCase):
] ]
def setUp(self): def setUp(self):
# Create a user for auth super().setUp()
user = get_user_model()
user.objects.create_user('testuser', 'test@testing.com', 'password')
self.client.login(username='testuser', password='password')
self.scan_url = reverse('api-barcode-scan') self.scan_url = reverse('api-barcode-scan')
self.assign_url = reverse('api-barcode-link') self.assign_url = reverse('api-barcode-link')

View File

@ -1,19 +1,18 @@
""" Unit tests for base mixins for plugins """ """ Unit tests for base mixins for plugins """
from django.test import TestCase
from django.conf import settings from django.conf import settings
from django.test import TestCase
from django.urls import include, re_path, reverse 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 error_report.models import Error
from InvenTree.helpers import InvenTreeTestCase
from plugin import InvenTreePlugin 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.helpers import MixinNotImplementedError
from plugin.mixins import (APICallMixin, AppMixin, NavigationMixin,
SettingsMixin, UrlsMixin)
from plugin.registry import registry from plugin.registry import registry
from plugin.urls import PLUGIN_BASE
class BaseMixinDefinition: class BaseMixinDefinition:
@ -24,7 +23,7 @@ class BaseMixinDefinition:
self.assertIn(self.MIXIN_HUMAN_NAME, [item['human_name'] for item in self.mixin.registered_mixins]) 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_HUMAN_NAME = 'Settings'
MIXIN_NAME = 'settings' MIXIN_NAME = 'settings'
MIXIN_ENABLE_CHECK = 'has_settings' MIXIN_ENABLE_CHECK = 'has_settings'
@ -40,9 +39,7 @@ class SettingsMixinTest(BaseMixinDefinition, TestCase):
pass pass
self.mixin_nothing = NoSettingsCls() self.mixin_nothing = NoSettingsCls()
user = get_user_model() super().setUp()
self.test_user = user.objects.create_user('testuser', 'test@testing.com', 'password')
self.test_user.is_staff = True
def test_function(self): def test_function(self):
# settings variable # settings variable
@ -54,7 +51,7 @@ class SettingsMixinTest(BaseMixinDefinition, TestCase):
self.assertEqual(self.mixin_nothing.get_setting('ABCD'), '') self.assertEqual(self.mixin_nothing.get_setting('ABCD'), '')
# right setting # 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') self.assertEqual(self.mixin.get_setting('SETTING1'), '12345')
# no setting # no setting
@ -251,7 +248,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
self.mixin_wrong2.has_api_call() self.mixin_wrong2.has_api_call()
class PanelMixinTests(TestCase): class PanelMixinTests(InvenTreeTestCase):
"""Test that the PanelMixin plugin operates correctly""" """Test that the PanelMixin plugin operates correctly"""
fixtures = [ fixtures = [
@ -261,32 +258,7 @@ class PanelMixinTests(TestCase):
'stock', 'stock',
] ]
def setUp(self): roles = 'all'
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')
def test_installed(self): def test_installed(self):
"""Test that the sample panel plugin is installed""" """Test that the sample panel plugin is installed"""

View File

@ -1,20 +1,15 @@
""" Unit tests for action plugins """ """ Unit tests for action plugins """
from django.test import TestCase from InvenTree.helpers import InvenTreeTestCase
from django.contrib.auth import get_user_model
from plugin.builtin.action.simpleactionplugin import SimpleActionPlugin from plugin.builtin.action.simpleactionplugin import SimpleActionPlugin
class SimpleActionPluginTests(TestCase): class SimpleActionPluginTests(InvenTreeTestCase):
""" Tests for SampleIntegrationPlugin """ """ Tests for SampleIntegrationPlugin """
def setUp(self): def setUp(self):
# Create a user for auth super().setUp()
user = get_user_model()
self.test_user = user.objects.create_user('testuser', 'test@testing.com', 'password')
self.client.login(username='testuser', password='password')
self.plugin = SimpleActionPlugin() self.plugin = SimpleActionPlugin()
def test_name(self): def test_name(self):
@ -33,7 +28,7 @@ class SimpleActionPluginTests(TestCase):
"action": 'simple', "action": 'simple',
"result": True, "result": True,
"info": { "info": {
"user": "testuser", "user": self.username,
"hello": "world", "hello": "world",
}, },
} }

View File

@ -1,14 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Unit tests for InvenTreeBarcodePlugin""" """Unit tests for InvenTreeBarcodePlugin"""
from django.contrib.auth import get_user_model
from django.urls import reverse from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework import status from rest_framework import status
from InvenTree.api_tester import InvenTreeAPITestCase
class TestInvenTreeBarcode(APITestCase):
class TestInvenTreeBarcode(InvenTreeAPITestCase):
fixtures = [ fixtures = [
'category', 'category',
@ -17,13 +17,6 @@ class TestInvenTreeBarcode(APITestCase):
'stock' '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): def test_errors(self):
""" """
Test all possible error cases for assigment action Test all possible error cases for assigment action

View File

@ -1,19 +1,11 @@
""" Unit tests for action plugins """ """ Unit tests for action plugins """
from django.test import TestCase from InvenTree.helpers import InvenTreeTestCase
from django.contrib.auth import get_user_model
class SampleIntegrationPluginTests(TestCase): class SampleIntegrationPluginTests(InvenTreeTestCase):
""" Tests for SampleIntegrationPlugin """ """ 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): def test_view(self):
"""check the function of the custom sample plugin """ """check the function of the custom sample plugin """
response = self.client.get('/plugin/sample/ho/he/') response = self.client.get('/plugin/sample/ho/he/')

View File

@ -1,14 +1,13 @@
""" Unit tests for Stock views (see views.py) """ """ Unit tests for Stock views (see views.py) """
from django.test import TestCase
from django.urls import reverse 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 # from common.models import InvenTreeSetting
class StockViewTestCase(TestCase): class StockViewTestCase(InvenTreeTestCase):
fixtures = [ fixtures = [
'category', 'category',
@ -19,35 +18,7 @@ class StockViewTestCase(TestCase):
'stock', 'stock',
] ]
def setUp(self): roles = 'all'
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')
class StockListTest(StockViewTestCase): class StockListTest(StockViewTestCase):

View File

@ -1,9 +1,8 @@
from django.test import TestCase
from django.db.models import Sum from django.db.models import Sum
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
import datetime import datetime
from InvenTree.helpers import InvenTreeTestCase
from InvenTree.status_codes import StockHistoryCode from InvenTree.status_codes import StockHistoryCode
@ -14,7 +13,7 @@ from part.models import Part
from build.models import Build from build.models import Build
class StockTest(TestCase): class StockTest(InvenTreeTestCase):
""" """
Tests to ensure that the stock location tree functions correcly Tests to ensure that the stock location tree functions correcly
""" """
@ -29,6 +28,8 @@ class StockTest(TestCase):
] ]
def setUp(self): def setUp(self):
super().setUp()
# Extract some shortcuts from the fixtures # Extract some shortcuts from the fixtures
self.home = StockLocation.objects.get(name='Home') self.home = StockLocation.objects.get(name='Home')
self.bathroom = StockLocation.objects.get(name='Bathroom') self.bathroom = StockLocation.objects.get(name='Bathroom')
@ -39,14 +40,6 @@ class StockTest(TestCase):
self.drawer2 = StockLocation.objects.get(name='Drawer_2') self.drawer2 = StockLocation.objects.get(name='Drawer_2')
self.drawer3 = StockLocation.objects.get(name='Drawer_3') 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 # Ensure the MPTT objects are correctly rebuild
Part.objects.rebuild() Part.objects.rebuild()
StockItem.objects.rebuild() StockItem.objects.rebuild()

View File

@ -15,16 +15,19 @@
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tbody> <tbody>
{% 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_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_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_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_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_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_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_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_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' %}
</tbody> </tbody>
</table> </table>
</div> </div>

View File

@ -561,6 +561,11 @@ function constructFormBody(fields, options) {
insertPersistButton(options); insertPersistButton(options);
} }
// Insert secondary buttons (if required)
if (options.buttons) {
insertSecondaryButtons(options);
}
// Display the modal // Display the modal
$(modal).modal('show'); $(modal).modal('show');
@ -650,6 +655,31 @@ function insertPersistButton(options) {
$(options.modal).find('#modal-footer-buttons').append(html); $(options.modal).find('#modal-footer-buttons').append(html);
} }
/*
* Add secondary buttons to the left of the close and submit buttons
* with callback functions
*/
function insertSecondaryButtons(options) {
for (var idx = 0; idx < options.buttons.length; idx++) {
var html = `
<button type="button" class="btn btn-outline-secondary" id="modal-form-${options.buttons[idx].name}">
${options.buttons[idx].title}
</button>
`;
$(options.modal).find('#modal-footer-secondary-buttons').append(html);
if (options.buttons[idx].onClick instanceof Function) {
// Copy callback reference to prevent errors if `idx` changes value before execution
var onclick_callback = options.buttons[idx].onClick;
$(options.modal).find(`#modal-form-${options.buttons[idx].name}`).click(function() {
onclick_callback(options);
});
}
}
}
/* /*
* Extract all specified form values as a single object * Extract all specified form values as a single object

View File

@ -75,6 +75,9 @@ function createNewModal(options={}) {
</div> </div>
<span class='flex-item' style='flex-grow: 1;'></span> <span class='flex-item' style='flex-grow: 1;'></span>
<h4><span id='modal-progress-spinner' class='fas fa-circle-notch fa-spin' style='display: none;'></span></h4> <h4><span id='modal-progress-spinner' class='fas fa-circle-notch fa-spin' style='display: none;'></span></h4>
<div id='modal-footer-secondary-buttons'>
<!-- Extra secondary buttons can be inserted here -->
</div>
<button type='button' class='btn btn-secondary' id='modal-form-close' data-bs-dismiss='modal'>{% trans "Cancel" %}</button> <button type='button' class='btn btn-secondary' id='modal-form-close' data-bs-dismiss='modal'>{% trans "Cancel" %}</button>
<button type='button' class='btn btn-${submitClass}' id='modal-form-submit'>{% trans "Submit" %}</button> <button type='button' class='btn btn-${submitClass}' id='modal-form-submit'>{% trans "Submit" %}</button>
</div> </div>
@ -99,7 +102,7 @@ function createNewModal(options={}) {
$(modal_name).focus(); $(modal_name).focus();
if (options.hideCloseButton) { if (options.hideCloseButton) {
$(modal_name).find('#modal-form-cancel').hide(); $(modal_name).find('#modal-form-close').hide();
} }
if (options.preventSubmit || options.hideSubmitButton) { if (options.preventSubmit || options.hideSubmitButton) {

View File

@ -24,6 +24,7 @@
cancelSalesOrder, cancelSalesOrder,
completePurchaseOrder, completePurchaseOrder,
completeShipment, completeShipment,
completePendingShipments,
createSalesOrder, createSalesOrder,
createSalesOrderShipment, createSalesOrderShipment,
editPurchaseOrderLineItem, editPurchaseOrderLineItem,
@ -69,7 +70,7 @@ function salesOrderShipmentFields(options={}) {
/* /*
* Complete a shipment * Complete a shipment
*/ */
function completeShipment(shipment_id) { function completeShipment(shipment_id, options={}) {
// Request the list of stock items which will be shipped // Request the list of stock items which will be shipped
inventreeGet(`/api/order/so/shipment/${shipment_id}/`, {}, { inventreeGet(`/api/order/so/shipment/${shipment_id}/`, {}, {
@ -126,27 +127,128 @@ function completeShipment(shipment_id) {
constructForm(`/api/order/so/shipment/${shipment_id}/ship/`, { constructForm(`/api/order/so/shipment/${shipment_id}/ship/`, {
method: 'POST', method: 'POST',
title: '{% trans "Complete Shipment" %}', title: `{% trans "Complete Shipment" %} ${shipment.reference}`,
fields: { fields: {
tracking_number: {}, tracking_number: {},
}, },
preFormContent: html, preFormContent: html,
confirm: true, confirm: true,
confirmMessage: '{% trans "Confirm Shipment" %}', confirmMessage: '{% trans "Confirm Shipment" %}',
buttons: options.buttons,
onSuccess: function(data) { onSuccess: function(data) {
// Reload tables // Reload tables
$('#so-lines-table').bootstrapTable('refresh'); $('#so-lines-table').bootstrapTable('refresh');
$('#pending-shipments-table').bootstrapTable('refresh'); $('#pending-shipments-table').bootstrapTable('refresh');
$('#completed-shipments-table').bootstrapTable('refresh'); $('#completed-shipments-table').bootstrapTable('refresh');
}
if (options.onSuccess instanceof Function) {
options.onSuccess(data);
}
},
reload: options.reload
}); });
} }
}); });
} }
/*
* Launches a modal to mark all allocated pending shipments as complete
*/
function completePendingShipments(order_id, options={}) {
var pending_shipments = null;
// Request the list of stock items which will be shipped
inventreeGet(`/api/order/so/shipment/.*`,
{
order: order_id,
shipped: false
},
{
async: false,
success: function(shipments) {
pending_shipments = shipments;
}
}
);
var allocated_shipments = [];
for (var idx = 0; idx < pending_shipments.length; idx++) {
if (pending_shipments[idx].allocations.length > 0) {
allocated_shipments.push(pending_shipments[idx]);
}
}
if (allocated_shipments.length > 0) {
completePendingShipmentsHelper(allocated_shipments, 0, options);
} else {
html = `
<div class='alert alert-block alert-danger'>
`;
if (!pending_shipments.length) {
html += `
{% trans "No pending shipments found" %}
`;
} else {
html += `
{% trans "No stock items have been allocated to pending shipments" %}
`;
}
html += `
</div>
`;
constructForm(`/api/order/so/shipment/0/ship/`, {
method: 'POST',
title: '{% trans "Complete Shipments" %}',
preFormContent: html,
onSubmit: function(fields, options) {
handleFormSuccess(fields, options);
},
closeText: 'Close',
hideSubmitButton: true,
});
}
}
/*
* Recursive helper for opening shipment completion modals
*/
function completePendingShipmentsHelper(shipments, shipment_idx, options={}) {
if (shipment_idx < shipments.length) {
completeShipment(shipments[shipment_idx].pk,
{
buttons: [
{
name: 'skip',
title: `{% trans "Skip" %}`,
onClick: function(form_options) {
if (form_options.modal) {
$(form_options.modal).modal('hide');
}
completePendingShipmentsHelper(shipments, shipment_idx + 1, options);
}
}
],
onSuccess: function(data) {
completePendingShipmentsHelper(shipments, shipment_idx + 1, options);
},
}
);
} else if (options.reload) {
location.reload();
}
}
/* /*
* Launches a modal form to mark a PurchaseOrder as "complete" * Launches a modal form to mark a PurchaseOrder as "complete"
*/ */
function completePurchaseOrder(order_id, options={}) { function completePurchaseOrder(order_id, options={}) {
constructForm( constructForm(

View File

@ -122,14 +122,22 @@ function updateSearch() {
if (user_settings.SEARCH_PREVIEW_SHOW_STOCK) { if (user_settings.SEARCH_PREVIEW_SHOW_STOCK) {
// Search for matching stock items // 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( addSearchQuery(
'stock', 'stock',
'{% trans "Stock Items" %}', '{% trans "Stock Items" %}',
'{% url "api-stock-list" %}', '{% url "api-stock-list" %}',
{ filters,
part_detail: true,
location_detail: true,
},
renderStockItem, renderStockItem,
{ {
url: '/stock/item', url: '/stock/item',
@ -167,15 +175,21 @@ function updateSearch() {
} }
if (user_settings.SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS) { 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 // Search for matching purchase orders
addSearchQuery( addSearchQuery(
'purchaseorder', 'purchaseorder',
'{% trans "Purchase Orders" %}', '{% trans "Purchase Orders" %}',
'{% url "api-po-list" %}', '{% url "api-po-list" %}',
{ filters,
supplier_detail: true,
outstanding: true,
},
renderPurchaseOrder, renderPurchaseOrder,
{ {
url: '/order/purchase-order', url: '/order/purchase-order',
@ -184,15 +198,22 @@ function updateSearch() {
} }
if (user_settings.SEARCH_PREVIEW_SHOW_SALES_ORDERS) { 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 // Search for matching sales orders
addSearchQuery( addSearchQuery(
'salesorder', 'salesorder',
'{% trans "Sales Orders" %}', '{% trans "Sales Orders" %}',
'{% url "api-so-list" %}', '{% url "api-so-list" %}',
{ filters,
customer_detail: true,
outstanding: true,
},
renderSalesOrder, renderSalesOrder,
{ {
url: '/order/sales-order', url: '/order/sales-order',

View File

@ -1,10 +1,10 @@
from django.test import TestCase from django.test import TestCase
from django.apps import apps from django.apps import apps
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from InvenTree.helpers import InvenTreeTestCase
from users.models import RuleSet, Owner from users.models import RuleSet, Owner
@ -160,20 +160,11 @@ class RuleSetModelTest(TestCase):
self.assertEqual(group.permissions.count(), 0) self.assertEqual(group.permissions.count(), 0)
class OwnerModelTest(TestCase): class OwnerModelTest(InvenTreeTestCase):
""" """
Some simplistic tests to ensure the Owner model is setup correctly. Some simplistic tests to ensure the Owner model is setup correctly.
""" """
def setUp(self):
""" Add users and groups """
# Create a new user
self.user = get_user_model().objects.create_user('username', 'user@email.com', 'password')
# Put the user into a new group
self.group = Group.objects.create(name='new_group')
self.user.groups.add(self.group)
def do_request(self, endpoint, filters, status_code=200): def do_request(self, endpoint, filters, status_code=200):
response = self.client.get(endpoint, filters, format='json') response = self.client.get(endpoint, filters, format='json')
self.assertEqual(response.status_code, status_code) self.assertEqual(response.status_code, status_code)
@ -212,11 +203,13 @@ class OwnerModelTest(TestCase):
""" """
Test user APIs Test user APIs
""" """
self.client.logout()
# not authed # not authed
self.do_request(reverse('api-owner-list'), {}, 401) self.do_request(reverse('api-owner-list'), {}, 401)
self.do_request(reverse('api-owner-detail', kwargs={'pk': self.user.id}), {}, 401) self.do_request(reverse('api-owner-detail', kwargs={'pk': self.user.id}), {}, 401)
self.client.login(username='username', password='password') self.client.login(username=self.username, password=self.password)
# user list # user list
self.do_request(reverse('api-owner-list'), {}) self.do_request(reverse('api-owner-list'), {})
# user list with search # user list with search
@ -229,12 +222,14 @@ class OwnerModelTest(TestCase):
""" """
Test token mechanisms Test token mechanisms
""" """
self.client.logout()
token = Token.objects.filter(user=self.user) token = Token.objects.filter(user=self.user)
# not authed # not authed
self.do_request(reverse('api-token'), {}, 401) self.do_request(reverse('api-token'), {}, 401)
self.client.login(username='username', password='password') self.client.login(username=self.username, password=self.password)
# token get # token get
response = self.do_request(reverse('api-token'), {}) response = self.do_request(reverse('api-token'), {})
self.assertEqual(response['token'], token.first().key) self.assertEqual(response['token'], token.first().key)

View File

@ -33,12 +33,14 @@ django-weasyprint==1.0.1 # django weasyprint integration
djangorestframework==3.12.4 # DRF framework djangorestframework==3.12.4 # DRF framework
django-xforwardedfor-middleware==2.0 # IP forwarding metadata django-xforwardedfor-middleware==2.0 # IP forwarding metadata
flake8==3.8.3 # PEP checking flake8==3.8.3 # PEP checking
flake8-docstrings==1.6.0 # docstring format testing
gunicorn>=20.1.0 # Gunicorn web server gunicorn>=20.1.0 # Gunicorn web server
importlib_metadata # Backport for importlib.metadata importlib_metadata # Backport for importlib.metadata
inventree # Install the latest version of the InvenTree API python library inventree # Install the latest version of the InvenTree API python library
isort==5.10.1 # DEV: python import sorting isort==5.10.1 # DEV: python import sorting
markdown==3.3.4 # Force particular version of markdown markdown==3.3.4 # Force particular version of markdown
pep8-naming==0.11.1 # PEP naming convention extension pep8-naming==0.11.1 # PEP naming convention extension
pre-commit==2.19.0 # Git pre-commit
pillow==9.1.0 # Image manipulation pillow==9.1.0 # Image manipulation
py-moneyed==0.8.0 # Specific version requirement for py-moneyed py-moneyed==0.8.0 # Specific version requirement for py-moneyed
pygments==2.7.4 # Syntax highlighting pygments==2.7.4 # Syntax highlighting

View File

@ -3,7 +3,7 @@ ignore =
# - W605 - invalid escape sequence # - W605 - invalid escape sequence
W605, W605,
# - E501 - line too long (82 characters) # - E501 - line too long (82 characters)
E501, E501,
# - E722 - do not use bare except # - E722 - do not use bare except
E722, E722,
# - C901 - function is too complex # - C901 - function is too complex
@ -15,8 +15,11 @@ ignore =
N806, N806,
# - N812 - lowercase imported as non-lowercase # - N812 - lowercase imported as non-lowercase
N812, N812,
# - D415 - First line should end with a period, question mark, or exclamation point
D415,
exclude = .git,__pycache__,*/migrations/*,*/lib/*,*/bin/*,*/media/*,*/static/*,InvenTree/plugins/* exclude = .git,__pycache__,*/migrations/*,*/lib/*,*/bin/*,*/media/*,*/static/*,InvenTree/plugins/*
max-complexity = 20 max-complexity = 20
docstring-convention=google
[coverage:run] [coverage:run]
source = ./InvenTree source = ./InvenTree
@ -25,6 +28,4 @@ source = ./InvenTree
src_paths=InvenTree src_paths=InvenTree
skip_glob =*/migrations/*.py skip_glob =*/migrations/*.py
known_django=django known_django=django
import_heading_firstparty=InvenTree imports
import_heading_thirdparty=Third-Party imports
sections=FUTURE, STDLIB, DJANGO, THIRDPARTY, FIRSTPARTY, LOCALFOLDER sections=FUTURE, STDLIB, DJANGO, THIRDPARTY, FIRSTPARTY, LOCALFOLDER

View File

@ -15,7 +15,6 @@ def apps():
""" """
return [ return [
'barcode',
'build', 'build',
'common', 'common',
'company', 'company',
@ -24,8 +23,9 @@ def apps():
'part', 'part',
'report', 'report',
'stock', 'stock',
'InvenTree',
'users', 'users',
'plugin',
'InvenTree',
] ]
@ -94,6 +94,23 @@ def install(c):
# Install required Python packages with PIP # Install required Python packages with PIP
c.run('pip3 install -U -r requirements.txt') c.run('pip3 install -U -r requirements.txt')
@task
def setup_dev(c):
"""
Sets up everything needed for the dev enviroment
"""
print("Installing required python packages from 'requirements.txt'")
# Install required Python packages with PIP
c.run('pip3 install -U -r requirements.txt')
# Install pre-commit hook
c.run('pre-commit install')
# Update all the hooks
c.run('pre-commit autoupdate')
@task @task
def shell(c): def shell(c):
""" """
@ -249,7 +266,7 @@ def update(c):
- static - static
- clean_settings - clean_settings
""" """
# Recompile the translation files (.mo) # Recompile the translation files (.mo)
# We do not run 'invoke translate' here, as that will touch the source (.po) files too! # We do not run 'invoke translate' here, as that will touch the source (.po) files too!
manage(c, 'compilemessages', pty=True) manage(c, 'compilemessages', pty=True)