From bfc421c50b67400ae99de770678bbf5f4f33be11 Mon Sep 17 00:00:00 2001 From: eeintech Date: Thu, 15 Jul 2021 14:52:33 -0400 Subject: [PATCH 01/18] Cleaner part details --- InvenTree/part/templates/part/part_base.html | 121 +++++++++++-------- 1 file changed, 72 insertions(+), 49 deletions(-) diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 9fe6c7b486..073f3eeff9 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -18,19 +18,14 @@

{{ part.full_name }} +

+

+

+ {% if user.is_staff and roles.part.change %} {% endif %} - {% if not part.active %} -
- {% trans 'Inactive' %} -
- {% endif %} -

- {% if part.description %} -

{{ part.description }}

- {% endif %} -

+

{% if part.virtual %} @@ -54,6 +49,13 @@ {% endif %}
+ + {% if not part.active %} +
+ {% trans 'Inactive' %} +
+ {% endif %} +

@@ -122,40 +124,6 @@
{% endif %}
- - - {% if part.keywords %} - - - - - - {% endif %} - {% if part.link %} - - - - - - {% endif %} - - - - - - {% if part.trackable and part.getLatestSerialNumber %} - - - - - - {% endif %} -
{% trans "Keywords" %}{{ part.keywords }}
{% trans "External Link" %}{{ part.link }}
{% trans "Creation Date" %} - {{ part.creation_date }} - {% if part.creation_user %} - {{ part.creation_user }} - {% endif %} -
{% trans "Latest Serial Number" %}{{ part.getLatestSerialNumber }}{% include "clip.html"%}
@@ -174,14 +142,69 @@
- +
+ {% if part.description %} + + + + {% endif %} + {% if part.revision %} + + + + + + {% endif %} + {% if part.keywords %} + + + + + + {% endif %} + {% if part.link %} + + + + + + {% endif %} + + + + + + {% if part.trackable and part.getLatestSerialNumber %} + + + + + + {% endif %} + {% if part.default_location %} + + + + + + {% endif %} + {% if part.default_supplier %} + + + + + + {% endif %} - - + + From f938e722b97fb9cb58293c979c2872e493d3e386 Mon Sep 17 00:00:00 2001 From: eeintech Date: Fri, 16 Jul 2021 12:08:26 -0400 Subject: [PATCH 02/18] Another shot at it! --- InvenTree/part/templates/part/part_base.html | 501 ++++++++++--------- 1 file changed, 253 insertions(+), 248 deletions(-) diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 073f3eeff9..3c518df780 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -10,262 +10,267 @@ {% block content %} -
+
+ +

{{ part.full_name }}

+
+
+
+ {% include "part/part_thumb.html" %} +
+

+

+ + {% if user.is_staff and roles.part.change %} +   + {% endif %} + +
+ {% if part.is_template %} + + {% endif %} + {% if part.assembly %} + + {% endif %} + {% if part.component %} + + {% endif %} + {% if part.trackable %} + + {% endif %} + {% if part.purchaseable %} + + {% endif %} + {% if part.salable %} + + {% endif %} +
+ + {% if not part.active %} +   +
+ + {% trans 'Inactive' %} +
+ {% endif %} + + {% if part.virtual and part.active %} +   +
+ + {% trans 'Virtual' %} +
+ {% endif %} +

+

-
-
- {% include "part/part_thumb.html" %} -
-

- {{ part.full_name }} -

-

-

- - {% if user.is_staff and roles.part.change %} - - {% endif %} - -
- {% if part.virtual %} - +
+ + + {% if barcodes %} + + {% endif %} - {% if part.is_template %} - - {% endif %} - {% if part.assembly %} - - {% endif %} - {% if part.component %} - - {% endif %} - {% if part.trackable %} - + {% if part.active %} + + {% if roles.stock.change %} + {% endif %} {% if part.purchaseable %} - - {% endif %} - {% if part.salable %} - - {% endif %} -
- - {% if not part.active %} -
- {% trans 'Inactive' %} -
- {% endif %} -

-

- -
- - - {% if barcodes %} - - - {% endif %} - {% if part.active %} - - {% if roles.stock.change %} -
- - -
- {% endif %} - {% if part.purchaseable %} - {% if roles.purchase_order.add %} - - {% endif %} - {% endif %} - {% endif %} - - {% if roles.part.add or roles.part.change or roles.part.delete %} -
- - -
- {% endif %} -
-
-
- -
- {% if part.virtual %} -
- {% trans "This is a virtual part" %} -
- {% endif %} - {% if part.variant_of %} -
- {% object_link 'part-detail' part.variant_of.id part.variant_of.full_name as link %} - {% blocktrans %}This part is a variant of {{link}}{% endblocktrans %} -
- {% endif %} -
-
-
-
{% trans "Description" %}{{ part.description }}{% include "clip.html"%}
{% trans "Revision" %}{{ part.revision }}{% include "clip.html"%}
{% trans "Keywords" %}{{ part.keywords }}{% include "clip.html"%}
{% trans "External Link" %}{{ part.link }}{% include "clip.html"%}
{% trans "Creation Date" %} + {{ part.creation_date }} + {% if part.creation_user %} + {{ part.creation_user }} + {% endif %} +
{% trans "Latest Serial Number" %}{{ part.getLatestSerialNumber }}{% include "clip.html"%}
{% trans "Default Location" %}{{ part.default_location }}
{% trans "Default Supplier" %}{{ part.default_supplier }}
-

