mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-04 14:10:52 +00:00
extend barcode scans API (#12233)
* extend barcode scans with user perm check * fix import * fix call * align error message * add missing permissions to test * remove erronous assign * ensure permission erros knock through
This commit is contained in:
@@ -23,6 +23,7 @@ from django_q.models import Task
|
||||
from error_report.models import Error
|
||||
from mptt.exceptions import InvalidMove
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from stdimage.models import StdImageField
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
@@ -1334,8 +1335,16 @@ class InvenTreeBarcodeMixin(models.Model):
|
||||
|
||||
return generate_barcode(self)
|
||||
|
||||
def format_matched_response(self):
|
||||
def format_matched_response(self, user, **kwargs):
|
||||
"""Format a standard response for a matched barcode."""
|
||||
# Check permission for this object
|
||||
from users.permissions import check_user_permission
|
||||
|
||||
if not check_user_permission(user, self, 'view'):
|
||||
raise PermissionDenied(
|
||||
_('User does not have permission to view this model')
|
||||
)
|
||||
|
||||
data = {'pk': self.pk}
|
||||
|
||||
if hasattr(self, 'get_api_url'):
|
||||
|
||||
@@ -153,7 +153,9 @@ class BarcodeView(CreateAPIView):
|
||||
|
||||
for current_plugin in plugins:
|
||||
try:
|
||||
result = current_plugin.scan(barcode)
|
||||
result = current_plugin.scan(barcode, user=request.user, **kwargs)
|
||||
except PermissionDenied as exc:
|
||||
raise exc
|
||||
except Exception:
|
||||
log_error('BarcodeView.scan_barcode', plugin=current_plugin.slug)
|
||||
continue
|
||||
@@ -282,7 +284,7 @@ class BarcodeAssign(BarcodeView):
|
||||
|
||||
# First check if the provided barcode matches an existing database entry
|
||||
if inventree_barcode_plugin:
|
||||
result = inventree_barcode_plugin.scan(barcode)
|
||||
result = inventree_barcode_plugin.scan(barcode, user=request.user, **kwargs)
|
||||
|
||||
if result is not None:
|
||||
result['error'] = _('Barcode matches existing item')
|
||||
@@ -459,7 +461,9 @@ class BarcodePOAllocate(BarcodeView):
|
||||
manufacturer_part=response.get('manufacturerpart', None),
|
||||
)
|
||||
response['success'] = _('Matched supplier part')
|
||||
response['supplierpart'] = supplier_part.format_matched_response()
|
||||
response['supplierpart'] = supplier_part.format_matched_response(
|
||||
user=request.user
|
||||
)
|
||||
except ValidationError as e:
|
||||
response['error'] = str(e)
|
||||
|
||||
@@ -524,7 +528,7 @@ class BarcodePOReceive(BarcodeView):
|
||||
filter(lambda plugin: plugin.name == 'InvenTreeBarcode', plugins)
|
||||
)
|
||||
|
||||
if result := internal_barcode_plugin.scan(barcode):
|
||||
if result := internal_barcode_plugin.scan(barcode, user=request.user, **kwargs):
|
||||
if 'stockitem' in result:
|
||||
response['error'] = _('Item has already been received')
|
||||
self.log_scan(request, response, False)
|
||||
|
||||
@@ -42,7 +42,7 @@ class BarcodeMixin:
|
||||
"""Does this plugin have everything needed to process a barcode."""
|
||||
return True
|
||||
|
||||
def scan(self, barcode_data):
|
||||
def scan(self, barcode_data: str, user, **kwargs) -> dict | None:
|
||||
"""Scan a barcode against this plugin.
|
||||
|
||||
This method is explicitly called from the /scan/ API endpoint,
|
||||
@@ -261,7 +261,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
||||
'extract_barcode_fields must be implemented by each plugin'
|
||||
)
|
||||
|
||||
def scan(self, barcode_data: str) -> dict | None:
|
||||
def scan(self, barcode_data: str, user, **kwargs) -> dict | None:
|
||||
"""Perform a generic 'scan' operation on a supplier barcode.
|
||||
|
||||
The supplier barcode may provide sufficient information to match against
|
||||
@@ -297,7 +297,7 @@ class SupplierBarcodeMixin(BarcodeMixin):
|
||||
for k, v in matches.items():
|
||||
if v and hasattr(v, 'pk'):
|
||||
has_match = True
|
||||
data[k] = v.format_matched_response()
|
||||
data[k] = v.format_matched_response(user=user)
|
||||
|
||||
if not has_match:
|
||||
return None
|
||||
|
||||
@@ -15,6 +15,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
|
||||
"""Tests for barcode api."""
|
||||
|
||||
fixtures = ['category', 'part', 'location', 'stock']
|
||||
roles = ['stock.view', 'stock_location.view', 'part.view']
|
||||
|
||||
def setUp(self):
|
||||
"""Setup for all tests."""
|
||||
@@ -259,6 +260,7 @@ class SOAllocateTest(InvenTreeAPITestCase):
|
||||
"""Unit tests for the barcode endpoint for allocating items to a sales order."""
|
||||
|
||||
fixtures = ['category', 'company', 'part', 'location', 'stock']
|
||||
roles = ['stock.view']
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -343,10 +345,14 @@ class SOAllocateTest(InvenTreeAPITestCase):
|
||||
# Test with barcode which points to a *part* instance
|
||||
item.part.assign_barcode(barcode_data='abcde')
|
||||
|
||||
# missing permission for viewing the part - error
|
||||
self.postBarcode('abcde', sales_order=self.sales_order.pk, expected_code=403)
|
||||
|
||||
# Add part.view role and test again
|
||||
self.assignRole('part.view')
|
||||
result = self.postBarcode(
|
||||
'abcde', sales_order=self.sales_order.pk, expected_code=400
|
||||
)
|
||||
|
||||
self.assertIn('does not match an existing stock item', str(result['error']))
|
||||
|
||||
def test_submit(self):
|
||||
|
||||
@@ -48,11 +48,11 @@ class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugi
|
||||
},
|
||||
}
|
||||
|
||||
def format_matched_response(self, label, model, instance):
|
||||
def format_matched_response(self, label, model, instance, user, **kwargs):
|
||||
"""Format a response for the scanned data."""
|
||||
return {label: instance.format_matched_response()}
|
||||
return {label: instance.format_matched_response(user=user, **kwargs)}
|
||||
|
||||
def scan(self, barcode_data):
|
||||
def scan(self, barcode_data, user, **kwargs):
|
||||
"""Scan a barcode against this plugin.
|
||||
|
||||
Here we are looking for a dict object which contains a reference to a particular InvenTree database object
|
||||
@@ -79,7 +79,7 @@ class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugi
|
||||
|
||||
try:
|
||||
instance = model.objects.get(pk=int(pk))
|
||||
return self.format_matched_response(label, model, instance)
|
||||
return self.format_matched_response(label, model, instance, user=user)
|
||||
except (ValueError, model.DoesNotExist):
|
||||
pass
|
||||
|
||||
@@ -111,7 +111,9 @@ class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugi
|
||||
instance = model.objects.get(pk=pk)
|
||||
|
||||
return {
|
||||
**self.format_matched_response(label, model, instance),
|
||||
**self.format_matched_response(
|
||||
label, model, instance, user=user
|
||||
),
|
||||
'success': succcess_message,
|
||||
}
|
||||
except (ValueError, model.DoesNotExist):
|
||||
@@ -129,7 +131,7 @@ class InvenTreeInternalBarcodePlugin(SettingsMixin, BarcodeMixin, InvenTreePlugi
|
||||
|
||||
if instance is not None:
|
||||
return {
|
||||
**self.format_matched_response(label, model, instance),
|
||||
**self.format_matched_response(label, model, instance, user=user),
|
||||
'success': succcess_message,
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
"""Tests for the integrated InvenTreeBarcode barcode plugin."""
|
||||
|
||||
fixtures = ['category', 'part', 'location', 'stock', 'company', 'supplier_part']
|
||||
roles = ['stock.view', 'stock_location.view', 'part.view']
|
||||
|
||||
def setUp(self):
|
||||
"""Set up the test case."""
|
||||
|
||||
@@ -14,6 +14,13 @@ class SupplierBarcodeTests(InvenTreeAPITestCase):
|
||||
"""Tests barcode parsing for all suppliers."""
|
||||
|
||||
SCAN_URL = reverse('api-barcode-scan')
|
||||
roles = [
|
||||
'stock.view',
|
||||
'stock_location.view',
|
||||
'part.view',
|
||||
'company.view',
|
||||
'order.view',
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -176,6 +183,8 @@ class SupplierBarcodeTests(InvenTreeAPITestCase):
|
||||
class SupplierBarcodePOReceiveTests(InvenTreeAPITestCase):
|
||||
"""Tests barcode scanning to receive a purchase order item."""
|
||||
|
||||
roles = ['stock.view', 'stock_location.view']
|
||||
|
||||
def setUp(self):
|
||||
"""Create supplier part and purchase_order."""
|
||||
super().setUp()
|
||||
|
||||
Reference in New Issue
Block a user