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
de4afb77fd
18
.github/workflows/translations.yml
vendored
18
.github/workflows/translations.yml
vendored
@ -19,12 +19,8 @@ jobs:
|
|||||||
INVENTREE_STATIC_ROOT: ./static
|
INVENTREE_STATIC_ROOT: ./static
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout Code
|
||||||
- name: Get Current Translations
|
uses: actions/checkout@v2
|
||||||
run: |
|
|
||||||
git fetch
|
|
||||||
git checkout origin/l10 -- `git ls-tree origin/l10 -r --name-only | grep ".po"`
|
|
||||||
git reset
|
|
||||||
- name: Set up Python 3.7
|
- name: Set up Python 3.7
|
||||||
uses: actions/setup-python@v1
|
uses: actions/setup-python@v1
|
||||||
with:
|
with:
|
||||||
@ -38,19 +34,11 @@ jobs:
|
|||||||
- name: Make Translations
|
- name: Make Translations
|
||||||
run: |
|
run: |
|
||||||
invoke translate
|
invoke translate
|
||||||
- name: stash changes
|
|
||||||
run: |
|
|
||||||
git stash
|
|
||||||
- name: Checkout Translation Branch
|
|
||||||
uses: actions/checkout@v2.3.4
|
|
||||||
with:
|
|
||||||
ref: l10
|
|
||||||
- name: Commit files
|
- name: Commit files
|
||||||
run: |
|
run: |
|
||||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
git config --local user.name "github-actions[bot]"
|
git config --local user.name "github-actions[bot]"
|
||||||
git checkout stash -- .
|
git checkout -b l10_local
|
||||||
git reset
|
|
||||||
git add "*.po"
|
git add "*.po"
|
||||||
git commit -m "updated translation base"
|
git commit -m "updated translation base"
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -85,3 +85,6 @@ maintenance_mode_state.txt
|
|||||||
|
|
||||||
# plugin dev directory
|
# plugin dev directory
|
||||||
plugins/
|
plugins/
|
||||||
|
|
||||||
|
# Compiled translation files
|
||||||
|
*.mo
|
||||||
|
@ -18,8 +18,6 @@ def assign_bom_items(apps, schema_editor):
|
|||||||
BomItem = apps.get_model('part', 'bomitem')
|
BomItem = apps.get_model('part', 'bomitem')
|
||||||
Part = apps.get_model('part', 'part')
|
Part = apps.get_model('part', 'part')
|
||||||
|
|
||||||
logger.info("Assigning BomItems to existing BuildItem objects")
|
|
||||||
|
|
||||||
count_valid = 0
|
count_valid = 0
|
||||||
count_total = 0
|
count_total = 0
|
||||||
|
|
||||||
@ -29,6 +27,10 @@ def assign_bom_items(apps, schema_editor):
|
|||||||
# Note: Before this migration, variant stock assignment was not allowed,
|
# Note: Before this migration, variant stock assignment was not allowed,
|
||||||
# so BomItem lookup should be pretty easy
|
# so BomItem lookup should be pretty easy
|
||||||
|
|
||||||
|
if count_total == 0:
|
||||||
|
# First time around
|
||||||
|
logger.info("Assigning BomItems to existing BuildItem objects")
|
||||||
|
|
||||||
count_total += 1
|
count_total += 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -406,6 +406,13 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
# Encode as native values
|
||||||
|
if self.is_int():
|
||||||
|
self.value = self.as_int()
|
||||||
|
|
||||||
|
elif self.is_bool():
|
||||||
|
self.value = self.as_bool()
|
||||||
|
|
||||||
validator = self.__class__.get_setting_validator(self.key, **kwargs)
|
validator = self.__class__.get_setting_validator(self.key, **kwargs)
|
||||||
|
|
||||||
if validator is not None:
|
if validator is not None:
|
||||||
@ -455,7 +462,14 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
if callable(validator):
|
if callable(validator):
|
||||||
# We can accept function validators with a single argument
|
# We can accept function validators with a single argument
|
||||||
validator(self.value)
|
|
||||||
|
if self.is_bool():
|
||||||
|
value = self.as_bool()
|
||||||
|
|
||||||
|
if self.is_int():
|
||||||
|
value = self.as_int()
|
||||||
|
|
||||||
|
validator(value)
|
||||||
|
|
||||||
def validate_unique(self, exclude=None, **kwargs):
|
def validate_unique(self, exclude=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -629,6 +643,10 @@ class BaseInvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
return setting.get('protected', False)
|
return setting.get('protected', False)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def protected(self):
|
||||||
|
return self.__class__.is_protected(self.key)
|
||||||
|
|
||||||
|
|
||||||
def settings_group_options():
|
def settings_group_options():
|
||||||
"""
|
"""
|
||||||
@ -1011,48 +1029,56 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'LOGIN_ENABLE_REG': {
|
'LOGIN_ENABLE_REG': {
|
||||||
'name': _('Enable registration'),
|
'name': _('Enable registration'),
|
||||||
'description': _('Enable self-registration for users on the login pages'),
|
'description': _('Enable self-registration for users on the login pages'),
|
||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'LOGIN_ENABLE_SSO': {
|
'LOGIN_ENABLE_SSO': {
|
||||||
'name': _('Enable SSO'),
|
'name': _('Enable SSO'),
|
||||||
'description': _('Enable SSO on the login pages'),
|
'description': _('Enable SSO on the login pages'),
|
||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'LOGIN_MAIL_REQUIRED': {
|
'LOGIN_MAIL_REQUIRED': {
|
||||||
'name': _('Email required'),
|
'name': _('Email required'),
|
||||||
'description': _('Require user to supply mail on signup'),
|
'description': _('Require user to supply mail on signup'),
|
||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'LOGIN_SIGNUP_SSO_AUTO': {
|
'LOGIN_SIGNUP_SSO_AUTO': {
|
||||||
'name': _('Auto-fill SSO users'),
|
'name': _('Auto-fill SSO users'),
|
||||||
'description': _('Automatically fill out user-details from SSO account-data'),
|
'description': _('Automatically fill out user-details from SSO account-data'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'LOGIN_SIGNUP_MAIL_TWICE': {
|
'LOGIN_SIGNUP_MAIL_TWICE': {
|
||||||
'name': _('Mail twice'),
|
'name': _('Mail twice'),
|
||||||
'description': _('On signup ask users twice for their mail'),
|
'description': _('On signup ask users twice for their mail'),
|
||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'LOGIN_SIGNUP_PWD_TWICE': {
|
'LOGIN_SIGNUP_PWD_TWICE': {
|
||||||
'name': _('Password twice'),
|
'name': _('Password twice'),
|
||||||
'description': _('On signup ask users twice for their password'),
|
'description': _('On signup ask users twice for their password'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'SIGNUP_GROUP': {
|
'SIGNUP_GROUP': {
|
||||||
'name': _('Group on signup'),
|
'name': _('Group on signup'),
|
||||||
'description': _('Group to which new users are assigned on registration'),
|
'description': _('Group to which new users are assigned on registration'),
|
||||||
'default': '',
|
'default': '',
|
||||||
'choices': settings_group_options
|
'choices': settings_group_options
|
||||||
},
|
},
|
||||||
|
|
||||||
'LOGIN_ENFORCE_MFA': {
|
'LOGIN_ENFORCE_MFA': {
|
||||||
'name': _('Enforce MFA'),
|
'name': _('Enforce MFA'),
|
||||||
'description': _('Users must use multifactor security.'),
|
'description': _('Users must use multifactor security.'),
|
||||||
@ -1067,6 +1093,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
'requires_restart': True,
|
'requires_restart': True,
|
||||||
},
|
},
|
||||||
|
|
||||||
# Settings for plugin mixin features
|
# Settings for plugin mixin features
|
||||||
'ENABLE_PLUGINS_URL': {
|
'ENABLE_PLUGINS_URL': {
|
||||||
'name': _('Enable URL integration'),
|
'name': _('Enable URL integration'),
|
||||||
@ -1075,6 +1102,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
'requires_restart': True,
|
'requires_restart': True,
|
||||||
},
|
},
|
||||||
|
|
||||||
'ENABLE_PLUGINS_NAVIGATION': {
|
'ENABLE_PLUGINS_NAVIGATION': {
|
||||||
'name': _('Enable navigation integration'),
|
'name': _('Enable navigation integration'),
|
||||||
'description': _('Enable plugins to integrate into navigation'),
|
'description': _('Enable plugins to integrate into navigation'),
|
||||||
@ -1082,6 +1110,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
'requires_restart': True,
|
'requires_restart': True,
|
||||||
},
|
},
|
||||||
|
|
||||||
'ENABLE_PLUGINS_APP': {
|
'ENABLE_PLUGINS_APP': {
|
||||||
'name': _('Enable app integration'),
|
'name': _('Enable app integration'),
|
||||||
'description': _('Enable plugins to add apps'),
|
'description': _('Enable plugins to add apps'),
|
||||||
@ -1089,6 +1118,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
'requires_restart': True,
|
'requires_restart': True,
|
||||||
},
|
},
|
||||||
|
|
||||||
'ENABLE_PLUGINS_SCHEDULE': {
|
'ENABLE_PLUGINS_SCHEDULE': {
|
||||||
'name': _('Enable schedule integration'),
|
'name': _('Enable schedule integration'),
|
||||||
'description': _('Enable plugins to run scheduled tasks'),
|
'description': _('Enable plugins to run scheduled tasks'),
|
||||||
@ -1096,6 +1126,7 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'validator': bool,
|
'validator': bool,
|
||||||
'requires_restart': True,
|
'requires_restart': True,
|
||||||
},
|
},
|
||||||
|
|
||||||
'ENABLE_PLUGINS_EVENTS': {
|
'ENABLE_PLUGINS_EVENTS': {
|
||||||
'name': _('Enable event integration'),
|
'name': _('Enable event integration'),
|
||||||
'description': _('Enable plugins to respond to internal events'),
|
'description': _('Enable plugins to respond to internal events'),
|
||||||
@ -1149,18 +1180,21 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
|
|||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_CATEGORY_STARRED': {
|
'HOMEPAGE_CATEGORY_STARRED': {
|
||||||
'name': _('Show subscribed categories'),
|
'name': _('Show subscribed categories'),
|
||||||
'description': _('Show subscribed part categories on the homepage'),
|
'description': _('Show subscribed part categories on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_PART_LATEST': {
|
'HOMEPAGE_PART_LATEST': {
|
||||||
'name': _('Show latest parts'),
|
'name': _('Show latest parts'),
|
||||||
'description': _('Show latest parts on the homepage'),
|
'description': _('Show latest parts on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'PART_RECENT_COUNT': {
|
'PART_RECENT_COUNT': {
|
||||||
'name': _('Recent Part Count'),
|
'name': _('Recent Part Count'),
|
||||||
'description': _('Number of recent parts to display on index page'),
|
'description': _('Number of recent parts to display on index page'),
|
||||||
@ -1174,78 +1208,91 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
|
|||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_STOCK_RECENT': {
|
'HOMEPAGE_STOCK_RECENT': {
|
||||||
'name': _('Show recent stock changes'),
|
'name': _('Show recent stock changes'),
|
||||||
'description': _('Show recently changed stock items on the homepage'),
|
'description': _('Show recently changed stock items on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'STOCK_RECENT_COUNT': {
|
'STOCK_RECENT_COUNT': {
|
||||||
'name': _('Recent Stock Count'),
|
'name': _('Recent Stock Count'),
|
||||||
'description': _('Number of recent stock items to display on index page'),
|
'description': _('Number of recent stock items to display on index page'),
|
||||||
'default': 10,
|
'default': 10,
|
||||||
'validator': [int, MinValueValidator(1)]
|
'validator': [int, MinValueValidator(1)]
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_STOCK_LOW': {
|
'HOMEPAGE_STOCK_LOW': {
|
||||||
'name': _('Show low stock'),
|
'name': _('Show low stock'),
|
||||||
'description': _('Show low stock items on the homepage'),
|
'description': _('Show low stock items on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_STOCK_DEPLETED': {
|
'HOMEPAGE_STOCK_DEPLETED': {
|
||||||
'name': _('Show depleted stock'),
|
'name': _('Show depleted stock'),
|
||||||
'description': _('Show depleted stock items on the homepage'),
|
'description': _('Show depleted stock items on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_STOCK_NEEDED': {
|
'HOMEPAGE_STOCK_NEEDED': {
|
||||||
'name': _('Show needed stock'),
|
'name': _('Show needed stock'),
|
||||||
'description': _('Show stock items needed for builds on the homepage'),
|
'description': _('Show stock items needed for builds on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_STOCK_EXPIRED': {
|
'HOMEPAGE_STOCK_EXPIRED': {
|
||||||
'name': _('Show expired stock'),
|
'name': _('Show expired stock'),
|
||||||
'description': _('Show expired stock items on the homepage'),
|
'description': _('Show expired stock items on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_STOCK_STALE': {
|
'HOMEPAGE_STOCK_STALE': {
|
||||||
'name': _('Show stale stock'),
|
'name': _('Show stale stock'),
|
||||||
'description': _('Show stale stock items on the homepage'),
|
'description': _('Show stale stock items on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_BUILD_PENDING': {
|
'HOMEPAGE_BUILD_PENDING': {
|
||||||
'name': _('Show pending builds'),
|
'name': _('Show pending builds'),
|
||||||
'description': _('Show pending builds on the homepage'),
|
'description': _('Show pending builds on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_BUILD_OVERDUE': {
|
'HOMEPAGE_BUILD_OVERDUE': {
|
||||||
'name': _('Show overdue builds'),
|
'name': _('Show overdue builds'),
|
||||||
'description': _('Show overdue builds on the homepage'),
|
'description': _('Show overdue builds on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_PO_OUTSTANDING': {
|
'HOMEPAGE_PO_OUTSTANDING': {
|
||||||
'name': _('Show outstanding POs'),
|
'name': _('Show outstanding POs'),
|
||||||
'description': _('Show outstanding POs on the homepage'),
|
'description': _('Show outstanding POs on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_PO_OVERDUE': {
|
'HOMEPAGE_PO_OVERDUE': {
|
||||||
'name': _('Show overdue POs'),
|
'name': _('Show overdue POs'),
|
||||||
'description': _('Show overdue POs on the homepage'),
|
'description': _('Show overdue POs on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_SO_OUTSTANDING': {
|
'HOMEPAGE_SO_OUTSTANDING': {
|
||||||
'name': _('Show outstanding SOs'),
|
'name': _('Show outstanding SOs'),
|
||||||
'description': _('Show outstanding SOs on the homepage'),
|
'description': _('Show outstanding SOs on the homepage'),
|
||||||
'default': True,
|
'default': True,
|
||||||
'validator': bool,
|
'validator': bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
'HOMEPAGE_SO_OVERDUE': {
|
'HOMEPAGE_SO_OVERDUE': {
|
||||||
'name': _('Show overdue SOs'),
|
'name': _('Show overdue SOs'),
|
||||||
'description': _('Show overdue SOs on the homepage'),
|
'description': _('Show overdue SOs on the homepage'),
|
||||||
|
@ -50,11 +50,12 @@ class SettingsSerializer(InvenTreeModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Make sure protected values are not returned
|
Make sure protected values are not returned
|
||||||
"""
|
"""
|
||||||
result = obj.value
|
|
||||||
|
|
||||||
# never return protected values
|
# never return protected values
|
||||||
if obj.is_protected:
|
if obj.protected:
|
||||||
result = '***'
|
result = '***'
|
||||||
|
else:
|
||||||
|
result = obj.value
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@ 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 .models import InvenTreeSetting, WebhookEndpoint, WebhookMessage, NotificationEntry
|
from InvenTree.helpers import str2bool
|
||||||
|
|
||||||
|
from .models import InvenTreeSetting, InvenTreeUserSetting, WebhookEndpoint, WebhookMessage, NotificationEntry
|
||||||
from .api import WebhookView
|
from .api import WebhookView
|
||||||
|
|
||||||
CONTENT_TYPE_JSON = 'application/json'
|
CONTENT_TYPE_JSON = 'application/json'
|
||||||
@ -156,13 +158,224 @@ class SettingsTest(TestCase):
|
|||||||
raise ValueError(f'Non-boolean default value specified for {key}') # pragma: no cover
|
raise ValueError(f'Non-boolean default value specified for {key}') # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
class SettingsApiTest(InvenTreeAPITestCase):
|
class GlobalSettingsApiTest(InvenTreeAPITestCase):
|
||||||
|
"""
|
||||||
|
Tests for the global settings API
|
||||||
|
"""
|
||||||
|
|
||||||
def test_settings_api(self):
|
def test_global_settings_api_list(self):
|
||||||
# test setting with choice
|
"""
|
||||||
|
Test list URL for global settings
|
||||||
|
"""
|
||||||
|
url = reverse('api-global-setting-list')
|
||||||
|
|
||||||
|
# Read out each of the global settings value, to ensure they are instantiated in the database
|
||||||
|
for key in InvenTreeSetting.SETTINGS:
|
||||||
|
InvenTreeSetting.get_setting_object(key)
|
||||||
|
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
# Number of results should match the number of settings
|
||||||
|
self.assertEqual(len(response.data), len(InvenTreeSetting.SETTINGS.keys()))
|
||||||
|
|
||||||
|
def test_company_name(self):
|
||||||
|
|
||||||
|
setting = InvenTreeSetting.get_setting_object('INVENTREE_COMPANY_NAME')
|
||||||
|
|
||||||
|
# Check default value
|
||||||
|
self.assertEqual(setting.value, 'My company name')
|
||||||
|
|
||||||
|
url = reverse('api-global-setting-detail', kwargs={'pk': setting.pk})
|
||||||
|
|
||||||
|
# Test getting via the API
|
||||||
|
for val in ['test', '123', 'My company nam3']:
|
||||||
|
setting.value = val
|
||||||
|
setting.save()
|
||||||
|
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['value'], val)
|
||||||
|
|
||||||
|
# Test setting via the API
|
||||||
|
for val in ['cat', 'hat', 'bat', 'mat']:
|
||||||
|
response = self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': val,
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['value'], val)
|
||||||
|
|
||||||
|
setting.refresh_from_db()
|
||||||
|
self.assertEqual(setting.value, val)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSettingsApiTest(InvenTreeAPITestCase):
|
||||||
|
"""
|
||||||
|
Tests for the user settings API
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_user_settings_api_list(self):
|
||||||
|
"""
|
||||||
|
Test list URL for user settings
|
||||||
|
"""
|
||||||
url = reverse('api-user-setting-list')
|
url = reverse('api-user-setting-list')
|
||||||
|
|
||||||
self.get(url, expected_code=200)
|
self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
def test_user_setting_boolean(self):
|
||||||
|
"""
|
||||||
|
Test a boolean user setting value
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Ensure we have a boolean setting available
|
||||||
|
setting = InvenTreeUserSetting.get_setting_object(
|
||||||
|
'SEARCH_PREVIEW_SHOW_PARTS',
|
||||||
|
user=self.user
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check default values
|
||||||
|
self.assertEqual(setting.to_native_value(), True)
|
||||||
|
|
||||||
|
# Fetch via API
|
||||||
|
url = reverse('api-user-setting-detail', kwargs={'pk': setting.pk})
|
||||||
|
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['pk'], setting.pk)
|
||||||
|
self.assertEqual(response.data['key'], 'SEARCH_PREVIEW_SHOW_PARTS')
|
||||||
|
self.assertEqual(response.data['description'], 'Display parts in search preview window')
|
||||||
|
self.assertEqual(response.data['type'], 'boolean')
|
||||||
|
self.assertEqual(len(response.data['choices']), 0)
|
||||||
|
self.assertTrue(str2bool(response.data['value']))
|
||||||
|
|
||||||
|
# Assign some truthy values
|
||||||
|
for v in ['true', True, 1, 'y', 'TRUE']:
|
||||||
|
self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': str(v),
|
||||||
|
},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
self.assertTrue(str2bool(response.data['value']))
|
||||||
|
|
||||||
|
# Assign some falsey values
|
||||||
|
for v in ['false', False, '0', 'n', 'FalSe']:
|
||||||
|
self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': str(v),
|
||||||
|
},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.get(url, expected_code=200)
|
||||||
|
|
||||||
|
self.assertFalse(str2bool(response.data['value']))
|
||||||
|
|
||||||
|
# Assign some invalid values
|
||||||
|
for v in ['x', '', 'invalid', None, '-1', 'abcde']:
|
||||||
|
response = self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': str(v),
|
||||||
|
},
|
||||||
|
expected_code=200
|
||||||
|
)
|
||||||
|
|
||||||
|
# Invalid values evaluate to False
|
||||||
|
self.assertFalse(str2bool(response.data['value']))
|
||||||
|
|
||||||
|
def test_user_setting_choice(self):
|
||||||
|
|
||||||
|
setting = InvenTreeUserSetting.get_setting_object(
|
||||||
|
'DATE_DISPLAY_FORMAT',
|
||||||
|
user=self.user
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse('api-user-setting-detail', kwargs={'pk': setting.pk})
|
||||||
|
|
||||||
|
# Check default value
|
||||||
|
self.assertEqual(setting.value, 'YYYY-MM-DD')
|
||||||
|
|
||||||
|
# Check that a valid option can be assigned via the API
|
||||||
|
for opt in ['YYYY-MM-DD', 'DD-MM-YYYY', 'MM/DD/YYYY']:
|
||||||
|
|
||||||
|
self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': opt,
|
||||||
|
},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
setting.refresh_from_db()
|
||||||
|
self.assertEqual(setting.value, opt)
|
||||||
|
|
||||||
|
# Send an invalid option
|
||||||
|
for opt in ['cat', 'dog', 12345]:
|
||||||
|
|
||||||
|
response = self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': opt,
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('Chosen value is not a valid option', str(response.data))
|
||||||
|
|
||||||
|
def test_user_setting_integer(self):
|
||||||
|
|
||||||
|
setting = InvenTreeUserSetting.get_setting_object(
|
||||||
|
'SEARCH_PREVIEW_RESULTS',
|
||||||
|
user=self.user
|
||||||
|
)
|
||||||
|
|
||||||
|
url = reverse('api-user-setting-detail', kwargs={'pk': setting.pk})
|
||||||
|
|
||||||
|
# Check default value for this setting
|
||||||
|
self.assertEqual(setting.value, 10)
|
||||||
|
|
||||||
|
for v in [1, 9, 99]:
|
||||||
|
setting.value = v
|
||||||
|
setting.save()
|
||||||
|
|
||||||
|
response = self.get(url)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['value'], str(v))
|
||||||
|
|
||||||
|
# Set valid options via the api
|
||||||
|
for v in [5, 15, 25]:
|
||||||
|
self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': v,
|
||||||
|
},
|
||||||
|
expected_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
setting.refresh_from_db()
|
||||||
|
self.assertEqual(setting.to_native_value(), v)
|
||||||
|
|
||||||
|
# Set invalid options via the API
|
||||||
|
# Note that this particular setting has a MinValueValidator(1) associated with it
|
||||||
|
for v in [0, -1, -5]:
|
||||||
|
|
||||||
|
response = self.patch(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
'value': v,
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WebhookMessageTests(TestCase):
|
class WebhookMessageTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{% extends "skeleton.html" %}
|
{% extends "skeleton.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load inventree_extras %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
|
@ -2413,7 +2413,7 @@ function showAllocationSubTable(index, row, element, options) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'buttons',
|
field: 'buttons',
|
||||||
title: '{% trans "" %}',
|
title: '',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
var html = `<div class='btn-group float-right' role='group'>`;
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
|
2
tasks.py
2
tasks.py
@ -236,7 +236,7 @@ def translate(c):
|
|||||||
manage(c, "compilemessages")
|
manage(c, "compilemessages")
|
||||||
|
|
||||||
|
|
||||||
@task(pre=[install, migrate, translate_stats, static, clean_settings])
|
@task(pre=[install, migrate, translate, static, clean_settings])
|
||||||
def update(c):
|
def update(c):
|
||||||
"""
|
"""
|
||||||
Update InvenTree installation.
|
Update InvenTree installation.
|
||||||
|
Loading…
Reference in New Issue
Block a user