mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-29 03:56:43 +00:00
Label api refactor (#4165)
* Add generic mixin classes for label printing API * Extend refactor to StockLocationLabel class * Refactor part label printing
This commit is contained in:
parent
09e02755ca
commit
942086741e
@ -23,9 +23,102 @@ from .serializers import (PartLabelSerializer, StockItemLabelSerializer,
|
|||||||
StockLocationLabelSerializer)
|
StockLocationLabelSerializer)
|
||||||
|
|
||||||
|
|
||||||
class LabelListView(ListAPI):
|
class LabelFilterMixin:
|
||||||
|
"""Mixin for filtering a queryset by a list of object ID values.
|
||||||
|
|
||||||
|
Each implementing class defines a database model to lookup,
|
||||||
|
and a "key" (query parameter) for providing a list of ID (PK) values.
|
||||||
|
|
||||||
|
This mixin defines a 'get_items' method which provides a generic
|
||||||
|
implementation to return a list of matching database model instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Database model for instances to actually be "printed" against this label template
|
||||||
|
ITEM_MODEL = None
|
||||||
|
|
||||||
|
# Default key for looking up database model instances
|
||||||
|
ITEM_KEY = 'item'
|
||||||
|
|
||||||
|
def get_items(self):
|
||||||
|
"""Return a list of database objects from query parameter"""
|
||||||
|
|
||||||
|
ids = []
|
||||||
|
|
||||||
|
# Construct a list of possible query parameter value options
|
||||||
|
# e.g. if self.ITEM_KEY = 'part' -> ['part', 'part', 'parts', parts[]']
|
||||||
|
for k in [self.ITEM_KEY + x for x in ['', '[]', 's', 's[]']]:
|
||||||
|
if ids := self.request.query_params.getlist(k, []):
|
||||||
|
# Return the first list of matches
|
||||||
|
break
|
||||||
|
|
||||||
|
# Next we must validate each provided object ID
|
||||||
|
valid_ids = []
|
||||||
|
|
||||||
|
for id in ids:
|
||||||
|
try:
|
||||||
|
valid_ids.append(int(id))
|
||||||
|
except (ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Filter queryset by matching ID values
|
||||||
|
return self.ITEM_MODEL.objects.filter(pk__in=valid_ids)
|
||||||
|
|
||||||
|
|
||||||
|
class LabelListView(LabelFilterMixin, ListAPI):
|
||||||
"""Generic API class for label templates."""
|
"""Generic API class for label templates."""
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
"""Filter the queryset based on the provided label ID values.
|
||||||
|
|
||||||
|
As each 'label' instance may optionally define its own filters,
|
||||||
|
the resulting queryset is the 'union' of the two.
|
||||||
|
"""
|
||||||
|
|
||||||
|
queryset = super().filter_queryset(queryset)
|
||||||
|
|
||||||
|
items = self.get_items()
|
||||||
|
|
||||||
|
if len(items) > 0:
|
||||||
|
"""
|
||||||
|
At this point, we are basically forced to be inefficient,
|
||||||
|
as we need to compare the 'filters' string of each label,
|
||||||
|
and see if it matches against each of the requested items.
|
||||||
|
|
||||||
|
TODO: In the future, if this becomes excessively slow, it
|
||||||
|
will need to be readdressed.
|
||||||
|
"""
|
||||||
|
valid_label_ids = set()
|
||||||
|
|
||||||
|
for label in queryset.all():
|
||||||
|
matches = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
filters = InvenTree.helpers.validateFilterString(label.filters)
|
||||||
|
except ValidationError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
item_query = self.ITEM_MODEL.objects.filter(pk=item.pk)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not item_query.filter(**filters).exists():
|
||||||
|
matches = False
|
||||||
|
break
|
||||||
|
except FieldError:
|
||||||
|
matches = False
|
||||||
|
break
|
||||||
|
|
||||||
|
# Matched all items
|
||||||
|
if matches:
|
||||||
|
valid_label_ids.add(label.pk)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Reduce queryset to only valid matches
|
||||||
|
queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids])
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
filters.SearchFilter
|
filters.SearchFilter
|
||||||
@ -41,9 +134,13 @@ class LabelListView(ListAPI):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class LabelPrintMixin:
|
class LabelPrintMixin(LabelFilterMixin):
|
||||||
"""Mixin for printing labels."""
|
"""Mixin for printing labels."""
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
"""Perform a GET request against this endpoint to print labels"""
|
||||||
|
return self.print(request, self.get_items())
|
||||||
|
|
||||||
def get_plugin(self, request):
|
def get_plugin(self, request):
|
||||||
"""Return the label printing plugin associated with this request.
|
"""Return the label printing plugin associated with this request.
|
||||||
|
|
||||||
@ -173,35 +270,7 @@ class LabelPrintMixin:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class StockItemLabelMixin:
|
class StockItemLabelList(LabelListView):
|
||||||
"""Mixin for extracting stock items from query params."""
|
|
||||||
|
|
||||||
def get_items(self):
|
|
||||||
"""Return a list of requested stock items."""
|
|
||||||
items = []
|
|
||||||
|
|
||||||
params = self.request.query_params
|
|
||||||
|
|
||||||
for key in ['item', 'item[]', 'items', 'items[]']:
|
|
||||||
if key in params:
|
|
||||||
items = params.getlist(key, [])
|
|
||||||
break
|
|
||||||
|
|
||||||
valid_ids = []
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
try:
|
|
||||||
valid_ids.append(int(item))
|
|
||||||
except (ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# List of StockItems which match provided values
|
|
||||||
valid_items = StockItem.objects.filter(pk__in=valid_ids)
|
|
||||||
|
|
||||||
return valid_items
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemLabelList(LabelListView, StockItemLabelMixin):
|
|
||||||
"""API endpoint for viewing list of StockItemLabel objects.
|
"""API endpoint for viewing list of StockItemLabel objects.
|
||||||
|
|
||||||
Filterable by:
|
Filterable by:
|
||||||
@ -214,59 +283,8 @@ class StockItemLabelList(LabelListView, StockItemLabelMixin):
|
|||||||
queryset = StockItemLabel.objects.all()
|
queryset = StockItemLabel.objects.all()
|
||||||
serializer_class = StockItemLabelSerializer
|
serializer_class = StockItemLabelSerializer
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
ITEM_MODEL = StockItem
|
||||||
"""Filter the StockItem label queryset."""
|
ITEM_KEY = 'item'
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
|
|
||||||
# List of StockItem objects to match against
|
|
||||||
items = self.get_items()
|
|
||||||
|
|
||||||
# We wish to filter by stock items
|
|
||||||
if len(items) > 0:
|
|
||||||
"""
|
|
||||||
At this point, we are basically forced to be inefficient,
|
|
||||||
as we need to compare the 'filters' string of each label,
|
|
||||||
and see if it matches against each of the requested items.
|
|
||||||
|
|
||||||
TODO: In the future, if this becomes excessively slow, it
|
|
||||||
will need to be readdressed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Keep track of which labels match every specified stockitem
|
|
||||||
valid_label_ids = set()
|
|
||||||
|
|
||||||
for label in queryset.all():
|
|
||||||
|
|
||||||
matches = True
|
|
||||||
|
|
||||||
# Filter string defined for the StockItemLabel object
|
|
||||||
try:
|
|
||||||
filters = InvenTree.helpers.validateFilterString(label.filters)
|
|
||||||
except ValidationError: # pragma: no cover
|
|
||||||
continue
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
|
|
||||||
item_query = StockItem.objects.filter(pk=item.pk)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not item_query.filter(**filters).exists():
|
|
||||||
matches = False
|
|
||||||
break
|
|
||||||
except FieldError:
|
|
||||||
matches = False
|
|
||||||
break
|
|
||||||
|
|
||||||
# Matched all items
|
|
||||||
if matches:
|
|
||||||
valid_label_ids.add(label.pk)
|
|
||||||
else:
|
|
||||||
continue # pragma: no cover
|
|
||||||
|
|
||||||
# Reduce queryset to only valid matches
|
|
||||||
queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids])
|
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class StockItemLabelDetail(RetrieveUpdateDestroyAPI):
|
class StockItemLabelDetail(RetrieveUpdateDestroyAPI):
|
||||||
@ -276,48 +294,17 @@ class StockItemLabelDetail(RetrieveUpdateDestroyAPI):
|
|||||||
serializer_class = StockItemLabelSerializer
|
serializer_class = StockItemLabelSerializer
|
||||||
|
|
||||||
|
|
||||||
class StockItemLabelPrint(RetrieveAPI, StockItemLabelMixin, LabelPrintMixin):
|
class StockItemLabelPrint(LabelPrintMixin, RetrieveAPI):
|
||||||
"""API endpoint for printing a StockItemLabel object."""
|
"""API endpoint for printing a StockItemLabel object."""
|
||||||
|
|
||||||
queryset = StockItemLabel.objects.all()
|
queryset = StockItemLabel.objects.all()
|
||||||
serializer_class = StockItemLabelSerializer
|
serializer_class = StockItemLabelSerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
ITEM_MODEL = StockItem
|
||||||
"""Check if valid stock item(s) have been provided."""
|
ITEM_KEY = 'item'
|
||||||
items = self.get_items()
|
|
||||||
|
|
||||||
return self.print(request, items)
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocationLabelMixin:
|
class StockLocationLabelList(LabelListView):
|
||||||
"""Mixin for extracting stock locations from query params."""
|
|
||||||
|
|
||||||
def get_locations(self):
|
|
||||||
"""Return a list of requested stock locations."""
|
|
||||||
locations = []
|
|
||||||
|
|
||||||
params = self.request.query_params
|
|
||||||
|
|
||||||
for key in ['location', 'location[]', 'locations', 'locations[]']:
|
|
||||||
|
|
||||||
if key in params:
|
|
||||||
locations = params.getlist(key, [])
|
|
||||||
|
|
||||||
valid_ids = []
|
|
||||||
|
|
||||||
for loc in locations:
|
|
||||||
try:
|
|
||||||
valid_ids.append(int(loc))
|
|
||||||
except (ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# List of StockLocation objects which match provided values
|
|
||||||
valid_locations = StockLocation.objects.filter(pk__in=valid_ids)
|
|
||||||
|
|
||||||
return valid_locations
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocationLabelList(LabelListView, StockLocationLabelMixin):
|
|
||||||
"""API endpoint for viewiing list of StockLocationLabel objects.
|
"""API endpoint for viewiing list of StockLocationLabel objects.
|
||||||
|
|
||||||
Filterable by:
|
Filterable by:
|
||||||
@ -330,59 +317,8 @@ class StockLocationLabelList(LabelListView, StockLocationLabelMixin):
|
|||||||
queryset = StockLocationLabel.objects.all()
|
queryset = StockLocationLabel.objects.all()
|
||||||
serializer_class = StockLocationLabelSerializer
|
serializer_class = StockLocationLabelSerializer
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
ITEM_MODEL = StockLocation
|
||||||
"""Filter the StockLocationLabel queryset."""
|
ITEM_KEY = 'location'
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
|
|
||||||
# List of StockLocation objects to match against
|
|
||||||
locations = self.get_locations()
|
|
||||||
|
|
||||||
# We wish to filter by stock location(s)
|
|
||||||
if len(locations) > 0:
|
|
||||||
"""
|
|
||||||
At this point, we are basically forced to be inefficient,
|
|
||||||
as we need to compare the 'filters' string of each label,
|
|
||||||
and see if it matches against each of the requested items.
|
|
||||||
|
|
||||||
TODO: In the future, if this becomes excessively slow, it
|
|
||||||
will need to be readdressed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
valid_label_ids = set()
|
|
||||||
|
|
||||||
for label in queryset.all():
|
|
||||||
|
|
||||||
matches = True
|
|
||||||
|
|
||||||
# Filter string defined for the StockLocationLabel object
|
|
||||||
try:
|
|
||||||
filters = InvenTree.helpers.validateFilterString(label.filters)
|
|
||||||
except Exception: # pragma: no cover
|
|
||||||
# Skip if there was an error validating the filters...
|
|
||||||
continue
|
|
||||||
|
|
||||||
for loc in locations:
|
|
||||||
|
|
||||||
loc_query = StockLocation.objects.filter(pk=loc.pk)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not loc_query.filter(**filters).exists():
|
|
||||||
matches = False
|
|
||||||
break
|
|
||||||
except FieldError:
|
|
||||||
matches = False
|
|
||||||
break
|
|
||||||
|
|
||||||
# Matched all items
|
|
||||||
if matches:
|
|
||||||
valid_label_ids.add(label.pk)
|
|
||||||
else:
|
|
||||||
continue # pragma: no cover
|
|
||||||
|
|
||||||
# Reduce queryset to only valid matches
|
|
||||||
queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids])
|
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocationLabelDetail(RetrieveUpdateDestroyAPI):
|
class StockLocationLabelDetail(RetrieveUpdateDestroyAPI):
|
||||||
@ -392,89 +328,24 @@ class StockLocationLabelDetail(RetrieveUpdateDestroyAPI):
|
|||||||
serializer_class = StockLocationLabelSerializer
|
serializer_class = StockLocationLabelSerializer
|
||||||
|
|
||||||
|
|
||||||
class StockLocationLabelPrint(RetrieveAPI, StockLocationLabelMixin, LabelPrintMixin):
|
class StockLocationLabelPrint(LabelPrintMixin, RetrieveAPI):
|
||||||
"""API endpoint for printing a StockLocationLabel object."""
|
"""API endpoint for printing a StockLocationLabel object."""
|
||||||
|
|
||||||
queryset = StockLocationLabel.objects.all()
|
queryset = StockLocationLabel.objects.all()
|
||||||
seiralizer_class = StockLocationLabelSerializer
|
seiralizer_class = StockLocationLabelSerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
ITEM_MODEL = StockLocation
|
||||||
"""Print labels based on the request parameters"""
|
ITEM_KEY = 'location'
|
||||||
locations = self.get_locations()
|
|
||||||
|
|
||||||
return self.print(request, locations)
|
|
||||||
|
|
||||||
|
|
||||||
class PartLabelMixin:
|
class PartLabelList(LabelListView):
|
||||||
"""Mixin for extracting Part objects from query parameters."""
|
|
||||||
|
|
||||||
def get_parts(self):
|
|
||||||
"""Return a list of requested Part objects."""
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
params = self.request.query_params
|
|
||||||
|
|
||||||
for key in ['part', 'part[]', 'parts', 'parts[]']:
|
|
||||||
if key in params:
|
|
||||||
parts = params.getlist(key, [])
|
|
||||||
break
|
|
||||||
|
|
||||||
valid_ids = []
|
|
||||||
|
|
||||||
for part in parts:
|
|
||||||
try:
|
|
||||||
valid_ids.append(int(part))
|
|
||||||
except (ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# List of Part objects which match provided values
|
|
||||||
return Part.objects.filter(pk__in=valid_ids)
|
|
||||||
|
|
||||||
|
|
||||||
class PartLabelList(LabelListView, PartLabelMixin):
|
|
||||||
"""API endpoint for viewing list of PartLabel objects."""
|
"""API endpoint for viewing list of PartLabel objects."""
|
||||||
|
|
||||||
queryset = PartLabel.objects.all()
|
queryset = PartLabel.objects.all()
|
||||||
serializer_class = PartLabelSerializer
|
serializer_class = PartLabelSerializer
|
||||||
|
|
||||||
def filter_queryset(self, queryset):
|
ITEM_MODEL = Part
|
||||||
"""Custom queryset filtering for the PartLabel list"""
|
ITEM_KEY = 'part'
|
||||||
queryset = super().filter_queryset(queryset)
|
|
||||||
|
|
||||||
parts = self.get_parts()
|
|
||||||
|
|
||||||
if len(parts) > 0:
|
|
||||||
|
|
||||||
valid_label_ids = set()
|
|
||||||
|
|
||||||
for label in queryset.all():
|
|
||||||
|
|
||||||
matches = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
filters = InvenTree.helpers.validateFilterString(label.filters)
|
|
||||||
except ValidationError: # pragma: no cover
|
|
||||||
continue
|
|
||||||
|
|
||||||
for part in parts:
|
|
||||||
|
|
||||||
part_query = Part.objects.filter(pk=part.pk)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not part_query.filter(**filters).exists():
|
|
||||||
matches = False
|
|
||||||
break
|
|
||||||
except FieldError:
|
|
||||||
matches = False
|
|
||||||
break
|
|
||||||
|
|
||||||
if matches:
|
|
||||||
valid_label_ids.add(label.pk)
|
|
||||||
|
|
||||||
# Reduce queryset to only valid matches
|
|
||||||
queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids])
|
|
||||||
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class PartLabelDetail(RetrieveUpdateDestroyAPI):
|
class PartLabelDetail(RetrieveUpdateDestroyAPI):
|
||||||
@ -484,17 +355,14 @@ class PartLabelDetail(RetrieveUpdateDestroyAPI):
|
|||||||
serializer_class = PartLabelSerializer
|
serializer_class = PartLabelSerializer
|
||||||
|
|
||||||
|
|
||||||
class PartLabelPrint(RetrieveAPI, PartLabelMixin, LabelPrintMixin):
|
class PartLabelPrint(LabelPrintMixin, RetrieveAPI):
|
||||||
"""API endpoint for printing a PartLabel object."""
|
"""API endpoint for printing a PartLabel object."""
|
||||||
|
|
||||||
queryset = PartLabel.objects.all()
|
queryset = PartLabel.objects.all()
|
||||||
serializer_class = PartLabelSerializer
|
serializer_class = PartLabelSerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
ITEM_MODEL = Part
|
||||||
"""Check if valid part(s) have been provided."""
|
ITEM_KEY = 'part'
|
||||||
parts = self.get_parts()
|
|
||||||
|
|
||||||
return self.print(request, parts)
|
|
||||||
|
|
||||||
|
|
||||||
label_api_urls = [
|
label_api_urls = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user