{% trans "Available Stock" %}

-

{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}

{% trans "Available Stock" %}{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}
- - {% if part.description %} - - - - {% endif %} - {% if part.revision %} - - - - - - {% endif %} - {% if part.keywords %} - - - - - - {% endif %} - {% if part.link %} - - - - - - {% endif %} - - - - - - {% if part.trackable and part.getLatestSerialNumber %} - - - - - - {% endif %} - {% if part.default_location %} - - - - - - {% endif %} - {% if part.default_supplier %} - - - - - - {% endif %} - - - - - - - - - - - {% if on_order > 0 %} - - - - - - {% endif %} - {% if required_build_order_quantity > 0 %} - - - - - {% endif %} - {% if required_sales_order_quantity > 0 %} - - - - - {% endif %} - {% if allocated > 0 %} - - - - - - {% endif %} + {% endif %} + {% endif %} + + {% if roles.part.add or roles.part.change or roles.part.delete %} +
+ + +
+ {% endif %} + + + +
+ {% if part.variant_of %} +
+ {% object_link 'part-detail' part.variant_of.id part.variant_of.full_name as link %} + {% blocktrans %}This part is a variant of {{link}}{% endblocktrans %} +
+ {% endif %} +
+ +
+
{% trans "Description" %}{{ part.description }}{% include "clip.html"%}
{% trans "Revision" %}{{ part.revision }}{% include "clip.html"%}
{% trans "Keywords" %}{{ part.keywords }}{% include "clip.html"%}
{% trans "External Link" %}{{ part.link }}{% include "clip.html"%}
{% trans "Creation Date" %} - {{ part.creation_date }} - {% if part.creation_user %} - {{ part.creation_user }} {% endif %} -
{% trans "Latest Serial Number" %}{{ part.getLatestSerialNumber }}{% include "clip.html"%}
{% trans "Default Location" %}{{ part.default_location }}
{% trans "Default Supplier" %}{{ part.default_supplier }}
{% trans "Available Stock" %}{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}
{% trans "In Stock" %}{% include "part/stock_count.html" %}
{% trans "On Order" %}{% decimal on_order %}
{% trans "Required for Build Orders" %}{% decimal required_build_order_quantity %} -
{% trans "Required for Sales Orders" %}{% decimal required_sales_order_quantity %} -
{% trans "Allocated to Orders" %}{% decimal allocated %}
+ + + + + + + + + + + + {% if on_order > 0 %} + + + + + + {% endif %} + {% if required_build_order_quantity > 0 %} + + + + + {% endif %} + {% if required_sales_order_quantity > 0 %} + + + + + {% endif %} + {% if allocated > 0 %} + + + + + + {% endif %} - {% if not part.is_template %} - {% if part.assembly %} - - - - - - - - - - {% if quantity_being_built > 0 %} - - - - - - {% endif %} - {% endif %} + {% if not part.is_template %} + {% if part.assembly %} + + + + + + + + + + {% if quantity_being_built > 0 %} + + + + + + {% endif %} + {% endif %} + {% endif %} +

{% trans "Available Stock" %}

{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}

{% trans "In Stock" %}{% include "part/stock_count.html" %}
{% trans "On Order" %}{% decimal on_order %}
{% trans "Required for Build Orders" %}{% decimal required_build_order_quantity %} +
{% trans "Required for Sales Orders" %}{% decimal required_sales_order_quantity %} +
{% trans "Allocated to Orders" %}{% decimal allocated %}
- {% trans "Build Status" %} -
{% trans "Can Build" %}{% decimal part.can_build %}
{% trans "Building" %}{% decimal quantity_being_built %}

+

{% trans "Build Status" %}

