mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'master' of https://github.com/inventree/InvenTree into add-report-check
This commit is contained in:
commit
b7087b83b5
@ -4,11 +4,15 @@ InvenTree API version information
|
|||||||
|
|
||||||
|
|
||||||
# InvenTree API version
|
# InvenTree API version
|
||||||
INVENTREE_API_VERSION = 46
|
INVENTREE_API_VERSION = 47
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
Increment this API version number whenever there is a significant change to the API that any clients need to know about
|
||||||
|
|
||||||
|
v47 -> 2022-05-10 : https://github.com/inventree/InvenTree/pull/2964
|
||||||
|
- Fixes barcode API error response when scanning a StockItem which does not exist
|
||||||
|
- Fixes barcode API error response when scanning a StockLocation which does not exist
|
||||||
|
|
||||||
v46 -> 2022-05-09
|
v46 -> 2022-05-09
|
||||||
- Fixes read permissions on settings API
|
- Fixes read permissions on settings API
|
||||||
- Allows non-staff users to read global settings via the API
|
- Allows non-staff users to read global settings via the API
|
||||||
|
@ -49,6 +49,8 @@ from InvenTree import validators
|
|||||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment, DataImportMixin
|
from InvenTree.models import InvenTreeTree, InvenTreeAttachment, DataImportMixin
|
||||||
from InvenTree.fields import InvenTreeURLField
|
from InvenTree.fields import InvenTreeURLField
|
||||||
from InvenTree.helpers import decimal2string, normalize, decimal2money
|
from InvenTree.helpers import decimal2string, normalize, decimal2money
|
||||||
|
|
||||||
|
import InvenTree.ready
|
||||||
import InvenTree.tasks
|
import InvenTree.tasks
|
||||||
|
|
||||||
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
|
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
|
||||||
@ -2292,7 +2294,7 @@ def after_save_part(sender, instance: Part, created, **kwargs):
|
|||||||
Function to be executed after a Part is saved
|
Function to be executed after a Part is saved
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not created:
|
if not created and not InvenTree.ready.isImportingData():
|
||||||
# Check part stock only if we are *updating* the part (not creating it)
|
# Check part stock only if we are *updating* the part (not creating it)
|
||||||
|
|
||||||
# Run this check in the background
|
# Run this check in the background
|
||||||
|
@ -84,7 +84,7 @@ class InvenTreeBarcodePlugin(BarcodeMixin, plugin.integration.IntegrationPluginB
|
|||||||
item = StockItem.objects.get(pk=pk)
|
item = StockItem.objects.get(pk=pk)
|
||||||
return item
|
return item
|
||||||
except (ValueError, StockItem.DoesNotExist): # pragma: no cover
|
except (ValueError, StockItem.DoesNotExist): # pragma: no cover
|
||||||
raise ValidationError({k, "Stock item does not exist"})
|
raise ValidationError({k: "Stock item does not exist"})
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ class InvenTreeBarcodePlugin(BarcodeMixin, plugin.integration.IntegrationPluginB
|
|||||||
loc = StockLocation.objects.get(pk=pk)
|
loc = StockLocation.objects.get(pk=pk)
|
||||||
return loc
|
return loc
|
||||||
except (ValueError, StockLocation.DoesNotExist): # pragma: no cover
|
except (ValueError, StockLocation.DoesNotExist): # pragma: no cover
|
||||||
raise ValidationError({k, "Stock location does not exist"})
|
raise ValidationError({k: "Stock location does not exist"})
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -133,12 +133,12 @@ class InvenTreeBarcodePlugin(BarcodeMixin, plugin.integration.IntegrationPluginB
|
|||||||
try:
|
try:
|
||||||
pk = self.data[k]['id']
|
pk = self.data[k]['id']
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
raise ValidationError({k, 'id parameter not supplied'})
|
raise ValidationError({k: 'id parameter not supplied'})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
part = Part.objects.get(pk=pk)
|
part = Part.objects.get(pk=pk)
|
||||||
return part
|
return part
|
||||||
except (ValueError, Part.DoesNotExist): # pragma: no cover
|
except (ValueError, Part.DoesNotExist): # pragma: no cover
|
||||||
raise ValidationError({k, 'Part does not exist'})
|
raise ValidationError({k: 'Part does not exist'})
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -17,7 +17,7 @@ from django.dispatch.dispatcher import receiver
|
|||||||
from common.models import InvenTreeSetting
|
from common.models import InvenTreeSetting
|
||||||
import common.notifications
|
import common.notifications
|
||||||
|
|
||||||
from InvenTree.ready import canAppAccessDatabase
|
from InvenTree.ready import canAppAccessDatabase, isImportingData
|
||||||
from InvenTree.tasks import offload_task
|
from InvenTree.tasks import offload_task
|
||||||
|
|
||||||
from plugin.registry import registry
|
from plugin.registry import registry
|
||||||
@ -113,6 +113,10 @@ def allow_table_event(table_name):
|
|||||||
We *do not* want events to be fired for some tables!
|
We *do not* want events to be fired for some tables!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if isImportingData():
|
||||||
|
# Prevent table events during the data import process
|
||||||
|
return False
|
||||||
|
|
||||||
table_name = table_name.lower().strip()
|
table_name = table_name.lower().strip()
|
||||||
|
|
||||||
# Ignore any tables which start with these prefixes
|
# Ignore any tables which start with these prefixes
|
||||||
|
@ -6,6 +6,6 @@
|
|||||||
|
|
||||||
<em>This location has no sublocations!</em>
|
<em>This location has no sublocations!</em>
|
||||||
<ul>
|
<ul>
|
||||||
<li><b>Location Name</b>: {{ location.name }}</li>
|
<li><strong>Location Name</strong>: {{ location.name }}</li>
|
||||||
<li><b>Location Path</b>: {{ location.pathstring }}</li>
|
<li><strong>Location Path</strong>: {{ location.pathstring }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -86,6 +86,22 @@ class BarcodeAPITest(APITestCase):
|
|||||||
self.assertIn('barcode_data', response.data)
|
self.assertIn('barcode_data', response.data)
|
||||||
self.assertEqual(response.data['part']['pk'], 1)
|
self.assertEqual(response.data['part']['pk'], 1)
|
||||||
|
|
||||||
|
def test_invalid_part(self):
|
||||||
|
"""Test response for invalid part"""
|
||||||
|
response = self.client.post(
|
||||||
|
self.scan_url,
|
||||||
|
{
|
||||||
|
'barcode': {
|
||||||
|
'part': 999999999,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
format='json'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['part'], 'Part does not exist')
|
||||||
|
|
||||||
def test_find_stock_item(self):
|
def test_find_stock_item(self):
|
||||||
"""
|
"""
|
||||||
Test that we can lookup a stock item based on ID
|
Test that we can lookup a stock item based on ID
|
||||||
@ -106,6 +122,23 @@ class BarcodeAPITest(APITestCase):
|
|||||||
self.assertIn('barcode_data', response.data)
|
self.assertIn('barcode_data', response.data)
|
||||||
self.assertEqual(response.data['stockitem']['pk'], 1)
|
self.assertEqual(response.data['stockitem']['pk'], 1)
|
||||||
|
|
||||||
|
def test_invalid_item(self):
|
||||||
|
"""Test response for invalid stock item"""
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
self.scan_url,
|
||||||
|
{
|
||||||
|
'barcode': {
|
||||||
|
'stockitem': 999999999,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
format='json'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['stockitem'], 'Stock item does not exist')
|
||||||
|
|
||||||
def test_find_location(self):
|
def test_find_location(self):
|
||||||
"""
|
"""
|
||||||
Test that we can lookup a stock location based on ID
|
Test that we can lookup a stock location based on ID
|
||||||
@ -126,6 +159,23 @@ class BarcodeAPITest(APITestCase):
|
|||||||
self.assertIn('barcode_data', response.data)
|
self.assertIn('barcode_data', response.data)
|
||||||
self.assertEqual(response.data['stocklocation']['pk'], 1)
|
self.assertEqual(response.data['stocklocation']['pk'], 1)
|
||||||
|
|
||||||
|
def test_invalid_location(self):
|
||||||
|
"""Test response for an invalid location"""
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
self.scan_url,
|
||||||
|
{
|
||||||
|
'barcode': {
|
||||||
|
'stocklocation': 999999999,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
format='json'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
|
self.assertEqual(response.data['stocklocation'], 'Stock location does not exist')
|
||||||
|
|
||||||
def test_integer_barcode(self):
|
def test_integer_barcode(self):
|
||||||
|
|
||||||
response = self.postBarcode(self.scan_url, '123456789')
|
response = self.postBarcode(self.scan_url, '123456789')
|
||||||
|
@ -30,7 +30,8 @@ from mptt.managers import TreeManager
|
|||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from InvenTree import helpers
|
import InvenTree.helpers
|
||||||
|
import InvenTree.ready
|
||||||
import InvenTree.tasks
|
import InvenTree.tasks
|
||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
@ -137,7 +138,7 @@ class StockLocation(InvenTreeTree):
|
|||||||
def format_barcode(self, **kwargs):
|
def format_barcode(self, **kwargs):
|
||||||
""" Return a JSON string for formatting a barcode for this StockLocation object """
|
""" Return a JSON string for formatting a barcode for this StockLocation object """
|
||||||
|
|
||||||
return helpers.MakeBarcode(
|
return InvenTree.helpers.MakeBarcode(
|
||||||
'stocklocation',
|
'stocklocation',
|
||||||
self.pk,
|
self.pk,
|
||||||
{
|
{
|
||||||
@ -577,7 +578,7 @@ class StockItem(MPTTModel):
|
|||||||
Voltagile data (e.g. stock quantity) should be looked up using the InvenTree API (as it may change)
|
Voltagile data (e.g. stock quantity) should be looked up using the InvenTree API (as it may change)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return helpers.MakeBarcode(
|
return InvenTree.helpers.MakeBarcode(
|
||||||
"stockitem",
|
"stockitem",
|
||||||
self.id,
|
self.id,
|
||||||
{
|
{
|
||||||
@ -1775,7 +1776,7 @@ class StockItem(MPTTModel):
|
|||||||
sn=self.serial)
|
sn=self.serial)
|
||||||
else:
|
else:
|
||||||
s = '{n} x {part}'.format(
|
s = '{n} x {part}'.format(
|
||||||
n=helpers.decimal2string(self.quantity),
|
n=InvenTree.helpers.decimal2string(self.quantity),
|
||||||
part=self.part.full_name)
|
part=self.part.full_name)
|
||||||
|
|
||||||
if self.location:
|
if self.location:
|
||||||
@ -1783,7 +1784,7 @@ class StockItem(MPTTModel):
|
|||||||
|
|
||||||
if self.purchase_order:
|
if self.purchase_order:
|
||||||
s += " ({pre}{po})".format(
|
s += " ({pre}{po})".format(
|
||||||
pre=helpers.getSetting("PURCHASEORDER_REFERENCE_PREFIX"),
|
pre=InvenTree.helpers.getSetting("PURCHASEORDER_REFERENCE_PREFIX"),
|
||||||
po=self.purchase_order,
|
po=self.purchase_order,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1851,7 +1852,7 @@ class StockItem(MPTTModel):
|
|||||||
result_map = {}
|
result_map = {}
|
||||||
|
|
||||||
for result in results:
|
for result in results:
|
||||||
key = helpers.generateTestKey(result.test)
|
key = InvenTree.helpers.generateTestKey(result.test)
|
||||||
result_map[key] = result
|
result_map[key] = result
|
||||||
|
|
||||||
# Do we wish to "cascade" and include test results from installed stock items?
|
# Do we wish to "cascade" and include test results from installed stock items?
|
||||||
@ -1898,7 +1899,7 @@ class StockItem(MPTTModel):
|
|||||||
failed = 0
|
failed = 0
|
||||||
|
|
||||||
for test in required:
|
for test in required:
|
||||||
key = helpers.generateTestKey(test.test_name)
|
key = InvenTree.helpers.generateTestKey(test.test_name)
|
||||||
|
|
||||||
if key in results:
|
if key in results:
|
||||||
result = results[key]
|
result = results[key]
|
||||||
@ -1949,7 +1950,7 @@ class StockItem(MPTTModel):
|
|||||||
|
|
||||||
# Attempt to validate report filter (skip if invalid)
|
# Attempt to validate report filter (skip if invalid)
|
||||||
try:
|
try:
|
||||||
filters = helpers.validateFilterString(test_report.filters)
|
filters = InvenTree.helpers.validateFilterString(test_report.filters)
|
||||||
if item_query.filter(**filters).exists():
|
if item_query.filter(**filters).exists():
|
||||||
reports.append(test_report)
|
reports.append(test_report)
|
||||||
except (ValidationError, FieldError):
|
except (ValidationError, FieldError):
|
||||||
@ -1977,7 +1978,7 @@ class StockItem(MPTTModel):
|
|||||||
for lbl in label.models.StockItemLabel.objects.filter(enabled=True):
|
for lbl in label.models.StockItemLabel.objects.filter(enabled=True):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
filters = helpers.validateFilterString(lbl.filters)
|
filters = InvenTree.helpers.validateFilterString(lbl.filters)
|
||||||
|
|
||||||
if item_query.filter(**filters).exists():
|
if item_query.filter(**filters).exists():
|
||||||
labels.append(lbl)
|
labels.append(lbl)
|
||||||
@ -2016,6 +2017,7 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs):
|
|||||||
Function to be executed after a StockItem object is deleted
|
Function to be executed after a StockItem object is deleted
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not InvenTree.ready.isImportingData():
|
||||||
# Run this check in the background
|
# Run this check in the background
|
||||||
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part)
|
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part)
|
||||||
|
|
||||||
@ -2026,6 +2028,7 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs):
|
|||||||
Hook function to be executed after StockItem object is saved/updated
|
Hook function to be executed after StockItem object is saved/updated
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if not InvenTree.ready.isImportingData():
|
||||||
# Run this check in the background
|
# Run this check in the background
|
||||||
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part)
|
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part)
|
||||||
|
|
||||||
@ -2170,7 +2173,7 @@ class StockItemTestResult(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self):
|
def key(self):
|
||||||
return helpers.generateTestKey(self.test)
|
return InvenTree.helpers.generateTestKey(self.test)
|
||||||
|
|
||||||
stock_item = models.ForeignKey(
|
stock_item = models.ForeignKey(
|
||||||
StockItem,
|
StockItem,
|
||||||
|
@ -1056,6 +1056,7 @@ function loadBuildOutputTable(build_info, options={}) {
|
|||||||
'{% url "api-stock-test-result-list" %}',
|
'{% url "api-stock-test-result-list" %}',
|
||||||
{
|
{
|
||||||
build: build_info.pk,
|
build: build_info.pk,
|
||||||
|
ordering: '-date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
success: function(results) {
|
success: function(results) {
|
||||||
|
@ -95,6 +95,9 @@ RUN echo "Downloading InvenTree from ${INVENTREE_GIT_REPO}"
|
|||||||
|
|
||||||
RUN git clone --branch ${INVENTREE_GIT_BRANCH} --depth 1 ${INVENTREE_GIT_REPO} ${INVENTREE_HOME}
|
RUN git clone --branch ${INVENTREE_GIT_BRANCH} --depth 1 ${INVENTREE_GIT_REPO} ${INVENTREE_HOME}
|
||||||
|
|
||||||
|
# Ref: https://github.blog/2022-04-12-git-security-vulnerability-announced/
|
||||||
|
RUN git config --global --add safe.directory ${INVENTREE_HOME}
|
||||||
|
|
||||||
# Checkout against a particular git tag
|
# Checkout against a particular git tag
|
||||||
RUN if [ -n "${INVENTREE_GIT_TAG}" ] ; then cd ${INVENTREE_HOME} && git fetch --all --tags && git checkout tags/${INVENTREE_GIT_TAG} -b v${INVENTREE_GIT_TAG}-branch ; fi
|
RUN if [ -n "${INVENTREE_GIT_TAG}" ] ; then cd ${INVENTREE_HOME} && git fetch --all --tags && git checkout tags/${INVENTREE_GIT_TAG} -b v${INVENTREE_GIT_TAG}-branch ; fi
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user