diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py
index 13b770539c..da96c7ee79 100644
--- a/InvenTree/InvenTree/helpers.py
+++ b/InvenTree/InvenTree/helpers.py
@@ -19,9 +19,18 @@ from django.contrib.auth.models import Permission
import InvenTree.version
+from common.models import InvenTreeSetting
from .settings import MEDIA_URL, STATIC_URL
+def getSetting(key, backup_value=None):
+ """
+ Shortcut for reading a setting value from the database
+ """
+
+ return InvenTreeSetting.get_setting(key, backup_value=backup_value)
+
+
def generateTestKey(test_name):
"""
Generate a test 'key' for a given test name.
diff --git a/InvenTree/InvenTree/static/script/inventree/modals.js b/InvenTree/InvenTree/static/script/inventree/modals.js
index 80ada1596f..49de96468f 100644
--- a/InvenTree/InvenTree/static/script/inventree/modals.js
+++ b/InvenTree/InvenTree/static/script/inventree/modals.js
@@ -354,7 +354,7 @@ function renderErrorMessage(xhr) {
var html = '' + xhr.statusText + '
';
- html += 'Status Code - ' + xhr.status + '
{{ build.title }}
- | {% trans "Build Title" %} | -{{ build.title }} | ++ | {% trans "Build Order Reference" %} | +{{ build }} | ||||||||||||||||||||||||||||
diff --git a/InvenTree/build/tests.py b/InvenTree/build/tests.py index 92ca034a4a..ded98a441c 100644 --- a/InvenTree/build/tests.py +++ b/InvenTree/build/tests.py @@ -54,7 +54,7 @@ class BuildTestSimple(TestCase): self.assertEqual(b.batch, 'B2') self.assertEqual(b.quantity, 21) - self.assertEqual(str(b), '21 x Orphan') + self.assertEqual(str(b), 'BO0002') def test_url(self): b1 = Build.objects.get(pk=1) diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index b2b8b24502..1296e42fae 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -400,7 +400,7 @@ class BuildCreate(AjaxCreateView): model = Build context_object_name = 'build' form_class = forms.EditBuildForm - ajax_form_title = _('Start new Build') + ajax_form_title = _('New Build Order') ajax_template_name = 'modal_form.html' role_required = 'build.add' @@ -415,6 +415,8 @@ class BuildCreate(AjaxCreateView): # User has provided a Part ID initials['part'] = self.request.GET.get('part', None) + initials['reference'] = Build.getNextBuildNumber() + initials['parent'] = self.request.GET.get('parent', None) # User has provided a SalesOrder ID diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index cb643deca4..da12852d83 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -14,7 +14,7 @@ class CurrencyAdmin(ImportExportModelAdmin): class SettingsAdmin(ImportExportModelAdmin): - list_display = ('key', 'value', 'description') + list_display = ('key', 'value') admin.site.register(Currency, CurrencyAdmin) diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py index 4661c69370..0535709686 100644 --- a/InvenTree/common/apps.py +++ b/InvenTree/common/apps.py @@ -1,9 +1,5 @@ from django.apps import AppConfig -from django.db.utils import OperationalError, ProgrammingError - -import os -import uuid -import yaml +from django.db.utils import OperationalError, ProgrammingError, IntegrityError class CommonConfig(AppConfig): @@ -12,45 +8,8 @@ class CommonConfig(AppConfig): def ready(self): """ Will be called when the Common app is first loaded """ - self.populate_default_settings() self.add_instance_name() - - def populate_default_settings(self): - """ Populate the default values for InvenTree key:value pairs. - If a setting does not exist, it will be created. - """ - - # Import this here, rather than at the global-level, - # otherwise it is called all the time, and we don't want that, - # as the InvenTreeSetting model may have not been instantiated yet. - from .models import InvenTreeSetting - - here = os.path.dirname(os.path.abspath(__file__)) - settings_file = os.path.join(here, 'kvp.yaml') - - with open(settings_file) as kvp: - values = yaml.safe_load(kvp) - - for value in values: - key = value['key'] - default = value['default'] - description = value['description'] - - try: - # If a particular setting does not exist in the database, create it now - if not InvenTreeSetting.objects.filter(key=key).exists(): - setting = InvenTreeSetting( - key=key, - value=default, - description=description - ) - - setting.save() - - print("Creating new key: '{k}' = '{v}'".format(k=key, v=default)) - except (OperationalError, ProgrammingError): - # Migrations have not yet been applied - table does not exist - break + self.add_default_settings() def add_instance_name(self): """ @@ -61,20 +20,73 @@ class CommonConfig(AppConfig): # See note above from .models import InvenTreeSetting + """ + Note: The "old" instance name was stored under the key 'InstanceName', + but has now been renamed to 'INVENTREE_INSTANCE'. + """ + try: - if not InvenTreeSetting.objects.filter(key='InstanceName').exists(): - val = uuid.uuid4().hex + # Quick exit if a value already exists for 'inventree_instance' + if InvenTreeSetting.objects.filter(key='INVENTREE_INSTANCE').exists(): + return - print("No 'InstanceName' found - generating random name '{n}'".format(n=val)) + # Default instance name + instance_name = 'InvenTree Server' - name = InvenTreeSetting( - key="InstanceName", - value=val, - description="Instance name for this InvenTree database installation." - ) + # Use the old name if it exists + if InvenTreeSetting.objects.filter(key='InstanceName').exists(): + instance = InvenTreeSetting.objects.get(key='InstanceName') + instance_name = instance.value - name.save() - except (OperationalError, ProgrammingError): + # Delete the legacy key + instance.delete() + + # Create new value + InvenTreeSetting.objects.create( + key='INVENTREE_INSTANCE', + value=instance_name + ) + + except (OperationalError, ProgrammingError, IntegrityError): # Migrations have not yet been applied - table does not exist pass + + def add_default_settings(self): + """ + Create all required settings, if they do not exist. + """ + + from .models import InvenTreeSetting + + for key in InvenTreeSetting.DEFAULT_VALUES.keys(): + try: + settings = InvenTreeSetting.objects.filter(key__iexact=key) + + if settings.count() == 0: + value = InvenTreeSetting.DEFAULT_VALUES[key] + + print(f"Creating default setting for {key} -> '{value}'") + + InvenTreeSetting.objects.create( + key=key, + value=value + ) + + return + + elif settings.count() > 1: + # Prevent multiple shadow copies of the same setting! + for setting in settings[1:]: + setting.delete() + + # Ensure that the key has the correct case + setting = settings[0] + + if not setting.key == key: + setting.key = key + setting.save() + + except (OperationalError, ProgrammingError, IntegrityError): + # Table might not yet exist + pass diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py index 00e6c15c7f..53493faff0 100644 --- a/InvenTree/common/forms.py +++ b/InvenTree/common/forms.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from InvenTree.forms import HelperForm -from .models import Currency +from .models import Currency, InvenTreeSetting class CurrencyEditForm(HelperForm): @@ -22,3 +22,17 @@ class CurrencyEditForm(HelperForm): 'value', 'base' ] + + +class SettingEditForm(HelperForm): + """ + Form for creating / editing a settings object + """ + + class Meta: + model = InvenTreeSetting + + fields = [ + 'key', + 'value' + ] diff --git a/InvenTree/common/kvp.yaml b/InvenTree/common/kvp.yaml deleted file mode 100644 index d0f7249dff..0000000000 --- a/InvenTree/common/kvp.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# This file contains the default values for the key:value settings available in InvenTree -# This file should not be edited locally. - -# Note: The description strings provided here will be translatable, -# so ensure that any translations are provided as appropriate. - -# TODO: Update the formatting here to include logical separators e.g. double-underscore -# TODO: This is so when there are enough options, we will be able to display them as a tree - -- key: 'part_ipn_regex' - default: '' - description: 'Format string for internal part number' - -- key: part_deep_copy - default: True - description: 'Parts are deep-copied by default' \ No newline at end of file diff --git a/InvenTree/common/migrations/0008_remove_inventreesetting_description.py b/InvenTree/common/migrations/0008_remove_inventreesetting_description.py new file mode 100644 index 0000000000..d7889ced17 --- /dev/null +++ b/InvenTree/common/migrations/0008_remove_inventreesetting_description.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-10-19 13:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0007_colortheme'), + ] + + operations = [ + migrations.RemoveField( + model_name='inventreesetting', + name='description', + ), + ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index b0a836118d..31f8e12260 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -27,6 +27,30 @@ class InvenTreeSetting(models.Model): even if that key does not exist. """ + # Dict of default values for various internal settings + DEFAULT_VALUES = { + # Global inventree settings + 'INVENTREE_INSTANCE': 'InvenTree Server', + + # Part settings + 'PART_IPN_REGEX': '', + 'PART_COPY_BOM': True, + 'PART_COPY_PARAMETERS': True, + 'PART_COPY_TESTS': True, + + # Stock settings + + # Build Order settings + 'BUILDORDER_REFERENCE_PREFIX': 'BO', + 'BUILDORDER_REFERENCE_REGEX': '', + + # Purchase Order Settings + 'PURCHASEORDER_REFERENCE_PREFIX': 'PO', + + # Sales Order Settings + 'SALESORDER_REFERENCE_PREFIX': 'SO', + } + class Meta: verbose_name = "InvenTree Setting" verbose_name_plural = "InvenTree Settings" @@ -38,9 +62,17 @@ class InvenTreeSetting(models.Model): If it does not exist, return the backup value (default = None) """ + # If no backup value is specified, atttempt to retrieve a "default" value + if backup_value is None: + backup_value = InvenTreeSetting.DEFAULT_VALUES.get(key, None) + try: - setting = InvenTreeSetting.objects.get(key__iexact=key) - return setting.value + settings = InvenTreeSetting.objects.filter(key__iexact=key) + + if len(settings) > 0: + return settings[0].value + else: + return backup_value except InvenTreeSetting.DoesNotExist: return backup_value @@ -69,15 +101,13 @@ class InvenTreeSetting(models.Model): else: return - setting.value = value + setting.value = str(value) setting.save() key = models.CharField(max_length=50, blank=False, unique=True, help_text=_('Settings key (must be unique - case insensitive')) value = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings value')) - description = models.CharField(max_length=200, blank=True, unique=False, help_text=_('Settings description')) - def validate_unique(self, exclude=None): """ Ensure that the key:value pair is unique. In addition to the base validators, this ensures that the 'key' diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 5c6171a65c..a5d32dc3d6 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -2,8 +2,9 @@ from __future__ import unicode_literals from django.test import TestCase +from django.contrib.auth import get_user_model -from .models import Currency +from .models import Currency, InvenTreeSetting class CurrencyTest(TestCase): @@ -17,3 +18,32 @@ class CurrencyTest(TestCase): # Simple test for now (improve this later!) self.assertEqual(Currency.objects.count(), 2) + + +class SettingsTest(TestCase): + """ + Tests for the 'settings' model + """ + + 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_defaults(self): + """ + Populate the settings with default values + """ + + for key in InvenTreeSetting.DEFAULT_VALUES.keys(): + + value = InvenTreeSetting.DEFAULT_VALUES[key] + + InvenTreeSetting.set_setting(key, value, self.user) + + self.assertEqual(str(value), InvenTreeSetting.get_setting(key)) diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index 5da634c6d3..a205b8b915 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -35,3 +35,14 @@ class CurrencyDelete(AjaxDeleteView): model = models.Currency ajax_form_title = _('Delete Currency') ajax_template_name = "common/delete_currency.html" + + +class SettingEdit(AjaxUpdateView): + """ + View for editing an InvenTree key:value settings object, + (or creating it if the key does not already exist) + """ + + model = models.InvenTreeSetting + ajax_form_title = _('Change Setting') + form_class = forms.SettingEditForm diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index dd3d170a89..829a6fa998 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -424,12 +424,16 @@ class SupplierPart(models.Model): return str(self) def __str__(self): - s = "{supplier} ({sku})".format( - sku=self.SKU, - supplier=self.supplier.name) + s = '' + + if self.part.IPN: + s += f'{self.part.IPN}' + s += ' | ' + + s += f'{self.supplier.name} | {self.SKU}' if self.manufacturer_string: - s = s + ' - ' + self.manufacturer_string + s = s + ' | ' + self.manufacturer_string return s diff --git a/InvenTree/company/templates/company/supplier_part_base.html b/InvenTree/company/templates/company/supplier_part_base.html index 383b942346..ca09caee93 100644 --- a/InvenTree/company/templates/company/supplier_part_base.html +++ b/InvenTree/company/templates/company/supplier_part_base.html @@ -94,7 +94,7 @@ src="{% static 'img/blank_image.png' %}" {% block js_ready %} {{ block.super }} -$('#order-part').click(function() { +$('#order-part, #order-part2').click(function() { launchModalForm( "{% url 'order-parts' %}", { diff --git a/InvenTree/company/templates/company/supplier_part_orders.html b/InvenTree/company/templates/company/supplier_part_orders.html index 381a2941e9..5c2ea6d1d4 100644 --- a/InvenTree/company/templates/company/supplier_part_orders.html +++ b/InvenTree/company/templates/company/supplier_part_orders.html @@ -6,18 +6,18 @@ {% include "company/supplier_part_tabs.html" with tab='orders' %} - |
{{ order.description }}
--
{{ order.description }}
{% trans "Reference Prefix" %} | +{% inventree_setting 'BUILDORDER_REFERENCE_PREFIX' backup='BO' %} | +{% trans "Prefix for Build Order reference" %} | +|
---|---|---|---|
{% trans "Reference Regex" %} | +{% inventree_setting 'BUILDORDER_REFERENCE_REGEX' %} | +{% trans "Regex validator for Build Order reference" %} | ++ |
Setting | -Value | -Description | -
---|---|---|
{{ setting.key }} | -{{ setting.value }} | -{{ setting.description }} | -
Username | -{{ user.username }} | -
First Name | -{{ user.first_name }} | -
Last Name | -{{ user.last_name }} | -
Email Address | -{{ user.email }} | -
{% trans "Username" %} | +{{ user.username }} | +
{% trans "First Name" %} | +{{ user.first_name }} | +
{% trans "Last Name" %} | +{{ user.last_name }} | +
{% trans "Email Address" %} | +{{ user.email }} | +