mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Refactor barcode endoint
- Moved code into 'barcode' directory
This commit is contained in:
		@@ -20,9 +20,6 @@ from .version import inventreeVersion, inventreeInstanceName
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from plugins import plugins as inventree_plugins
 | 
					from plugins import plugins as inventree_plugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Load barcode plugins
 | 
					 | 
				
			||||||
print("Loading barcode plugins")
 | 
					 | 
				
			||||||
barcode_plugins = inventree_plugins.load_barcode_plugins()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
print("Loading action plugins")
 | 
					print("Loading action plugins")
 | 
				
			||||||
action_plugins = inventree_plugins.load_action_plugins()
 | 
					action_plugins = inventree_plugins.load_action_plugins()
 | 
				
			||||||
@@ -100,66 +97,3 @@ class ActionPluginView(APIView):
 | 
				
			|||||||
            'error': _("No matching action found"),
 | 
					            'error': _("No matching action found"),
 | 
				
			||||||
            "action": action,
 | 
					            "action": action,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BarcodePluginView(APIView):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Endpoint for handling barcode scan requests.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Barcode data are decoded by the client application,
 | 
					 | 
				
			||||||
    and sent to this endpoint (as a JSON object) for validation.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    A barcode could follow the internal InvenTree barcode format,
 | 
					 | 
				
			||||||
    or it could match to a third-party barcode format (e.g. Digikey).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    permission_classes = [
 | 
					 | 
				
			||||||
        permissions.IsAuthenticated,
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def post(self, request, *args, **kwargs):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        response = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        barcode_data = request.data.get('barcode', None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        print("Barcode data:")
 | 
					 | 
				
			||||||
        print(barcode_data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if barcode_data is None:
 | 
					 | 
				
			||||||
            response['error'] = _('No barcode data provided')
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            # Look for a barcode plugin that knows how to handle the data
 | 
					 | 
				
			||||||
            for plugin_class in barcode_plugins:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                # Instantiate the plugin with the provided plugin data
 | 
					 | 
				
			||||||
                plugin = plugin_class(barcode_data)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if plugin.validate():
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    # Plugin should return a dict response
 | 
					 | 
				
			||||||
                    response = plugin.decode()
 | 
					 | 
				
			||||||
                    
 | 
					 | 
				
			||||||
                    if type(response) is dict:
 | 
					 | 
				
			||||||
                        if 'success' not in response.keys() and 'error' not in response.keys():
 | 
					 | 
				
			||||||
                            response['success'] = _('Barcode successfully decoded')
 | 
					 | 
				
			||||||
                    else:
 | 
					 | 
				
			||||||
                        response = {
 | 
					 | 
				
			||||||
                            'error': _('Barcode plugin returned incorrect response')
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    response['plugin'] = plugin.plugin_name()
 | 
					 | 
				
			||||||
                    response['hash'] = plugin.hash()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    break
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if 'error' not in response and 'success' not in response:
 | 
					 | 
				
			||||||
            response = {
 | 
					 | 
				
			||||||
                'error': _('Unknown barcode format'),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Include the original barcode data
 | 
					 | 
				
			||||||
        response['barcode_data'] = barcode_data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return Response(response)
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										44
									
								
								InvenTree/InvenTree/plugins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								InvenTree/InvenTree/plugins.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import importlib
 | 
				
			||||||
 | 
					import pkgutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_namespace(pkg):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return pkgutil.iter_modules(pkg.__path__, pkg.__name__ + ".")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_modules(pkg):
 | 
				
			||||||
 | 
					    # Return all modules in a given package
 | 
				
			||||||
 | 
					    return [importlib.import_module(name) for finder, name, ispkg in iter_namespace(pkg)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_classes(module):
 | 
				
			||||||
 | 
					    # Return all classes in a given module
 | 
				
			||||||
 | 
					    return inspect.getmembers(module, inspect.isclass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_plugins(pkg, baseclass):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Return a list of all modules under a given package.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - Modules must be a subclass of the provided 'baseclass'
 | 
				
			||||||
 | 
					    - Modules must have a non-empty PLUGIN_NAME parameter
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    plugins = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    modules = get_modules(pkg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Iterate through each module in the package
 | 
				
			||||||
 | 
					    for mod in modules:
 | 
				
			||||||
 | 
					        print("mod:", mod)
 | 
				
			||||||
 | 
					        # Iterate through each class in the module
 | 
				
			||||||
 | 
					        for item in get_classes(mod):
 | 
				
			||||||
 | 
					            plugin = item[1]
 | 
				
			||||||
 | 
					            if issubclass(plugin, baseclass) and plugin.PLUGIN_NAME:
 | 
				
			||||||
 | 
					                plugins.append(plugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return plugins
 | 
				
			||||||
@@ -20,6 +20,7 @@ from stock.urls import stock_urls
 | 
				
			|||||||
from build.urls import build_urls
 | 
					from build.urls import build_urls
 | 
				
			||||||
from order.urls import order_urls
 | 
					from order.urls import order_urls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from barcode.api import barcode_api_urls
 | 
				
			||||||
from common.api import common_api_urls
 | 
					from common.api import common_api_urls
 | 
				
			||||||
from part.api import part_api_urls, bom_api_urls
 | 
					from part.api import part_api_urls, bom_api_urls
 | 
				
			||||||
from company.api import company_api_urls
 | 
					from company.api import company_api_urls
 | 
				
			||||||
@@ -37,13 +38,15 @@ from .views import IndexView, SearchView, DatabaseStatsView
 | 
				
			|||||||
from .views import SettingsView, EditUserView, SetPasswordView
 | 
					from .views import SettingsView, EditUserView, SetPasswordView
 | 
				
			||||||
from .views import DynamicJsView
 | 
					from .views import DynamicJsView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .api import InfoView, BarcodePluginView, ActionPluginView
 | 
					from .api import InfoView
 | 
				
			||||||
 | 
					from .api import ActionPluginView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from users.urls import user_urls
 | 
					from users.urls import user_urls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
admin.site.site_header = "InvenTree Admin"
 | 
					admin.site.site_header = "InvenTree Admin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
apipatterns = [
 | 
					apipatterns = [
 | 
				
			||||||
 | 
					    url(r'^barcode/', include(barcode_api_urls)),
 | 
				
			||||||
    url(r'^common/', include(common_api_urls)),
 | 
					    url(r'^common/', include(common_api_urls)),
 | 
				
			||||||
    url(r'^part/', include(part_api_urls)),
 | 
					    url(r'^part/', include(part_api_urls)),
 | 
				
			||||||
    url(r'^bom/', include(bom_api_urls)),
 | 
					    url(r'^bom/', include(bom_api_urls)),
 | 
				
			||||||
@@ -56,7 +59,6 @@ apipatterns = [
 | 
				
			|||||||
    url(r'^user/', include(user_urls)),
 | 
					    url(r'^user/', include(user_urls)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Plugin endpoints
 | 
					    # Plugin endpoints
 | 
				
			||||||
    url(r'^barcode/', BarcodePluginView.as_view(), name='api-barcode-plugin'),
 | 
					 | 
				
			||||||
    url(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'),
 | 
					    url(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # InvenTree information endpoint
 | 
					    # InvenTree information endpoint
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										132
									
								
								InvenTree/barcode/api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								InvenTree/barcode/api.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,132 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
 | 
					from django.conf.urls import url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework.exceptions import ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework import permissions
 | 
				
			||||||
 | 
					from rest_framework.response import Response
 | 
				
			||||||
 | 
					from rest_framework.views import APIView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import InvenTree.plugins as InvenTreePlugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from stock.models import StockItem
 | 
				
			||||||
 | 
					from stock.serializers import StockItemSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import barcode.plugins as BarcodePlugins
 | 
				
			||||||
 | 
					from barcode.barcode import BarcodePlugin, load_barcode_plugins, hash_barcode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BarcodeScan(APIView):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Endpoint for handling generic barcode scan requests.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Barcode data are decoded by the client application,
 | 
				
			||||||
 | 
					    and sent to this endpoint (as a JSON object) for validation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    A barcode could follow the internal InvenTree barcode format,
 | 
				
			||||||
 | 
					    or it could match to a third-party barcode format (e.g. Digikey).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    When a barcode is sent to the server, the following parameters must be provided:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    - barcode: The raw barcode data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    plugins:
 | 
				
			||||||
 | 
					    Third-party barcode formats may be supported using 'plugins'
 | 
				
			||||||
 | 
					    (more information to follow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hashing:
 | 
				
			||||||
 | 
					    Barcode hashes are calculated using MD5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    permission_classes = [
 | 
				
			||||||
 | 
					        permissions.IsAuthenticated,
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def post(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Respond to a barcode POST request
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data = request.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 'barcode' not in data:
 | 
				
			||||||
 | 
					            raise ValidationError({'barcode': 'Must provide barcode_data parameter'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        plugins = load_barcode_plugins()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        barcode_data = data.get('barcode')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Look for a barcode plugin which knows how to deal with this barcode
 | 
				
			||||||
 | 
					        plugin = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for plugin_class in plugins:
 | 
				
			||||||
 | 
					            plugin_instance = plugin_class(barcode_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if plugin_instance.validate():
 | 
				
			||||||
 | 
					                plugin = plugin_instance
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match_found = False
 | 
				
			||||||
 | 
					        response = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response['barcode_data'] = barcode_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # A plugin has been found!
 | 
				
			||||||
 | 
					        if plugin is not None:
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Try to associate with a stock item
 | 
				
			||||||
 | 
					            item = plugin.getStockItem()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if item is not None:
 | 
				
			||||||
 | 
					                response['stockitem'] = plugin.renderStockItem(item)
 | 
				
			||||||
 | 
					                match_found = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Try to associate with a stock location
 | 
				
			||||||
 | 
					            loc = plugin.getStockLocation()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if loc is not None:
 | 
				
			||||||
 | 
					                response['stocklocation'] = plugin.renderStockLocation(loc)
 | 
				
			||||||
 | 
					                match_found = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Try to associate with a part
 | 
				
			||||||
 | 
					            part = plugin.getPart()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if part is not None:
 | 
				
			||||||
 | 
					                response['part'] = plugin.renderPart(part)
 | 
				
			||||||
 | 
					                match_found = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response['hash'] = plugin.hash()
 | 
				
			||||||
 | 
					            response['plugin'] = plugin.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # No plugin is found!
 | 
				
			||||||
 | 
					        # However, the hash of the barcode may still be associated with a StockItem!
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            hash = hash_barcode(barcode_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response['hash'] = hash
 | 
				
			||||||
 | 
					            response['plugin'] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Try to look for a matching StockItem
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                item = StockItem.objects.get(uid=hash)
 | 
				
			||||||
 | 
					                serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
 | 
				
			||||||
 | 
					                response['stockitem'] = serializer.data
 | 
				
			||||||
 | 
					                match_found = True
 | 
				
			||||||
 | 
					            except StockItem.DoesNotExist:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not match_found:
 | 
				
			||||||
 | 
					            response['error'] = 'No match found for barcode data'
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            response['success'] = 'Match found for barcode data'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Response(response)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					barcode_api_urls = [
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Catch-all performs barcode 'scan'
 | 
				
			||||||
 | 
					    url(r'^.*$', BarcodeScan.as_view(), name='api-barcode-plugin'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										126
									
								
								InvenTree/barcode/barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								InvenTree/barcode/barcode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import hashlib
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from InvenTree import plugins as InvenTreePlugins
 | 
				
			||||||
 | 
					from barcode import plugins as BarcodePlugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from stock.serializers import StockItemSerializer, LocationSerializer
 | 
				
			||||||
 | 
					from part.serializers import PartSerializer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def hash_barcode(barcode_data):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Calculate an MD5 hash of barcode data
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    hash = hashlib.md5(str(barcode_data).encode())
 | 
				
			||||||
 | 
					    return str(hash.hexdigest())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BarcodePlugin:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base class for barcode handling.
 | 
				
			||||||
 | 
					    Custom barcode plugins should extend this class as necessary.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Override the barcode plugin name for each sub-class
 | 
				
			||||||
 | 
					    PLUGIN_NAME = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def name(self):
 | 
				
			||||||
 | 
					        return self.PLUGIN_NAME
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, barcode_data):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.data = barcode_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getStockItem(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Attempt to retrieve a StockItem associated with this barcode.
 | 
				
			||||||
 | 
					        Default implementation returns None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def renderStockItem(self, item):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Render a stock item to JSON response
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
 | 
				
			||||||
 | 
					        return serializer.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getStockLocation(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Attempt to retrieve a StockLocation associated with this barcode.
 | 
				
			||||||
 | 
					        Default implementation returns None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def renderStockLocation(self, loc):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Render a stock location to a JSON response
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serializer = LocationSerializer(loc)
 | 
				
			||||||
 | 
					        return serializer.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getPart(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Attempt to retrieve a Part associated with this barcode.
 | 
				
			||||||
 | 
					        Default implementation returns None
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def renderPart(self, part):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Render a part to JSON response
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serializer = PartSerializer(part)
 | 
				
			||||||
 | 
					        return serializer.data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def hash(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Calculate a hash for the barcode data.
 | 
				
			||||||
 | 
					        This is supposed to uniquely identify the barcode contents,
 | 
				
			||||||
 | 
					        at least within the bardcode sub-type.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The default implementation simply returns an MD5 hash of the barcode data,
 | 
				
			||||||
 | 
					        encoded to a string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        This may be sufficient for most applications, but can obviously be overridden
 | 
				
			||||||
 | 
					        by a subclass.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return hash_barcode(self.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Default implementation returns False
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_barcode_plugins():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Function to load all barcode plugins
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print("Loading barcode plugins")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    plugins = InvenTreePlugins.get_plugins(BarcodePlugins, BarcodePlugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if len(plugins) > 0:
 | 
				
			||||||
 | 
					        print("Discovered {n} plugins:".format(n=len(plugins)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for p in plugins:
 | 
				
			||||||
 | 
					            print(" - {p}".format(p=p.PLUGIN_NAME))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return plugins
 | 
				
			||||||
							
								
								
									
										5
									
								
								InvenTree/barcode/plugins/digikey_barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								InvenTree/barcode/plugins/digikey_barcode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					DigiKey barcode decoding
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
							
								
								
									
										108
									
								
								InvenTree/barcode/plugins/inventree_barcode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								InvenTree/barcode/plugins/inventree_barcode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					The InvenTreeBarcodePlugin validates barcodes generated by InvenTree itself.
 | 
				
			||||||
 | 
					It can be used as a template for developing third-party barcode plugins.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The data format is very simple, and maps directly to database objects,
 | 
				
			||||||
 | 
					via the "id" parameter.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Parsing an InvenTree barcode simply involves validating that the
 | 
				
			||||||
 | 
					references model objects actually exist in the database.
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from barcode.barcode import BarcodePlugin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from stock.models import StockItem, StockLocation
 | 
				
			||||||
 | 
					from part.models import Part
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework.exceptions import ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InvenTreeBarcodePlugin(BarcodePlugin):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    PLUGIN_NAME = "InvenTreeBarcode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        An "InvenTree" barcode must be a jsonnable-dict with the following tags:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            'tool': 'InvenTree',
 | 
				
			||||||
 | 
					            'version': <anything>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The data must either be dict or be able to dictified
 | 
				
			||||||
 | 
					        if type(self.data) is dict:
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        elif type(self.data) is str:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                self.data = json.loads(self.data)
 | 
				
			||||||
 | 
					            except json.JSONDecodeError:
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in ['tool', 'version']:
 | 
				
			||||||
 | 
					            if key not in self.data.keys():
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not self.data['tool'] == 'InvenTree':
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getStockItem(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for k in self.data.keys():
 | 
				
			||||||
 | 
					            if k.lower() == 'stockitem':
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    pk = self.data[k]['id']
 | 
				
			||||||
 | 
					                except (AttributeError, KeyError):
 | 
				
			||||||
 | 
					                    raise ValidationError({k: "id parameter not supplied"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    item = StockItem.objects.get(pk=pk)
 | 
				
			||||||
 | 
					                    return item
 | 
				
			||||||
 | 
					                except (ValueError, StockItem.DoesNotExist):
 | 
				
			||||||
 | 
					                    raise ValidationError({k, "Stock item does not exist"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getStockLocation(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for k in self.data.keys():
 | 
				
			||||||
 | 
					            if k.lower() == 'stocklocation':
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    pk = self.data[k]['id']
 | 
				
			||||||
 | 
					                except (AttributeError, KeyError):
 | 
				
			||||||
 | 
					                    raise ValidationError({k: "id parameter not supplied"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    loc = StockLocation.objects.get(pk=pk)
 | 
				
			||||||
 | 
					                    return loc
 | 
				
			||||||
 | 
					                except (ValueError, StockLocation.DoesNotExist):
 | 
				
			||||||
 | 
					                    raise ValidationError({k, "Stock location does not exist"})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def getPart(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for k in self.data.keys():
 | 
				
			||||||
 | 
					            if k.lower() == 'part':
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    pk = self.data[k]['id']
 | 
				
			||||||
 | 
					                except (AttributeError, KeyError):
 | 
				
			||||||
 | 
					                    raise ValidationError({k, 'id parameter not supplied'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    part = Part.objects.get(pk=pk)
 | 
				
			||||||
 | 
					                    return part
 | 
				
			||||||
 | 
					                except (ValueError, Part.DoesNotExist):
 | 
				
			||||||
 | 
					                    raise ValidationError({k, 'Part does not exist'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
@@ -1,79 +0,0 @@
 | 
				
			|||||||
# -*- coding: utf-8 -*-
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import hashlib
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from stock.serializers import StockItemSerializer, LocationSerializer
 | 
					 | 
				
			||||||
from part.serializers import PartSerializer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import plugins.plugin as plugin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BarcodePlugin(plugin.InvenTreePlugin):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    The BarcodePlugin class is the base class for any barcode plugin.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, barcode_data):
 | 
					 | 
				
			||||||
        plugin.InvenTreePlugin.__init__(self)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self.data = barcode_data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def hash(self):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Calculate a hash for the barcode data.
 | 
					 | 
				
			||||||
        This is supposed to uniquely identify the barcode contents,
 | 
					 | 
				
			||||||
        at least within the bardcode sub-type.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        The default implementation simply returns an MD5 hash of the barcode data,
 | 
					 | 
				
			||||||
        encoded to a string.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        This may be sufficient for most applications, but can obviously be overridden
 | 
					 | 
				
			||||||
        by a subclass.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        hash = hashlib.md5(str(self.data).encode())
 | 
					 | 
				
			||||||
        return str(hash.hexdigest())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def validate(self):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Default implementation returns False
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def decode(self):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Decode the barcode, and craft a response
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def render_part(self, part):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Render a Part object to JSON
 | 
					 | 
				
			||||||
        Use the existing serializer to do this.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        serializer = PartSerializer(part)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return serializer.data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def render_stock_location(self, loc):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Render a StockLocation object to JSON
 | 
					 | 
				
			||||||
        Use the existing serializer to do this.
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        serializer = LocationSerializer(loc)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return serializer.data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def render_stock_item(self, item):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Render a StockItem object to JSON.
 | 
					 | 
				
			||||||
        Use the existing serializer to do this
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return serializer.data
 | 
					 | 
				
			||||||
@@ -1,8 +0,0 @@
 | 
				
			|||||||
# -*- coding: utf-8 -*-
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from . import barcode
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DigikeyBarcodePlugin(barcode.BarcodePlugin):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PLUGIN_NAME = "DigikeyBarcodePlugin"
 | 
					 | 
				
			||||||
@@ -1,94 +0,0 @@
 | 
				
			|||||||
"""
 | 
					 | 
				
			||||||
The InvenTreeBarcodePlugin validates barcodes generated by InvenTree itself.
 | 
					 | 
				
			||||||
It can be used as a template for developing third-party barcode plugins.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The data format is very simple, and maps directly to database objects,
 | 
					 | 
				
			||||||
via the "id" parameter.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Parsing an InvenTree barcode simply involves validating that the
 | 
					 | 
				
			||||||
references model objects actually exist in the database.
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import json
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from . import barcode
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from stock.models import StockItem, StockLocation
 | 
					 | 
				
			||||||
from part.models import Part
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from django.utils.translation import ugettext as _
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class InvenTreeBarcodePlugin(barcode.BarcodePlugin):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    PLUGIN_NAME = "InvenTreeBarcodePlugin"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def validate(self):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        An "InvenTree" barcode must include the following tags:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            'tool': 'InvenTree',
 | 
					 | 
				
			||||||
            'version': <anything>
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # The data must either be dict or be able to dictified
 | 
					 | 
				
			||||||
        if type(self.data) is dict:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        elif type(self.data) is str:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                self.data = json.loads(self.data)
 | 
					 | 
				
			||||||
            except json.JSONDecodeError:
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for key in ['tool', 'version']:
 | 
					 | 
				
			||||||
            if key not in self.data.keys():
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not self.data['tool'] == 'InvenTree':
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def decode(self):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        response = {}
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        if 'part' in self.data.keys():
 | 
					 | 
				
			||||||
            id = self.data['part'].get('id', None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                part = Part.objects.get(id=id)
 | 
					 | 
				
			||||||
                response['part'] = self.render_part(part)
 | 
					 | 
				
			||||||
            except (ValueError, Part.DoesNotExist):
 | 
					 | 
				
			||||||
                response['error'] = _('Part does not exist')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        elif 'stocklocation' in self.data.keys():
 | 
					 | 
				
			||||||
            id = self.data['stocklocation'].get('id', None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                loc = StockLocation.objects.get(id=id)
 | 
					 | 
				
			||||||
                response['stocklocation'] = self.render_stock_location(loc)
 | 
					 | 
				
			||||||
            except (ValueError, StockLocation.DoesNotExist):
 | 
					 | 
				
			||||||
                response['error'] = _('StockLocation does not exist')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        elif 'stockitem' in self.data.keys():
 | 
					 | 
				
			||||||
            
 | 
					 | 
				
			||||||
            id = self.data['stockitem'].get('id', None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                item = StockItem.objects.get(id=id)
 | 
					 | 
				
			||||||
                response['stockitem'] = self.render_stock_item(item)
 | 
					 | 
				
			||||||
            except (ValueError, StockItem.DoesNotExist):
 | 
					 | 
				
			||||||
                response['error'] = _('StockItem does not exist')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            response['error'] = _('No matching data')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return response
 | 
					 | 
				
			||||||
@@ -4,10 +4,6 @@ import inspect
 | 
				
			|||||||
import importlib
 | 
					import importlib
 | 
				
			||||||
import pkgutil
 | 
					import pkgutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Barcode plugins
 | 
					 | 
				
			||||||
import plugins.barcode as barcode
 | 
					 | 
				
			||||||
from plugins.barcode.barcode import BarcodePlugin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Action plugins
 | 
					# Action plugins
 | 
				
			||||||
import plugins.action as action
 | 
					import plugins.action as action
 | 
				
			||||||
from plugins.action.action import ActionPlugin
 | 
					from plugins.action.action import ActionPlugin
 | 
				
			||||||
@@ -51,23 +47,6 @@ def get_plugins(pkg, baseclass):
 | 
				
			|||||||
    return plugins
 | 
					    return plugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def load_barcode_plugins():
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Return a list of all registered barcode plugins
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    print("Loading barcode plugins")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    plugins = get_plugins(barcode, BarcodePlugin)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if len(plugins) > 0:
 | 
					 | 
				
			||||||
        print("Discovered {n} barcode plugins:".format(n=len(plugins)))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for bp in plugins:
 | 
					 | 
				
			||||||
            print(" - {bp}".format(bp=bp.PLUGIN_NAME))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return plugins
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def load_action_plugins():
 | 
					def load_action_plugins():
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user