mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Adds a metadata serializer class for accessing instance metadata via the API
- Adds endpoint for Part - Adds endpoint for PartCategory - Adds endpoint for StockItem - Adds endpoint for StockLocation
This commit is contained in:
		@@ -44,6 +44,7 @@ from stock.models import StockItem, StockLocation
 | 
			
		||||
from common.models import InvenTreeSetting
 | 
			
		||||
from build.models import Build, BuildItem
 | 
			
		||||
import order.models
 | 
			
		||||
from plugin.serializers import MetadataSerializer
 | 
			
		||||
 | 
			
		||||
from . import serializers as part_serializers
 | 
			
		||||
 | 
			
		||||
@@ -203,6 +204,15 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
 | 
			
		||||
        return response
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CategoryMetadata(generics.RetrieveUpdateAPIView):
 | 
			
		||||
    """API endpoint for viewing / updating PartCategory metadata"""
 | 
			
		||||
 | 
			
		||||
    def get_serializer(self, *args, **kwargs):
 | 
			
		||||
        return MetadataSerializer(PartCategory, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    queryset = PartCategory.objects.all()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CategoryParameterList(generics.ListAPIView):
 | 
			
		||||
    """ API endpoint for accessing a list of PartCategoryParameterTemplate objects.
 | 
			
		||||
 | 
			
		||||
@@ -587,6 +597,17 @@ class PartScheduling(generics.RetrieveAPIView):
 | 
			
		||||
        return Response(schedule)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartMetadata(generics.RetrieveUpdateAPIView):
 | 
			
		||||
    """
 | 
			
		||||
    API endpoint for viewing / updating Part metadata
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def get_serializer(self, *args, **kwargs):
 | 
			
		||||
        return MetadataSerializer(Part, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    queryset = Part.objects.all()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartSerialNumberDetail(generics.RetrieveAPIView):
 | 
			
		||||
    """
 | 
			
		||||
    API endpoint for returning extra serial number information about a particular part
 | 
			
		||||
@@ -1912,7 +1933,15 @@ part_api_urls = [
 | 
			
		||||
        re_path(r'^tree/', CategoryTree.as_view(), name='api-part-category-tree'),
 | 
			
		||||
        re_path(r'^parameters/', CategoryParameterList.as_view(), name='api-part-category-parameter-list'),
 | 
			
		||||
 | 
			
		||||
        re_path(r'^(?P<pk>\d+)/?', CategoryDetail.as_view(), name='api-part-category-detail'),
 | 
			
		||||
        # Category detail endpoints
 | 
			
		||||
        re_path(r'^(?P<pk>\d+)/', include([
 | 
			
		||||
 | 
			
		||||
            re_path(r'^metadata/', CategoryMetadata.as_view(), name='api-part-category-metadata'),
 | 
			
		||||
 | 
			
		||||
            # PartCategory detail endpoint
 | 
			
		||||
            re_path(r'^.*$', CategoryDetail.as_view(), name='api-part-category-detail'),
 | 
			
		||||
        ])),
 | 
			
		||||
 | 
			
		||||
        path('', CategoryList.as_view(), name='api-part-category-list'),
 | 
			
		||||
    ])),
 | 
			
		||||
 | 
			
		||||
@@ -1973,6 +2002,9 @@ part_api_urls = [
 | 
			
		||||
        # Endpoint for validating a BOM for the specific Part
 | 
			
		||||
        re_path(r'^bom-validate/', PartValidateBOM.as_view(), name='api-part-bom-validate'),
 | 
			
		||||
 | 
			
		||||
        # Part metadata
 | 
			
		||||
        re_path(r'^metadata/', PartMetadata.as_view(), name='api-part-metadata'),
 | 
			
		||||
 | 
			
		||||
        # Part detail endpoint
 | 
			
		||||
        re_path(r'^.*$', PartDetail.as_view(), name='api-part-detail'),
 | 
			
		||||
    ])),
 | 
			
		||||
 
 | 
			
		||||
@@ -1021,6 +1021,59 @@ class PartDetailTests(InvenTreeAPITestCase):
 | 
			
		||||
        self.assertEqual(data['in_stock'], 9000)
 | 
			
		||||
        self.assertEqual(data['unallocated_stock'], 9000)
 | 
			
		||||
 | 
			
		||||
    def test_part_metadata(self):
 | 
			
		||||
        """
 | 
			
		||||
        Tests for the part metadata endpoint
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        url = reverse('api-part-metadata', kwargs={'pk': 1})
 | 
			
		||||
 | 
			
		||||
        part = Part.objects.get(pk=1)
 | 
			
		||||
 | 
			
		||||
        # Metadata is initially null
 | 
			
		||||
        self.assertIsNone(part.metadata)
 | 
			
		||||
 | 
			
		||||
        part.metadata = {'foo': 'bar'}
 | 
			
		||||
        part.save()
 | 
			
		||||
 | 
			
		||||
        response = self.get(url, expected_code=200)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(response.data['metadata']['foo'], 'bar')
 | 
			
		||||
 | 
			
		||||
        # Add more data via the API
 | 
			
		||||
        # Using the 'patch' method causes the new data to be merged in
 | 
			
		||||
        self.patch(
 | 
			
		||||
            url,
 | 
			
		||||
            {
 | 
			
		||||
                'metadata': {
 | 
			
		||||
                    'hello': 'world',
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            expected_code=200
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        part.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(part.metadata['foo'], 'bar')
 | 
			
		||||
        self.assertEqual(part.metadata['hello'], 'world')
 | 
			
		||||
 | 
			
		||||
        # Now, issue a PUT request (existing data will be replacted)
 | 
			
		||||
        self.put(
 | 
			
		||||
            url,
 | 
			
		||||
            {
 | 
			
		||||
                'metadata': {
 | 
			
		||||
                    'x': 'y'
 | 
			
		||||
                },
 | 
			
		||||
            },
 | 
			
		||||
            expected_code=200
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        part.refresh_from_db()
 | 
			
		||||
 | 
			
		||||
        self.assertFalse('foo' in part.metadata)
 | 
			
		||||
        self.assertFalse('hello' in part.metadata)
 | 
			
		||||
        self.assertEqual(part.metadata['x'], 'y')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartAPIAggregationTest(InvenTreeAPITestCase):
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -245,6 +245,24 @@ class PartTest(TestCase):
 | 
			
		||||
        self.assertEqual(float(self.r1.get_internal_price(1)), 0.08)
 | 
			
		||||
        self.assertEqual(float(self.r1.get_internal_price(10)), 0.5)
 | 
			
		||||
 | 
			
		||||
    def test_metadata(self):
 | 
			
		||||
        """Unit tests for the Part metadata field"""
 | 
			
		||||
 | 
			
		||||
        p = Part.objects.get(pk=1)
 | 
			
		||||
        self.assertIsNone(p.metadata)
 | 
			
		||||
 | 
			
		||||
        self.assertIsNone(p.get_metadata('test'))
 | 
			
		||||
        self.assertEqual(p.get_metadata('test', backup_value=123), 123)
 | 
			
		||||
 | 
			
		||||
        # Test update via the set_metadata() method
 | 
			
		||||
        p.set_metadata('test', 3)
 | 
			
		||||
        self.assertEqual(p.get_metadata('test'), 3)
 | 
			
		||||
 | 
			
		||||
        for k in ['apple', 'banana', 'carrot', 'carrot', 'banana']:
 | 
			
		||||
            p.set_metadata(k, k)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(len(p.metadata.keys()), 4)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestTemplateTest(TestCase):
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user