mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
0439ab84fd
14
.github/workflows/qc_checks.yaml
vendored
14
.github/workflows/qc_checks.yaml
vendored
@ -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
19
.pre-commit-config.yaml
Normal 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
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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')
|
||||||
|
@ -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'))
|
||||||
|
|
||||||
|
@ -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 """
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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 = []
|
||||||
|
@ -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))
|
||||||
|
@ -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):
|
||||||
|
@ -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__)),
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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):
|
||||||
|
@ -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'])
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
|
||||||
|
@ -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"""
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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"""
|
||||||
|
@ -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",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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/')
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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(
|
||||||
|
@ -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',
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
23
tasks.py
23
tasks.py
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user