mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 11:10:54 +00:00
Add Metadata to further models (#4410)
* Add metadata for ManufacturerPart * Add Metadata for SupplierPart * Add metadata to label models * Add metadata to order line items * Add metadata to shipment * Add metadata to Build and BuildItem * Add metadata to BomItem * Add metadata to PartParameterTemplate * Syntax, lint * Fix isort style * Lint * Correction of model name * Metadata for Reports * Fix silly error * Fix silly error * Correct model name * Correct model name * Correction * Correct company urls * Apply generic model to Report metadat * Rename/remove redundant import * Remove shadowing of report in loop variable * Update import ordering * More corrections * better docstrings * Correct names for API endpoints * Default to PO, required for api-doc to work * Changes by @matmair * Suppress metadata from Bom export * Add migration files * Increment API version * Add tests for all Metadata models, even previously existing ones * Update tests * Fix * Delay tests * Fix imports * Fix tests * API Version number * Remove unused import * isort * Revent unintended change of cache
This commit is contained in:
@ -604,6 +604,16 @@ class PurchaseOrderLineItemDetail(RetrieveUpdateDestroyAPI):
|
||||
return queryset
|
||||
|
||||
|
||||
class PurchaseOrderLineItemMetadata(RetrieveUpdateAPI):
|
||||
"""API endpoint for viewing / updating PurchaseOrderLineItem metadata."""
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
"""Return MetadataSerializer instance for a Company"""
|
||||
return MetadataSerializer(models.PurchaseOrderLineItem, *args, **kwargs)
|
||||
|
||||
queryset = models.PurchaseOrderLineItem.objects.all()
|
||||
|
||||
|
||||
class PurchaseOrderExtraLineList(GeneralExtraLineList, ListCreateAPI):
|
||||
"""API endpoint for accessing a list of PurchaseOrderExtraLine objects."""
|
||||
|
||||
@ -627,6 +637,16 @@ class PurchaseOrderExtraLineDetail(RetrieveUpdateDestroyAPI):
|
||||
serializer_class = serializers.PurchaseOrderExtraLineSerializer
|
||||
|
||||
|
||||
class PurchaseOrderExtraLineItemMetadata(RetrieveUpdateAPI):
|
||||
"""API endpoint for viewing / updating PurchaseOrderExtraLineItem metadata."""
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
"""Return MetadataSerializer instance"""
|
||||
return MetadataSerializer(models.PurchaseOrderExtraLine, *args, **kwargs)
|
||||
|
||||
queryset = models.PurchaseOrderExtraLine.objects.all()
|
||||
|
||||
|
||||
class SalesOrderAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a SalesOrderAttachment (file upload)"""
|
||||
|
||||
@ -943,6 +963,16 @@ class SalesOrderLineItemList(APIDownloadMixin, ListCreateAPI):
|
||||
]
|
||||
|
||||
|
||||
class SalesOrderLineItemMetadata(RetrieveUpdateAPI):
|
||||
"""API endpoint for viewing / updating SalesOrderLineItem metadata."""
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
"""Return MetadataSerializer instance"""
|
||||
return MetadataSerializer(models.SalesOrderLineItem, *args, **kwargs)
|
||||
|
||||
queryset = models.SalesOrderLineItem.objects.all()
|
||||
|
||||
|
||||
class SalesOrderExtraLineList(GeneralExtraLineList, ListCreateAPI):
|
||||
"""API endpoint for accessing a list of SalesOrderExtraLine objects."""
|
||||
|
||||
@ -966,6 +996,16 @@ class SalesOrderExtraLineDetail(RetrieveUpdateDestroyAPI):
|
||||
serializer_class = serializers.SalesOrderExtraLineSerializer
|
||||
|
||||
|
||||
class SalesOrderExtraLineItemMetadata(RetrieveUpdateAPI):
|
||||
"""API endpoint for viewing / updating SalesOrderExtraLine metadata."""
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
"""Return MetadataSerializer instance"""
|
||||
return MetadataSerializer(models.SalesOrderExtraLine, *args, **kwargs)
|
||||
|
||||
queryset = models.SalesOrderExtraLine.objects.all()
|
||||
|
||||
|
||||
class SalesOrderLineItemDetail(RetrieveUpdateDestroyAPI):
|
||||
"""API endpoint for detail view of a SalesOrderLineItem object."""
|
||||
|
||||
@ -1191,6 +1231,16 @@ class SalesOrderShipmentComplete(CreateAPI):
|
||||
return ctx
|
||||
|
||||
|
||||
class SalesOrderShipmentMetadata(RetrieveUpdateAPI):
|
||||
"""API endpoint for viewing / updating SalesOrderShipment metadata."""
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
"""Return MetadataSerializer instance"""
|
||||
return MetadataSerializer(models.SalesOrderShipment, *args, **kwargs)
|
||||
|
||||
queryset = models.SalesOrderShipment.objects.all()
|
||||
|
||||
|
||||
class PurchaseOrderAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
|
||||
"""API endpoint for listing (and creating) a PurchaseOrderAttachment (file upload)"""
|
||||
|
||||
@ -1391,13 +1441,19 @@ order_api_urls = [
|
||||
|
||||
# API endpoints for purchase order line items
|
||||
re_path(r'^po-line/', include([
|
||||
path('<int:pk>/', PurchaseOrderLineItemDetail.as_view(), name='api-po-line-detail'),
|
||||
path('<int:pk>/', include([
|
||||
re_path(r'^metadata/', PurchaseOrderLineItemMetadata.as_view(), name='api-po-line-metadata'),
|
||||
re_path(r'^.*$', PurchaseOrderLineItemDetail.as_view(), name='api-po-line-detail'),
|
||||
])),
|
||||
re_path(r'^.*$', PurchaseOrderLineItemList.as_view(), name='api-po-line-list'),
|
||||
])),
|
||||
|
||||
# API endpoints for purchase order extra line
|
||||
re_path(r'^po-extra-line/', include([
|
||||
path('<int:pk>/', PurchaseOrderExtraLineDetail.as_view(), name='api-po-extra-line-detail'),
|
||||
path('<int:pk>/', include([
|
||||
re_path(r'^metadata/', PurchaseOrderExtraLineItemMetadata.as_view(), name='api-po-extra-line-metadata'),
|
||||
re_path(r'^.*$', PurchaseOrderExtraLineDetail.as_view(), name='api-po-extra-line-detail'),
|
||||
])),
|
||||
path('', PurchaseOrderExtraLineList.as_view(), name='api-po-extra-line-list'),
|
||||
])),
|
||||
|
||||
@ -1411,6 +1467,7 @@ order_api_urls = [
|
||||
re_path(r'^shipment/', include([
|
||||
re_path(r'^(?P<pk>\d+)/', include([
|
||||
path('ship/', SalesOrderShipmentComplete.as_view(), name='api-so-shipment-ship'),
|
||||
re_path(r'^metadata/', SalesOrderShipmentMetadata.as_view(), name='api-so-shipment-metadata'),
|
||||
re_path(r'^.*$', SalesOrderShipmentDetail.as_view(), name='api-so-shipment-detail'),
|
||||
])),
|
||||
re_path(r'^.*$', SalesOrderShipmentList.as_view(), name='api-so-shipment-list'),
|
||||
@ -1434,13 +1491,19 @@ order_api_urls = [
|
||||
|
||||
# API endpoints for sales order line items
|
||||
re_path(r'^so-line/', include([
|
||||
path('<int:pk>/', SalesOrderLineItemDetail.as_view(), name='api-so-line-detail'),
|
||||
path('<int:pk>/', include([
|
||||
re_path(r'^metadata/', SalesOrderLineItemMetadata.as_view(), name='api-so-line-metadata'),
|
||||
re_path(r'^.*$', SalesOrderLineItemDetail.as_view(), name='api-so-line-detail'),
|
||||
])),
|
||||
path('', SalesOrderLineItemList.as_view(), name='api-so-line-list'),
|
||||
])),
|
||||
|
||||
# API endpoints for sales order extra line
|
||||
re_path(r'^so-extra-line/', include([
|
||||
path('<int:pk>/', SalesOrderExtraLineDetail.as_view(), name='api-so-extra-line-detail'),
|
||||
path('<int:pk>/', include([
|
||||
re_path(r'^metadata/', SalesOrderExtraLineItemMetadata.as_view(), name='api-so-extra-line-metadata'),
|
||||
re_path(r'^.*$', SalesOrderExtraLineDetail.as_view(), name='api-so-extra-line-detail'),
|
||||
])),
|
||||
path('', SalesOrderExtraLineList.as_view(), name='api-so-extra-line-list'),
|
||||
])),
|
||||
|
||||
|
@ -110,3 +110,11 @@
|
||||
order: 1
|
||||
part: 5
|
||||
quantity: 1
|
||||
|
||||
# An extra line item
|
||||
- model: order.purchaseorderextraline
|
||||
pk: 1
|
||||
fields:
|
||||
order: 7
|
||||
reference: 'Freight cost'
|
||||
quantity: 1
|
||||
|
38
InvenTree/order/migrations/0080_auto_20230317_0816.py
Normal file
38
InvenTree/order/migrations/0080_auto_20230317_0816.py
Normal file
@ -0,0 +1,38 @@
|
||||
# Generated by Django 3.2.18 on 2023-03-17 08:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0079_auto_20230304_0904'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='purchaseorderextraline',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='purchaseorderlineitem',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='salesorderextraline',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='salesorderlineitem',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='salesordershipment',
|
||||
name='metadata',
|
||||
field=models.JSONField(blank=True, help_text='JSON metadata field, for use by external plugins', null=True, verbose_name='Plugin Metadata'),
|
||||
),
|
||||
]
|
@ -938,7 +938,7 @@ class SalesOrderAttachment(InvenTreeAttachment):
|
||||
order = models.ForeignKey(SalesOrder, on_delete=models.CASCADE, related_name='attachments')
|
||||
|
||||
|
||||
class OrderLineItem(models.Model):
|
||||
class OrderLineItem(MetadataMixin, models.Model):
|
||||
"""Abstract model for an order line item.
|
||||
|
||||
Attributes:
|
||||
@ -1256,7 +1256,7 @@ class SalesOrderLineItem(OrderLineItem):
|
||||
return self.shipped >= self.quantity
|
||||
|
||||
|
||||
class SalesOrderShipment(models.Model):
|
||||
class SalesOrderShipment(MetadataMixin, models.Model):
|
||||
"""The SalesOrderShipment model represents a physical shipment made against a SalesOrder.
|
||||
|
||||
- Points to a single SalesOrder object
|
||||
|
@ -11,7 +11,8 @@ import order.tasks
|
||||
from common.models import InvenTreeSetting, NotificationMessage
|
||||
from company.models import Company
|
||||
from InvenTree import status_codes as status
|
||||
from order.models import (SalesOrder, SalesOrderAllocation, SalesOrderLineItem,
|
||||
from order.models import (SalesOrder, SalesOrderAllocation,
|
||||
SalesOrderExtraLine, SalesOrderLineItem,
|
||||
SalesOrderShipment)
|
||||
from part.models import Part
|
||||
from stock.models import StockItem
|
||||
@ -54,6 +55,9 @@ class SalesOrderTest(TestCase):
|
||||
# Create a line item
|
||||
cls.line = SalesOrderLineItem.objects.create(quantity=50, order=cls.order, part=cls.part)
|
||||
|
||||
# Create an extra line
|
||||
cls.extraline = SalesOrderExtraLine.objects.create(quantity=1, order=cls.order, reference="Extra line")
|
||||
|
||||
def test_so_reference(self):
|
||||
"""Unit tests for sales order generation"""
|
||||
|
||||
@ -293,3 +297,22 @@ class SalesOrderTest(TestCase):
|
||||
|
||||
# However *no* notification should have been generated for the creating user
|
||||
self.assertFalse(messages.filter(user__pk=3).exists())
|
||||
|
||||
def test_metadata(self):
|
||||
"""Unit tests for the metadata field."""
|
||||
for model in [SalesOrder, SalesOrderLineItem, SalesOrderExtraLine, SalesOrderShipment]:
|
||||
p = model.objects.first()
|
||||
|
||||
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)
|
||||
|
@ -18,7 +18,8 @@ from part.models import Part
|
||||
from stock.models import StockItem, StockLocation
|
||||
from users.models import Owner
|
||||
|
||||
from .models import PurchaseOrder, PurchaseOrderLineItem
|
||||
from .models import (PurchaseOrder, PurchaseOrderExtraLine,
|
||||
PurchaseOrderLineItem)
|
||||
|
||||
|
||||
class OrderTest(TestCase):
|
||||
@ -384,3 +385,21 @@ class OrderTest(TestCase):
|
||||
|
||||
# However *no* notification should have been generated for the creating user
|
||||
self.assertFalse(messages.filter(user__pk=3).exists())
|
||||
|
||||
def test_metadata(self):
|
||||
"""Unit tests for the metadata field."""
|
||||
for model in [PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderExtraLine]:
|
||||
p = model.objects.first()
|
||||
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)
|
||||
|
Reference in New Issue
Block a user