\d+)/', include([
- re_path(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'),
re_path(r'^install/', StockItemInstall.as_view(), name='api-stock-item-install'),
+ re_path(r'^metadata/', StockMetadata.as_view(), name='api-stock-item-metadata'),
+ re_path(r'^serialize/', StockItemSerialize.as_view(), name='api-stock-item-serialize'),
re_path(r'^uninstall/', StockItemUninstall.as_view(), name='api-stock-item-uninstall'),
re_path(r'^.*$', StockDetail.as_view(), name='api-stock-detail'),
])),
diff --git a/InvenTree/stock/migrations/0075_auto_20220515_1440.py b/InvenTree/stock/migrations/0075_auto_20220515_1440.py
new file mode 100644
index 0000000000..814a97edb3
--- /dev/null
+++ b/InvenTree/stock/migrations/0075_auto_20220515_1440.py
@@ -0,0 +1,27 @@
+# Generated by Django 3.2.13 on 2022-05-15 14:40
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('stock', '0074_alter_stockitem_batch'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='stockitem',
+ name='metadata',
+ field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
+ ),
+ migrations.AddField(
+ model_name='stocklocation',
+ name='metadata',
+ field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='stocklocation',
+ unique_together=set(),
+ ),
+ ]
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index a46d43b007..f63f4c9241 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -38,6 +38,7 @@ import common.models
import report.models
import label.models
+from plugin.models import MetadataMixin
from plugin.events import trigger_event
from InvenTree.status_codes import StockStatus, StockHistoryCode
@@ -51,7 +52,7 @@ from company import models as CompanyModels
from part import models as PartModels
-class StockLocation(InvenTreeTree):
+class StockLocation(MetadataMixin, InvenTreeTree):
""" Organization tree for StockItem objects
A "StockLocation" can be considered a warehouse, or storage location
Stock locations can be heirarchical as required
@@ -242,7 +243,7 @@ def generate_batch_code():
return Template(batch_template).render(context)
-class StockItem(MPTTModel):
+class StockItem(MetadataMixin, MPTTModel):
"""
A StockItem object represents a quantity of physical instances of a part.
@@ -404,7 +405,7 @@ class StockItem(MPTTModel):
deltas = {}
# Status changed?
- if not old.status == self.status:
+ if old.status != self.status:
deltas['status'] = self.status
# TODO - Other interesting changes we are interested in...
@@ -493,7 +494,7 @@ class StockItem(MPTTModel):
try:
if self.part.trackable:
# Trackable parts must have integer values for quantity field!
- if not self.quantity == int(self.quantity):
+ if self.quantity != int(self.quantity):
raise ValidationError({
'quantity': _('Quantity must be integer value for trackable parts')
})
@@ -511,7 +512,7 @@ class StockItem(MPTTModel):
# The 'supplier_part' field must point to the same part!
try:
if self.supplier_part is not None:
- if not self.supplier_part.part == self.part:
+ if self.supplier_part.part != self.part:
raise ValidationError({'supplier_part': _("Part type ('{pf}') must be {pe}").format(
pf=str(self.supplier_part.part),
pe=str(self.part))
@@ -1321,10 +1322,10 @@ class StockItem(MPTTModel):
if quantity > self.quantity:
raise ValidationError({"quantity": _("Quantity must not exceed available stock quantity ({n})").format(n=self.quantity)})
- if not type(serials) in [list, tuple]:
+ if type(serials) not in [list, tuple]:
raise ValidationError({"serial_numbers": _("Serial numbers must be a list of integers")})
- if not quantity == len(serials):
+ if quantity != len(serials):
raise ValidationError({"quantity": _("Quantity does not match serial numbers")})
# Test if each of the serial numbers are valid
diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html
index 944e432026..da4b832266 100644
--- a/InvenTree/stock/templates/stock/item_base.html
+++ b/InvenTree/stock/templates/stock/item_base.html
@@ -1,5 +1,6 @@
{% extends "page_base.html" %}
{% load static %}
+{% load plugin_extras %}
{% load inventree_extras %}
{% load status_codes %}
{% load i18n %}
@@ -18,7 +19,6 @@
{% endblock breadcrumb_tree %}
-
{% block heading %}
{% trans "Stock Item" %}: {{ item.part.full_name}}
{% endblock heading %}
@@ -29,6 +29,12 @@
{% url 'admin:stock_stockitem_change' item.pk as url %}
{% include "admin_button.html" with url=url %}
{% endif %}
+{% mixin_available "locate" as locate_available %}
+{% if plugins_enabled and locate_available %}
+
+{% endif %}
{% if barcodes %}
@@ -514,6 +520,14 @@ $("#barcode-scan-into-location").click(function() {
});
});
+{% if plugins_enabled %}
+$('#locate-item-button').click(function() {
+ locateItemOrLocation({
+ item: {{ item.pk }},
+ });
+});
+{% endif %}
+
function itemAdjust(action) {
inventreeGet(
diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html
index 61320a2676..1066adf6ea 100644
--- a/InvenTree/stock/templates/stock/location.html
+++ b/InvenTree/stock/templates/stock/location.html
@@ -1,6 +1,7 @@
{% extends "stock/stock_app_base.html" %}
{% load static %}
{% load inventree_extras %}
+{% load plugin_extras %}
{% load i18n %}
{% block sidebar %}
@@ -27,6 +28,14 @@
{% include "admin_button.html" with url=url %}
{% endif %}
+{% mixin_available "locate" as locate_available %}
+{% if location and plugins_enabled and locate_available %}
+
+
+{% endif %}
+
{% if barcodes %}
{% if location %}
@@ -206,6 +215,14 @@
{% block js_ready %}
{{ block.super }}
+ {% if plugins_enabled and location %}
+ $('#locate-location-button').click(function() {
+ locateItemOrLocation({
+ location: {{ location.pk }},
+ });
+ });
+ {% endif %}
+
onPanelLoad('sublocations', function() {
loadStockLocationTable($('#sublocation-table'), {
params: {
diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html
index 0d8272892a..795a5679aa 100644
--- a/InvenTree/templates/base.html
+++ b/InvenTree/templates/base.html
@@ -2,6 +2,7 @@
{% load i18n %}
{% load inventree_extras %}
+{% plugins_enabled as plugins_enabled %}
{% settings_value 'BARCODE_ENABLE' as barcodes %}
{% settings_value 'REPORT_ENABLE_TEST_REPORT' as test_report_enabled %}
{% settings_value "REPORT_ENABLE" as report_enabled %}
diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js
index d19c403861..388509c8bf 100644
--- a/InvenTree/templates/js/translated/label.js
+++ b/InvenTree/templates/js/translated/label.js
@@ -236,17 +236,13 @@ function selectLabel(labels, items, options={}) {
if (plugins_enabled) {
inventreeGet(
`/api/plugin/`,
- {},
+ {
+ mixin: 'labels',
+ },
{
async: false,
success: function(response) {
- response.forEach(function(plugin) {
- // Look for active plugins which implement the 'labels' mixin class
- if (plugin.active && plugin.mixins && plugin.mixins.labels) {
- // This plugin supports label printing
- plugins.push(plugin);
- }
- });
+ plugins = response;
}
}
);
diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js
index 372dc70a9a..e2bee865fd 100644
--- a/InvenTree/templates/js/translated/order.js
+++ b/InvenTree/templates/js/translated/order.js
@@ -1568,23 +1568,10 @@ function loadPurchaseOrderLineItemTable(table, options={}) {
$(table).find('.button-line-edit').click(function() {
var pk = $(this).attr('pk');
+ var fields = poLineItemFields(options);
+
constructForm(`/api/order/po-line/${pk}/`, {
- fields: {
- part: {
- filters: {
- part_detail: true,
- supplier_detail: true,
- supplier: options.supplier,
- }
- },
- quantity: {},
- reference: {},
- purchase_price: {},
- purchase_price_currency: {},
- target_date: {},
- destination: {},
- notes: {},
- },
+ fields: fields,
title: '{% trans "Edit Line Item" %}',
onSuccess: function() {
$(table).bootstrapTable('refresh');
diff --git a/InvenTree/templates/js/translated/plugin.js b/InvenTree/templates/js/translated/plugin.js
index c612dd1e8c..62555c8ff4 100644
--- a/InvenTree/templates/js/translated/plugin.js
+++ b/InvenTree/templates/js/translated/plugin.js
@@ -7,6 +7,7 @@
/* exported
installPlugin,
+ locateItemOrLocation
*/
function installPlugin() {
@@ -24,3 +25,50 @@ function installPlugin() {
}
});
}
+
+
+function locateItemOrLocation(options={}) {
+
+ if (!options.item && !options.location) {
+ console.error(`locateItemOrLocation: Either 'item' or 'location' must be provided!`);
+ return;
+ }
+
+ function performLocate(plugin) {
+ inventreePut(
+ '{% url "api-locate-plugin" %}',
+ {
+ plugin: plugin,
+ item: options.item,
+ location: options.location,
+ },
+ {
+ method: 'POST',
+ },
+ );
+ }
+
+ // Request the list of available 'locate' plugins
+ inventreeGet(
+ `/api/plugin/`,
+ {
+ mixin: 'locate',
+ },
+ {
+ success: function(plugins) {
+ // No 'locate' plugins are available!
+ if (plugins.length == 0) {
+ console.warn(`No 'locate' plugins are available`);
+ } else if (plugins.length == 1) {
+ // Only a single locate plugin is available
+ performLocate(plugins[0].key);
+ } else {
+ // More than 1 location plugin available
+ // Select from a list
+ }
+ }
+ },
+ );
+}
+
+
diff --git a/InvenTree/templates/sample/sample.html b/InvenTree/templates/sample/sample.html
new file mode 100644
index 0000000000..0db05e3152
--- /dev/null
+++ b/InvenTree/templates/sample/sample.html
@@ -0,0 +1 @@
+
{{abc}}
\ No newline at end of file
diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py
index 7c5c406687..7ed689f4a9 100644
--- a/InvenTree/users/models.py
+++ b/InvenTree/users/models.py
@@ -648,36 +648,6 @@ class Owner(models.Model):
owner_type=content_type_id)
except Owner.DoesNotExist:
pass
- else:
- # Check whether user_or_group is a Group instance
- try:
- group = Group.objects.get(pk=user_or_group.id)
- except Group.DoesNotExist:
- group = None
-
- if group:
- try:
- owner = Owner.objects.get(owner_id=user_or_group.id,
- owner_type=content_type_id_list[0])
- except Owner.DoesNotExist:
- pass
-
- return owner
-
- # Check whether user_or_group is a User instance
- try:
- user = user_model.objects.get(pk=user_or_group.id)
- except user_model.DoesNotExist:
- user = None
-
- if user:
- try:
- owner = Owner.objects.get(owner_id=user_or_group.id,
- owner_type=content_type_id_list[1])
- except Owner.DoesNotExist:
- pass
-
- return owner
return owner
diff --git a/ci/check_version_number.py b/ci/check_version_number.py
index 0514854407..b071afbe86 100644
--- a/ci/check_version_number.py
+++ b/ci/check_version_number.py
@@ -25,7 +25,7 @@ if __name__ == '__main__':
# Extract the InvenTree software version
results = re.findall(r'INVENTREE_SW_VERSION = "(.*)"', text)
- if not len(results) == 1:
+ if len(results) != 1:
print(f"Could not find INVENTREE_SW_VERSION in {version_file}")
sys.exit(1)
@@ -91,7 +91,7 @@ if __name__ == '__main__':
sys.exit(1)
if args.tag:
- if not args.tag == version:
+ if args.tag != version:
print(f"Release tag '{args.tag}' does not match INVENTREE_SW_VERSION '{version}'")
sys.exit(1)