2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-14 21:22:20 +00:00

refactor(backend): move serializer context enrichment to mixin (#10456)

* add output options for PurchaseOrder, SalesOrder, and ReturnOrder endpoints

* add output options for PurchaseOrder, SalesOrder, and ReturnOrder endpoints

* add serializer context handling and update sales order fixture with additional line item

* bump API version to 398 and update output options tests for PurchaseOrder endpoint

* refactor(backend): move serializer context enrichtment to mixin

* cleanup other get_serializer instances

* add output options tests for SalesOrder and ReturnOrder detail endpoints

* fix typo

* fix api

---------

Co-authored-by: Silver <reza.sh.7798@gmail.com>
This commit is contained in:
Matthias Mair
2025-10-06 00:04:06 +02:00
committed by GitHub
parent 66a488b6a2
commit d7b4997da2
7 changed files with 45 additions and 145 deletions

View File

@@ -228,3 +228,12 @@ class OutputOptionsMixin:
kwargs.update(self.output_options.format_params(params))
return super().get_serializer(*args, **kwargs)
class SerializerContextMixin:
"""Mixin to add context to serializer."""
def get_serializer(self, *args, **kwargs):
"""Add context to serializer."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)

View File

@@ -39,6 +39,7 @@ from InvenTree.mixins import (
ListCreateAPI,
OutputOptionsMixin,
RetrieveUpdateDestroyAPI,
SerializerContextMixin,
)
from users.models import Owner
@@ -387,14 +388,7 @@ class BuildList(DataExportViewMixin, BuildMixin, ListCreateAPI):
def get_serializer(self, *args, **kwargs):
"""Add extra context information to the endpoint serializer."""
try:
part_detail = str2bool(self.request.GET.get('part_detail', True))
except AttributeError:
part_detail = True
kwargs['part_detail'] = part_detail
kwargs['create'] = True
return super().get_serializer(*args, **kwargs)
@@ -527,17 +521,12 @@ class BuildLineFilter(FilterSet):
return queryset.exclude(flt)
class BuildLineMixin:
class BuildLineMixin(SerializerContextMixin):
"""Mixin class for BuildLine API endpoints."""
queryset = BuildLine.objects.all()
serializer_class = build.serializers.BuildLineSerializer
def get_serializer(self, *args, **kwargs):
"""Return the serializer instance for this endpoint."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_source_build(self) -> Build:
"""Return the source Build object for the BuildLine queryset.

View File

@@ -12,7 +12,12 @@ from data_exporter.mixins import DataExportViewMixin
from InvenTree.api import ListCreateDestroyAPIView, MetadataView
from InvenTree.fields import InvenTreeOutputOption, OutputConfiguration
from InvenTree.filters import SEARCH_ORDER_FILTER, SEARCH_ORDER_FILTER_ALIAS
from InvenTree.mixins import ListCreateAPI, OutputOptionsMixin, RetrieveUpdateDestroyAPI
from InvenTree.mixins import (
ListCreateAPI,
OutputOptionsMixin,
RetrieveUpdateDestroyAPI,
SerializerContextMixin,
)
from .models import (
Address,
@@ -170,7 +175,10 @@ class ManufacturerOutputOptions(OutputConfiguration):
class ManufacturerPartList(
DataExportViewMixin, OutputOptionsMixin, ListCreateDestroyAPIView
SerializerContextMixin,
DataExportViewMixin,
OutputOptionsMixin,
ListCreateDestroyAPIView,
):
"""API endpoint for list view of ManufacturerPart object.
@@ -181,13 +189,10 @@ class ManufacturerPartList(
queryset = ManufacturerPart.objects.all().prefetch_related(
'part', 'manufacturer', 'supplier_parts', 'tags'
)
serializer_class = ManufacturerPartSerializer
filterset_class = ManufacturerPartFilter
output_options = ManufacturerOutputOptions
filter_backends = SEARCH_ORDER_FILTER
search_fields = [
'manufacturer__name',
'description',
@@ -242,16 +247,16 @@ class ManufacturerPartParameterOptions(OutputConfiguration):
]
class ManufacturerPartParameterList(ListCreateDestroyAPIView, OutputOptionsMixin):
class ManufacturerPartParameterList(
SerializerContextMixin, ListCreateDestroyAPIView, OutputOptionsMixin
):
"""API endpoint for list view of ManufacturerPartParamater model."""
queryset = ManufacturerPartParameter.objects.all()
serializer_class = ManufacturerPartParameterSerializer
filterset_class = ManufacturerPartParameterFilter
output_options = ManufacturerPartParameterOptions
filter_backends = SEARCH_ORDER_FILTER
search_fields = ['name', 'value', 'units']
@@ -472,7 +477,7 @@ class SupplierPriceBreakOutputOptions(OutputConfiguration):
]
class SupplierPriceBreakList(OutputOptionsMixin, ListCreateAPI):
class SupplierPriceBreakList(SerializerContextMixin, OutputOptionsMixin, ListCreateAPI):
"""API endpoint for list view of SupplierPriceBreak object.
- GET: Retrieve list of SupplierPriceBreak objects

View File

@@ -43,6 +43,7 @@ from InvenTree.mixins import (
ListCreateAPI,
OutputOptionsMixin,
RetrieveUpdateDestroyAPI,
SerializerContextMixin,
)
from order import models, serializers
from order.status_codes import (
@@ -57,22 +58,9 @@ from part.models import Part
from users.models import Owner
class GeneralExtraLineList(DataExportViewMixin):
class GeneralExtraLineList(SerializerContextMixin, DataExportViewMixin):
"""General template for ExtraLine API classes."""
def get_serializer(self, *args, **kwargs):
"""Return the serializer instance for this endpoint."""
try:
params = self.request.query_params3
kwargs['order_detail'] = str2bool(params.get('order_detail', False))
except AttributeError:
pass
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return the annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)
@@ -358,18 +346,12 @@ class PurchaseOrderOutputOptions(OutputConfiguration):
OPTIONS = [InvenTreeOutputOption('supplier_detail')]
class PurchaseOrderMixin:
class PurchaseOrderMixin(SerializerContextMixin):
"""Mixin class for PurchaseOrder endpoints."""
queryset = models.PurchaseOrder.objects.all()
serializer_class = serializers.PurchaseOrderSerializer
def get_serializer(self, *args, **kwargs):
"""Return the serializer instance for this endpoint."""
# Ensure the request context is passed through
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return the annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)
@@ -628,7 +610,7 @@ class PurchaseOrderLineItemOutputOptions(OutputConfiguration):
]
class PurchaseOrderLineItemMixin:
class PurchaseOrderLineItemMixin(SerializerContextMixin):
"""Mixin class for PurchaseOrderLineItem endpoints."""
queryset = models.PurchaseOrderLineItem.objects.all()
@@ -644,12 +626,6 @@ class PurchaseOrderLineItemMixin:
return queryset
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def perform_update(self, serializer):
"""Override the perform_update method to auto-update pricing if required."""
super().perform_update(serializer)
@@ -831,19 +807,12 @@ class SalesOrderFilter(OrderFilter):
)
class SalesOrderMixin:
class SalesOrderMixin(SerializerContextMixin):
"""Mixin class for SalesOrder endpoints."""
queryset = models.SalesOrder.objects.all()
serializer_class = serializers.SalesOrderSerializer
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint."""
# Ensure the context is passed through to the serializer
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)
@@ -1022,18 +991,12 @@ class SalesOrderLineItemFilter(LineItemFilter):
return queryset.exclude(order__status__in=SalesOrderStatusGroups.OPEN)
class SalesOrderLineItemMixin:
class SalesOrderLineItemMixin(SerializerContextMixin):
"""Mixin class for SalesOrderLineItem endpoints."""
queryset = models.SalesOrderLineItem.objects.all()
serializer_class = serializers.SalesOrderLineItemSerializer
def get_serializer(self, *args, **kwargs):
"""Return serializer for this endpoint with extra data as requested."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)
@@ -1493,19 +1456,12 @@ class ReturnOrderFilter(OrderFilter):
)
class ReturnOrderMixin:
class ReturnOrderMixin(SerializerContextMixin):
"""Mixin class for ReturnOrder endpoints."""
queryset = models.ReturnOrder.objects.all()
serializer_class = serializers.ReturnOrderSerializer
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint."""
# Ensure the context is passed through to the serializer
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)
@@ -1650,18 +1606,12 @@ class ReturnOrderLineItemFilter(LineItemFilter):
return queryset.filter(received_date=None)
class ReturnOrderLineItemMixin:
class ReturnOrderLineItemMixin(SerializerContextMixin):
"""Mixin class for ReturnOrderLineItem endpoints."""
queryset = models.ReturnOrderLineItem.objects.all()
serializer_class = serializers.ReturnOrderLineItemSerializer
def get_serializer(self, *args, **kwargs):
"""Return serializer for this endpoint with extra data as requested."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)

View File

@@ -43,6 +43,7 @@ from InvenTree.mixins import (
RetrieveAPI,
RetrieveUpdateAPI,
RetrieveUpdateDestroyAPI,
SerializerContextMixin,
UpdateAPI,
)
from InvenTree.serializers import EmptySerializer
@@ -1014,7 +1015,7 @@ class PartFilter(FilterSet):
return queryset.filter(category__in=children)
class PartMixin:
class PartMixin(SerializerContextMixin):
"""Mixin class for Part API endpoints."""
serializer_class = part_serializers.PartSerializer
@@ -1037,9 +1038,6 @@ class PartMixin:
def get_serializer(self, *args, **kwargs):
"""Return a serializer instance for this endpoint."""
# Ensure the request context is passed through
kwargs['context'] = self.get_serializer_context()
# Indicate that we can create a new Part via this endpoint
kwargs['create'] = self.is_create
@@ -1053,7 +1051,6 @@ class PartMixin:
self.starred_parts = [
star.part for star in self.request.user.starred_parts.all()
]
kwargs['starred_parts'] = self.starred_parts
return super().get_serializer(*args, **kwargs)
@@ -1387,23 +1384,6 @@ class PartParameterAPIMixin:
return context
def get_serializer(self, *args, **kwargs):
"""Return the serializer instance for this API endpoint.
If requested, extra detail fields are annotated to the queryset:
- part_detail
- template_detail
"""
try:
kwargs['part_detail'] = str2bool(self.request.GET.get('part_detail', False))
kwargs['template_detail'] = str2bool(
self.request.GET.get('template_detail', True)
)
except AttributeError:
pass
return super().get_serializer(*args, **kwargs)
class PartParameterFilter(FilterSet):
"""Custom filters for the PartParameterList API endpoint."""
@@ -1614,19 +1594,12 @@ class BomFilter(FilterSet):
return queryset.filter(part.get_used_in_bom_item_filter())
class BomMixin:
class BomMixin(SerializerContextMixin):
"""Mixin class for BomItem API endpoints."""
serializer_class = part_serializers.BomItemSerializer
queryset = BomItem.objects.all()
def get_serializer(self, *args, **kwargs):
"""Return the serializer instance for this API endpoint."""
# Ensure the request context is passed through!
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return the queryset object for this endpoint."""
queryset = super().get_queryset(*args, **kwargs)

