diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py index c75a827cc7..9981e52ff7 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,8 @@ 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') diff --git a/InvenTree/InvenTree/management/commands/clean_settings.py b/InvenTree/InvenTree/management/commands/clean_settings.py new file mode 100644 index 0000000000..e0fd09e6c7 --- /dev/null +++ b/InvenTree/InvenTree/management/commands/clean_settings.py @@ -0,0 +1,38 @@ +""" +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: + 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: + if setting.key not in model_settings: + setting.delete() + print(f"deleted user setting '{setting.key}'") + + print("checked all settings") diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 54dc21c1b5..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 @@ -58,12 +59,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(): @@ -90,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/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index d7b196917d..5191399f0a 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -10,132 +10,253 @@ {% block content %} -<div class='panel panel-default panel-inventree'> +<div class="panel panel-default panel-inventree"> + <!-- Default panel contents --> + <div class="panel-heading"><h3>{{ part.full_name }}</h3></div> + <div class="panel-body"> + <div class="row"> + <div class="col-sm-6"> + {% include "part/part_thumb.html" %} + <div class="media-body"> + <p> + <h3> + <!-- Admin View --> + {% if user.is_staff and roles.part.change %} + <a href="{% url 'admin:part_part_change' part.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>  + {% endif %} + <!-- Properties --> + <div id='part-properties' class='btn-group' role='group'> + {% if part.is_template %} + <span class='fas fa-clone' title='{% trans "Part is a template part (variants can be made from this part)" %}'></span> + {% endif %} + {% if part.assembly %} + <span class='fas fa-tools' title='{% trans "Part can be assembled from other parts" %}'></span> + {% endif %} + {% if part.component %} + <span class='fas fa-th' title='{% trans "Part can be used in assemblies" %}'></span> + {% endif %} + {% if part.trackable %} + <span class='fas fa-directions' title='{% trans "Part stock is tracked by serial number" %}'></span> + {% endif %} + {% if part.purchaseable %} + <span class='fas fa-shopping-cart' title='{% trans "Part can be purchased from external suppliers" %}'></span> + {% endif %} + {% if part.salable %} + <span class='fas fa-dollar-sign' title='{% trans "Part can be sold to customers" %}'></span> + {% endif %} + </div> + <!-- Part active --> + {% if not part.active %} +   + <div class='label label-large label-large-red'> + <span class='fas fa-skull-crossbones' title='{% trans "Part is virtual (not a physical part)" %}'></span> + {% trans 'Inactive' %} + </div> + {% endif %} + <!-- Part virtual --> + {% if part.virtual and part.active %} +   + <div class='label label-large label-large-yellow'> + <span class='fas fa-ghost' title='{% trans "Part is virtual (not a physical part)" %}'></span> + {% trans 'Virtual' %} + </div> + {% endif %} + </h3> + </p> - <div class="row"> - <div class="col-sm-6"> - {% include "part/part_thumb.html" %} - <div class="media-body"> - <h3> - {{ part.full_name }} - {% if user.is_staff and roles.part.change %} - <a href="{% url 'admin:part_part_change' part.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> - {% endif %} - {% if not part.active %} - <div class='label label-large label-large-red'> - {% trans 'Inactive' %} - </div> - {% endif %} - </h3> - {% if part.description %} - <p><em>{{ part.description }}</em></p> - {% endif %} - <p> - <div id='part-properties' class='btn-group' role='group'> - {% if part.virtual %} - <span class='fas fa-ghost' title='{% trans "Part is virtual (not a physical part)" %}'></span> + <div class='btn-group action-buttons' role='group'> + <button type='button' class='btn btn-default' id='toggle-starred' title='{% trans "Star this part" %}'> + <span id='part-star-icon' class='fas fa-star {% if starred %}icon-yellow{% endif %}'/> + </button> + + {% if barcodes %} + <!-- Barcode actions menu --> + <div class='btn-group'> + <button id='barcode-options' title='{% trans "Barcode actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-qrcode'></span> <span class='caret'></span></button> + <ul class='dropdown-menu'> + <li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li> + <li><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li> + </ul> + </div> {% endif %} - {% if part.is_template %} - <span class='fas fa-clone' title='{% trans "Part is a template part (variants can be made from this part)" %}'></span> - {% endif %} - {% if part.assembly %} - <span class='fas fa-tools' title='{% trans "Part can be assembled from other parts" %}'></span> - {% endif %} - {% if part.component %} - <span class='fas fa-th' title='{% trans "Part can be used in assemblies" %}'></span> - {% endif %} - {% if part.trackable %} - <span class='fas fa-directions' title='{% trans "Part stock is tracked by serial number" %}'></span> + {% if part.active %} + <button type='button' class='btn btn-default' id='price-button' title='{% trans "Show pricing information" %}'> + <span id='part-price-icon' class='fas fa-dollar-sign'/> + </button> + {% if roles.stock.change %} + <div class='btn-group'> + <button id='stock-actions' title='{% trans "Stock actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> + <span class='fas fa-boxes'></span> <span class='caret'></span> + </button> + <ul class='dropdown-menu'> + <li> + <a href='#' id='part-count'> + <span class='fas fa-clipboard-list'></span> + {% trans "Count part stock" %} + </a> + </li> + <li> + <a href='#' id='part-move'> + <span class='fas fa-exchange-alt'></span> + {% trans "Transfer part stock" %} + </a> + </li> + </ul> + </div> {% endif %} {% if part.purchaseable %} - <span class='fas fa-shopping-cart' title='{% trans "Part can be purchased from external suppliers" %}'></span> - {% endif %} - {% if part.salable %} - <span class='fas fa-dollar-sign' title='{% trans "Part can be sold to customers" %}'></span> - {% endif %} - </div> - </p> - - <div class='btn-group action-buttons' role='group'> - <button type='button' class='btn btn-default' id='toggle-starred' title='{% trans "Star this part" %}'> - <span id='part-star-icon' class='fas fa-star {% if starred %}icon-yellow{% endif %}'/> - </button> - - {% if barcodes %} - <!-- Barcode actions menu --> - <div class='btn-group'> - <button id='barcode-options' title='{% trans "Barcode actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'><span class='fas fa-qrcode'></span> <span class='caret'></span></button> - <ul class='dropdown-menu'> - <li><a href='#' id='show-qr-code'><span class='fas fa-qrcode'></span> {% trans "Show QR Code" %}</a></li> - <li><a href='#' id='print-label'><span class='fas fa-tag'></span> {% trans "Print Label" %}</a></li> - </ul> - </div> - {% endif %} - {% if part.active %} - <button type='button' class='btn btn-default' id='price-button' title='{% trans "Show pricing information" %}'> - <span id='part-price-icon' class='fas fa-dollar-sign'/> - </button> - {% if roles.stock.change %} - <div class='btn-group'> - <button id='stock-actions' title='{% trans "Stock actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> - <span class='fas fa-boxes'></span> <span class='caret'></span> + {% if roles.purchase_order.add %} + <button type='button' class='btn btn-default' id='part-order' title='{% trans "Order part" %}'> + <span id='part-order-icon' class='fas fa-shopping-cart'/> </button> - <ul class='dropdown-menu'> - <li> - <a href='#' id='part-count'> - <span class='fas fa-clipboard-list'></span> - {% trans "Count part stock" %} - </a> - </li> - <li> - <a href='#' id='part-move'> - <span class='fas fa-exchange-alt'></span> - {% trans "Transfer part stock" %} - </a> - </li> - </ul> + {% endif %} + {% endif %} + {% endif %} + <!-- Part actions --> + {% if roles.part.add or roles.part.change or roles.part.delete %} + <div class='btn-group'> + <button id='part-actions' title='{% trans "Part actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> <span class='fas fa-shapes'></span> <span class='caret'></span></button> + <ul class='dropdown-menu'> + {% if roles.part.add %} + <li><a href='#' id='part-duplicate'><span class='fas fa-copy'></span> {% trans "Duplicate part" %}</a></li> + {% endif %} + {% if roles.part.change %} + <li><a href='#' id='part-edit'><span class='fas fa-edit icon-blue'></span> {% trans "Edit part" %}</a></li> + {% endif %} + {% if not part.active and roles.part.delete %} + <li><a href='#' id='part-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete part" %}</a></li> + {% endif %} + </ul> + </div> + {% endif %} </div> - {% endif %} - {% if part.purchaseable %} - {% if roles.purchase_order.add %} - <button type='button' class='btn btn-default' id='part-order' title='{% trans "Order part" %}'> - <span id='part-order-icon' class='fas fa-shopping-cart'/> - </button> - {% endif %} - {% endif %} - {% endif %} - <!-- Part actions --> - {% if roles.part.add or roles.part.change or roles.part.delete %} - <div class='btn-group'> - <button id='part-actions' title='{% trans "Part actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle='dropdown'> <span class='fas fa-shapes'></span> <span class='caret'></span></button> - <ul class='dropdown-menu'> - {% if roles.part.add %} - <li><a href='#' id='part-duplicate'><span class='fas fa-copy'></span> {% trans "Duplicate part" %}</a></li> - {% endif %} - {% if roles.part.change %} - <li><a href='#' id='part-edit'><span class='fas fa-edit icon-blue'></span> {% trans "Edit part" %}</a></li> - {% endif %} - {% if not part.active and roles.part.delete %} - <li><a href='#' id='part-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete part" %}</a></li> - {% endif %} - </ul> + </div> + </div> + <div class='info-messages'> + {% if part.variant_of %} + <div class='alert alert-info alert-block' style='padding: 10px;'> + {% object_link 'part-detail' part.variant_of.id part.variant_of.full_name as link %} + {% blocktrans %}This part is a variant of {{link}}{% endblocktrans %} </div> {% endif %} </div> - <table class='table table-condensed'> + </div> + <div class="col-sm-6"> + <table class='table table-condensed table-striped'> + <col width='25'> + <tr> + <td><h4><span class='fas fa-boxes'></span></h4></td> + <td><h4>{% trans "Available Stock" %}</h4></td> + <td><h4>{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}</h4></td> + </tr> + <tr> + <td><span class='fas fa-map-marker-alt'></span></td> + <td>{% trans "In Stock" %}</td> + <td>{% include "part/stock_count.html" %}</td> + </tr> + {% if on_order > 0 %} + <tr> + <td><span class='fas fa-shopping-cart'></span></td> + <td>{% trans "On Order" %}</td> + <td>{% decimal on_order %}</td> + </tr> + {% endif %} + {% if required_build_order_quantity > 0 %} + <tr> + <td><span class='fas fa-clipboard-list'></span></td> + <td>{% trans "Required for Build Orders" %}</td> + <td>{% decimal required_build_order_quantity %} + </tr> + {% endif %} + {% if required_sales_order_quantity > 0 %} + <tr> + <td><span class='fas fa-clipboard-list'></span></td> + <td>{% trans "Required for Sales Orders" %}</td> + <td>{% decimal required_sales_order_quantity %} + </tr> + {% endif %} + {% if allocated > 0 %} + <tr> + <td><span class='fas fa-dolly'></span></td> + <td>{% trans "Allocated to Orders" %}</td> + <td>{% decimal allocated %}</td> + </tr> + {% endif %} + + {% if not part.is_template %} + {% if part.assembly %} + <tr> + <td><h4><span class='fas fa-tools'></span></h4></td> + <td colspan='2'> + <h4>{% trans "Build Status" %}</h4> + </td> + </tr> + <tr> + <td></td> + <td>{% trans "Can Build" %}</td> + <td>{% decimal part.can_build %}</td> + </tr> + {% if quantity_being_built > 0 %} + <tr> + <td></td> + <td>{% trans "Building" %}</td> + <td>{% decimal quantity_being_built %}</td> + </tr> + {% endif %} + {% endif %} + {% endif %} + </table> + </div> + </div> + </div> + + <p> + <!-- Details show/hide button --> + <button id="toggle-part-details" class="btn btn-primary" data-toggle="collapse" data-target="#collapsible-part-details" value="show"> + </button> + </p> + + <div class="collapse" id="collapsible-part-details"> + <div class="card card-body"> + <!-- Details Table --> + <table class="table table-striped"> <col width='25'> + {% if part.IPN %} + <tr> + <td><span class='fas fa-tag'></span></td> + <td>{% trans "IPN" %}</td> + <td>{{ part.IPN }}{% include "clip.html"%}</td> + </tr> + {% endif %} + <tr> + <td><span class='fas fa-shapes'></span></td> + <td>{% trans "Name" %}</td> + <td>{{ part.name }}{% include "clip.html"%}</td> + </tr> + <tr> + <td><span class='fas fa-info-circle'></span></td> + <td>{% trans "Description" %}</td> + <td>{{ part.description }}{% include "clip.html"%}</td> + </tr> + {% if part.revision %} + <tr> + <td><span class='fas fa-code-branch'></span></td> + <td>{% trans "Revision" %}</td> + <td>{{ part.revision }}{% include "clip.html"%}</td> + </tr> + {% endif %} {% if part.keywords %} <tr> <td><span class='fas fa-key'></span></td> <td>{% trans "Keywords" %}</td> - <td>{{ part.keywords }}</td> + <td>{{ part.keywords }}{% include "clip.html"%}</td> </tr> {% endif %} {% if part.link %} <tr> <td><span class='fas fa-link'></span></td> <td>{% trans "External Link" %}</td> - <td><a href="{{ part.link }}">{{ part.link }}</a></td> + <td><a href="{{ part.link }}">{{ part.link }}</a>{% include "clip.html"%}</td> </tr> {% endif %} <tr> @@ -154,95 +275,26 @@ <td>{% trans "Latest Serial Number" %}</td> <td>{{ part.getLatestSerialNumber }}{% include "clip.html"%}</td> </tr> - {% endif %} + {% endif %} + {% if part.default_location %} + <tr> + <td><span class='fas fa-search-location'></span></td> + <td>{% trans "Default Location" %}</td> + <td>{{ part.default_location }}</td> + </tr> + {% endif %} + {% if part.default_supplier %} + <tr> + <td><span class='fas fa-building'></span></td> + <td>{% trans "Default Supplier" %}</td> + <td>{{ part.default_supplier }}</td> + </tr> + {% endif %} </table> </div> - </div> - - <div class='info-messages'> - {% if part.virtual %} - <div class='alert alert-warning alert-block'> - {% trans "This is a virtual part" %} - </div> - {% endif %} - {% if part.variant_of %} - <div class='alert alert-info alert-block'> - {% object_link 'part-detail' part.variant_of.id part.variant_of.full_name as link %} - {% blocktrans %}This part is a variant of {{link}}{% endblocktrans %} - </div> - {% endif %} - </div> - </div> - <div class="col-sm-6"> - <table class="table table-striped"> - <col width='25'> - <tr> - <td><span class='fas fa-boxes'></span></td> - <td> - <h4>{% trans "Available Stock" %}</h4> - </td> - <td><h4>{% decimal available %}{% if part.units %} {{ part.units }}{% endif %}</h4></td> - </tr> - <tr> - <td><span class='fas fa-map-marker-alt'></span></td> - <td>{% trans "In Stock" %}</td> - <td>{% include "part/stock_count.html" %}</td> - </tr> - {% if on_order > 0 %} - <tr> - <td><span class='fas fa-shopping-cart'></span></td> - <td>{% trans "On Order" %}</td> - <td>{% decimal on_order %}</td> - </tr> - {% endif %} - {% if required_build_order_quantity > 0 %} - <tr> - <td><span class='fas fa-clipboard-list'></span></td> - <td>{% trans "Required for Build Orders" %}</td> - <td>{% decimal required_build_order_quantity %} - </tr> - {% endif %} - {% if required_sales_order_quantity > 0 %} - <tr> - <td><span class='fas fa-clipboard-list'></span></td> - <td>{% trans "Required for Sales Orders" %}</td> - <td>{% decimal required_sales_order_quantity %} - </tr> - {% endif %} - {% if allocated > 0 %} - <tr> - <td><span class='fas fa-dolly'></span></td> - <td>{% trans "Allocated to Orders" %}</td> - <td>{% decimal allocated %}</td> - </tr> - {% endif %} - - {% if not part.is_template %} - {% if part.assembly %} - <tr> - <td><span class='fas fa-tools'></span></td> - <td colspan='2'> - <strong>{% trans "Build Status" %}</strong> - </td> - </tr> - <tr> - <td></td> - <td>{% trans "Can Build" %}</td> - <td>{% decimal part.can_build %}</td> - </tr> - {% if quantity_being_built > 0 %} - <tr> - <td></td> - <td>{% trans "Building" %}</td> - <td>{% decimal quantity_being_built %}</td> - </tr> - {% endif %} - {% endif %} - {% endif %} - </table> - </div> </div> + </div> {% block page_content %} @@ -450,4 +502,42 @@ }); {% endif %} + $("#toggle-part-details").click(function() { + if (this.value == 'show') { + this.innerHTML = '<span class="fas fa-chevron-up"></span> {% trans "Hide Part Details" %}'; + this.value = 'hide'; + // Store state of part details section + localStorage.setItem("part-details-show", true); + } else { + this.innerHTML = '<span class="fas fa-chevron-down"></span> {% 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 = '<span class="fas fa-chevron-up"></span> {% trans "Hide Part Details" %}'; + toggle.value = 'hide'; + } else { + // Get toggle + toggle = document.getElementById('toggle-part-details'); + // Change state of toggle + toggle.innerHTML = '<span class="fas fa-chevron-down"></span> {% trans "Show Part Details" %}'; + toggle.value = 'show'; + } + } + {% endblock %} \ No newline at end of file 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 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.