mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master' into build-fixes
# Conflicts: # InvenTree/locale/de/LC_MESSAGES/django.po # InvenTree/locale/en/LC_MESSAGES/django.po # InvenTree/locale/es/LC_MESSAGES/django.po # InvenTree/templates/js/build.html
This commit is contained in:
commit
a4f6efc05d
@ -120,6 +120,19 @@ def str2bool(text, test=True):
|
|||||||
return str(text).lower() in ['0', 'n', 'no', 'none', 'f', 'false', 'off', ]
|
return str(text).lower() in ['0', 'n', 'no', 'none', 'f', 'false', 'off', ]
|
||||||
|
|
||||||
|
|
||||||
|
def is_bool(text):
|
||||||
|
"""
|
||||||
|
Determine if a string value 'looks' like a boolean.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if str2bool(text, True):
|
||||||
|
return True
|
||||||
|
elif str2bool(text, False):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def isNull(text):
|
def isNull(text):
|
||||||
"""
|
"""
|
||||||
Test if a string 'looks' like a null value.
|
Test if a string 'looks' like a null value.
|
||||||
|
@ -39,6 +39,8 @@ from .views import IndexView, SearchView, DatabaseStatsView
|
|||||||
from .views import SettingsView, EditUserView, SetPasswordView, ColorThemeSelectView
|
from .views import SettingsView, EditUserView, SetPasswordView, ColorThemeSelectView
|
||||||
from .views import DynamicJsView
|
from .views import DynamicJsView
|
||||||
|
|
||||||
|
from common.views import SettingEdit
|
||||||
|
|
||||||
from .api import InfoView
|
from .api import InfoView
|
||||||
from .api import ActionPluginView
|
from .api import ActionPluginView
|
||||||
|
|
||||||
@ -71,6 +73,7 @@ settings_urls = [
|
|||||||
url(r'^user/?', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings-user'),
|
url(r'^user/?', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings-user'),
|
||||||
url(r'^theme/?', ColorThemeSelectView.as_view(), name='settings-theme'),
|
url(r'^theme/?', ColorThemeSelectView.as_view(), name='settings-theme'),
|
||||||
|
|
||||||
|
url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'),
|
||||||
url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'),
|
url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'),
|
||||||
url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'),
|
url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'),
|
||||||
url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'),
|
url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'),
|
||||||
@ -78,6 +81,8 @@ settings_urls = [
|
|||||||
url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'),
|
url(r'^purchase-order/?', SettingsView.as_view(template_name='InvenTree/settings/po.html'), name='settings-po'),
|
||||||
url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'),
|
url(r'^sales-order/?', SettingsView.as_view(template_name='InvenTree/settings/so.html'), name='settings-so'),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/edit/?', SettingEdit.as_view(), name='setting-edit'),
|
||||||
|
|
||||||
# Catch any other urls
|
# Catch any other urls
|
||||||
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),
|
url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'),
|
||||||
]
|
]
|
||||||
|
@ -128,9 +128,13 @@ class InvenTreeRoleMixin(PermissionRequiredMixin):
|
|||||||
|
|
||||||
def has_permission(self):
|
def has_permission(self):
|
||||||
"""
|
"""
|
||||||
Determine if the current user
|
Determine if the current user has specified permissions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.permission_required:
|
||||||
|
# Ignore role-based permissions
|
||||||
|
return super().has_permission()
|
||||||
|
|
||||||
roles_required = []
|
roles_required = []
|
||||||
|
|
||||||
if type(self.role_required) is str:
|
if type(self.role_required) is str:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.utils import OperationalError, ProgrammingError
|
from django.db.utils import OperationalError, ProgrammingError, IntegrityError
|
||||||
|
|
||||||
|
|
||||||
class CommonConfig(AppConfig):
|
class CommonConfig(AppConfig):
|
||||||
@ -32,7 +32,7 @@ class CommonConfig(AppConfig):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Default instance name
|
# Default instance name
|
||||||
instance_name = 'InvenTree Server'
|
instance_name = InvenTreeSetting.get_default_value('INVENTREE_INSTANCE')
|
||||||
|
|
||||||
# Use the old name if it exists
|
# Use the old name if it exists
|
||||||
if InvenTreeSetting.objects.filter(key='InstanceName').exists():
|
if InvenTreeSetting.objects.filter(key='InstanceName').exists():
|
||||||
@ -48,7 +48,7 @@ class CommonConfig(AppConfig):
|
|||||||
value=instance_name
|
value=instance_name
|
||||||
)
|
)
|
||||||
|
|
||||||
except (OperationalError, ProgrammingError):
|
except (OperationalError, ProgrammingError, IntegrityError):
|
||||||
# Migrations have not yet been applied - table does not exist
|
# Migrations have not yet been applied - table does not exist
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -59,12 +59,12 @@ class CommonConfig(AppConfig):
|
|||||||
|
|
||||||
from .models import InvenTreeSetting
|
from .models import InvenTreeSetting
|
||||||
|
|
||||||
for key in InvenTreeSetting.DEFAULT_VALUES.keys():
|
for key in InvenTreeSetting.GLOBAL_SETTINGS.keys():
|
||||||
try:
|
try:
|
||||||
settings = InvenTreeSetting.objects.filter(key__iexact=key)
|
settings = InvenTreeSetting.objects.filter(key__iexact=key)
|
||||||
|
|
||||||
if settings.count() == 0:
|
if settings.count() == 0:
|
||||||
value = InvenTreeSetting.DEFAULT_VALUES[key]
|
value = InvenTreeSetting.get_default_value(key)
|
||||||
|
|
||||||
print(f"Creating default setting for {key} -> '{value}'")
|
print(f"Creating default setting for {key} -> '{value}'")
|
||||||
|
|
||||||
@ -87,6 +87,6 @@ class CommonConfig(AppConfig):
|
|||||||
setting.key = key
|
setting.key = key
|
||||||
setting.save()
|
setting.save()
|
||||||
|
|
||||||
except (OperationalError, ProgrammingError):
|
except (OperationalError, ProgrammingError, IntegrityError):
|
||||||
# Table might not yet exist
|
# Table might not yet exist
|
||||||
pass
|
pass
|
||||||
|
@ -33,6 +33,5 @@ class SettingEditForm(HelperForm):
|
|||||||
model = InvenTreeSetting
|
model = InvenTreeSetting
|
||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
'key',
|
|
||||||
'value'
|
'value'
|
||||||
]
|
]
|
||||||
|
@ -11,10 +11,12 @@ import decimal
|
|||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
|
import InvenTree.helpers
|
||||||
import InvenTree.fields
|
import InvenTree.fields
|
||||||
|
|
||||||
|
|
||||||
@ -27,34 +29,205 @@ class InvenTreeSetting(models.Model):
|
|||||||
even if that key does not exist.
|
even if that key does not exist.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Dict of default values for various internal settings
|
"""
|
||||||
DEFAULT_VALUES = {
|
Dict of all global settings values:
|
||||||
# Global inventree settings
|
|
||||||
'INVENTREE_INSTANCE': 'InvenTree Server',
|
|
||||||
|
|
||||||
# Part settings
|
The key of each item is the name of the value as it appears in the database.
|
||||||
'PART_IPN_REGEX': '',
|
|
||||||
'PART_COPY_BOM': True,
|
|
||||||
'PART_COPY_PARAMETERS': True,
|
|
||||||
'PART_COPY_TESTS': True,
|
|
||||||
|
|
||||||
# Stock settings
|
Each global setting has the following parameters:
|
||||||
|
|
||||||
# Build Order settings
|
- name: Translatable string name of the setting (required)
|
||||||
'BUILDORDER_REFERENCE_PREFIX': 'BO',
|
- description: Translatable string description of the setting (required)
|
||||||
'BUILDORDER_REFERENCE_REGEX': '',
|
- default: Default value (optional)
|
||||||
|
- units: Units of the particular setting (optional)
|
||||||
|
- validator: Validation function for the setting (optional)
|
||||||
|
|
||||||
# Purchase Order Settings
|
The keys must be upper-case
|
||||||
'PURCHASEORDER_REFERENCE_PREFIX': 'PO',
|
"""
|
||||||
|
|
||||||
# Sales Order Settings
|
GLOBAL_SETTINGS = {
|
||||||
'SALESORDER_REFERENCE_PREFIX': 'SO',
|
|
||||||
|
'INVENTREE_INSTANCE': {
|
||||||
|
'name': _('InvenTree Instance Name'),
|
||||||
|
'default': 'InvenTree server',
|
||||||
|
'description': _('String descriptor for the server instance'),
|
||||||
|
},
|
||||||
|
|
||||||
|
'INVENTREE_COMPANY_NAME': {
|
||||||
|
'name': _('Company name'),
|
||||||
|
'description': _('Internal company name'),
|
||||||
|
'default': 'My company name',
|
||||||
|
},
|
||||||
|
|
||||||
|
'PART_IPN_REGEX': {
|
||||||
|
'name': _('IPN Regex'),
|
||||||
|
'description': _('Regular expression pattern for matching Part IPN')
|
||||||
|
},
|
||||||
|
|
||||||
|
'PART_COPY_BOM': {
|
||||||
|
'name': _('Copy Part BOM Data'),
|
||||||
|
'description': _('Copy BOM data by default when duplicating a part'),
|
||||||
|
'default': True,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
'PART_COPY_PARAMETERS': {
|
||||||
|
'name': _('Copy Part Parameter Data'),
|
||||||
|
'description': _('Copy parameter data by default when duplicating a part'),
|
||||||
|
'default': True,
|
||||||
|
'validator': bool,
|
||||||
|
},
|
||||||
|
|
||||||
|
'PART_COPY_TESTS': {
|
||||||
|
'name': _('Copy Part Test Data'),
|
||||||
|
'description': _('Copy test data by default when duplicating a part'),
|
||||||
|
'default': True,
|
||||||
|
'validator': bool
|
||||||
|
},
|
||||||
|
|
||||||
|
'BUILDORDER_REFERENCE_PREFIX': {
|
||||||
|
'name': _('Build Order Reference Prefix'),
|
||||||
|
'description': _('Prefix value for build order reference'),
|
||||||
|
'default': 'BO',
|
||||||
|
},
|
||||||
|
|
||||||
|
'BUILDORDER_REFERENCE_REGEX': {
|
||||||
|
'name': _('Build Order Reference Regex'),
|
||||||
|
'description': _('Regular expression pattern for matching build order reference')
|
||||||
|
},
|
||||||
|
|
||||||
|
'SALESORDER_REFERENCE_PREFIX': {
|
||||||
|
'name': _('Sales Order Reference Prefix'),
|
||||||
|
'description': _('Prefix value for sales order reference'),
|
||||||
|
},
|
||||||
|
|
||||||
|
'PURCHASEORDER_REFERENCE_PREFIX': {
|
||||||
|
'name': _('Purchase Order Reference Prefix'),
|
||||||
|
'description': _('Prefix value for purchase order reference'),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "InvenTree Setting"
|
verbose_name = "InvenTree Setting"
|
||||||
verbose_name_plural = "InvenTree Settings"
|
verbose_name_plural = "InvenTree Settings"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_setting_name(cls, key):
|
||||||
|
"""
|
||||||
|
Return the name of a particular setting.
|
||||||
|
|
||||||
|
If it does not exist, return an empty string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = str(key).strip().upper()
|
||||||
|
|
||||||
|
if key in cls.GLOBAL_SETTINGS:
|
||||||
|
setting = cls.GLOBAL_SETTINGS[key]
|
||||||
|
return setting.get('name', '')
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_setting_description(cls, key):
|
||||||
|
"""
|
||||||
|
Return the description for a particular setting.
|
||||||
|
|
||||||
|
If it does not exist, return an empty string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = str(key).strip().upper()
|
||||||
|
|
||||||
|
if key in cls.GLOBAL_SETTINGS:
|
||||||
|
setting = cls.GLOBAL_SETTINGS[key]
|
||||||
|
return setting.get('description', '')
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_setting_units(cls, key):
|
||||||
|
"""
|
||||||
|
Return the units for a particular setting.
|
||||||
|
|
||||||
|
If it does not exist, return an empty string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = str(key).strip().upper()
|
||||||
|
|
||||||
|
if key in cls.GLOBAL_SETTINGS:
|
||||||
|
setting = cls.GLOBAL_SETTINGS[key]
|
||||||
|
return setting.get('units', '')
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_setting_validator(cls, key):
|
||||||
|
"""
|
||||||
|
Return the validator for a particular setting.
|
||||||
|
|
||||||
|
If it does not exist, return None
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = str(key).strip().upper()
|
||||||
|
|
||||||
|
if key in cls.GLOBAL_SETTINGS:
|
||||||
|
setting = cls.GLOBAL_SETTINGS[key]
|
||||||
|
return setting.get('validator', None)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_default_value(cls, key):
|
||||||
|
"""
|
||||||
|
Return the default value for a particular setting.
|
||||||
|
|
||||||
|
If it does not exist, return an empty string
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = str(key).strip().upper()
|
||||||
|
|
||||||
|
if key in cls.GLOBAL_SETTINGS:
|
||||||
|
setting = cls.GLOBAL_SETTINGS[key]
|
||||||
|
return setting.get('default', '')
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_setting_object(cls, key):
|
||||||
|
"""
|
||||||
|
Return an InvenTreeSetting object matching the given key.
|
||||||
|
|
||||||
|
- Key is case-insensitive
|
||||||
|
- Returns None if no match is made
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = str(key).strip().upper()
|
||||||
|
|
||||||
|
try:
|
||||||
|
setting = InvenTreeSetting.objects.filter(key__iexact=key).first()
|
||||||
|
except (InvenTreeSetting.DoesNotExist):
|
||||||
|
# Create the setting if it does not exist
|
||||||
|
setting = InvenTreeSetting.create(
|
||||||
|
key=key,
|
||||||
|
value=InvenTreeSetting.get_default_value(key)
|
||||||
|
)
|
||||||
|
|
||||||
|
return setting
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_setting_pk(cls, key):
|
||||||
|
"""
|
||||||
|
Return the primary-key value for a given setting.
|
||||||
|
|
||||||
|
If the setting does not exist, return None
|
||||||
|
"""
|
||||||
|
|
||||||
|
setting = InvenTreeSetting.get_setting_object(cls)
|
||||||
|
|
||||||
|
if setting:
|
||||||
|
return setting.pk
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_setting(cls, key, backup_value=None):
|
def get_setting(cls, key, backup_value=None):
|
||||||
"""
|
"""
|
||||||
@ -64,16 +237,13 @@ class InvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
# If no backup value is specified, atttempt to retrieve a "default" value
|
# If no backup value is specified, atttempt to retrieve a "default" value
|
||||||
if backup_value is None:
|
if backup_value is None:
|
||||||
backup_value = InvenTreeSetting.DEFAULT_VALUES.get(key, None)
|
backup_value = cls.get_default_value(key)
|
||||||
|
|
||||||
try:
|
setting = InvenTreeSetting.get_setting_object(key)
|
||||||
settings = InvenTreeSetting.objects.filter(key__iexact=key)
|
|
||||||
|
|
||||||
if len(settings) > 0:
|
if setting:
|
||||||
return settings[0].value
|
return setting.value
|
||||||
else:
|
else:
|
||||||
return backup_value
|
|
||||||
except InvenTreeSetting.DoesNotExist:
|
|
||||||
return backup_value
|
return backup_value
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -108,6 +278,59 @@ class InvenTreeSetting(models.Model):
|
|||||||
|
|
||||||
value = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings value'))
|
value = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings value'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return InvenTreeSetting.get_setting_name(self.key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
return InvenTreeSetting.get_setting_description(self.key)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def units(self):
|
||||||
|
return InvenTreeSetting.get_setting_units(self.key)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""
|
||||||
|
If a validator (or multiple validators) are defined for a particular setting key,
|
||||||
|
run them against the 'value' field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().clean()
|
||||||
|
|
||||||
|
validator = InvenTreeSetting.get_setting_validator(self.key)
|
||||||
|
|
||||||
|
if validator is not None:
|
||||||
|
self.run_validator(validator)
|
||||||
|
|
||||||
|
def run_validator(self, validator):
|
||||||
|
"""
|
||||||
|
Run a validator against the 'value' field for this InvenTreeSetting object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if validator is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# If a list of validators is supplied, iterate through each one
|
||||||
|
if type(validator) in [list, tuple]:
|
||||||
|
for v in validator:
|
||||||
|
self.run_validator(v)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if a 'type' has been specified for this value
|
||||||
|
if type(validator) == type:
|
||||||
|
|
||||||
|
if validator == bool:
|
||||||
|
# Value must "look like" a boolean value
|
||||||
|
if InvenTree.helpers.is_bool(self.value):
|
||||||
|
# Coerce into either "True" or "False"
|
||||||
|
self.value = str(InvenTree.helpers.str2bool(self.value))
|
||||||
|
else:
|
||||||
|
raise ValidationError({
|
||||||
|
'value': _('Value must be a boolean value')
|
||||||
|
})
|
||||||
|
|
||||||
def validate_unique(self, exclude=None):
|
def validate_unique(self, exclude=None):
|
||||||
""" Ensure that the key:value pair is unique.
|
""" Ensure that the key:value pair is unique.
|
||||||
In addition to the base validators, this ensures that the 'key'
|
In addition to the base validators, this ensures that the 'key'
|
||||||
@ -123,6 +346,24 @@ class InvenTreeSetting(models.Model):
|
|||||||
except InvenTreeSetting.DoesNotExist:
|
except InvenTreeSetting.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def is_bool(self):
|
||||||
|
"""
|
||||||
|
Check if this setting is required to be a boolean value
|
||||||
|
"""
|
||||||
|
|
||||||
|
validator = InvenTreeSetting.get_setting_validator(self.key)
|
||||||
|
|
||||||
|
return validator == bool
|
||||||
|
|
||||||
|
def as_bool(self):
|
||||||
|
"""
|
||||||
|
Return the value of this setting converted to a boolean value.
|
||||||
|
|
||||||
|
Warning: Only use on values where is_bool evaluates to true!
|
||||||
|
"""
|
||||||
|
|
||||||
|
return InvenTree.helpers.str2bool(self.value)
|
||||||
|
|
||||||
|
|
||||||
class Currency(models.Model):
|
class Currency(models.Model):
|
||||||
"""
|
"""
|
||||||
|
14
InvenTree/common/templates/common/edit_setting.html
Normal file
14
InvenTree/common/templates/common/edit_setting.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block pre_form_content %}
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
<!--
|
||||||
|
<p>
|
||||||
|
<b>{{ name }}</b><br>
|
||||||
|
{{ description }}<br>
|
||||||
|
<i>{% trans "Current value" %}: {{ value }}</i>
|
||||||
|
</p>
|
||||||
|
-->
|
||||||
|
{% endblock %}
|
@ -35,14 +35,37 @@ class SettingsTest(TestCase):
|
|||||||
|
|
||||||
self.client.login(username='username', password='password')
|
self.client.login(username='username', password='password')
|
||||||
|
|
||||||
|
def test_required_values(self):
|
||||||
|
"""
|
||||||
|
- Ensure that every global setting has a name.
|
||||||
|
- Ensure that every global setting has a description.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for key in InvenTreeSetting.GLOBAL_SETTINGS.keys():
|
||||||
|
|
||||||
|
setting = InvenTreeSetting.GLOBAL_SETTINGS[key]
|
||||||
|
|
||||||
|
name = setting.get('name', None)
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
raise ValueError(f'Missing GLOBAL_SETTING name for {key}')
|
||||||
|
|
||||||
|
description = setting.get('description', None)
|
||||||
|
|
||||||
|
if description is None:
|
||||||
|
raise ValueError(f'Missing GLOBAL_SETTING description for {key}')
|
||||||
|
|
||||||
|
if not key == key.upper():
|
||||||
|
raise ValueError(f"GLOBAL_SETTINGS key '{key}' is not uppercase")
|
||||||
|
|
||||||
def test_defaults(self):
|
def test_defaults(self):
|
||||||
"""
|
"""
|
||||||
Populate the settings with default values
|
Populate the settings with default values
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for key in InvenTreeSetting.DEFAULT_VALUES.keys():
|
for key in InvenTreeSetting.GLOBAL_SETTINGS.keys():
|
||||||
|
|
||||||
value = InvenTreeSetting.DEFAULT_VALUES[key]
|
value = InvenTreeSetting.get_default_value(key)
|
||||||
|
|
||||||
InvenTreeSetting.set_setting(key, value, self.user)
|
InvenTreeSetting.set_setting(key, value, self.user)
|
||||||
|
|
||||||
|
@ -6,8 +6,10 @@ Django views for interacting with common models
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.forms import CheckboxInput
|
||||||
|
|
||||||
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||||
|
from InvenTree.helpers import str2bool
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from . import forms
|
from . import forms
|
||||||
@ -46,3 +48,47 @@ class SettingEdit(AjaxUpdateView):
|
|||||||
model = models.InvenTreeSetting
|
model = models.InvenTreeSetting
|
||||||
ajax_form_title = _('Change Setting')
|
ajax_form_title = _('Change Setting')
|
||||||
form_class = forms.SettingEditForm
|
form_class = forms.SettingEditForm
|
||||||
|
ajax_template_name = "common/edit_setting.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Add extra context information about the particular setting object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
setting = self.get_object()
|
||||||
|
|
||||||
|
ctx['key'] = setting.key
|
||||||
|
ctx['value'] = setting.value
|
||||||
|
ctx['name'] = models.InvenTreeSetting.get_setting_name(setting.key)
|
||||||
|
ctx['description'] = models.InvenTreeSetting.get_setting_description(setting.key)
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
def get_form(self):
|
||||||
|
"""
|
||||||
|
Override default get_form behaviour
|
||||||
|
"""
|
||||||
|
|
||||||
|
form = super().get_form()
|
||||||
|
|
||||||
|
setting = self.get_object()
|
||||||
|
|
||||||
|
if setting.is_bool():
|
||||||
|
form.fields['value'].widget = CheckboxInput()
|
||||||
|
|
||||||
|
self.object.value = str2bool(setting.value)
|
||||||
|
form.fields['value'].value = str2bool(setting.value)
|
||||||
|
|
||||||
|
name = models.InvenTreeSetting.get_setting_name(setting.key)
|
||||||
|
|
||||||
|
if name:
|
||||||
|
form.fields['value'].label = name
|
||||||
|
|
||||||
|
description = models.InvenTreeSetting.get_setting_description(setting.key)
|
||||||
|
|
||||||
|
if description:
|
||||||
|
form.fields['value'].help_text = description
|
||||||
|
|
||||||
|
return form
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def make_empty_email_field_null(apps, schema_editor):
|
||||||
|
Company = apps.get_model('company', 'Company')
|
||||||
|
for company in Company.objects.all():
|
||||||
|
if company.email == '':
|
||||||
|
company.email = None
|
||||||
|
company.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('company', '0023_auto_20200808_0715'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
# Allow email field to be NULL
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='company',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(blank=True, help_text='Contact email address', max_length=254, null=True, unique=False, verbose_name='Email'),
|
||||||
|
),
|
||||||
|
# Convert empty email string to NULL
|
||||||
|
migrations.RunPython(make_empty_email_field_null),
|
||||||
|
# Remove unique constraint on name field
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='company',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(help_text='Company name', max_length=100, verbose_name='Company name'),
|
||||||
|
),
|
||||||
|
# Add unique constraint on name/email pair
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='company',
|
||||||
|
constraint=models.UniqueConstraint(fields=('name', 'email'), name='unique_name_email_pair'),
|
||||||
|
),
|
||||||
|
]
|
@ -12,7 +12,7 @@ import math
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Sum, Q
|
from django.db.models import Sum, Q, UniqueConstraint
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -81,8 +81,11 @@ class Company(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['name', ]
|
ordering = ['name', ]
|
||||||
|
constraints = [
|
||||||
|
UniqueConstraint(fields=['name', 'email'], name='unique_name_email_pair')
|
||||||
|
]
|
||||||
|
|
||||||
name = models.CharField(max_length=100, blank=False, unique=True,
|
name = models.CharField(max_length=100, blank=False,
|
||||||
help_text=_('Company name'),
|
help_text=_('Company name'),
|
||||||
verbose_name=_('Company name'))
|
verbose_name=_('Company name'))
|
||||||
|
|
||||||
@ -98,7 +101,8 @@ class Company(models.Model):
|
|||||||
verbose_name=_('Phone number'),
|
verbose_name=_('Phone number'),
|
||||||
blank=True, help_text=_('Contact phone number'))
|
blank=True, help_text=_('Contact phone number'))
|
||||||
|
|
||||||
email = models.EmailField(blank=True, verbose_name=_('Email'), help_text=_('Contact email address'))
|
email = models.EmailField(blank=True, null=True,
|
||||||
|
verbose_name=_('Email'), help_text=_('Contact email address'))
|
||||||
|
|
||||||
contact = models.CharField(max_length=100,
|
contact = models.CharField(max_length=100,
|
||||||
verbose_name=_('Contact'),
|
verbose_name=_('Contact'),
|
||||||
|
@ -23,23 +23,27 @@ InvenTree | {% trans "Company" %} - {{ company.name }}
|
|||||||
<hr>
|
<hr>
|
||||||
<h4>
|
<h4>
|
||||||
{{ company.name }}
|
{{ company.name }}
|
||||||
{% if user.is_staff and roles.company.change %}
|
{% if user.is_staff and perms.company.change_company %}
|
||||||
<a href="{% url 'admin:company_company_change' company.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
|
<a href="{% url 'admin:company_company_change' company.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
<p>{{ company.description }}</p>
|
<p>{{ company.description }}</p>
|
||||||
<div class='btn-group action-buttons'>
|
<div class='btn-group action-buttons'>
|
||||||
{% if company.is_supplier %}
|
{% if company.is_supplier and roles.purchase_order.add %}
|
||||||
<button type='button' class='btn btn-default' id='company-order-2' title='Create purchase order'>
|
<button type='button' class='btn btn-default' id='company-order-2' title='Create purchase order'>
|
||||||
<span class='fas fa-shopping-cart'/>
|
<span class='fas fa-shopping-cart'/>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if perms.company.change_company %}
|
||||||
<button type='button' class='btn btn-default' id='company-edit' title='Edit company information'>
|
<button type='button' class='btn btn-default' id='company-edit' title='Edit company information'>
|
||||||
<span class='fas fa-edit icon-green'/>
|
<span class='fas fa-edit icon-green'/>
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.company.delete_company %}
|
||||||
<button type='button' class='btn btn-default' id='company-delete' title='Delete company'>
|
<button type='button' class='btn btn-default' id='company-delete' title='Delete company'>
|
||||||
<span class='fas fa-trash-alt icon-red'/>
|
<span class='fas fa-trash-alt icon-red'/>
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -9,17 +9,25 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
{% if roles.purchase_order.change %}
|
||||||
<div id='button-toolbar' class='btn-group'>
|
<div id='button-toolbar' class='btn-group'>
|
||||||
|
{% if roles.purchase_order.add %}
|
||||||
<button class="btn btn-success" id='part-create' title='{% trans "Create new supplier part" %}'>{% trans "New Supplier Part" %}</button>
|
<button class="btn btn-success" id='part-create' title='{% trans "Create new supplier part" %}'>{% trans "New Supplier Part" %}</button>
|
||||||
|
{% endif %}
|
||||||
<div class="dropdown" style="float: right;">
|
<div class="dropdown" style="float: right;">
|
||||||
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}
|
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">{% trans "Options" %}
|
||||||
<span class="caret"></span></button>
|
<span class="caret"></span></button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
{% if roles.purchase_order.add %}
|
||||||
<li><a href='#' id='multi-part-order' title='{% trans "Order parts" %}'>{% trans "Order Parts" %}</a></li>
|
<li><a href='#' id='multi-part-order' title='{% trans "Order parts" %}'>{% trans "Order Parts" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if roles.purchase_order.delete %}
|
||||||
<li><a href='#' id='multi-part-delete' title='{% trans "Delete parts" %}'>{% trans "Delete Parts" %}</a></li>
|
<li><a href='#' id='multi-part-delete' title='{% trans "Delete parts" %}'>{% trans "Delete Parts" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='part-table' data-toolbar='#button-toolbar'>
|
<table class='table table-striped table-condensed' id='part-table' data-toolbar='#button-toolbar'>
|
||||||
</table>
|
</table>
|
||||||
|
@ -12,12 +12,13 @@ InvenTree | {% trans "Supplier List" %}
|
|||||||
<h3>{{ title }}</h3>
|
<h3>{{ title }}</h3>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
{% if pagetype == 'manufacturers' and roles.purchase_order.add or pagetype == 'suppliers' and roles.purchase_order.add or pagetype == 'customers' and roles.sales_order.add %}
|
||||||
<div id='button-toolbar'>
|
<div id='button-toolbar'>
|
||||||
<div class='btn-group'>
|
<div class='btn-group'>
|
||||||
<button type='button' class="btn btn-success" id='new-company'>{{ button_text }}</button>
|
<button type='button' class="btn btn-success" id='new-company'>{{ button_text }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped' id='company-table' data-toolbar='#button-toolbar'>
|
<table class='table table-striped' id='company-table' data-toolbar='#button-toolbar'>
|
||||||
</table>
|
</table>
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<h4>{% trans "Purchase Orders" %}</h4>
|
<h4>{% trans "Purchase Orders" %}</h4>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
{% if roles.purchase_order.add %}
|
||||||
<div id='button-bar'>
|
<div id='button-bar'>
|
||||||
<div class='button-toolbar container-fluid' style='float: right;'>
|
<div class='button-toolbar container-fluid' style='float: right;'>
|
||||||
<button class='btn btn-primary' type='button' id='company-order2' title='{% trans "Create new purchase order" %}'>{% trans "New Purchase Order" %}</button>
|
<button class='btn btn-primary' type='button' id='company-order2' title='{% trans "Create new purchase order" %}'>{% trans "New Purchase Order" %}</button>
|
||||||
@ -17,6 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'>
|
<table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'>
|
||||||
</table>
|
</table>
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
<h4>{% trans "Sales Orders" %}</h4>
|
<h4>{% trans "Sales Orders" %}</h4>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
{% if roles.sales_order.add %}
|
||||||
<div id='button-bar'>
|
<div id='button-bar'>
|
||||||
<div class='button-toolbar container-fluid' style='float: right;'>
|
<div class='button-toolbar container-fluid' style='float: right;'>
|
||||||
<button class='btn btn-primary' type='button' id='new-sales-order' title='{% trans "Create new sales order" %}'>{% trans "New Sales Order" %}</button>
|
<button class='btn btn-primary' type='button' id='new-sales-order' title='{% trans "Create new sales order" %}'>{% trans "New Sales Order" %}</button>
|
||||||
@ -17,6 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped table-condensed po-table' id='sales-order-table' data-toolbar='#button-bar'>
|
<table class='table table-striped table-condensed po-table' id='sales-order-table' data-toolbar='#button-bar'>
|
||||||
</table>
|
</table>
|
||||||
|
@ -18,19 +18,27 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
{% block page_data %}
|
{% block page_data %}
|
||||||
<h3>{% trans "Supplier Part" %}</h3>
|
<h3>{% trans "Supplier Part" %}</h3>
|
||||||
<p>{{ part.supplier.name }} - {{ part.SKU }}</p>
|
<p>{{ part.supplier.name }} - {{ part.SKU }}</p>
|
||||||
|
|
||||||
|
{% if roles.purchase_order.change %}
|
||||||
<div class='btn-row'>
|
<div class='btn-row'>
|
||||||
<div class='btn-group action-buttons' role='group'>
|
<div class='btn-group action-buttons' role='group'>
|
||||||
|
{% if roles.purchase_order.add %}
|
||||||
<button type='button' class='btn btn-default btn-glyph' id='order-part' title='{% trans "Order part" %}'>
|
<button type='button' class='btn btn-default btn-glyph' id='order-part' title='{% trans "Order part" %}'>
|
||||||
<span class='fas fa-shopping-cart'></span>
|
<span class='fas fa-shopping-cart'></span>
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
<button type='button' class='btn btn-default btn-glyph' id='edit-part' title='{% trans "Edit supplier part" %}'>
|
<button type='button' class='btn btn-default btn-glyph' id='edit-part' title='{% trans "Edit supplier part" %}'>
|
||||||
<span class='fas fa-edit icon-green'/>
|
<span class='fas fa-edit icon-green'/>
|
||||||
</button>
|
</button>
|
||||||
|
{% if roles.purchase_order.delete %}
|
||||||
<button type='button' class='btn btn-default btn-glyph' id='delete-part' title='{% trans "Delete supplier part" %}'>
|
<button type='button' class='btn btn-default btn-glyph' id='delete-part' title='{% trans "Delete supplier part" %}'>
|
||||||
<span class='fas fa-trash-alt icon-red'/>
|
<span class='fas fa-trash-alt icon-red'/>
|
||||||
</button>
|
</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_details %}
|
{% block page_details %}
|
||||||
|
@ -10,11 +10,13 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
{% if roles.purchase_order.add %}
|
||||||
<div id='button-bar'>
|
<div id='button-bar'>
|
||||||
<div class='btn-group'>
|
<div class='btn-group'>
|
||||||
<button class='btn btn-primary' type='button' id='order-part2' title='Order part'>Order Part</button>
|
<button class='btn btn-primary' type='button' id='order-part2' title='Order part'>Order Part</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'>
|
<table class='table table-striped table-condensed po-table' id='purchase-order-table' data-toolbar='#button-bar'>
|
||||||
</table>
|
</table>
|
||||||
|
@ -11,9 +11,11 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
{% if roles.purchase_order.add %}
|
||||||
<div id='price-break-toolbar' class='btn-group'>
|
<div id='price-break-toolbar' class='btn-group'>
|
||||||
<button class='btn btn-primary' id='new-price-break' type='button'>{% trans "Add Price Break" %}</button>
|
<button class='btn btn-primary' id='new-price-break' type='button'>{% trans "Add Price Break" %}</button>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='price-break-table' data-toolbar='#price-break-toolbar'>
|
<table class='table table-striped table-condensed' id='price-break-table' data-toolbar='#price-break-toolbar'>
|
||||||
</table>
|
</table>
|
||||||
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
|||||||
from django.test import TestCase
|
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 import get_user_model
|
||||||
|
from django.contrib.auth.models import Group
|
||||||
|
|
||||||
from .models import SupplierPart
|
from .models import SupplierPart
|
||||||
|
|
||||||
@ -25,7 +26,24 @@ class CompanyViewTest(TestCase):
|
|||||||
|
|
||||||
# Create a user
|
# Create a user
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
User.objects.create_user('username', 'user@email.com', 'password')
|
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.client.login(username='username', password='password')
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ from django.forms import HiddenInput
|
|||||||
|
|
||||||
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||||
from InvenTree.helpers import str2bool
|
from InvenTree.helpers import str2bool
|
||||||
|
from InvenTree.views import InvenTreeRoleMixin
|
||||||
|
|
||||||
from common.models import Currency
|
from common.models import Currency
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ from .forms import EditSupplierPartForm
|
|||||||
from .forms import EditPriceBreakForm
|
from .forms import EditPriceBreakForm
|
||||||
|
|
||||||
|
|
||||||
class CompanyIndex(ListView):
|
class CompanyIndex(InvenTreeRoleMixin, ListView):
|
||||||
""" View for displaying list of companies
|
""" View for displaying list of companies
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -37,6 +38,7 @@ class CompanyIndex(ListView):
|
|||||||
template_name = 'company/index.html'
|
template_name = 'company/index.html'
|
||||||
context_object_name = 'companies'
|
context_object_name = 'companies'
|
||||||
paginate_by = 50
|
paginate_by = 50
|
||||||
|
permission_required = 'company.view_company'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|
||||||
@ -116,8 +118,8 @@ class CompanyNotes(UpdateView):
|
|||||||
context_object_name = 'company'
|
context_object_name = 'company'
|
||||||
template_name = 'company/notes.html'
|
template_name = 'company/notes.html'
|
||||||
model = Company
|
model = Company
|
||||||
|
|
||||||
fields = ['notes']
|
fields = ['notes']
|
||||||
|
permission_required = 'company.view_company'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse('company-notes', kwargs={'pk': self.get_object().id})
|
return reverse('company-notes', kwargs={'pk': self.get_object().id})
|
||||||
@ -137,6 +139,7 @@ class CompanyDetail(DetailView):
|
|||||||
template_name = 'company/detail.html'
|
template_name = 'company/detail.html'
|
||||||
queryset = Company.objects.all()
|
queryset = Company.objects.all()
|
||||||
model = Company
|
model = Company
|
||||||
|
permission_required = 'company.view_company'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
@ -150,6 +153,7 @@ class CompanyImage(AjaxUpdateView):
|
|||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
ajax_form_title = _('Update Company Image')
|
ajax_form_title = _('Update Company Image')
|
||||||
form_class = CompanyImageForm
|
form_class = CompanyImageForm
|
||||||
|
permission_required = 'company.change_company'
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
@ -164,6 +168,7 @@ class CompanyEdit(AjaxUpdateView):
|
|||||||
context_object_name = 'company'
|
context_object_name = 'company'
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
ajax_form_title = _('Edit Company')
|
ajax_form_title = _('Edit Company')
|
||||||
|
permission_required = 'company.change_company'
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
@ -177,6 +182,7 @@ class CompanyCreate(AjaxCreateView):
|
|||||||
context_object_name = 'company'
|
context_object_name = 'company'
|
||||||
form_class = EditCompanyForm
|
form_class = EditCompanyForm
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
|
permission_required = 'company.add_company'
|
||||||
|
|
||||||
def get_form_title(self):
|
def get_form_title(self):
|
||||||
|
|
||||||
@ -230,6 +236,7 @@ class CompanyDelete(AjaxDeleteView):
|
|||||||
ajax_template_name = 'company/delete.html'
|
ajax_template_name = 'company/delete.html'
|
||||||
ajax_form_title = _('Delete Company')
|
ajax_form_title = _('Delete Company')
|
||||||
context_object_name = 'company'
|
context_object_name = 'company'
|
||||||
|
permission_required = 'company.delete_company'
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
@ -243,6 +250,7 @@ class SupplierPartDetail(DetailView):
|
|||||||
template_name = 'company/supplier_part_detail.html'
|
template_name = 'company/supplier_part_detail.html'
|
||||||
context_object_name = 'part'
|
context_object_name = 'part'
|
||||||
queryset = SupplierPart.objects.all()
|
queryset = SupplierPart.objects.all()
|
||||||
|
permission_required = 'purchase_order.view'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
@ -258,6 +266,7 @@ class SupplierPartEdit(AjaxUpdateView):
|
|||||||
form_class = EditSupplierPartForm
|
form_class = EditSupplierPartForm
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
ajax_form_title = _('Edit Supplier Part')
|
ajax_form_title = _('Edit Supplier Part')
|
||||||
|
role_required = 'purchase_order.change'
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartCreate(AjaxCreateView):
|
class SupplierPartCreate(AjaxCreateView):
|
||||||
@ -268,6 +277,7 @@ class SupplierPartCreate(AjaxCreateView):
|
|||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
ajax_form_title = _('Create new Supplier Part')
|
ajax_form_title = _('Create new Supplier Part')
|
||||||
context_object_name = 'part'
|
context_object_name = 'part'
|
||||||
|
role_required = 'purchase_order.add'
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
""" Create Form instance to create a new SupplierPart object.
|
""" Create Form instance to create a new SupplierPart object.
|
||||||
@ -327,6 +337,7 @@ class SupplierPartDelete(AjaxDeleteView):
|
|||||||
success_url = '/supplier/'
|
success_url = '/supplier/'
|
||||||
ajax_template_name = 'company/partdelete.html'
|
ajax_template_name = 'company/partdelete.html'
|
||||||
ajax_form_title = _('Delete Supplier Part')
|
ajax_form_title = _('Delete Supplier Part')
|
||||||
|
role_required = 'purchase_order.delete'
|
||||||
|
|
||||||
parts = []
|
parts = []
|
||||||
|
|
||||||
@ -398,6 +409,7 @@ class PriceBreakCreate(AjaxCreateView):
|
|||||||
form_class = EditPriceBreakForm
|
form_class = EditPriceBreakForm
|
||||||
ajax_form_title = _('Add Price Break')
|
ajax_form_title = _('Add Price Break')
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
|
role_required = 'purchase_order.add'
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return {
|
return {
|
||||||
@ -440,6 +452,7 @@ class PriceBreakEdit(AjaxUpdateView):
|
|||||||
form_class = EditPriceBreakForm
|
form_class = EditPriceBreakForm
|
||||||
ajax_form_title = _('Edit Price Break')
|
ajax_form_title = _('Edit Price Break')
|
||||||
ajax_template_name = 'modal_form.html'
|
ajax_template_name = 'modal_form.html'
|
||||||
|
role_required = 'purchase_order.change'
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
|
|
||||||
@ -455,3 +468,4 @@ class PriceBreakDelete(AjaxDeleteView):
|
|||||||
model = SupplierPriceBreak
|
model = SupplierPriceBreak
|
||||||
ajax_form_title = _("Delete Price Break")
|
ajax_form_title = _("Delete Price Break")
|
||||||
ajax_template_name = 'modal_delete_form.html'
|
ajax_template_name = 'modal_delete_form.html'
|
||||||
|
role_required = 'purchase_order.delete'
|
||||||
|
@ -13,9 +13,11 @@
|
|||||||
|
|
||||||
<h4>{% trans "Sales Order Items" %}</h4>
|
<h4>{% trans "Sales Order Items" %}</h4>
|
||||||
|
|
||||||
|
{% if roles.sales_order.change %}
|
||||||
<div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
|
<div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
|
||||||
<button type='button' class='btn btn-default' id='new-so-line'>{% trans "Add Line Item" %}</button>
|
<button type='button' class='btn btn-default' id='new-so-line'>{% trans "Add Line Item" %}</button>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='so-lines-table' data-toolbar='#order-toolbar-buttons'>
|
<table class='table table-striped table-condensed' id='so-lines-table' data-toolbar='#order-toolbar-buttons'>
|
||||||
|
|
||||||
|
@ -37,7 +37,9 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<div class='panel panel-default'>
|
<div class='panel panel-default'>
|
||||||
<div class='panel-content'>
|
<div class='panel-content'>
|
||||||
|
{% if part.notes %}
|
||||||
{{ part.notes | markdownify }}
|
{{ part.notes | markdownify }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,7 +5,8 @@ import os
|
|||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from InvenTree import version, settings
|
from InvenTree import version, settings
|
||||||
from InvenTree.helpers import decimal2string
|
|
||||||
|
import InvenTree.helpers
|
||||||
|
|
||||||
from common.models import InvenTreeSetting, ColorTheme
|
from common.models import InvenTreeSetting, ColorTheme
|
||||||
|
|
||||||
@ -29,7 +30,14 @@ def define(value, *args, **kwargs):
|
|||||||
def decimal(x, *args, **kwargs):
|
def decimal(x, *args, **kwargs):
|
||||||
""" Simplified rendering of a decimal number """
|
""" Simplified rendering of a decimal number """
|
||||||
|
|
||||||
return decimal2string(x)
|
return InvenTree.helpers.decimal2string(x)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag()
|
||||||
|
def str2bool(x, *args, **kwargs):
|
||||||
|
""" Convert a string to a boolean value """
|
||||||
|
|
||||||
|
return InvenTree.helpers.str2bool(x)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@ -41,7 +49,7 @@ def inrange(n, *args, **kwargs):
|
|||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def multiply(x, y, *args, **kwargs):
|
def multiply(x, y, *args, **kwargs):
|
||||||
""" Multiply two numbers together """
|
""" Multiply two numbers together """
|
||||||
return decimal2string(x * y)
|
return InvenTree.helpers.decimal2string(x * y)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@ -54,7 +62,7 @@ def add(x, y, *args, **kwargs):
|
|||||||
def part_allocation_count(build, part, *args, **kwargs):
|
def part_allocation_count(build, part, *args, **kwargs):
|
||||||
""" Return the total number of <part> allocated to <build> """
|
""" Return the total number of <part> allocated to <build> """
|
||||||
|
|
||||||
return decimal2string(build.getAllocatedQuantity(part))
|
return InvenTree.helpers.decimal2string(build.getAllocatedQuantity(part))
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
@ -100,8 +108,15 @@ def inventree_docs_url(*args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def inventree_setting(key, *args, **kwargs):
|
def setting_object(key, *args, **kwargs):
|
||||||
return InvenTreeSetting.get_setting(key, backup_value=kwargs.get('backup', None))
|
"""
|
||||||
|
Return a setting object speciifed by the given key
|
||||||
|
(Or return None if the setting does not exist)
|
||||||
|
"""
|
||||||
|
|
||||||
|
setting = InvenTreeSetting.get_setting_object(key)
|
||||||
|
|
||||||
|
return setting
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
|
@ -15,17 +15,8 @@
|
|||||||
<table class='table table-striped table-condensed'>
|
<table class='table table-striped table-condensed'>
|
||||||
<thead></thead>
|
<thead></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PREFIX" %}
|
||||||
<th>{% trans "Reference Prefix" %}</th>
|
{% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_REGEX" %}
|
||||||
<th>{% inventree_setting 'BUILDORDER_REFERENCE_PREFIX' backup='BO' %}</th>
|
|
||||||
<td>{% trans "Prefix for Build Order reference" %}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Reference Regex" %}</th>
|
|
||||||
<th>{% inventree_setting 'BUILDORDER_REFERENCE_REGEX' %}</th>
|
|
||||||
<td>{% trans "Regex validator for Build Order reference" %}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
23
InvenTree/templates/InvenTree/settings/global.html
Normal file
23
InvenTree/templates/InvenTree/settings/global.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends "InvenTree/settings/settings.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load inventree_extras %}
|
||||||
|
|
||||||
|
{% block tabs %}
|
||||||
|
{% include "InvenTree/settings/tabs.html" with tab='global' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subtitle %}
|
||||||
|
{% trans "Global InvenTree Settings" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block settings %}
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<thead></thead>
|
||||||
|
<tbody>
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="INVENTREE_INSTANCE" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -11,6 +11,16 @@
|
|||||||
|
|
||||||
{% block settings %}
|
{% block settings %}
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<thead></thead>
|
||||||
|
<tbody>
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="PART_COPY_BOM" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="PART_COPY_PARAMETERS" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="PART_COPY_TESTS" %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<h4>{% trans "Part Parameter Templates" %}</h4>
|
<h4>{% trans "Part Parameter Templates" %}</h4>
|
||||||
|
|
||||||
<div id='param-buttons'>
|
<div id='param-buttons'>
|
||||||
@ -53,7 +63,7 @@
|
|||||||
var bEdit = "<button title='{% trans "Edit Template" %}' class='template-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-edit'></span></button>";
|
var bEdit = "<button title='{% trans "Edit Template" %}' class='template-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-edit'></span></button>";
|
||||||
var bDel = "<button title='{% trans "Delete Template" %}' class='template-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-trash-alt icon-red'></span></button>";
|
var bDel = "<button title='{% trans "Delete Template" %}' class='template-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-trash-alt icon-red'></span></button>";
|
||||||
|
|
||||||
var html = "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
|
var html = "<div class='btn-group float-right' role='group'>" + bEdit + bDel + "</div>";
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,10 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block settings %}
|
{% block settings %}
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<thead></thead>
|
||||||
|
<tbody>
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PREFIX" %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
29
InvenTree/templates/InvenTree/settings/setting.html
Normal file
29
InvenTree/templates/InvenTree/settings/setting.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% load inventree_extras %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% setting_object key as setting %}
|
||||||
|
<tr>
|
||||||
|
<td><b>{{ setting.name }}</b></td>
|
||||||
|
<td>
|
||||||
|
{% if setting.is_bool %}
|
||||||
|
<div>
|
||||||
|
<input fieldname='{{ setting.key }}' class='slidey' type='checkbox' data-offstyle='warning' data-onstyle='success' data-size='small' data-toggle='toggle' disabled autocomplete='off' {% if setting.as_bool %}checked=''{% endif %}>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% if setting.value %}
|
||||||
|
<b>{{ setting.value }}</b>{{ setting.units }}</td>
|
||||||
|
{% else %}
|
||||||
|
<i>{% trans "No value set" %}</i>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<td>
|
||||||
|
{{ setting.description }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class='btn-group float-right'>
|
||||||
|
<button class='btn btn-default btn-glyph btn-edit-setting' pk='{{ setting.pk }}' setting='{{ key }}' title='{% trans "Edit setting" %}'>
|
||||||
|
<span class='fas fa-edit icon-green'></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
@ -37,3 +37,20 @@ InvenTree | {% trans "Settings" %}
|
|||||||
{% block js_load %}
|
{% block js_load %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_ready %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
$('table').find('.btn-edit-setting').click(function() {
|
||||||
|
var setting = $(this).attr('setting');
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
launchModalForm(
|
||||||
|
`/settings/${pk}/edit/`,
|
||||||
|
{
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
@ -10,4 +10,12 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block settings %}
|
{% block settings %}
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<thead></thead>
|
||||||
|
<tbody>
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PREFIX" %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -9,8 +9,12 @@
|
|||||||
<a href="{% url 'settings-theme' %}"><span class='fas fa-fill'></span> {% trans "Theme" %}</a>
|
<a href="{% url 'settings-theme' %}"><span class='fas fa-fill'></span> {% trans "Theme" %}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% if user.is_staff %}
|
||||||
<h4><span class='fas fa-cogs'></span> {% trans "InvenTree Settings" %}</h4>
|
<h4><span class='fas fa-cogs'></span> {% trans "InvenTree Settings" %}</h4>
|
||||||
<ul class='nav nav-pills nav-stacked'>
|
<ul class='nav nav-pills nav-stacked'>
|
||||||
|
<li {% if tab == 'global' %} class='active' {% endif %}>
|
||||||
|
<a href='{% url "settings-global" %}'><span class='fas fa-globe'></span> {% trans "Global" %}</a>
|
||||||
|
</li>
|
||||||
<li{% ifequal tab 'currency' %} class='active'{% endifequal %}>
|
<li{% ifequal tab 'currency' %} class='active'{% endifequal %}>
|
||||||
<a href="{% url 'settings-currency' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currency" %}</a>
|
<a href="{% url 'settings-currency' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currency" %}</a>
|
||||||
</li>
|
</li>
|
||||||
@ -30,3 +34,4 @@
|
|||||||
<a href="{% url 'settings-so' %}"><span class='fas fa-truck'></span> {% trans "Sales Orders" %}</a>
|
<a href="{% url 'settings-so' %}"><span class='fas fa-truck'></span> {% trans "Sales Orders" %}</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
@ -139,7 +139,7 @@ function loadPurchaseOrderTable(table, options) {
|
|||||||
title: '{% trans "Purchase Order" %}',
|
title: '{% trans "Purchase Order" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
var prefix = "{% inventree_setting 'PURCHASEORDER_REFERENCE_PREFIX' %}";
|
var prefix = "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}";
|
||||||
|
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
value = `${prefix}${value}`;
|
value = `${prefix}${value}`;
|
||||||
@ -221,7 +221,7 @@ function loadSalesOrderTable(table, options) {
|
|||||||
title: '{% trans "Sales Order" %}',
|
title: '{% trans "Sales Order" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
var prefix = "{% inventree_setting 'SALESORDER_REFERENCE_PREFIX' %}";
|
var prefix = "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}";
|
||||||
|
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
value = `${prefix}${value}`;
|
value = `${prefix}${value}`;
|
||||||
|
Loading…
Reference in New Issue
Block a user