mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 03:26:45 +00:00
Download params (#9413)
* Pass extra options through to data export plugins * Add PartParameterExporter plugin - Useful for including part parameter data in exporter * Fix bug in InvenTreeTableHeader * enable export for partparametertable * Simplify tests * Add typing hints
This commit is contained in:
parent
e30786b068
commit
f7a4469590
3
docs/.gitignore
vendored
3
docs/.gitignore
vendored
@ -20,5 +20,8 @@ invoke-commands.txt
|
||||
# Temp files
|
||||
releases.json
|
||||
versions.json
|
||||
inventree_filters.yml
|
||||
inventree_settings.json
|
||||
inventree_tags.yml
|
||||
|
||||
.vscode/
|
||||
|
@ -280,9 +280,14 @@ class DataExportViewMixin:
|
||||
# Get the base model associated with this view
|
||||
try:
|
||||
serializer_class = self.get_serializer_class()
|
||||
export_kwargs['serializer_class'] = serializer_class
|
||||
export_kwargs['model_class'] = serializer_class.Meta.model
|
||||
export_kwargs['view_class'] = self.__class__
|
||||
except AttributeError:
|
||||
# If the serializer class is not available, set to None
|
||||
export_kwargs['serializer_class'] = None
|
||||
export_kwargs['model_class'] = None
|
||||
export_kwargs['view_class'] = None
|
||||
|
||||
return data_exporter.serializers.DataExportOptionsSerializer(
|
||||
*args, **export_kwargs
|
||||
|
@ -4,6 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
import InvenTree.exceptions
|
||||
import InvenTree.helpers
|
||||
import InvenTree.serializers
|
||||
from plugin import PluginMixinEnum, registry
|
||||
@ -28,7 +29,10 @@ class DataExportOptionsSerializer(serializers.Serializer):
|
||||
|
||||
# Generate a list of plugins to choose from
|
||||
# If a model type is provided, use this to filter the list of plugins
|
||||
serializer_class = kwargs.pop('serializer_class', None)
|
||||
model_class = kwargs.pop('model_class', None)
|
||||
view_class = kwargs.pop('view_class', None)
|
||||
|
||||
request = kwargs.pop('request', None)
|
||||
|
||||
# Is a plugin serializer provided?
|
||||
@ -46,7 +50,18 @@ class DataExportOptionsSerializer(serializers.Serializer):
|
||||
plugin_options = []
|
||||
|
||||
for plugin in registry.with_mixin(PluginMixinEnum.EXPORTER):
|
||||
if plugin.supports_export(model_class, request.user):
|
||||
try:
|
||||
supports_export = plugin.supports_export(
|
||||
model_class,
|
||||
user=request.user,
|
||||
serializer_class=serializer_class,
|
||||
view_class=view_class,
|
||||
)
|
||||
except Exception:
|
||||
InvenTree.exceptions.log_error(f'plugin.{plugin.slug}.supports_export')
|
||||
supports_export = False
|
||||
|
||||
if supports_export:
|
||||
plugin_options.append((plugin.slug, plugin.name))
|
||||
|
||||
self.fields['export_plugin'].choices = plugin_options
|
||||
|
@ -6,7 +6,7 @@ from typing import Union
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from rest_framework import serializers
|
||||
from rest_framework import serializers, views
|
||||
|
||||
from common.models import DataOutput
|
||||
from InvenTree.helpers import current_date
|
||||
@ -32,12 +32,22 @@ class DataExportMixin:
|
||||
super().__init__()
|
||||
self.add_mixin(PluginMixinEnum.EXPORTER, True, __class__)
|
||||
|
||||
def supports_export(self, model_class: type, user: User, *args, **kwargs) -> bool:
|
||||
def supports_export(
|
||||
self,
|
||||
model_class: type,
|
||||
user: User,
|
||||
serializer_class: serializers.Serializer = None,
|
||||
view_class: views.APIView = None,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> bool:
|
||||
"""Return True if this plugin supports exporting data for the given model.
|
||||
|
||||
Args:
|
||||
model_class: The model class to check
|
||||
user: The user requesting the export
|
||||
serializer_class: The serializer class to use for exporting the data
|
||||
view_class: The view class to use for exporting the data
|
||||
|
||||
Returns:
|
||||
True if the plugin supports exporting data for the given model
|
||||
|
@ -0,0 +1,130 @@
|
||||
"""Custom exporter for PartParameters."""
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from part.models import Part
|
||||
from part.serializers import PartSerializer
|
||||
from plugin import InvenTreePlugin
|
||||
from plugin.mixins import DataExportMixin
|
||||
|
||||
|
||||
class PartParameterExportOptionsSerializer(serializers.Serializer):
|
||||
"""Custom export options for the PartParameterExporter plugin."""
|
||||
|
||||
export_stock_data = serializers.BooleanField(
|
||||
default=True, label=_('Stock Data'), help_text=_('Include part stock data')
|
||||
)
|
||||
|
||||
export_pricing_data = serializers.BooleanField(
|
||||
default=True, label=_('Pricing Data'), help_text=_('Include part pricing data')
|
||||
)
|
||||
|
||||
|
||||
class PartParameterExporter(DataExportMixin, InvenTreePlugin):
|
||||
"""Builtin plugin for exporting PartParameter data.
|
||||
|
||||
Extends the "part" export process, to include all associated PartParameter data.
|
||||
"""
|
||||
|
||||
NAME = 'Part Parameter Exporter'
|
||||
SLUG = 'parameter-exporter'
|
||||
TITLE = _('Part Parameter Exporter')
|
||||
DESCRIPTION = _('Exporter for part parameter data')
|
||||
VERSION = '1.0.0'
|
||||
AUTHOR = _('InvenTree contributors')
|
||||
|
||||
ExportOptionsSerializer = PartParameterExportOptionsSerializer
|
||||
|
||||
def supports_export(
|
||||
self,
|
||||
model_class: type,
|
||||
user=None,
|
||||
serializer_class=None,
|
||||
view_class=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> bool:
|
||||
"""Supported if the base model is Part."""
|
||||
return model_class == Part and serializer_class == PartSerializer
|
||||
|
||||
def update_headers(self, headers, context, **kwargs):
|
||||
"""Update headers for the export."""
|
||||
if not self.export_stock_data:
|
||||
# Remove stock data from the headers
|
||||
for field in [
|
||||
'allocated_to_build_orders',
|
||||
'allocated_to_sales_orders',
|
||||
'available_stock',
|
||||
'available_substitute_stock',
|
||||
'available_variant_stock',
|
||||
'building',
|
||||
'can_build',
|
||||
'external_stock',
|
||||
'in_stock',
|
||||
'on_order',
|
||||
'ordering',
|
||||
'required_for_build_orders',
|
||||
'required_for_sales_orders',
|
||||
'stock_item_count',
|
||||
'total_in_stock',
|
||||
'unallocated_stock',
|
||||
'variant_stock',
|
||||
]:
|
||||
headers.pop(field, None)
|
||||
|
||||
if not self.export_pricing_data:
|
||||
# Remove pricing data from the headers
|
||||
for field in [
|
||||
'pricing_min',
|
||||
'pricing_max',
|
||||
'pricing_min_total',
|
||||
'pricing_max_total',
|
||||
'pricing_updated',
|
||||
]:
|
||||
headers.pop(field, None)
|
||||
|
||||
# Add in a header for each part parameter
|
||||
for pk, name in self.parameters.items():
|
||||
headers[f'parameter_{pk}'] = str(name)
|
||||
|
||||
return headers
|
||||
|
||||
def prefetch_queryset(self, queryset):
|
||||
"""Ensure that the part parameters are prefetched."""
|
||||
queryset = queryset.prefetch_related('parameters', 'parameters__template')
|
||||
|
||||
return queryset
|
||||
|
||||
def export_data(
|
||||
self, queryset, serializer_class, headers, context, output, **kwargs
|
||||
):
|
||||
"""Export part and parameter data."""
|
||||
# Extract custom serializer options and cache
|
||||
self.export_stock_data = context.get('export_stock_data', True)
|
||||
self.export_pricing_data = context.get('export_pricing_data', True)
|
||||
|
||||
queryset = self.prefetch_queryset(queryset)
|
||||
self.serializer_class = serializer_class
|
||||
|
||||
# Keep a dict of observed part parameters against their primary key
|
||||
self.parameters = {}
|
||||
|
||||
# Serialize the queryset using DRF first
|
||||
parts = self.serializer_class(
|
||||
queryset, parameters=True, exporting=True, many=True
|
||||
).data
|
||||
|
||||
for part in parts:
|
||||
# Extract the part parameters from the serialized data
|
||||
for parameter in part.get('parameters', []):
|
||||
if template := parameter.get('template_detail', None):
|
||||
template_id = template['pk']
|
||||
|
||||
if template_id not in self.parameters:
|
||||
self.parameters[template_id] = template['name']
|
||||
|
||||
part[f'parameter_{template_id}'] = parameter['data']
|
||||
|
||||
return parts
|
@ -66,6 +66,7 @@ class PluginsRegistry:
|
||||
'inventreelabel',
|
||||
'inventreelabelmachine',
|
||||
'inventreelabelsheet',
|
||||
'parameter-exporter',
|
||||
]
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
@ -81,6 +81,8 @@ export default function InvenTreeTableHeader({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filters;
|
||||
}, [tableProps.params, tableState.filterSet, tableState.queryFilters]);
|
||||
|
||||
const exportModal = useDataExport({
|
||||
|
@ -269,7 +269,7 @@ export default function ParametricPartTable({
|
||||
tableState={table}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
enableDownload: false,
|
||||
enableDownload: true,
|
||||
tableFilters: tableFilters,
|
||||
params: {
|
||||
category: categoryId,
|
||||
|
Loading…
x
Reference in New Issue
Block a user