2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

Report template caching (#4503)

* Refactoring for report.api

- Adds generic mixins for filtering queryset (based on updates to label.api)
- Reduces repeated code a *lot*

(cherry picked from commit 5de0b42c414ece7c8a33fa06dd9e5e8facc4dc07)

* Reduce caching of report printing template

- Cache for 5 seconds
- Ensures that uploaded templates are made available quickly

* Refactoring for label printing API

* Caching timeout fix for label printing

* Typo fix
This commit is contained in:
Oliver 2023-03-17 15:39:36 +11:00 committed by GitHub
parent c3b2bb0380
commit 25d1bceaad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 226 additions and 490 deletions

View File

@ -4,6 +4,8 @@ from django.conf import settings
from django.core.exceptions import FieldError, ValidationError from django.core.exceptions import FieldError, ValidationError
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse, JsonResponse
from django.urls import include, re_path from django.urls import include, re_path
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page, never_cache
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters from rest_framework import filters
@ -45,7 +47,7 @@ class LabelFilterMixin:
ids = [] ids = []
# Construct a list of possible query parameter value options # Construct a list of possible query parameter value options
# e.g. if self.ITEM_KEY = 'part' -> ['part', 'part', 'parts', parts[]'] # e.g. if self.ITEM_KEY = 'part' -> ['part', 'part[]', 'parts', parts[]']
for k in [self.ITEM_KEY + x for x in ['', '[]', 's', 's[]']]: for k in [self.ITEM_KEY + x for x in ['', '[]', 's', 's[]']]:
if ids := self.request.query_params.getlist(k, []): if ids := self.request.query_params.getlist(k, []):
# Return the first list of matches # Return the first list of matches
@ -134,9 +136,15 @@ class LabelListView(LabelFilterMixin, ListAPI):
] ]
@method_decorator(cache_page(5), name='dispatch')
class LabelPrintMixin(LabelFilterMixin): class LabelPrintMixin(LabelFilterMixin):
"""Mixin for printing labels.""" """Mixin for printing labels."""
@method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
"""Prevent caching when printing report templates"""
return super().dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Perform a GET request against this endpoint to print labels""" """Perform a GET request against this endpoint to print labels"""
return self.print(request, self.get_items()) return self.print(request, self.get_items())
@ -270,7 +278,17 @@ class LabelPrintMixin(LabelFilterMixin):
) )
class StockItemLabelList(LabelListView): class StockItemLabelMixin:
"""Mixin for StockItemLabel endpoints"""
queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer
ITEM_MODEL = StockItem
ITEM_KEY = 'item'
class StockItemLabelList(StockItemLabelMixin, LabelListView):
"""API endpoint for viewing list of StockItemLabel objects. """API endpoint for viewing list of StockItemLabel objects.
Filterable by: Filterable by:
@ -279,32 +297,30 @@ class StockItemLabelList(LabelListView):
- item: Filter by single stock item - item: Filter by single stock item
- items: Filter by list of stock items - items: Filter by list of stock items
""" """
pass
queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer
ITEM_MODEL = StockItem
ITEM_KEY = 'item'
class StockItemLabelDetail(RetrieveUpdateDestroyAPI): class StockItemLabelDetail(StockItemLabelMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single StockItemLabel object.""" """API endpoint for a single StockItemLabel object."""
pass
queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer
class StockItemLabelPrint(LabelPrintMixin, RetrieveAPI): class StockItemLabelPrint(StockItemLabelMixin, LabelPrintMixin, RetrieveAPI):
"""API endpoint for printing a StockItemLabel object.""" """API endpoint for printing a StockItemLabel object."""
pass
queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer
ITEM_MODEL = StockItem
ITEM_KEY = 'item'
class StockLocationLabelList(LabelListView): class StockLocationLabelMixin:
"""Mixin for StockLocationLabel endpoints"""
queryset = StockLocationLabel.objects.all()
serializer_class = StockLocationLabelSerializer
ITEM_MODEL = StockLocation
ITEM_KEY = 'location'
class StockLocationLabelList(StockLocationLabelMixin, LabelListView):
"""API endpoint for viewiing list of StockLocationLabel objects. """API endpoint for viewiing list of StockLocationLabel objects.
Filterable by: Filterable by:
@ -313,56 +329,41 @@ class StockLocationLabelList(LabelListView):
- location: Filter by a single stock location - location: Filter by a single stock location
- locations: Filter by list of stock locations - locations: Filter by list of stock locations
""" """
pass
queryset = StockLocationLabel.objects.all()
serializer_class = StockLocationLabelSerializer
ITEM_MODEL = StockLocation
ITEM_KEY = 'location'
class StockLocationLabelDetail(RetrieveUpdateDestroyAPI): class StockLocationLabelDetail(StockLocationLabelMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single StockLocationLabel object.""" """API endpoint for a single StockLocationLabel object."""
pass
queryset = StockLocationLabel.objects.all()
serializer_class = StockLocationLabelSerializer
class StockLocationLabelPrint(LabelPrintMixin, RetrieveAPI): class StockLocationLabelPrint(StockLocationLabelMixin, LabelPrintMixin, RetrieveAPI):
"""API endpoint for printing a StockLocationLabel object.""" """API endpoint for printing a StockLocationLabel object."""
pass
queryset = StockLocationLabel.objects.all()
seiralizer_class = StockLocationLabelSerializer
ITEM_MODEL = StockLocation
ITEM_KEY = 'location'
class PartLabelList(LabelListView): class PartLabelMixin:
"""Mixin for PartLabel endpoints"""
queryset = PartLabel.objects.all()
serializer_class = PartLabelSerializer
ITEM_MODEL = Part
ITEM_KEY = 'part'
class PartLabelList(PartLabelMixin, LabelListView):
"""API endpoint for viewing list of PartLabel objects.""" """API endpoint for viewing list of PartLabel objects."""
pass
queryset = PartLabel.objects.all()
serializer_class = PartLabelSerializer
ITEM_MODEL = Part
ITEM_KEY = 'part'
class PartLabelDetail(RetrieveUpdateDestroyAPI): class PartLabelDetail(PartLabelMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single PartLabel object.""" """API endpoint for a single PartLabel object."""
pass
queryset = PartLabel.objects.all()
serializer_class = PartLabelSerializer
class PartLabelPrint(LabelPrintMixin, RetrieveAPI): class PartLabelPrint(PartLabelMixin, LabelPrintMixin, RetrieveAPI):
"""API endpoint for printing a PartLabel object.""" """API endpoint for printing a PartLabel object."""
pass
queryset = PartLabel.objects.all()
serializer_class = PartLabelSerializer
ITEM_MODEL = Part
ITEM_KEY = 'part'
label_api_urls = [ label_api_urls = [

View File

@ -5,7 +5,9 @@ from django.core.files.base import ContentFile
from django.http import HttpResponse from django.http import HttpResponse
from django.template.exceptions import TemplateDoesNotExist from django.template.exceptions import TemplateDoesNotExist
from django.urls import include, path, re_path from django.urls import include, path, re_path
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import cache_page, never_cache
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters from rest_framework import filters
@ -44,122 +46,109 @@ class ReportListView(ListAPI):
] ]
class StockItemReportMixin: class ReportFilterMixin:
"""Mixin for extracting stock items from query params.""" """Mixin for extracting multiple objects from query params.
def get_items(self): Each subclass *must* have an attribute called 'ITEM_KEY',
"""Return a list of requested stock items.""" which is used to determine what 'key' is used in the query parameters.
items = []
params = self.request.query_params This mixin defines a 'get_items' method which provides a generic implementation
to return a list of matching database model instances
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 BuildReportMixin:
"""Mixin for extracting Build items from query params."""
def get_builds(self):
"""Return a list of requested Build objects."""
builds = []
params = self.request.query_params
for key in ['build', 'build[]', 'builds', 'builds[]']:
if key in params:
builds = params.getlist(key, [])
break
valid_ids = []
for b in builds:
try:
valid_ids.append(int(b))
except (ValueError):
continue
return build.models.Build.objects.filter(pk__in=valid_ids)
class OrderReportMixin:
"""Mixin for extracting order items from query params.
requires the OrderModel class attribute to be set!
""" """
def get_orders(self): # Database model for instances to actually be "printed" against this report template
"""Return a list of order objects.""" ITEM_MODEL = None
orders = []
params = self.request.query_params # Default key for looking up database model instances
ITEM_KEY = 'item'
for key in ['order', 'order[]', 'orders', 'orders[]']: def get_items(self):
if key in params: """Return a list of database objects from query parameters"""
orders = params.getlist(key, [])
if not self.ITEM_MODEL:
raise NotImplementedError(f"ITEM_MODEL attribute not defined for {__class__}")
ids = []
# Construct a list of possible query parameter value options
# e.g. if self.ITEM_KEY = 'order' -> ['order', 'order[]', 'orders', 'orders[]']
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 break
# Next we must validated each provided object ID
valid_ids = [] valid_ids = []
for o in orders: for id in ids:
try: try:
valid_ids.append(int(o)) valid_ids.append(int(id))
except (ValueError): except ValueError:
pass pass
valid_orders = self.OrderModel.objects.filter(pk__in=valid_ids) # Filter queryset by matching ID values
return self.ITEM_MODEL.objects.filter(pk__in=valid_ids)
return valid_orders def filter_queryset(self, queryset):
"""Filter the queryset based on the provided report ID values.
class PartReportMixin: As each 'report' instance may optionally define its own filters,
"""Mixin for extracting part items from query params.""" the resulting queryset is the 'union' of the two
"""
def get_parts(self):
"""Return a list of requested part objects.""" queryset = super().filter_queryset(queryset)
parts = []
items = self.get_items()
params = self.request.query_params
if len(items) > 0:
for key in ['part', 'part[]', 'parts', 'parts[]']: """At this point, we are basically forced to be inefficient:
if key in params: We need to compare the 'filters' string of each report template,
parts = params.getlist(key, []) and see if it matches against each of the requested items.
valid_ids = [] In practice, this is not too bad.
"""
for p in parts:
try: valid_report_ids = set()
valid_ids.append(int(p))
except (ValueError): for report in queryset.all():
continue matches = True
# Extract a valid set of Part objects try:
valid_parts = part.models.Part.objects.filter(pk__in=valid_ids) filters = InvenTree.helpers.validateFilterString(report.filters)
except ValidationError:
return valid_parts 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_report_ids.add(report.pk)
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids])
return queryset
@method_decorator(cache_page(5), name='dispatch')
class ReportPrintMixin: class ReportPrintMixin:
"""Mixin for printing reports.""" """Mixin for printing reports."""
@method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
"""Prevent caching when printing report templates"""
return super().dispatch(*args, **kwargs)
def report_callback(self, object, report, request): def report_callback(self, object, report, request):
"""Callback function for each object/report combination. """Callback function for each object/report combination.
@ -263,8 +252,25 @@ class ReportPrintMixin:
inline=inline, inline=inline,
) )
def get(self, request, *args, **kwargs):
"""Default implementation of GET for a print endpoint.
class StockItemTestReportList(ReportListView, StockItemReportMixin): Note that it expects the class has defined a get_items() method
"""
items = self.get_items()
return self.print(request, items)
class StockItemTestReportMixin(ReportFilterMixin):
"""Mixin for StockItemTestReport report template"""
ITEM_MODEL = StockItem
ITEM_KEY = 'item'
queryset = TestReport.objects.all()
serializer_class = TestReportSerializer
class StockItemTestReportList(StockItemTestReportMixin, ReportListView):
"""API endpoint for viewing list of TestReport objects. """API endpoint for viewing list of TestReport objects.
Filterable by: Filterable by:
@ -272,73 +278,17 @@ class StockItemTestReportList(ReportListView, StockItemReportMixin):
- enabled: Filter by enabled / disabled status - enabled: Filter by enabled / disabled status
- item: Filter by stock item(s) - item: Filter by stock item(s)
""" """
pass
queryset = TestReport.objects.all()
serializer_class = TestReportSerializer
def filter_queryset(self, queryset):
"""Custom queryset filtering"""
queryset = super().filter_queryset(queryset)
# List of StockItem objects to match against
items = self.get_items()
if len(items) > 0:
"""
We wish to filter by stock items.
We need to compare the 'filters' string of each report,
and see if it matches against each of the specified stock items.
TODO: In the future, perhaps there is a way to make this more efficient.
"""
valid_report_ids = set()
for report in queryset.all():
matches = True
# Filter string defined for the report object
try:
filters = InvenTree.helpers.validateFilterString(report.filters)
except Exception:
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
if matches:
valid_report_ids.add(report.pk)
else:
continue
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids])
return queryset
class StockItemTestReportDetail(RetrieveUpdateDestroyAPI): class StockItemTestReportDetail(StockItemTestReportMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single TestReport object.""" """API endpoint for a single TestReport object."""
pass
queryset = TestReport.objects.all()
serializer_class = TestReportSerializer
class StockItemTestReportPrint(RetrieveAPI, StockItemReportMixin, ReportPrintMixin): class StockItemTestReportPrint(StockItemTestReportMixin, ReportPrintMixin, RetrieveAPI):
"""API endpoint for printing a TestReport object.""" """API endpoint for printing a TestReport object."""
queryset = TestReport.objects.all()
serializer_class = TestReportSerializer
def report_callback(self, item, report, request): def report_callback(self, item, report, request):
"""Callback to (optionally) save a copy of the generated report""" """Callback to (optionally) save a copy of the generated report"""
@ -355,14 +305,18 @@ class StockItemTestReportPrint(RetrieveAPI, StockItemReportMixin, ReportPrintMix
comment=_("Test report") comment=_("Test report")
) )
def get(self, request, *args, **kwargs):
"""Check if valid stock item(s) have been provided."""
items = self.get_items()
return self.print(request, items) class BOMReportMixin(ReportFilterMixin):
"""Mixin for BillOfMaterialsReport report template"""
ITEM_MODEL = part.models.Part
ITEM_KEY = 'part'
queryset = BillOfMaterialsReport.objects.all()
serializer_class = BOMReportSerializer
class BOMReportList(ReportListView, PartReportMixin): class BOMReportList(BOMReportMixin, ReportListView):
"""API endpoint for viewing a list of BillOfMaterialReport objects. """API endpoint for viewing a list of BillOfMaterialReport objects.
Filterably by: Filterably by:
@ -370,80 +324,30 @@ class BOMReportList(ReportListView, PartReportMixin):
- enabled: Filter by enabled / disabled status - enabled: Filter by enabled / disabled status
- part: Filter by part(s) - part: Filter by part(s)
""" """
pass
queryset = BillOfMaterialsReport.objects.all()
serializer_class = BOMReportSerializer
def filter_queryset(self, queryset):
"""Custom queryset filtering"""
queryset = super().filter_queryset(queryset)
# List of Part objects to match against
parts = self.get_parts()
if len(parts) > 0:
"""
We wish to filter by part(s).
We need to compare the 'filters' string of each report,
and see if it matches against each of the specified parts.
"""
valid_report_ids = set()
for report in queryset.all():
matches = True
try:
filters = InvenTree.helpers.validateFilterString(report.filters)
except ValidationError:
# Filters are ill-defined
continue
for p in parts:
part_query = part.models.Part.objects.filter(pk=p.pk)
try:
if not part_query.filter(**filters).exists():
matches = False
break
except FieldError:
matches = False
break
if matches:
valid_report_ids.add(report.pk)
else:
continue
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids])
return queryset
class BOMReportDetail(RetrieveUpdateDestroyAPI): class BOMReportDetail(BOMReportMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single BillOfMaterialReport object.""" """API endpoint for a single BillOfMaterialReport object."""
pass
queryset = BillOfMaterialsReport.objects.all()
serializer_class = BOMReportSerializer
class BOMReportPrint(RetrieveAPI, PartReportMixin, ReportPrintMixin): class BOMReportPrint(BOMReportMixin, ReportPrintMixin, RetrieveAPI):
"""API endpoint for printing a BillOfMaterialReport object.""" """API endpoint for printing a BillOfMaterialReport object."""
pass
queryset = BillOfMaterialsReport.objects.all()
serializer_class = BOMReportSerializer
def get(self, request, *args, **kwargs):
"""Check if valid part item(s) have been provided."""
parts = self.get_parts()
return self.print(request, parts)
class BuildReportList(ReportListView, BuildReportMixin): class BuildReportMixin(ReportFilterMixin):
"""Mixin for the BuildReport report template"""
ITEM_MODEL = build.models.Build
ITEM_KEY = 'build'
queryset = BuildReport.objects.all()
serializer_class = BuildReportSerializer
class BuildReportList(BuildReportMixin, ReportListView):
"""API endpoint for viewing a list of BuildReport objects. """API endpoint for viewing a list of BuildReport objects.
Can be filtered by: Can be filtered by:
@ -451,236 +355,67 @@ class BuildReportList(ReportListView, BuildReportMixin):
- enabled: Filter by enabled / disabled status - enabled: Filter by enabled / disabled status
- build: Filter by Build object - build: Filter by Build object
""" """
pass
queryset = BuildReport.objects.all()
serializer_class = BuildReportSerializer
def filter_queryset(self, queryset):
"""Custom queryset filtering"""
queryset = super().filter_queryset(queryset)
# List of Build objects to match against
builds = self.get_builds()
if len(builds) > 0:
"""
We wish to filter by Build(s)
We need to compare the 'filters' string of each report,
and see if it matches against each of the specified parts
# TODO: This code needs some refactoring!
"""
valid_build_ids = set()
for report in queryset.all():
matches = True
try:
filters = InvenTree.helpers.validateFilterString(report.filters)
except ValidationError:
continue
for b in builds:
build_query = build.models.Build.objects.filter(pk=b.pk)
try:
if not build_query.filter(**filters).exists():
matches = False
break
except FieldError:
matches = False
break
if matches:
valid_build_ids.add(report.pk)
else:
continue
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_build_ids])
return queryset
class BuildReportDetail(RetrieveUpdateDestroyAPI): class BuildReportDetail(BuildReportMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single BuildReport object.""" """API endpoint for a single BuildReport object."""
pass
queryset = BuildReport.objects.all()
serializer_class = BuildReportSerializer
class BuildReportPrint(RetrieveAPI, BuildReportMixin, ReportPrintMixin): class BuildReportPrint(BuildReportMixin, ReportPrintMixin, RetrieveAPI):
"""API endpoint for printing a BuildReport.""" """API endpoint for printing a BuildReport."""
pass
queryset = BuildReport.objects.all()
serializer_class = BuildReportSerializer
def get(self, request, *ars, **kwargs):
"""Perform a GET action to print the report"""
builds = self.get_builds()
return self.print(request, builds)
class PurchaseOrderReportList(ReportListView, OrderReportMixin): class PurchaseOrderReportMixin(ReportFilterMixin):
"""Mixin for the PurchaseOrderReport report template"""
ITEM_MODEL = order.models.PurchaseOrder
ITEM_KEY = 'order'
queryset = PurchaseOrderReport.objects.all()
serializer_class = PurchaseOrderReportSerializer
class PurchaseOrderReportList(PurchaseOrderReportMixin, ReportListView):
"""API list endpoint for the PurchaseOrderReport model""" """API list endpoint for the PurchaseOrderReport model"""
OrderModel = order.models.PurchaseOrder pass
queryset = PurchaseOrderReport.objects.all()
serializer_class = PurchaseOrderReportSerializer
def filter_queryset(self, queryset):
"""Custom queryset filter for the PurchaseOrderReport list"""
queryset = super().filter_queryset(queryset)
orders = self.get_orders()
if len(orders) > 0:
"""
We wish to filter by purchase orders.
We need to compare the 'filters' string of each report,
and see if it matches against each of the specified orders.
TODO: In the future, perhaps there is a way to make this more efficient.
"""
valid_report_ids = set()
for report in queryset.all():
matches = True
# Filter string defined for the report object
try:
filters = InvenTree.helpers.validateFilterString(report.filters)
except Exception:
continue
for o in orders:
order_query = order.models.PurchaseOrder.objects.filter(pk=o.pk)
try:
if not order_query.filter(**filters).exists():
matches = False
break
except FieldError:
matches = False
break
if matches:
valid_report_ids.add(report.pk)
else:
continue
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids])
return queryset
class PurchaseOrderReportDetail(RetrieveUpdateDestroyAPI): class PurchaseOrderReportDetail(PurchaseOrderReportMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single PurchaseOrderReport object.""" """API endpoint for a single PurchaseOrderReport object."""
pass
queryset = PurchaseOrderReport.objects.all()
serializer_class = PurchaseOrderReportSerializer
class PurchaseOrderReportPrint(RetrieveAPI, OrderReportMixin, ReportPrintMixin): class PurchaseOrderReportPrint(PurchaseOrderReportMixin, ReportPrintMixin, RetrieveAPI):
"""API endpoint for printing a PurchaseOrderReport object.""" """API endpoint for printing a PurchaseOrderReport object."""
pass
OrderModel = order.models.PurchaseOrder
queryset = PurchaseOrderReport.objects.all()
serializer_class = PurchaseOrderReportSerializer
def get(self, request, *args, **kwargs):
"""Perform GET request to print the report"""
orders = self.get_orders()
return self.print(request, orders)
class SalesOrderReportList(ReportListView, OrderReportMixin): class SalesOrderReportMixin(ReportFilterMixin):
"""Mixin for the SalesOrderReport report template"""
ITEM_MODEL = order.models.SalesOrder
ITEM_KEY = 'order'
queryset = SalesOrderReport.objects.all()
serializer_class = SalesOrderReportSerializer
class SalesOrderReportList(SalesOrderReportMixin, ReportListView):
"""API list endpoint for the SalesOrderReport model""" """API list endpoint for the SalesOrderReport model"""
OrderModel = order.models.SalesOrder pass
queryset = SalesOrderReport.objects.all()
serializer_class = SalesOrderReportSerializer
def filter_queryset(self, queryset):
"""Custom queryset filtering for the SalesOrderReport API list"""
queryset = super().filter_queryset(queryset)
orders = self.get_orders()
if len(orders) > 0:
"""
We wish to filter by purchase orders.
We need to compare the 'filters' string of each report,
and see if it matches against each of the specified orders.
TODO: In the future, perhaps there is a way to make this more efficient.
"""
valid_report_ids = set()
for report in queryset.all():
matches = True
# Filter string defined for the report object
try:
filters = InvenTree.helpers.validateFilterString(report.filters)
except Exception:
continue
for o in orders:
order_query = order.models.SalesOrder.objects.filter(pk=o.pk)
try:
if not order_query.filter(**filters).exists():
matches = False
break
except FieldError:
matches = False
break
if matches:
valid_report_ids.add(report.pk)
else:
continue
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids])
return queryset
class SalesOrderReportDetail(RetrieveUpdateDestroyAPI): class SalesOrderReportDetail(SalesOrderReportMixin, RetrieveUpdateDestroyAPI):
"""API endpoint for a single SalesOrderReport object.""" """API endpoint for a single SalesOrderReport object."""
pass
queryset = SalesOrderReport.objects.all()
serializer_class = SalesOrderReportSerializer
class SalesOrderReportPrint(RetrieveAPI, OrderReportMixin, ReportPrintMixin): class SalesOrderReportPrint(SalesOrderReportMixin, ReportPrintMixin, RetrieveAPI):
"""API endpoint for printing a PurchaseOrderReport object.""" """API endpoint for printing a PurchaseOrderReport object."""
pass
OrderModel = order.models.SalesOrder
queryset = SalesOrderReport.objects.all()
serializer_class = SalesOrderReportSerializer
def get(self, request, *args, **kwargs):
"""Perform a GET request to print the report"""
orders = self.get_orders()
return self.print(request, orders)
report_api_urls = [ report_api_urls = [