+
{% trans "Can Build" %}{% decimal part.can_build %}
{% trans "Building" %}{% decimal quantity_being_built %}
+
+ + + + + + + {% if part.description %} + + + {% endif %} -
{% trans "Description" %}{{ part.description }}{% include "clip.html"%}
- - - + {% if part.revision %} + + + {% trans "Revision" %} + {{ part.revision }}{% include "clip.html"%} + + {% endif %} + {% if part.keywords %} + + + {% trans "Keywords" %} + {{ part.keywords }}{% include "clip.html"%} + + {% endif %} + {% if part.link %} + + + {% trans "External Link" %} + {{ part.link }}{% include "clip.html"%} + + {% endif %} + + + {% trans "Creation Date" %} + + {{ part.creation_date }} + {% if part.creation_user %} + {{ part.creation_user }} + {% endif %} + + + {% if part.trackable and part.getLatestSerialNumber %} + + + {% trans "Latest Serial Number" %} + {{ part.getLatestSerialNumber }}{% include "clip.html"%} + + {% endif %} + {% if part.default_location %} + + + {% trans "Default Location" %} + {{ part.default_location }} + + {% endif %} + {% if part.default_supplier %} + + + {% trans "Default Supplier" %} + {{ part.default_supplier }} + + {% endif %} + {% block page_content %} From 13898d6687a71d97fa984017b50052165965cda2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 30 Jul 2021 11:51:06 +1000 Subject: [PATCH 03/18] Publish on tags also --- .github/workflows/docker_publish.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker_publish.yaml b/.github/workflows/docker_publish.yaml index 53ec505003..1509991146 100644 --- a/.github/workflows/docker_publish.yaml +++ b/.github/workflows/docker_publish.yaml @@ -5,6 +5,7 @@ name: Docker Publish on: release: types: [published] + tags: [1-9]+.[0-9]+.[0-9]+ jobs: publish_image: From 1d19393442b94f09063f150491df42ae9b7020fb Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 30 Jul 2021 12:42:03 +1000 Subject: [PATCH 04/18] Revert "Publish on tags also" --- .github/workflows/docker_publish.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docker_publish.yaml b/.github/workflows/docker_publish.yaml index 1509991146..53ec505003 100644 --- a/.github/workflows/docker_publish.yaml +++ b/.github/workflows/docker_publish.yaml @@ -5,7 +5,6 @@ name: Docker Publish on: release: types: [published] - tags: [1-9]+.[0-9]+.[0-9]+ jobs: publish_image: From 634e5e0da6b1ca2eae0496f5298e2255a954e6d5 Mon Sep 17 00:00:00 2001 From: eeintech Date: Fri, 30 Jul 2021 14:55:12 -0400 Subject: [PATCH 05/18] Added toggle for part details Added persistence for page refresh or new part page --- InvenTree/part/templates/part/part_base.html | 184 +++++++++++++------ 1 file changed, 123 insertions(+), 61 deletions(-) diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 53444e4f8e..5191399f0a 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -209,68 +209,92 @@ - - - - - {% if part.description %} - - - - {% endif %} - {% if part.revision %} - - - - - - {% endif %} - {% if part.keywords %} - - - - - - {% endif %} - {% if part.link %} - - - - - - {% endif %} - - - - + + + + + + + + + + {% if part.revision %} + + + + + + {% endif %} + {% if part.keywords %} + + + + + + {% endif %} + {% if part.link %} + + + + + + {% endif %} + + + + + + {% if part.trackable and part.getLatestSerialNumber %} + + + + + + {% endif %} + {% if part.default_location %} + + + + + + {% endif %} + {% if part.default_supplier %} + + + + + + {% endif %} +
{% trans "Description" %}{{ part.description }}{% include "clip.html"%}
{% trans "Revision" %}{{ part.revision }}{% include "clip.html"%}
{% trans "Keywords" %}{{ part.keywords }}{% include "clip.html"%}
{% trans "External Link" %}{{ part.link }}{% include "clip.html"%}
{% trans "Creation Date" %} - {{ part.creation_date }} - {% if part.creation_user %} - {{ part.creation_user }} + +

+ + +

