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:
parent
c3b2bb0380
commit
25d1bceaad
@ -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 = [
|
||||||
|
@ -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 = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user