mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge branch 'inventree:master' into matmair/issue2279
This commit is contained in:
commit
c7b6dc5929
@ -315,7 +315,7 @@ def WrapWithQuotes(text, quote='"'):
|
|||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
def MakeBarcode(object_name, object_pk, object_data={}, **kwargs):
|
def MakeBarcode(object_name, object_pk, object_data=None, **kwargs):
|
||||||
""" Generate a string for a barcode. Adds some global InvenTree parameters.
|
""" Generate a string for a barcode. Adds some global InvenTree parameters.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -327,6 +327,8 @@ def MakeBarcode(object_name, object_pk, object_data={}, **kwargs):
|
|||||||
Returns:
|
Returns:
|
||||||
json string of the supplied data plus some other data
|
json string of the supplied data plus some other data
|
||||||
"""
|
"""
|
||||||
|
if object_data is None:
|
||||||
|
object_data = {}
|
||||||
|
|
||||||
url = kwargs.get('url', False)
|
url = kwargs.get('url', False)
|
||||||
brief = kwargs.get('brief', True)
|
brief = kwargs.get('brief', True)
|
||||||
|
@ -109,14 +109,14 @@ class BarcodeScan(APIView):
|
|||||||
# No plugin is found!
|
# No plugin is found!
|
||||||
# However, the hash of the barcode may still be associated with a StockItem!
|
# However, the hash of the barcode may still be associated with a StockItem!
|
||||||
else:
|
else:
|
||||||
hash = hash_barcode(barcode_data)
|
result_hash = hash_barcode(barcode_data)
|
||||||
|
|
||||||
response['hash'] = hash
|
response['hash'] = result_hash
|
||||||
response['plugin'] = None
|
response['plugin'] = None
|
||||||
|
|
||||||
# Try to look for a matching StockItem
|
# Try to look for a matching StockItem
|
||||||
try:
|
try:
|
||||||
item = StockItem.objects.get(uid=hash)
|
item = StockItem.objects.get(uid=result_hash)
|
||||||
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
|
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
|
||||||
response['stockitem'] = serializer.data
|
response['stockitem'] = serializer.data
|
||||||
response['url'] = reverse('stock-item-detail', kwargs={'pk': item.id})
|
response['url'] = reverse('stock-item-detail', kwargs={'pk': item.id})
|
||||||
@ -182,8 +182,8 @@ class BarcodeAssign(APIView):
|
|||||||
# Matching plugin was found
|
# Matching plugin was found
|
||||||
if plugin is not None:
|
if plugin is not None:
|
||||||
|
|
||||||
hash = plugin.hash()
|
result_hash = plugin.hash()
|
||||||
response['hash'] = hash
|
response['hash'] = result_hash
|
||||||
response['plugin'] = plugin.name
|
response['plugin'] = plugin.name
|
||||||
|
|
||||||
# Ensure that the barcode does not already match a database entry
|
# Ensure that the barcode does not already match a database entry
|
||||||
@ -208,14 +208,14 @@ class BarcodeAssign(APIView):
|
|||||||
match_found = True
|
match_found = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
hash = hash_barcode(barcode_data)
|
result_hash = hash_barcode(barcode_data)
|
||||||
|
|
||||||
response['hash'] = hash
|
response['hash'] = result_hash
|
||||||
response['plugin'] = None
|
response['plugin'] = None
|
||||||
|
|
||||||
# Lookup stock item by hash
|
# Lookup stock item by hash
|
||||||
try:
|
try:
|
||||||
item = StockItem.objects.get(uid=hash)
|
item = StockItem.objects.get(uid=result_hash)
|
||||||
response['error'] = _('Barcode hash already matches Stock Item')
|
response['error'] = _('Barcode hash already matches Stock Item')
|
||||||
match_found = True
|
match_found = True
|
||||||
except StockItem.DoesNotExist:
|
except StockItem.DoesNotExist:
|
||||||
|
@ -124,12 +124,12 @@ class BarcodeAPITest(APITestCase):
|
|||||||
|
|
||||||
self.assertIn('success', data)
|
self.assertIn('success', data)
|
||||||
|
|
||||||
hash = data['hash']
|
result_hash = data['hash']
|
||||||
|
|
||||||
# Read the item out from the database again
|
# Read the item out from the database again
|
||||||
item = StockItem.objects.get(pk=522)
|
item = StockItem.objects.get(pk=522)
|
||||||
|
|
||||||
self.assertEqual(hash, item.uid)
|
self.assertEqual(result_hash, item.uid)
|
||||||
|
|
||||||
# Ensure that the same UID cannot be assigned to a different stock item!
|
# Ensure that the same UID cannot be assigned to a different stock item!
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
|
@ -193,7 +193,7 @@ class BuildOutputCompleteTest(BuildAPITest):
|
|||||||
self.assertTrue('accept_unallocated' in response.data)
|
self.assertTrue('accept_unallocated' in response.data)
|
||||||
|
|
||||||
# Accept unallocated stock
|
# Accept unallocated stock
|
||||||
response = self.post(
|
self.post(
|
||||||
finish_url,
|
finish_url,
|
||||||
{
|
{
|
||||||
'accept_unallocated': True,
|
'accept_unallocated': True,
|
||||||
|
@ -67,7 +67,6 @@ class WebhookView(CsrfExemptMixin, APIView):
|
|||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
|
|
||||||
# return results
|
|
||||||
data = self.webhook.get_return(payload, headers, request)
|
data = self.webhook.get_return(payload, headers, request)
|
||||||
return HttpResponse(data)
|
return HttpResponse(data)
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ import os
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
# from company.models import ManufacturerPart, SupplierPart
|
|
||||||
|
|
||||||
|
|
||||||
class FileManager:
|
class FileManager:
|
||||||
""" Class for managing an uploaded file """
|
""" Class for managing an uploaded file """
|
||||||
|
@ -1480,11 +1480,9 @@ class WebhookEndpoint(models.Model):
|
|||||||
|
|
||||||
def process_webhook(self):
|
def process_webhook(self):
|
||||||
if self.token:
|
if self.token:
|
||||||
self.token = self.token
|
|
||||||
self.verify = VerificationMethod.TOKEN
|
self.verify = VerificationMethod.TOKEN
|
||||||
# TODO make a object-setting
|
# TODO make a object-setting
|
||||||
if self.secret:
|
if self.secret:
|
||||||
self.secret = self.secret
|
|
||||||
self.verify = VerificationMethod.HMAC
|
self.verify = VerificationMethod.HMAC
|
||||||
# TODO make a object-setting
|
# TODO make a object-setting
|
||||||
return True
|
return True
|
||||||
@ -1494,6 +1492,7 @@ class WebhookEndpoint(models.Model):
|
|||||||
|
|
||||||
# no token
|
# no token
|
||||||
if self.verify == VerificationMethod.NONE:
|
if self.verify == VerificationMethod.NONE:
|
||||||
|
# do nothing as no method was chosen
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# static token
|
# static token
|
||||||
|
@ -10,6 +10,8 @@ from django.contrib.auth import get_user_model
|
|||||||
from .models import InvenTreeSetting, WebhookEndpoint, WebhookMessage, NotificationEntry
|
from .models import InvenTreeSetting, WebhookEndpoint, WebhookMessage, NotificationEntry
|
||||||
from .api import WebhookView
|
from .api import WebhookView
|
||||||
|
|
||||||
|
CONTENT_TYPE_JSON = 'application/json'
|
||||||
|
|
||||||
|
|
||||||
class SettingsTest(TestCase):
|
class SettingsTest(TestCase):
|
||||||
"""
|
"""
|
||||||
@ -105,7 +107,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
def test_missing_token(self):
|
def test_missing_token(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.FORBIDDEN
|
assert response.status_code == HTTPStatus.FORBIDDEN
|
||||||
@ -116,7 +118,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
def test_bad_token(self):
|
def test_bad_token(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
**{'HTTP_TOKEN': '1234567fghj'},
|
**{'HTTP_TOKEN': '1234567fghj'},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -126,7 +128,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
def test_bad_url(self):
|
def test_bad_url(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
'/api/webhook/1234/',
|
'/api/webhook/1234/',
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.NOT_FOUND
|
assert response.status_code == HTTPStatus.NOT_FOUND
|
||||||
@ -135,7 +137,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
data="{'this': 123}",
|
data="{'this': 123}",
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
**{'HTTP_TOKEN': str(self.endpoint_def.token)},
|
**{'HTTP_TOKEN': str(self.endpoint_def.token)},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -152,7 +154,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
# check
|
# check
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.OK
|
assert response.status_code == HTTPStatus.OK
|
||||||
@ -167,7 +169,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
# check
|
# check
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == HTTPStatus.FORBIDDEN
|
assert response.status_code == HTTPStatus.FORBIDDEN
|
||||||
@ -182,7 +184,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
# check
|
# check
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
**{'HTTP_TOKEN': str('68MXtc/OiXdA5e2Nq9hATEVrZFpLb3Zb0oau7n8s31I=')},
|
**{'HTTP_TOKEN': str('68MXtc/OiXdA5e2Nq9hATEVrZFpLb3Zb0oau7n8s31I=')},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -193,7 +195,7 @@ class WebhookMessageTests(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.url,
|
self.url,
|
||||||
data={"this": "is a message"},
|
data={"this": "is a message"},
|
||||||
content_type='application/json',
|
content_type=CONTENT_TYPE_JSON,
|
||||||
**{'HTTP_TOKEN': str(self.endpoint_def.token)},
|
**{'HTTP_TOKEN': str(self.endpoint_def.token)},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,5 +92,4 @@ class OrderMatchItemForm(MatchItemForm):
|
|||||||
default_amount=clean_decimal(row.get('purchase_price', '')),
|
default_amount=clean_decimal(row.get('purchase_price', '')),
|
||||||
)
|
)
|
||||||
|
|
||||||
# return default
|
|
||||||
return super().get_special_field(col_guess, row, file_manager)
|
return super().get_special_field(col_guess, row, file_manager)
|
||||||
|
@ -446,10 +446,10 @@ class PartSerialNumberDetail(generics.RetrieveAPIView):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if latest is not None:
|
if latest is not None:
|
||||||
next = increment(latest)
|
next_serial = increment(latest)
|
||||||
|
|
||||||
if next != increment:
|
if next_serial != increment:
|
||||||
data['next'] = next
|
data['next'] = next_serial
|
||||||
|
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
||||||
|
@ -75,7 +75,6 @@ class BomMatchItemForm(MatchItemForm):
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
# return default
|
|
||||||
return super().get_special_field(col_guess, row, file_manager)
|
return super().get_special_field(col_guess, row, file_manager)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1530,15 +1530,15 @@ class Part(MPTTModel):
|
|||||||
returns a string representation of a hash object which can be compared with a stored value
|
returns a string representation of a hash object which can be compared with a stored value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
hash = hashlib.md5(str(self.id).encode())
|
result_hash = hashlib.md5(str(self.id).encode())
|
||||||
|
|
||||||
# List *all* BOM items (including inherited ones!)
|
# List *all* BOM items (including inherited ones!)
|
||||||
bom_items = self.get_bom_items().all().prefetch_related('sub_part')
|
bom_items = self.get_bom_items().all().prefetch_related('sub_part')
|
||||||
|
|
||||||
for item in bom_items:
|
for item in bom_items:
|
||||||
hash.update(str(item.get_item_hash()).encode())
|
result_hash.update(str(item.get_item_hash()).encode())
|
||||||
|
|
||||||
return str(hash.digest())
|
return str(result_hash.digest())
|
||||||
|
|
||||||
def is_bom_valid(self):
|
def is_bom_valid(self):
|
||||||
""" Check if the BOM is 'valid' - if the calculated checksum matches the stored value
|
""" Check if the BOM is 'valid' - if the calculated checksum matches the stored value
|
||||||
@ -2188,9 +2188,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 created:
|
if not created:
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# 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
|
||||||
@ -2678,18 +2676,18 @@ class BomItem(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Seed the hash with the ID of this BOM item
|
# Seed the hash with the ID of this BOM item
|
||||||
hash = hashlib.md5(str(self.id).encode())
|
result_hash = hashlib.md5(str(self.id).encode())
|
||||||
|
|
||||||
# Update the hash based on line information
|
# Update the hash based on line information
|
||||||
hash.update(str(self.sub_part.id).encode())
|
result_hash.update(str(self.sub_part.id).encode())
|
||||||
hash.update(str(self.sub_part.full_name).encode())
|
result_hash.update(str(self.sub_part.full_name).encode())
|
||||||
hash.update(str(self.quantity).encode())
|
result_hash.update(str(self.quantity).encode())
|
||||||
hash.update(str(self.note).encode())
|
result_hash.update(str(self.note).encode())
|
||||||
hash.update(str(self.reference).encode())
|
result_hash.update(str(self.reference).encode())
|
||||||
hash.update(str(self.optional).encode())
|
result_hash.update(str(self.optional).encode())
|
||||||
hash.update(str(self.inherited).encode())
|
result_hash.update(str(self.inherited).encode())
|
||||||
|
|
||||||
return str(hash.digest())
|
return str(result_hash.digest())
|
||||||
|
|
||||||
def validate_hash(self, valid=True):
|
def validate_hash(self, valid=True):
|
||||||
""" Mark this item as 'valid' (store the checksum hash).
|
""" Mark this item as 'valid' (store the checksum hash).
|
||||||
|
@ -293,7 +293,7 @@ def progress_bar(val, max, *args, **kwargs):
|
|||||||
Render a progress bar element
|
Render a progress bar element
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = kwargs.get('id', 'progress-bar')
|
item_id = kwargs.get('id', 'progress-bar')
|
||||||
|
|
||||||
if val > max:
|
if val > max:
|
||||||
style = 'progress-bar-over'
|
style = 'progress-bar-over'
|
||||||
@ -317,7 +317,7 @@ def progress_bar(val, max, *args, **kwargs):
|
|||||||
style_tags.append(f'max-width: {max_width};')
|
style_tags.append(f'max-width: {max_width};')
|
||||||
|
|
||||||
html = f"""
|
html = f"""
|
||||||
<div id='{id}' class='progress' style='{" ".join(style_tags)}'>
|
<div id='{item_id}' class='progress' style='{" ".join(style_tags)}'>
|
||||||
<div class='progress-bar {style}' role='progressbar' aria-valuemin='0' aria-valuemax='100' style='width:{percent}%'></div>
|
<div class='progress-bar {style}' role='progressbar' aria-valuemin='0' aria-valuemax='100' style='width:{percent}%'></div>
|
||||||
<div class='progress-value'>{val} / {max}</div>
|
<div class='progress-value'>{val} / {max}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,8 +31,8 @@ class TemplateTagTest(TestCase):
|
|||||||
self.assertEqual(type(inventree_extras.inventree_version()), str)
|
self.assertEqual(type(inventree_extras.inventree_version()), str)
|
||||||
|
|
||||||
def test_hash(self):
|
def test_hash(self):
|
||||||
hash = inventree_extras.inventree_commit_hash()
|
result_hash = inventree_extras.inventree_commit_hash()
|
||||||
self.assertGreater(len(hash), 5)
|
self.assertGreater(len(result_hash), 5)
|
||||||
|
|
||||||
def test_date(self):
|
def test_date(self):
|
||||||
d = inventree_extras.inventree_commit_date()
|
d = inventree_extras.inventree_commit_date()
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class SupplierPartTest(TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
pass
|
|
@ -25,8 +25,8 @@ def hash_barcode(barcode_data):
|
|||||||
|
|
||||||
barcode_data = ''.join(list(printable_chars))
|
barcode_data = ''.join(list(printable_chars))
|
||||||
|
|
||||||
hash = hashlib.md5(str(barcode_data).encode())
|
result_hash = hashlib.md5(str(barcode_data).encode())
|
||||||
return str(hash.hexdigest())
|
return str(result_hash.hexdigest())
|
||||||
|
|
||||||
|
|
||||||
class BarcodeMixin:
|
class BarcodeMixin:
|
||||||
|
@ -173,8 +173,8 @@ class IntegrationPluginBase(MixinBase, plugin_base.InvenTreePluginBase):
|
|||||||
"""
|
"""
|
||||||
License of plugin
|
License of plugin
|
||||||
"""
|
"""
|
||||||
license = getattr(self, 'LICENSE', None)
|
lic = getattr(self, 'LICENSE', None)
|
||||||
return license
|
return lic
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -94,10 +94,8 @@ class PluginConfig(models.Model):
|
|||||||
ret = super().save(force_insert, force_update, *args, **kwargs)
|
ret = super().save(force_insert, force_update, *args, **kwargs)
|
||||||
|
|
||||||
if not reload:
|
if not reload:
|
||||||
if self.active is False and self.__org_active is True:
|
if (self.active is False and self.__org_active is True) or \
|
||||||
registry.reload_plugins()
|
(self.active is True and self.__org_active is False):
|
||||||
|
|
||||||
elif self.active is True and self.__org_active is False:
|
|
||||||
registry.reload_plugins()
|
registry.reload_plugins()
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
@ -390,6 +390,10 @@ class PluginsRegistry:
|
|||||||
logger.warning("activate_integration_schedule failed, database not ready")
|
logger.warning("activate_integration_schedule failed, database not ready")
|
||||||
|
|
||||||
def deactivate_integration_schedule(self):
|
def deactivate_integration_schedule(self):
|
||||||
|
"""
|
||||||
|
Deactivate ScheduleMixin
|
||||||
|
currently nothing is done
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def activate_integration_app(self, plugins, force_reload=False):
|
def activate_integration_app(self, plugins, force_reload=False):
|
||||||
|
@ -1022,7 +1022,7 @@ class StockItem(MPTTModel):
|
|||||||
def has_tracking_info(self):
|
def has_tracking_info(self):
|
||||||
return self.tracking_info_count > 0
|
return self.tracking_info_count > 0
|
||||||
|
|
||||||
def add_tracking_entry(self, entry_type, user, deltas={}, notes='', **kwargs):
|
def add_tracking_entry(self, entry_type, user, deltas=None, notes='', **kwargs):
|
||||||
"""
|
"""
|
||||||
Add a history tracking entry for this StockItem
|
Add a history tracking entry for this StockItem
|
||||||
|
|
||||||
@ -1033,6 +1033,8 @@ class StockItem(MPTTModel):
|
|||||||
notes - User notes associated with this tracking entry
|
notes - User notes associated with this tracking entry
|
||||||
url - Optional URL associated with this tracking entry
|
url - Optional URL associated with this tracking entry
|
||||||
"""
|
"""
|
||||||
|
if deltas is None:
|
||||||
|
deltas = {}
|
||||||
|
|
||||||
# Has a location been specified?
|
# Has a location been specified?
|
||||||
location = kwargs.get('location', None)
|
location = kwargs.get('location', None)
|
||||||
|
Loading…
Reference in New Issue
Block a user