+ +
+
+ + + + {% if part.IPN %} + + + + + {% endif %} - - - {% if part.trackable and part.getLatestSerialNumber %} - - - - - - {% endif %} - {% if part.default_location %} - - - - - - {% endif %} - {% if part.default_supplier %} - - - - - - {% endif %} -
{% trans "IPN" %}{{ part.IPN }}{% include "clip.html"%}
{% trans "Latest Serial Number" %}{{ part.getLatestSerialNumber }}{% include "clip.html"%}
{% trans "Default Location" %}{{ part.default_location }}
{% trans "Default Supplier" %}{{ part.default_supplier }}
+
{% trans "Name" %}{{ part.name }}{% include "clip.html"%}
{% trans "Description" %}{{ part.description }}{% include "clip.html"%}
{% trans "Revision" %}{{ part.revision }}{% include "clip.html"%}
{% trans "Keywords" %}{{ part.keywords }}{% include "clip.html"%}
{% trans "External Link" %}{{ part.link }}{% include "clip.html"%}
{% trans "Creation Date" %} + {{ part.creation_date }} + {% if part.creation_user %} + {{ part.creation_user }} + {% endif %} +
{% trans "Latest Serial Number" %}{{ part.getLatestSerialNumber }}{% include "clip.html"%}
{% trans "Default Location" %}{{ part.default_location }}
{% trans "Default Supplier" %}{{ part.default_supplier }}
+ + + + {% block page_content %} @@ -478,4 +502,42 @@ }); {% endif %} + $("#toggle-part-details").click(function() { + if (this.value == 'show') { + this.innerHTML = ' {% trans "Hide Part Details" %}'; + this.value = 'hide'; + // Store state of part details section + localStorage.setItem("part-details-show", true); + } else { + this.innerHTML = ' {% trans "Show Part Details" %}'; + this.value = 'show'; + // Store state of part details section + localStorage.setItem("part-details-show", false); + } + }); + + // Load part details section + window.onload = function() { + details_show = localStorage.getItem("part-details-show") + + if (details_show === 'true') { + console.log(details_show) + // Get collapsible details section + details = document.getElementById('collapsible-part-details'); + // Add "show" class + details.classList.add("in"); + // Get toggle + toggle = document.getElementById('toggle-part-details'); + // Change state of toggle + toggle.innerHTML = ' {% trans "Hide Part Details" %}'; + toggle.value = 'hide'; + } else { + // Get toggle + toggle = document.getElementById('toggle-part-details'); + // Change state of toggle + toggle.innerHTML = ' {% trans "Show Part Details" %}'; + toggle.value = 'show'; + } + } + {% endblock %} \ No newline at end of file From 369864574e4099427c4340b9e0db1a84d0c57c77 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 30 Jul 2021 23:25:45 +0200 Subject: [PATCH 06/18] only include setting in the settings that have a key --- InvenTree/common/models.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 54dc21c1b5..38dcce28d9 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -58,12 +58,13 @@ class BaseInvenTreeSetting(models.Model): # Query the database for setting in results: - settings.append({ - "key": setting.key.upper(), - "value": setting.value - }) + if setting.key: + settings.append({ + "key": setting.key.upper(), + "value": setting.value + }) - keys.add(setting.key.upper()) + keys.add(setting.key.upper()) # Specify any "default" values which are not in the database for key in cls.GLOBAL_SETTINGS.keys(): From 2347f15c2ee05ad6a2dca59bfb6c34846aa15d17 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 01:05:43 +0200 Subject: [PATCH 07/18] new command to cleanup old settings in db --- .../management/commands/clean_settings.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 InvenTree/InvenTree/management/commands/clean_settings.py diff --git a/InvenTree/InvenTree/management/commands/clean_settings.py b/InvenTree/InvenTree/management/commands/clean_settings.py new file mode 100644 index 0000000000..9e94497e98 --- /dev/null +++ b/InvenTree/InvenTree/management/commands/clean_settings.py @@ -0,0 +1,40 @@ +""" +Custom management command to cleanup old settings that are not defined anymore +""" + +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + """ + Cleanup old (undefined) settings in the database + """ + + def handle(self, *args, **kwargs): + + print("Collecting settings") + from common.models import InvenTreeSetting, InvenTreeUserSetting + + # general settings + db_settings = InvenTreeSetting.objects.all() + model_settings = InvenTreeSetting.GLOBAL_SETTINGS + + # check if key exist and delete if not + for setting in db_settings: + print(setting.key) + if setting.key not in model_settings: + setting.delete() + print(f"deleted setting '{setting.key}'") + + # user settings + db_settings = InvenTreeUserSetting.objects.all() + model_settings = InvenTreeUserSetting.GLOBAL_SETTINGS + + # check if key exist and delete if not + for setting in db_settings: + print(setting.key) + if setting.key not in model_settings: + setting.delete() + print(f"deleted user setting '{setting.key}'") + + print("checked all settings") From ae8e58ac12bdba8e19136b4563821533f049abac Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 01:06:17 +0200 Subject: [PATCH 08/18] invoke task for celan_settings --- tasks.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index b78a135b08..a9168f4649 100644 --- a/tasks.py +++ b/tasks.py @@ -137,6 +137,14 @@ def rebuild(c): manage(c, "rebuild_models") +@task +def clean_settings(c): + """ + Clean the setting tables of old settings + """ + + manage(c, "clean_settings") + @task def migrate(c): """ @@ -167,7 +175,7 @@ def static(c): manage(c, "collectstatic --no-input") -@task(pre=[install, migrate, static]) +@task(pre=[install, migrate, static, clean_settings]) def update(c): """ Update InvenTree installation. From c0921fc7cedeef1522a969db9fdc9d61188018d0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 01:16:10 +0200 Subject: [PATCH 09/18] removing unneeded prints --- InvenTree/InvenTree/management/commands/clean_settings.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/InvenTree/management/commands/clean_settings.py b/InvenTree/InvenTree/management/commands/clean_settings.py index 9e94497e98..e0fd09e6c7 100644 --- a/InvenTree/InvenTree/management/commands/clean_settings.py +++ b/InvenTree/InvenTree/management/commands/clean_settings.py @@ -21,7 +21,6 @@ class Command(BaseCommand): # check if key exist and delete if not for setting in db_settings: - print(setting.key) if setting.key not in model_settings: setting.delete() print(f"deleted setting '{setting.key}'") @@ -32,7 +31,6 @@ class Command(BaseCommand): # check if key exist and delete if not for setting in db_settings: - print(setting.key) if setting.key not in model_settings: setting.delete() print(f"deleted user setting '{setting.key}'") From 55762f2a96c9bc58c1b95c0851d8d4fdc99c23db Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 01:41:46 +0200 Subject: [PATCH 10/18] do not use safe in template that can cause wrong escaping and generally is considered unsafe --- InvenTree/common/models.py | 5 +++-- InvenTree/templates/js/dynamic/settings.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 38dcce28d9..5d75a4dd74 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -20,6 +20,7 @@ from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.exceptions import MissingRate from django.utils.translation import ugettext_lazy as _ +from django.utils.html import format_html from django.core.validators import MinValueValidator, URLValidator from django.core.exceptions import ValidationError @@ -91,10 +92,10 @@ class BaseInvenTreeSetting(models.Model): # Numerical values remain the same elif cls.validator_is_int(validator): pass - + # Wrap strings with quotes else: - value = f"'{value}'" + value = format_html("'{}'", value) setting["value"] = value diff --git a/InvenTree/templates/js/dynamic/settings.js b/InvenTree/templates/js/dynamic/settings.js index 4cc824ed6c..ad4e297c4a 100644 --- a/InvenTree/templates/js/dynamic/settings.js +++ b/InvenTree/templates/js/dynamic/settings.js @@ -6,12 +6,12 @@ var user_settings = { {% for setting in USER_SETTINGS %} - {{ setting.key }}: {{ setting.value|safe }}, + {{ setting.key }}: {{ setting.value }}, {% endfor %} }; var global_settings = { {% for setting in GLOBAL_SETTINGS %} - {{ setting.key }}: {{ setting.value|safe }}, + {{ setting.key }}: {{ setting.value }}, {% endfor %} }; \ No newline at end of file From 83dab558d796b74b153afe564d59064cc63f7147 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 20:44:26 +0200 Subject: [PATCH 11/18] catch connection errors in exchange update #Fixes #1888 --- InvenTree/InvenTree/exchange.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index c75a827cc7..e329b157af 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -1,4 +1,5 @@ from common.settings import currency_code_default, currency_codes +from urllib.error import HTTPError, URLError from djmoney.contrib.exchange.backends.base import SimpleExchangeBackend @@ -26,4 +27,9 @@ class InvenTreeExchange(SimpleExchangeBackend): symbols = ','.join(currency_codes()) - super().update_rates(base=base_currency, symbols=symbols) + try: + super().update_rates(base=base_currency, symbols=symbols) + # catch connection errors + except (HTTPError, URLError): + print('Encountered connection error while updating') + From 0f11ab527fd0f94f1ce81f14e6027f5be6ae7ff0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 Aug 2021 20:58:57 +0200 Subject: [PATCH 12/18] PEP fix --- InvenTree/InvenTree/exchange.py | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index e329b157af..9981e52ff7 100644 --- a/InvenTree/InvenTree/exchange.py +++ b/InvenTree/InvenTree/exchange.py @@ -32,4 +32,3 @@ class InvenTreeExchange(SimpleExchangeBackend): # catch connection errors except (HTTPError, URLError): print('Encountered connection error while updating') - From ac3dcac64167700ea701c898117aaf71171d57a4 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 2 Aug 2021 15:05:24 -0400 Subject: [PATCH 13/18] Re-enabled installing stock items into others --- .../build/templates/build/build_base.html | 4 +- InvenTree/stock/forms.py | 23 +++-- InvenTree/stock/templates/stock/item.html | 19 ++++ .../stock/templates/stock/item_base.html | 24 ++++- .../stock/templates/stock/item_install.html | 22 ++++- InvenTree/stock/views.py | 92 +++++++++++++++---- 6 files changed, 146 insertions(+), 38 deletions(-) diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index 5770777d28..e3119e6fdb 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -111,8 +111,8 @@ src="{% static 'img/blank_image.png' %}"
  • {% trans "Cancel Build" %}
  • {% endif %} {% if build.status == BuildStatus.CANCELLED and roles.build.delete %} -
  • {% trans "Delete Build"% } - {% endif %} +
  • {% trans "Delete Build" %} + {% endif %} {% endif %} diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index b23b71a2d6..b088a6cdef 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -241,16 +241,21 @@ class InstallStockForm(HelperForm): help_text=_('Stock item to install') ) - quantity_to_install = RoundingDecimalFormField( - max_digits=10, decimal_places=5, - initial=1, - label=_('Quantity'), - help_text=_('Stock quantity to assign'), - validators=[ - MinValueValidator(0.001) - ] + to_install = forms.BooleanField( + widget=forms.HiddenInput(), + required=False ) + # quantity_to_install = RoundingDecimalFormField( + # max_digits=10, decimal_places=5, + # initial=1, + # label=_('Quantity'), + # help_text=_('Stock quantity to assign'), + # validators=[ + # MinValueValidator(0.001) + # ] + # ) + notes = forms.CharField( required=False, help_text=_('Notes') @@ -261,7 +266,7 @@ class InstallStockForm(HelperForm): fields = [ 'part', 'stock_item', - 'quantity_to_install', + # 'quantity_to_install', 'notes', ] diff --git a/InvenTree/stock/templates/stock/item.html b/InvenTree/stock/templates/stock/item.html index 8a00c1c5e6..d380ea3369 100644 --- a/InvenTree/stock/templates/stock/item.html +++ b/InvenTree/stock/templates/stock/item.html @@ -119,6 +119,11 @@

    {% trans "Installed Stock Items" %}

    +
    + +
    @@ -128,6 +133,20 @@ {% block js_ready %} {{ block.super }} + $('#stock-item-install').click(function() { + + launchModalForm( + "{% url 'stock-item-install' item.pk %}", + { + data: { + 'part': {{ item.part.pk }}, + 'install_item': true, + }, + reload: true, + } + ); + }); + loadInstalledInTable( $('#installed-table'), { diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index b16d9b0b1a..93698e3d05 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -127,9 +127,11 @@
  • {% trans "Return to stock" %}
  • {% endif %} {% if item.belongs_to %} -
  • - {% trans "Uninstall" %} -
  • +
  • {% trans "Uninstall" %}
  • + {% else %} + {% if item.part.get_used_in %} +
  • {% trans "Install" %}
  • + {% endif %} {% endif %} @@ -461,13 +463,27 @@ $("#stock-serialize").click(function() { ); }); +$('#stock-install-in').click(function() { + + launchModalForm( + "{% url 'stock-item-install' item.pk %}", + { + data: { + 'part': {{ item.part.pk }}, + 'install_in': true, + }, + reload: true, + } + ); +}); + $('#stock-uninstall').click(function() { launchModalForm( "{% url 'stock-item-uninstall' %}", { data: { - 'items[]': [{{ item.pk}}], + 'items[]': [{{ item.pk }}], }, reload: true, } diff --git a/InvenTree/stock/templates/stock/item_install.html b/InvenTree/stock/templates/stock/item_install.html index 04798972d2..8a94f304d3 100644 --- a/InvenTree/stock/templates/stock/item_install.html +++ b/InvenTree/stock/templates/stock/item_install.html @@ -3,15 +3,31 @@ {% block pre_form_content %} +{% if install_item %}

    - {% trans "Install another StockItem into this item." %} + {% trans "Install another Stock Item into this item." %}

    {% trans "Stock items can only be installed if they meet the following criteria" %}:

      -
    • {% trans "The StockItem links to a Part which is in the BOM for this StockItem" %}
    • -
    • {% trans "The StockItem is currently in stock" %}
    • +
    • {% trans "The Stock Item links to a Part which is in the BOM for this Stock Item" %}
    • +
    • {% trans "The Stock Item is currently in stock" %}
    • +
    • {% trans "The Stock Item is serialized and does not belong to another item" %}

    +{% elif install_in %} +

    + {% trans "Install this Stock Item in another stock item." %} +

    +

    + {% trans "Stock items can only be installed if they meet the following criteria" %}: + +

      +
    • {% trans "The part associated to this Stock Item belongs to another part's BOM" %}
    • +
    • {% trans "This Stock Item is serialized and does not belong to another item" %}
    • +
    +

    +{% endif %} + {% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 80968e5aa9..25f8cefac1 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -518,36 +518,73 @@ class StockItemInstall(AjaxUpdateView): part = None + def get_params(self): + """ Retrieve GET parameters """ + + # Look at GET params + self.part_id = self.request.GET.get('part', None) + self.install_in = self.request.GET.get('install_in', False) + self.install_item = self.request.GET.get('install_item', False) + + if self.part_id is None: + # Look at POST params + self.part_id = self.request.POST.get('part', None) + + try: + self.part = Part.objects.get(pk=self.part_id) + except (ValueError, Part.DoesNotExist): + self.part = None + def get_stock_items(self): """ Return a list of stock items suitable for displaying to the user. Requirements: - Items must be in stock - - Filters: - - Items can be filtered by Part reference + - Items must be in BOM of stock item + - Items must be serialized """ - + + # Filter items in stock items = StockItem.objects.filter(StockItem.IN_STOCK_FILTER) - # Filter by Part association + # Filter serialized stock items + items = items.exclude(serial__isnull=True).exclude(serial__exact='') - # Look at GET params - part_id = self.request.GET.get('part', None) + if self.part: + # Filter for parts to install this item in + if self.install_in: + # Get parts using this part + allowed_parts = self.part.get_used_in() + # Filter + items = items.filter(part__in=allowed_parts) - if part_id is None: - # Look at POST params - part_id = self.request.POST.get('part', None) - - try: - self.part = Part.objects.get(pk=part_id) - items = items.filter(part=self.part) - except (ValueError, Part.DoesNotExist): - self.part = None + # Filter for parts to install in this item + if self.install_item: + # Get parts used in this part's BOM + bom_items = self.part.get_bom_items() + allowed_parts = [item.sub_part for item in bom_items] + # Filter + items = items.filter(part__in=allowed_parts) return items + def get_context_data(self, **kwargs): + """ Retrieve parameters and update context """ + + ctx = super().get_context_data(**kwargs) + + # Get request parameters + self.get_params() + + ctx.update({ + 'part': self.part, + 'install_in': self.install_in, + 'install_item': self.install_item, + }) + + return ctx + def get_initial(self): initials = super().get_initial() @@ -558,11 +595,17 @@ class StockItemInstall(AjaxUpdateView): if items.count() == 1: item = items.first() initials['stock_item'] = item.pk - initials['quantity_to_install'] = item.quantity + # initials['quantity_to_install'] = item.quantity if self.part: initials['part'] = self.part + try: + # Is this stock item being installed in the other stock item? + initials['to_install'] = self.install_in or not self.install_item + except AttributeError: + pass + return initials def get_form(self): @@ -575,6 +618,8 @@ class StockItemInstall(AjaxUpdateView): def post(self, request, *args, **kwargs): + self.get_params() + form = self.get_form() valid = form.is_valid() @@ -584,13 +629,20 @@ class StockItemInstall(AjaxUpdateView): data = form.cleaned_data other_stock_item = data['stock_item'] - quantity = data['quantity_to_install'] + # quantity = data['quantity_to_install'] + # Quantity will always be 1 for serialized item + quantity = 1 notes = data['notes'] - # Install the other stock item into this one + # Get stock item this_stock_item = self.get_object() - this_stock_item.installStockItem(other_stock_item, quantity, request.user, notes) + if data['to_install']: + # Install this stock item into the other stock item + other_stock_item.installStockItem(this_stock_item, quantity, request.user, notes) + else: + # Install the other stock item into this one + this_stock_item.installStockItem(other_stock_item, quantity, request.user, notes) data = { 'form_valid': valid, From 1c4924a4a5ebb95c52cc6a40c4c8959954d98544 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 2 Aug 2021 15:14:55 -0400 Subject: [PATCH 14/18] Style duh --- InvenTree/stock/forms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index b088a6cdef..c0d6e9026f 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -8,7 +8,6 @@ from __future__ import unicode_literals from django import forms from django.forms.utils import ErrorDict from django.utils.translation import ugettext_lazy as _ -from django.core.validators import MinValueValidator from django.core.exceptions import ValidationError from mptt.fields import TreeNodeChoiceField @@ -242,8 +241,8 @@ class InstallStockForm(HelperForm): ) to_install = forms.BooleanField( - widget=forms.HiddenInput(), - required=False + widget=forms.HiddenInput(), + required=False, ) # quantity_to_install = RoundingDecimalFormField( From f057937df01fcac3b00b17e9ab2416ab5560151d Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 3 Aug 2021 09:46:28 +1000 Subject: [PATCH 15/18] Fix for non-integer serial numbers --- .../stock/templates/stock/item_base.html | 2 +- InvenTree/stock/views.py | 44 +++++++++++++------ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index b16d9b0b1a..91c3209013 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -253,7 +253,7 @@ {{ previous.serial }} ‹ {% endif %} - {{ item.serial }} + {{ item.serial }} {% if next %} {{ next.serial }} diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 80968e5aa9..7b6fbc527e 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -92,21 +92,39 @@ class StockItemDetail(InvenTreeRoleMixin, DetailView): data = super().get_context_data(**kwargs) if self.object.serialized: - serial_elem = {int(a.serial): a for a in self.object.part.stock_items.all() if a.serialized} - serials = serial_elem.keys() - current = int(self.object.serial) - # previous - for nbr in range(current - 1, -1, -1): - if nbr in serials: - data['previous'] = serial_elem.get(nbr, None) - break + serial_elem = {} - # next - for nbr in range(current + 1, max(serials) + 1): - if nbr in serials: - data['next'] = serial_elem.get(nbr, None) - break + try: + current = int(self.object.serial) + + for item in self.object.part.stock_items.all(): + + if item.serialized: + try: + sn = int(item.serial) + serial_elem[sn] = item + except ValueError: + # We only support integer serial number progression + pass + + serials = serial_elem.keys() + + # previous + for nbr in range(current - 1, min(serials), -1): + if nbr in serials: + data['previous'] = serial_elem.get(nbr, None) + break + + # next + for nbr in range(current + 1, max(serials) + 1): + if nbr in serials: + data['next'] = serial_elem.get(nbr, None) + break + + except ValueError: + # We only support integer serial number progression + pass return data From 172a08fbba3b100c09ca5875ee12507efb0661d5 Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 3 Aug 2021 09:53:08 -0400 Subject: [PATCH 16/18] Removed old quantity setting lines --- InvenTree/stock/forms.py | 10 ---------- InvenTree/stock/views.py | 2 -- 2 files changed, 12 deletions(-) diff --git a/InvenTree/stock/forms.py b/InvenTree/stock/forms.py index c0d6e9026f..7e739306b0 100644 --- a/InvenTree/stock/forms.py +++ b/InvenTree/stock/forms.py @@ -245,16 +245,6 @@ class InstallStockForm(HelperForm): required=False, ) - # quantity_to_install = RoundingDecimalFormField( - # max_digits=10, decimal_places=5, - # initial=1, - # label=_('Quantity'), - # help_text=_('Stock quantity to assign'), - # validators=[ - # MinValueValidator(0.001) - # ] - # ) - notes = forms.CharField( required=False, help_text=_('Notes') diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 25f8cefac1..8214ae75b4 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -595,7 +595,6 @@ class StockItemInstall(AjaxUpdateView): if items.count() == 1: item = items.first() initials['stock_item'] = item.pk - # initials['quantity_to_install'] = item.quantity if self.part: initials['part'] = self.part @@ -629,7 +628,6 @@ class StockItemInstall(AjaxUpdateView): data = form.cleaned_data other_stock_item = data['stock_item'] - # quantity = data['quantity_to_install'] # Quantity will always be 1 for serialized item quantity = 1 notes = data['notes'] From 29c8daed0af0b2a9c06106e03b7bb03bc0a9724b Mon Sep 17 00:00:00 2001 From: eeintech Date: Tue, 3 Aug 2021 12:21:44 -0400 Subject: [PATCH 17/18] 'has_ipn' filter method did not return queryset --- InvenTree/part/api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 3612a1c9f9..c0d049ecc7 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -443,6 +443,8 @@ class PartFilter(rest_filters.FilterSet): else: queryset = queryset.filter(IPN='') + return queryset + # Regex filter for name name_regex = rest_filters.CharFilter(label='Filter by name (regex)', field_name='name', lookup_expr='iregex') From fa3c5ae1081c1b709db50fe82b3ec4d7c3a5edc0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 4 Aug 2021 00:45:56 +0200 Subject: [PATCH 18/18] updating language to be clearer see https://github.com/inventree/InvenTree/issues/1889#issuecomment-891901070 --- InvenTree/part/templates/part/detail.html | 6 +++--- InvenTree/part/templates/part/prices.html | 2 +- InvenTree/templates/js/translated/bom.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 00d7f01e47..59aec17944 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -899,7 +899,7 @@ {% for line in price_history %}'{{ line.date }}',{% endfor %} ], datasets: [{ - label: '{% blocktrans %}Single Price - {{currency}}{% endblocktrans %}', + label: '{% blocktrans %}Purchase Unit Price - {{currency}}{% endblocktrans %}', backgroundColor: 'rgba(255, 99, 132, 0.2)', borderColor: 'rgb(255, 99, 132)', yAxisID: 'y', @@ -911,7 +911,7 @@ }, {% if 'price_diff' in price_history.0 %} { - label: '{% blocktrans %}Single Price Difference - {{currency}}{% endblocktrans %}', + label: '{% blocktrans %}Unit Price-Cost Difference - {{currency}}{% endblocktrans %}', backgroundColor: 'rgba(68, 157, 68, 0.2)', borderColor: 'rgb(68, 157, 68)', yAxisID: 'y2', @@ -923,7 +923,7 @@ hidden: true, }, { - label: '{% blocktrans %}Part Single Price - {{currency}}{% endblocktrans %}', + label: '{% blocktrans %}Supplier Unit Cost - {{currency}}{% endblocktrans %}', backgroundColor: 'rgba(70, 127, 155, 0.2)', borderColor: 'rgb(70, 127, 155)', yAxisID: 'y', diff --git a/InvenTree/part/templates/part/prices.html b/InvenTree/part/templates/part/prices.html index 7581d659e1..e498bc09ba 100644 --- a/InvenTree/part/templates/part/prices.html +++ b/InvenTree/part/templates/part/prices.html @@ -161,7 +161,7 @@

    {% trans 'Stock Pricing' %} + The Supplier Unit Cost is the current purchase price for that supplier part.">

    {% if price_history|length > 0 %}
    diff --git a/InvenTree/templates/js/translated/bom.js b/InvenTree/templates/js/translated/bom.js index 32166d972a..20829bad79 100644 --- a/InvenTree/templates/js/translated/bom.js +++ b/InvenTree/templates/js/translated/bom.js @@ -262,13 +262,13 @@ function loadBomTable(table, options) { cols.push( { field: 'price_range', - title: '{% trans "Buy Price" %}', + title: '{% trans "Supplier Cost" %}', sortable: true, formatter: function(value, row, index, field) { if (value) { return value; } else { - return "{% trans 'No pricing available' %}"; + return "{% trans 'No supplier pricing available' %}"; } } });