From ac82640c6c722a23620f9096dfc9cd6d235cf2c1 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 12 Oct 2020 17:51:48 -0500 Subject: [PATCH 01/22] Company: allowed duplicate names, made email field unique, custom migration --- .../migrations/0024_auto_20201012_2238.py | 30 +++++++++++++++++++ InvenTree/company/models.py | 5 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 InvenTree/company/migrations/0024_auto_20201012_2238.py diff --git a/InvenTree/company/migrations/0024_auto_20201012_2238.py b/InvenTree/company/migrations/0024_auto_20201012_2238.py new file mode 100644 index 0000000000..8066ea960d --- /dev/null +++ b/InvenTree/company/migrations/0024_auto_20201012_2238.py @@ -0,0 +1,30 @@ +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 = [ + migrations.RunPython(make_empty_email_field_null), + migrations.AlterField( + model_name='company', + name='email', + field=models.EmailField(blank=True, help_text='Contact email address', max_length=254, null=True, unique=True, verbose_name='Email'), + ), + migrations.AlterField( + model_name='company', + name='name', + field=models.CharField(help_text='Company name', max_length=100, verbose_name='Company name'), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 2a8d907b76..dd3d170a89 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -82,7 +82,7 @@ class Company(models.Model): class Meta: ordering = ['name', ] - name = models.CharField(max_length=100, blank=False, unique=True, + name = models.CharField(max_length=100, blank=False, help_text=_('Company name'), verbose_name=_('Company name')) @@ -98,7 +98,8 @@ class Company(models.Model): verbose_name=_('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, unique=True, + verbose_name=_('Email'), help_text=_('Contact email address')) contact = models.CharField(max_length=100, verbose_name=_('Contact'), From 70a3b7f8913cb35f81513ee778c92cf684af2c04 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 13 Oct 2020 07:43:57 -0500 Subject: [PATCH 02/22] Improved migration, still fails if email duplicates exist in current DB --- .../company/migrations/0024_auto_20201012_2238.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/InvenTree/company/migrations/0024_auto_20201012_2238.py b/InvenTree/company/migrations/0024_auto_20201012_2238.py index 8066ea960d..32ca981e3b 100644 --- a/InvenTree/company/migrations/0024_auto_20201012_2238.py +++ b/InvenTree/company/migrations/0024_auto_20201012_2238.py @@ -6,7 +6,7 @@ def make_empty_email_field_null(apps, schema_editor): for company in Company.objects.all(): if company.email == '': company.email = None - company.save() + company.save() class Migration(migrations.Migration): @@ -16,12 +16,21 @@ class Migration(migrations.Migration): ] 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), + # Make email field unique migrations.AlterField( model_name='company', name='email', field=models.EmailField(blank=True, help_text='Contact email address', max_length=254, null=True, unique=True, verbose_name='Email'), ), + # Remove unique constraint on name field migrations.AlterField( model_name='company', name='name', From 5793839a0137b344670241269f050fe41b09eff5 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 20 Oct 2020 07:37:07 -0500 Subject: [PATCH 03/22] Added UniqueConstraint on name/email pair, renamed migration file --- ...2_2238.py => 0024_unique_name_email_constraint.py} | 11 +++++------ InvenTree/company/models.py | 7 +++++-- 2 files changed, 10 insertions(+), 8 deletions(-) rename InvenTree/company/migrations/{0024_auto_20201012_2238.py => 0024_unique_name_email_constraint.py} (82%) diff --git a/InvenTree/company/migrations/0024_auto_20201012_2238.py b/InvenTree/company/migrations/0024_unique_name_email_constraint.py similarity index 82% rename from InvenTree/company/migrations/0024_auto_20201012_2238.py rename to InvenTree/company/migrations/0024_unique_name_email_constraint.py index 32ca981e3b..3a8781f98d 100644 --- a/InvenTree/company/migrations/0024_auto_20201012_2238.py +++ b/InvenTree/company/migrations/0024_unique_name_email_constraint.py @@ -24,16 +24,15 @@ class Migration(migrations.Migration): ), # Convert empty email string to NULL migrations.RunPython(make_empty_email_field_null), - # Make email field unique - migrations.AlterField( - model_name='company', - name='email', - field=models.EmailField(blank=True, help_text='Contact email address', max_length=254, null=True, unique=True, verbose_name='Email'), - ), # 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'), + ), ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 829a6fa998..b9fed2ee7b 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -12,7 +12,7 @@ import math from django.utils.translation import gettext_lazy as _ from django.core.validators import MinValueValidator 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.urls import reverse @@ -81,6 +81,9 @@ class Company(models.Model): class Meta: ordering = ['name', ] + constraints = [ + UniqueConstraint(fields=['name', 'email'], name='unique_name_email_pair') + ] name = models.CharField(max_length=100, blank=False, help_text=_('Company name'), @@ -98,7 +101,7 @@ class Company(models.Model): verbose_name=_('Phone number'), blank=True, help_text=_('Contact phone number')) - email = models.EmailField(blank=True, null=True, unique=True, + email = models.EmailField(blank=True, null=True, verbose_name=_('Email'), help_text=_('Contact email address')) contact = models.CharField(max_length=100, From 39eddc7203edc567e1727ec118d675f3eda6177f Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 20 Oct 2020 14:11:40 -0500 Subject: [PATCH 04/22] Added user permissions on company views --- InvenTree/InvenTree/views.py | 6 +++++- InvenTree/company/views.py | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index bb7c1e6f5d..b4bc6ebe06 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -128,9 +128,13 @@ class InvenTreeRoleMixin(PermissionRequiredMixin): 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 = [] if type(self.role_required) is str: diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index 9ef6adea0e..dce341d184 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -14,6 +14,7 @@ from django.forms import HiddenInput from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.helpers import str2bool +from InvenTree.views import InvenTreeRoleMixin from common.models import Currency @@ -29,7 +30,7 @@ from .forms import EditSupplierPartForm from .forms import EditPriceBreakForm -class CompanyIndex(ListView): +class CompanyIndex(InvenTreeRoleMixin, ListView): """ View for displaying list of companies """ @@ -37,6 +38,7 @@ class CompanyIndex(ListView): template_name = 'company/index.html' context_object_name = 'companies' paginate_by = 50 + permission_required = 'company.view_company' def get_context_data(self, **kwargs): @@ -116,8 +118,8 @@ class CompanyNotes(UpdateView): context_object_name = 'company' template_name = 'company/notes.html' model = Company - fields = ['notes'] + permission_required = 'company.view_company' def get_success_url(self): return reverse('company-notes', kwargs={'pk': self.get_object().id}) @@ -137,6 +139,7 @@ class CompanyDetail(DetailView): template_name = 'company/detail.html' queryset = Company.objects.all() model = Company + permission_required = 'company.view_company' def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) @@ -150,6 +153,7 @@ class CompanyImage(AjaxUpdateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Update Company Image') form_class = CompanyImageForm + permission_required = 'company.change_company' def get_data(self): return { @@ -164,6 +168,7 @@ class CompanyEdit(AjaxUpdateView): context_object_name = 'company' ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Company') + permission_required = 'company.change_company' def get_data(self): return { @@ -177,6 +182,7 @@ class CompanyCreate(AjaxCreateView): context_object_name = 'company' form_class = EditCompanyForm ajax_template_name = 'modal_form.html' + permission_required = 'company.add_company' def get_form_title(self): @@ -230,6 +236,7 @@ class CompanyDelete(AjaxDeleteView): ajax_template_name = 'company/delete.html' ajax_form_title = _('Delete Company') context_object_name = 'company' + permission_required = 'company.delete_company' def get_data(self): return { @@ -243,6 +250,7 @@ class SupplierPartDetail(DetailView): template_name = 'company/supplier_part_detail.html' context_object_name = 'part' queryset = SupplierPart.objects.all() + permission_required = 'purchase_order.view' def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) @@ -258,6 +266,7 @@ class SupplierPartEdit(AjaxUpdateView): form_class = EditSupplierPartForm ajax_template_name = 'modal_form.html' ajax_form_title = _('Edit Supplier Part') + role_required = 'purchase_order.change' class SupplierPartCreate(AjaxCreateView): @@ -268,6 +277,7 @@ class SupplierPartCreate(AjaxCreateView): ajax_template_name = 'modal_form.html' ajax_form_title = _('Create new Supplier Part') context_object_name = 'part' + role_required = 'purchase_order.add' def get_form(self): """ Create Form instance to create a new SupplierPart object. @@ -327,6 +337,7 @@ class SupplierPartDelete(AjaxDeleteView): success_url = '/supplier/' ajax_template_name = 'company/partdelete.html' ajax_form_title = _('Delete Supplier Part') + role_required = 'purchase_order.delete' parts = [] @@ -398,6 +409,7 @@ class PriceBreakCreate(AjaxCreateView): form_class = EditPriceBreakForm ajax_form_title = _('Add Price Break') ajax_template_name = 'modal_form.html' + role_required = 'purchase_order.add' def get_data(self): return { @@ -440,6 +452,7 @@ class PriceBreakEdit(AjaxUpdateView): form_class = EditPriceBreakForm ajax_form_title = _('Edit Price Break') ajax_template_name = 'modal_form.html' + role_required = 'purchase_order.change' def get_form(self): @@ -455,3 +468,4 @@ class PriceBreakDelete(AjaxDeleteView): model = SupplierPriceBreak ajax_form_title = _("Delete Price Break") ajax_template_name = 'modal_delete_form.html' + role_required = 'purchase_order.delete' From de65e1631dd0fc4087be2457a6bd7112786e1657 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 20 Oct 2020 14:46:10 -0500 Subject: [PATCH 05/22] Updated company templates permissions --- InvenTree/company/templates/company/company_base.html | 8 ++++++-- InvenTree/company/templates/company/detail_part.html | 8 ++++++++ InvenTree/company/templates/company/index.html | 3 ++- InvenTree/company/templates/company/purchase_orders.html | 2 ++ InvenTree/company/templates/company/sales_orders.html | 2 ++ .../company/templates/company/supplier_part_base.html | 8 ++++++++ .../company/templates/company/supplier_part_orders.html | 2 ++ .../company/templates/company/supplier_part_pricing.html | 2 ++ InvenTree/order/templates/order/sales_order_detail.html | 2 ++ 9 files changed, 34 insertions(+), 3 deletions(-) diff --git a/InvenTree/company/templates/company/company_base.html b/InvenTree/company/templates/company/company_base.html index f20107277d..cdca22fa36 100644 --- a/InvenTree/company/templates/company/company_base.html +++ b/InvenTree/company/templates/company/company_base.html @@ -23,23 +23,27 @@ InvenTree | {% trans "Company" %} - {{ company.name }}

{{ company.name }} - {% if user.is_staff and roles.company.change %} + {% if user.is_staff and perms.company.change_company %} {% endif %}

{{ company.description }}

- {% if company.is_supplier %} + {% if company.is_supplier and roles.purchase_order.add %} {% endif %} + {% if perms.company.change_company %} + {% endif %} + {% if perms.company.delete_company %} + {% endif %}
{% endblock %} diff --git a/InvenTree/company/templates/company/detail_part.html b/InvenTree/company/templates/company/detail_part.html index e07304dd13..463bf5814d 100644 --- a/InvenTree/company/templates/company/detail_part.html +++ b/InvenTree/company/templates/company/detail_part.html @@ -9,17 +9,25 @@
+{% if roles.purchase_order.change %}
+ {% if roles.purchase_order.add %} + {% endif %}
+{% endif %}
diff --git a/InvenTree/company/templates/company/index.html b/InvenTree/company/templates/company/index.html index 253085568e..c8efbe804b 100644 --- a/InvenTree/company/templates/company/index.html +++ b/InvenTree/company/templates/company/index.html @@ -12,12 +12,13 @@ InvenTree | {% trans "Supplier List" %}

{{ title }}


+{% if title == 'Manufacturers' and roles.purchase_order.add or title == 'Suppliers' and roles.purchase_order.add or title == 'Customers' and roles.sales_order.add %}
- +{% endif %}
diff --git a/InvenTree/company/templates/company/purchase_orders.html b/InvenTree/company/templates/company/purchase_orders.html index bab5cd4bce..a0ef1612fa 100644 --- a/InvenTree/company/templates/company/purchase_orders.html +++ b/InvenTree/company/templates/company/purchase_orders.html @@ -9,6 +9,7 @@

{% trans "Purchase Orders" %}


+{% if roles.purchase_order.add %}
@@ -17,6 +18,7 @@
+{% endif %}
diff --git a/InvenTree/company/templates/company/sales_orders.html b/InvenTree/company/templates/company/sales_orders.html index 0b64bed2f5..03c64d5b88 100644 --- a/InvenTree/company/templates/company/sales_orders.html +++ b/InvenTree/company/templates/company/sales_orders.html @@ -9,6 +9,7 @@

{% trans "Sales Orders" %}


+{% if roles.sales_order.add %}
@@ -17,6 +18,7 @@
+{% endif %}
diff --git a/InvenTree/company/templates/company/supplier_part_base.html b/InvenTree/company/templates/company/supplier_part_base.html index ca09caee93..7476a7c606 100644 --- a/InvenTree/company/templates/company/supplier_part_base.html +++ b/InvenTree/company/templates/company/supplier_part_base.html @@ -18,19 +18,27 @@ src="{% static 'img/blank_image.png' %}" {% block page_data %}

{% trans "Supplier Part" %}

{{ part.supplier.name }} - {{ part.SKU }}

+ +{% if roles.purchase_order.change %}
+ {% if roles.purchase_order.add %} + {% endif %} + {% if roles.purchase_order.delete %} + {% endif %}
+{% endif %} + {% endblock %} {% block page_details %} diff --git a/InvenTree/company/templates/company/supplier_part_orders.html b/InvenTree/company/templates/company/supplier_part_orders.html index 5c2ea6d1d4..29eb8ee874 100644 --- a/InvenTree/company/templates/company/supplier_part_orders.html +++ b/InvenTree/company/templates/company/supplier_part_orders.html @@ -10,11 +10,13 @@
+{% if roles.purchase_order.add %}
+{% endif %}
diff --git a/InvenTree/company/templates/company/supplier_part_pricing.html b/InvenTree/company/templates/company/supplier_part_pricing.html index f9f5063190..97022024f5 100644 --- a/InvenTree/company/templates/company/supplier_part_pricing.html +++ b/InvenTree/company/templates/company/supplier_part_pricing.html @@ -11,9 +11,11 @@
+{% if roles.purchase_order.add %}
+{% endif %}
diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index b6cc761cc7..d21a4e950e 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -13,9 +13,11 @@

{% trans "Sales Order Items" %}

+{% if roles.sales_order.change %}
+{% endif %} From 46b889c57299137609ff0ac6b9ea6354e7de8e49 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 20 Oct 2020 14:52:34 -0500 Subject: [PATCH 06/22] Assigned all user permissions for company tests --- InvenTree/company/test_views.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/InvenTree/company/test_views.py b/InvenTree/company/test_views.py index b9bb69e503..d895c18957 100644 --- a/InvenTree/company/test_views.py +++ b/InvenTree/company/test_views.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.urls import reverse from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group from .models import SupplierPart @@ -25,7 +26,24 @@ class CompanyViewTest(TestCase): # Create a user 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') From 1ef21700c0ce663c221a752e3211ee2cffd3a894 Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 21 Oct 2020 09:26:07 -0500 Subject: [PATCH 07/22] Fixed Part notes template (check for NULL value before mardown render) --- InvenTree/part/templates/part/notes.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/part/templates/part/notes.html b/InvenTree/part/templates/part/notes.html index 3f833325cd..1c46f53bcd 100644 --- a/InvenTree/part/templates/part/notes.html +++ b/InvenTree/part/templates/part/notes.html @@ -37,7 +37,9 @@
+ {% if part.notes %} {{ part.notes | markdownify }} + {% endif %}
From 9131edc43d3a0e8970e65f449c08d28fe6587aca Mon Sep 17 00:00:00 2001 From: eeintech Date: Wed, 21 Oct 2020 09:34:49 -0500 Subject: [PATCH 08/22] Company index: swapped 'title' for 'pagetype' for adding company button --- InvenTree/company/templates/company/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/company/templates/company/index.html b/InvenTree/company/templates/company/index.html index c8efbe804b..f110950c0b 100644 --- a/InvenTree/company/templates/company/index.html +++ b/InvenTree/company/templates/company/index.html @@ -12,7 +12,7 @@ InvenTree | {% trans "Supplier List" %}

{{ title }}


-{% if title == 'Manufacturers' and roles.purchase_order.add or title == 'Suppliers' and roles.purchase_order.add or title == 'Customers' and roles.sales_order.add %} +{% if pagetype == 'manufacturers' and roles.purchase_order.add or pagetype == 'suppliers' and roles.purchase_order.add or pagetype == 'customers' and roles.sales_order.add %}
From 10758a9626aa4874a48c47b48973348c8b26fc32 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 25 Oct 2020 07:49:38 +1100 Subject: [PATCH 09/22] Improvements for global settings - Name and description are defined in models.py - Lookup functions for name / description / units / default - Shortcut template for rending settings - More unit testing --- InvenTree/common/apps.py | 6 +- InvenTree/common/models.py | 146 +++++++++++++++--- InvenTree/common/tests.py | 27 +++- .../part/templatetags/inventree_extras.py | 29 +++- .../templates/InvenTree/settings/build.html | 13 +- .../templates/InvenTree/settings/part.html | 12 +- .../templates/InvenTree/settings/po.html | 6 + .../templates/InvenTree/settings/setting.html | 15 ++ .../templates/InvenTree/settings/so.html | 8 + InvenTree/templates/js/build.html | 2 +- InvenTree/templates/js/order.html | 4 +- 11 files changed, 229 insertions(+), 39 deletions(-) create mode 100644 InvenTree/templates/InvenTree/settings/setting.html diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py index 0535709686..06b825c574 100644 --- a/InvenTree/common/apps.py +++ b/InvenTree/common/apps.py @@ -32,7 +32,7 @@ class CommonConfig(AppConfig): return # Default instance name - instance_name = 'InvenTree Server' + instance_name = InvenTreeSetting.get_default_value('INVENTREE_INSTANCE') # Use the old name if it exists if InvenTreeSetting.objects.filter(key='InstanceName').exists(): @@ -59,12 +59,12 @@ class CommonConfig(AppConfig): from .models import InvenTreeSetting - for key in InvenTreeSetting.DEFAULT_VALUES.keys(): + for key in InvenTreeSetting.GLOBAL_SETTINGS.keys(): try: settings = InvenTreeSetting.objects.filter(key__iexact=key) if settings.count() == 0: - value = InvenTreeSetting.DEFAULT_VALUES[key] + value = InvenTreeSetting.get_default_value(key) print(f"Creating default setting for {key} -> '{value}'") diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 31f8e12260..1c460225c8 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -27,34 +27,144 @@ 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', + """ + Dict of all global settings values: - # Part settings - 'PART_IPN_REGEX': '', - 'PART_COPY_BOM': True, - 'PART_COPY_PARAMETERS': True, - 'PART_COPY_TESTS': True, + The key of each item is the name of the value as it appears in the database. - # Stock settings + Each global setting has the following parameters: + + - name: Translatable string name of the setting (required) + - description: Translatable string description of the setting (required) + - default: Default value (optional) + - units: Units of the particular setting (optional) + - validator: Validation function for the setting (optional) - # Build Order settings - 'BUILDORDER_REFERENCE_PREFIX': 'BO', - 'BUILDORDER_REFERENCE_REGEX': '', + The keys must be upper-case + """ - # Purchase Order Settings - 'PURCHASEORDER_REFERENCE_PREFIX': 'PO', + GLOBAL_SETTINGS = { - # Sales Order Settings - 'SALESORDER_REFERENCE_PREFIX': 'SO', + 'INVENTREE_INSTANCE': { + 'name': _('InvenTree Instance Name'), + 'default': 'InvenTree server', + 'description': _('String descriptor for the server instance'), + }, + + '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, + }, + + 'PART_COPY_PARAMETERS': { + 'name': _('Copy Part Parameter Data'), + 'description': _('Copy parameter data by default when duplicating a part'), + 'default': True, + }, + + 'PART_COPY_TESTS': { + 'name': _('Copy Part Test Data'), + 'description': _('Copy test data by default when duplicating a part'), + 'default': True, + }, + + '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: verbose_name = "InvenTree Setting" 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_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(cls, key, backup_value=None): """ @@ -64,7 +174,7 @@ class InvenTreeSetting(models.Model): # 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) + backup_value = cls.get_default_value(key) try: settings = InvenTreeSetting.objects.filter(key__iexact=key) diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index a5d32dc3d6..e0b6812f40 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -35,14 +35,37 @@ class SettingsTest(TestCase): 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): """ 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) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 2c175003d0..228d530934 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -87,7 +87,34 @@ def inventree_docs_url(*args, **kwargs): @register.simple_tag() -def inventree_setting(key, *args, **kwargs): +def settings_name(key, *args, **kwargs): + """ + Returns the name of a GLOBAL_SETTINGS object + """ + + return InvenTreeSetting.get_setting_name(key) + +@register.simple_tag() +def settings_description(key, *args, **kwargs): + """ + Returns the description of a GLOBAL_SETTINGS object + """ + + return InvenTreeSetting.get_setting_description(key) + +@register.simple_tag() +def settings_units(key, *args, **kwargs): + """ + Return the units of a GLOBAL_SETTINGS object + """ + + return InvenTreeSetting.get_setting_units(key) + +@register.simple_tag() +def settings_value(key, *args, **kwargs): + """ + Returns the value of a GLOBAL_SETTINGS object + """ return InvenTreeSetting.get_setting(key, backup_value=kwargs.get('backup', None)) diff --git a/InvenTree/templates/InvenTree/settings/build.html b/InvenTree/templates/InvenTree/settings/build.html index 6d19e21f1a..781402795b 100644 --- a/InvenTree/templates/InvenTree/settings/build.html +++ b/InvenTree/templates/InvenTree/settings/build.html @@ -15,17 +15,8 @@
- - - - - - - - - - - + {% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_PREFIX" %} + {% include "InvenTree/settings/setting.html" with key="BUILDORDER_REFERENCE_REGEX" %}
{% 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" %}
diff --git a/InvenTree/templates/InvenTree/settings/part.html b/InvenTree/templates/InvenTree/settings/part.html index 7c028ca6d6..cac04a60ff 100644 --- a/InvenTree/templates/InvenTree/settings/part.html +++ b/InvenTree/templates/InvenTree/settings/part.html @@ -11,6 +11,16 @@ {% block settings %} + + + + {% 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" %} + +
+

{% trans "Part Parameter Templates" %}

@@ -53,7 +63,7 @@ var bEdit = ""; var bDel = ""; - var html = "
" + bEdit + bDel + "
"; + var html = "
" + bEdit + bDel + "
"; return html; } diff --git a/InvenTree/templates/InvenTree/settings/po.html b/InvenTree/templates/InvenTree/settings/po.html index 7d32611404..a709d40dd3 100644 --- a/InvenTree/templates/InvenTree/settings/po.html +++ b/InvenTree/templates/InvenTree/settings/po.html @@ -10,4 +10,10 @@ {% endblock %} {% block settings %} + + + + {% include "InvenTree/settings/setting.html" with key="PURCHASEORDER_REFERENCE_PREFIX" %} + +
{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html new file mode 100644 index 0000000000..347b93553d --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -0,0 +1,15 @@ +{% load inventree_extras %} +{% load i18n %} + + + {% settings_name key %} + {% settings_value key %}{% settings_units key %} + {% settings_description key %} + +
+ +
+ + \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/so.html b/InvenTree/templates/InvenTree/settings/so.html index e66fd85148..368374532f 100644 --- a/InvenTree/templates/InvenTree/settings/so.html +++ b/InvenTree/templates/InvenTree/settings/so.html @@ -10,4 +10,12 @@ {% endblock %} {% block settings %} + + + + + {% include "InvenTree/settings/setting.html" with key="SALESORDER_REFERENCE_PREFIX" %} + +
+ {% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/js/build.html b/InvenTree/templates/js/build.html index 6a12b97bfd..1f577802b2 100644 --- a/InvenTree/templates/js/build.html +++ b/InvenTree/templates/js/build.html @@ -42,7 +42,7 @@ function loadBuildTable(table, options) { switchable: false, formatter: function(value, row, index, field) { - var prefix = "{% inventree_setting 'BUILDORDER_REFERENCE_PREFIX' %}"; + var prefix = "{% settings_value 'BUILDORDER_REFERENCE_PREFIX' %}"; if (prefix) { value = `${prefix}${value}`; diff --git a/InvenTree/templates/js/order.html b/InvenTree/templates/js/order.html index 4dbfbefa13..0c958c65a2 100644 --- a/InvenTree/templates/js/order.html +++ b/InvenTree/templates/js/order.html @@ -139,7 +139,7 @@ function loadPurchaseOrderTable(table, options) { title: '{% trans "Purchase Order" %}', formatter: function(value, row, index, field) { - var prefix = "{% inventree_setting 'PURCHASEORDER_REFERENCE_PREFIX' %}"; + var prefix = "{% settings_value 'PURCHASEORDER_REFERENCE_PREFIX' %}"; if (prefix) { value = `${prefix}${value}`; @@ -221,7 +221,7 @@ function loadSalesOrderTable(table, options) { title: '{% trans "Sales Order" %}', formatter: function(value, row, index, field) { - var prefix = "{% inventree_setting 'SALESORDER_REFERENCE_PREFIX' %}"; + var prefix = "{% settings_value 'SALESORDER_REFERENCE_PREFIX' %}"; if (prefix) { value = `${prefix}${value}`; From 3e17bf33167bba7b84a98943d44978a81075d8df Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 25 Oct 2020 08:02:46 +1100 Subject: [PATCH 10/22] Edit setting directly --- InvenTree/InvenTree/urls.py | 4 ++++ InvenTree/common/forms.py | 1 - InvenTree/common/models.py | 16 ++++++++++++++++ InvenTree/part/templatetags/inventree_extras.py | 9 +++++++++ .../templates/InvenTree/settings/setting.html | 2 +- .../templates/InvenTree/settings/settings.html | 17 +++++++++++++++++ 6 files changed, 47 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 0b4292cbd9..0c5c988ca0 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -39,6 +39,8 @@ from .views import IndexView, SearchView, DatabaseStatsView from .views import SettingsView, EditUserView, SetPasswordView, ColorThemeSelectView from .views import DynamicJsView +from common.views import SettingEdit + from .api import InfoView from .api import ActionPluginView @@ -78,6 +80,8 @@ settings_urls = [ 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'^(?P\d+)/edit/?', SettingEdit.as_view(), name='setting-edit'), + # Catch any other urls url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'), ] diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py index 53493faff0..ba6289221e 100644 --- a/InvenTree/common/forms.py +++ b/InvenTree/common/forms.py @@ -33,6 +33,5 @@ class SettingEditForm(HelperForm): model = InvenTreeSetting fields = [ - 'key', 'value' ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 1c460225c8..434f0571e5 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -165,6 +165,22 @@ class InvenTreeSetting(models.Model): else: return '' + @classmethod + def get_setting_pk(cls, key): + """ + Return the primary-key value for a given setting. + + If the setting does not exist, return None + """ + + key = str(key).strip().upper() + + try: + setting = InvenTreeSetting.objects.filter(key__iexact=key).first() + return setting.pk + except InvenTreeSettingSetting.DoesNotExist: + return None + @classmethod def get_setting(cls, key, backup_value=None): """ diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 228d530934..54e9a0ba24 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -115,8 +115,17 @@ def settings_value(key, *args, **kwargs): """ Returns the value of a GLOBAL_SETTINGS object """ + return InvenTreeSetting.get_setting(key, backup_value=kwargs.get('backup', None)) +@register.simple_tag() +def settings_pk(key, *args, **kwargs): + """ + Return the ID (pk) of a GLOBAL_SETTINGS Object + """ + + return InvenTreeSetting.get_setting_pk(key) + @register.simple_tag() def get_color_theme_css(username): diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html index 347b93553d..6f6a516b84 100644 --- a/InvenTree/templates/InvenTree/settings/setting.html +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -7,7 +7,7 @@ {% settings_description key %}
-
diff --git a/InvenTree/templates/InvenTree/settings/settings.html b/InvenTree/templates/InvenTree/settings/settings.html index 90adfd5fed..fe9fd00e53 100644 --- a/InvenTree/templates/InvenTree/settings/settings.html +++ b/InvenTree/templates/InvenTree/settings/settings.html @@ -37,3 +37,20 @@ InvenTree | {% trans "Settings" %} {% block js_load %} {{ block.super }} {% 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 %} From e3f5e8fbb16e2a48fe4662d46680212d0898b155 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 25 Oct 2020 08:04:04 +1100 Subject: [PATCH 11/22] PEP fixes --- InvenTree/InvenTree/urls.py | 2 +- InvenTree/common/models.py | 5 ++--- InvenTree/part/templatetags/inventree_extras.py | 4 ++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 0c5c988ca0..3e8e400729 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -80,7 +80,7 @@ settings_urls = [ 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'^(?P\d+)/edit/?', SettingEdit.as_view(), name='setting-edit'), + url(r'^(?P\d+)/edit/?', SettingEdit.as_view(), name='setting-edit'), # Catch any other urls url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'), diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 434f0571e5..ef6ebce8bf 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -34,7 +34,7 @@ class InvenTreeSetting(models.Model): Each global setting has the following parameters: - - name: Translatable string name of the setting (required) + - name: Translatable string name of the setting (required) - description: Translatable string description of the setting (required) - default: Default value (optional) - units: Units of the particular setting (optional) @@ -148,7 +148,6 @@ class InvenTreeSetting(models.Model): else: return '' - @classmethod def get_default_value(cls, key): """ @@ -178,7 +177,7 @@ class InvenTreeSetting(models.Model): try: setting = InvenTreeSetting.objects.filter(key__iexact=key).first() return setting.pk - except InvenTreeSettingSetting.DoesNotExist: + except InvenTreeSetting.DoesNotExist: return None @classmethod diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 54e9a0ba24..c5a45bc836 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -94,6 +94,7 @@ def settings_name(key, *args, **kwargs): return InvenTreeSetting.get_setting_name(key) + @register.simple_tag() def settings_description(key, *args, **kwargs): """ @@ -102,6 +103,7 @@ def settings_description(key, *args, **kwargs): return InvenTreeSetting.get_setting_description(key) + @register.simple_tag() def settings_units(key, *args, **kwargs): """ @@ -110,6 +112,7 @@ def settings_units(key, *args, **kwargs): return InvenTreeSetting.get_setting_units(key) + @register.simple_tag() def settings_value(key, *args, **kwargs): """ @@ -118,6 +121,7 @@ def settings_value(key, *args, **kwargs): return InvenTreeSetting.get_setting(key, backup_value=kwargs.get('backup', None)) + @register.simple_tag() def settings_pk(key, *args, **kwargs): """ From b57a78dea40049cefe19420e2c412a9f02fca9fc Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 25 Oct 2020 08:10:52 +1100 Subject: [PATCH 12/22] Add some context data to the view for editing a setting --- .../common/templates/common/edit_setting.html | 14 ++++++++++++++ InvenTree/common/views.py | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 InvenTree/common/templates/common/edit_setting.html diff --git a/InvenTree/common/templates/common/edit_setting.html b/InvenTree/common/templates/common/edit_setting.html new file mode 100644 index 0000000000..815ac76702 --- /dev/null +++ b/InvenTree/common/templates/common/edit_setting.html @@ -0,0 +1,14 @@ +{% extends "modal_form.html" %} +{% load i18n %} + +{% block pre_form_content %} + +{{ block.super }} + +

+ {{ name }}
+ {{ description }}
+ {% trans "Current value" %}: {{ value }} +

+ +{% endblock %} \ No newline at end of file diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index a205b8b915..8a91248063 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -46,3 +46,20 @@ class SettingEdit(AjaxUpdateView): model = models.InvenTreeSetting ajax_form_title = _('Change Setting') 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 From 4d96b385b1e36dc2e4ffed19491a6fc74cfff6ba Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 25 Oct 2020 08:17:41 +1100 Subject: [PATCH 13/22] Add page for global settings --- InvenTree/InvenTree/urls.py | 1 + InvenTree/common/models.py | 6 +++++ .../templates/InvenTree/settings/global.html | 23 +++++++++++++++++++ .../templates/InvenTree/settings/tabs.html | 3 +++ 4 files changed, 33 insertions(+) create mode 100644 InvenTree/templates/InvenTree/settings/global.html diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 3e8e400729..334f285e2c 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -73,6 +73,7 @@ settings_urls = [ 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'^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'^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'), diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index ef6ebce8bf..76320edd98 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -51,6 +51,12 @@ class InvenTreeSetting(models.Model): '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') diff --git a/InvenTree/templates/InvenTree/settings/global.html b/InvenTree/templates/InvenTree/settings/global.html new file mode 100644 index 0000000000..7dcdd54bea --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/global.html @@ -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 %} + + + + + {% include "InvenTree/settings/setting.html" with key="INVENTREE_INSTANCE" %} + {% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" %} + +
+ +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/tabs.html b/InvenTree/templates/InvenTree/settings/tabs.html index ee402feb4f..30dcec4ddc 100644 --- a/InvenTree/templates/InvenTree/settings/tabs.html +++ b/InvenTree/templates/InvenTree/settings/tabs.html @@ -11,6 +11,9 @@

{% trans "InvenTree Settings" %}

+{% if user.is_staff %}

{% trans "InvenTree Settings" %}

\ No newline at end of file + +{% endif %} \ No newline at end of file From 590889072607974581ee403ecfb70286f039763a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 25 Oct 2020 22:07:11 +1100 Subject: [PATCH 21/22] Create setting if it does not exist --- InvenTree/common/models.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 3150dad2a4..0d3afbe226 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -204,9 +204,14 @@ class InvenTreeSetting(models.Model): try: setting = InvenTreeSetting.objects.filter(key__iexact=key).first() - return setting except (InvenTreeSetting.DoesNotExist): - return None + # 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): From e978e1df52f877dd71e881cac635500a79fc1753 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sun, 25 Oct 2020 22:11:24 +1100 Subject: [PATCH 22/22] Style fixes --- InvenTree/part/templatetags/inventree_extras.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 37a5c3055d..3af495cc1c 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -6,7 +6,7 @@ import os from django import template from InvenTree import version, settings -from InvenTree.helpers import decimal2string +import InvenTree.helpers from common.models import InvenTreeSetting, ColorTheme @@ -17,7 +17,7 @@ register = template.Library() def decimal(x, *args, **kwargs): """ Simplified rendering of a decimal number """ - return decimal2string(x) + return InvenTree.helpers.decimal2string(x) @register.simple_tag() @@ -26,6 +26,7 @@ def str2bool(x, *args, **kwargs): return InvenTree.helpers.str2bool(x) + @register.simple_tag() def inrange(n, *args, **kwargs): """ Return range(n) for iterating through a numeric quantity """ @@ -35,7 +36,7 @@ def inrange(n, *args, **kwargs): @register.simple_tag() def multiply(x, y, *args, **kwargs): """ Multiply two numbers together """ - return decimal2string(x * y) + return InvenTree.helpers.decimal2string(x * y) @register.simple_tag() @@ -48,7 +49,7 @@ def add(x, y, *args, **kwargs): def part_allocation_count(build, part, *args, **kwargs): """ Return the total number of allocated to """ - return decimal2string(build.getAllocatedQuantity(part)) + return InvenTree.helpers.decimal2string(build.getAllocatedQuantity(part)) @register.simple_tag()