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