diff --git a/src/backend/InvenTree/common/filters.py b/src/backend/InvenTree/common/filters.py index 6e6a00a58b..1600255634 100644 --- a/src/backend/InvenTree/common/filters.py +++ b/src/backend/InvenTree/common/filters.py @@ -3,7 +3,18 @@ import re from django.contrib.contenttypes.models import ContentType -from django.db.models import Q +from django.db.models import ( + Case, + CharField, + Exists, + FloatField, + Model, + OuterRef, + Q, + Subquery, + Value, + When, +) from django.db.models.query import QuerySet import InvenTree.conversion @@ -207,3 +218,81 @@ def filter_parametric_data(queryset: QuerySet, parameters: dict[str, str]) -> Qu ) return queryset + + +def order_by_parameter( + queryset: QuerySet, model_type: Model, ordering: str | None +) -> QuerySet: + """Order the provided queryset by a parameter value. + + Arguments: + queryset: The initial queryset to order. + model_type: The model type of the items in the queryset. + ordering: The ordering string provided by the user. + + Returns: + Ordered queryset. + + Used to order returned parts based on their parameter values. + + To order based on parameter value, supply an ordering string like: + - parameter_ + - -parameter_ + + where: + - is the ID of the PartParameterTemplate. + - A leading '-' indicates descending order. + """ + import common.models + + if not ordering: + # No ordering provided - return the original queryset + return queryset + + result = re.match(r'^-?parameter_(\d+)$', ordering) + + if not result: + # Ordering does not match the expected pattern - return the original queryset + return queryset + + template_id = result.group(1) + ascending = not ordering.startswith('-') + + template_exists_filter = common.models.Parameter.objects.filter( + template__id=template_id, + model_type=ContentType.objects.get_for_model(model_type), + model_id=OuterRef('id'), + ) + + queryset = queryset.annotate(parameter_exists=Exists(template_exists_filter)) + + # Annotate the queryset with the parameter value for the provided template + queryset = queryset.annotate( + parameter_value=Case( + When( + parameter_exists=True, + then=Subquery( + template_exists_filter.values('data')[:1], output_field=CharField() + ), + ), + default=Value('', output_field=CharField()), + ), + parameter_value_numeric=Case( + When( + parameter_exists=True, + then=Subquery( + template_exists_filter.values('data_numeric')[:1], + output_field=FloatField(), + ), + ), + default=Value(0, output_field=FloatField()), + ), + ) + + prefix = '' if ascending else '-' + + return queryset.order_by( + '-parameter_exists', + f'{prefix}parameter_value_numeric', + f'{prefix}parameter_value', + ) diff --git a/src/backend/InvenTree/part/api.py b/src/backend/InvenTree/part/api.py index fbab4e3d96..555cc15db5 100644 --- a/src/backend/InvenTree/part/api.py +++ b/src/backend/InvenTree/part/api.py @@ -1,7 +1,5 @@ """Provides a JSON API for the Part app.""" -import re - from django.db.models import Count, F, Q from django.urls import include, path from django.utils.translation import gettext_lazy as _ @@ -14,7 +12,6 @@ from drf_spectacular.utils import extend_schema_field from rest_framework import serializers from rest_framework.response import Response -import part.filters import part.tasks as part_tasks from data_exporter.mixins import DataExportViewMixin from InvenTree.api import ( @@ -1088,32 +1085,10 @@ class PartList( queryset, self.request.query_params ) - # queryset = self.filter_parametric_data(queryset) - queryset = self.order_by_parameter(queryset) - - return queryset - - def order_by_parameter(self, queryset): - """Perform queryset ordering based on parameter value. - - - Used if the 'ordering' query param points to a parameter - - e.g. '&ordering=param_' where specifies the PartParameterTemplate - - Only parts which have a matching parameter are returned - - Queryset is ordered based on parameter value - """ - # Extract "ordering" parameter from query args - ordering = self.request.query_params.get('ordering', None) - - if ordering: - # Ordering value must match required regex pattern - result = re.match(r'^\-?parameter_(\d+)$', ordering) - - if result: - template_id = result.group(1) - ascending = not ordering.startswith('-') - queryset = part.filters.order_by_parameter( - queryset, template_id, ascending - ) + # Apply ordering based on query parameter + queryset = common.filters.order_by_parameter( + queryset, Part, self.request.query_params.get('ordering', None) + ) return queryset