View File

@@ -52,6 +52,7 @@ from InvenTree.mixins import (
OutputOptionsMixin,
RetrieveAPI,
RetrieveUpdateDestroyAPI,
SerializerContextMixin,
)
from order.models import PurchaseOrder, ReturnOrder, SalesOrder
from order.serializers import (
@@ -373,18 +374,12 @@ class StockLocationFilter(FilterSet):
return queryset
class StockLocationMixin:
class StockLocationMixin(SerializerContextMixin):
"""Mixin class for StockLocation API endpoints."""
queryset = StockLocation.objects.all()
serializer_class = StockSerializers.LocationSerializer
def get_serializer(self, *args, **kwargs):
"""Set context before returning serializer."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self, *args, **kwargs):
"""Return annotated queryset for the StockLocationList endpoint."""
queryset = super().get_queryset(*args, **kwargs)
@@ -1012,7 +1007,7 @@ class StockFilter(FilterSet):
return queryset.filter(location__in=children)
class StockApiMixin:
class StockApiMixin(SerializerContextMixin):
"""Mixin class for StockItem API endpoints."""
serializer_class = StockSerializers.StockItemSerializer
@@ -1032,12 +1027,6 @@ class StockApiMixin:
return ctx
def get_serializer(self, *args, **kwargs):
"""Set context before returning serializer."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
class StockOutputOptions(OutputConfiguration):
"""Output options for StockItem serializers."""
@@ -1317,7 +1306,7 @@ class StockItemSerialNumbers(RetrieveAPI):
serializer_class = StockSerializers.StockItemSerialNumbersSerializer
class StockItemTestResultMixin:
class StockItemTestResultMixin(SerializerContextMixin):
"""Mixin class for the StockItemTestResult API endpoints."""
queryset = StockItemTestResult.objects.all()
@@ -1329,12 +1318,6 @@ class StockItemTestResultMixin:
ctx['request'] = self.request
return ctx
def get_serializer(self, *args, **kwargs):
"""Set context before returning serializer."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
class StockItemTestResultOutputOptions(OutputConfiguration):
"""Output options for StockItemTestResult endpoint."""
@@ -1487,7 +1470,9 @@ class StockTrackingOutputOptions(OutputConfiguration):
]
class StockTrackingList(DataExportViewMixin, OutputOptionsMixin, ListAPI):
class StockTrackingList(
SerializerContextMixin, DataExportViewMixin, OutputOptionsMixin, ListAPI
):
"""API endpoint for list view of StockItemTracking objects.
StockItemTracking objects are read-only
@@ -1500,12 +1485,6 @@ class StockTrackingList(DataExportViewMixin, OutputOptionsMixin, ListAPI):
serializer_class = StockSerializers.StockTrackingSerializer
output_options = StockTrackingOutputOptions
def get_serializer(self, *args, **kwargs):
"""Set context before returning serializer."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_delta_model_map(self) -> dict:
"""Return a mapping of delta models to their respective models and serializers.

View File

@@ -30,6 +30,7 @@ from InvenTree.mixins import (
RetrieveAPI,
RetrieveUpdateAPI,
RetrieveUpdateDestroyAPI,
SerializerContextMixin,
UpdateAPI,
)
from InvenTree.settings import FRONTEND_URL_BASE
@@ -278,7 +279,7 @@ class UserList(ListCreateAPI):
filterset_fields = ['is_staff', 'is_active', 'is_superuser']
class GroupMixin:
class GroupMixin(SerializerContextMixin):
"""Mixin for Group API endpoints to add permissions filter.
Permissions:
@@ -290,12 +291,6 @@ class GroupMixin:
serializer_class = GroupSerializer
permission_classes = [InvenTree.permissions.IsStaffOrReadOnlyScope]
def get_serializer(self, *args, **kwargs):
"""Return serializer instance for this endpoint."""
kwargs['context'] = self.get_serializer_context()
return super().get_serializer(*args, **kwargs)
def get_queryset(self):
"""Return queryset for this endpoint.