2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-12-16 17:28:11 +00:00

Implement generic "ordering" by parameter

This commit is contained in:
Oliver Walters
2025-11-25 02:56:36 +00:00
parent 7f82276df7
commit 262bbd5cf3
2 changed files with 94 additions and 30 deletions

View File

@@ -3,7 +3,18 @@
import re import re
from django.contrib.contenttypes.models import ContentType 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 from django.db.models.query import QuerySet
import InvenTree.conversion import InvenTree.conversion
@@ -207,3 +218,81 @@ def filter_parametric_data(queryset: QuerySet, parameters: dict[str, str]) -> Qu
) )
return queryset 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_<x>
- -parameter_<x>
where:
- <x> 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',
)

View File

@@ -1,7 +1,5 @@
"""Provides a JSON API for the Part app.""" """Provides a JSON API for the Part app."""
import re
from django.db.models import Count, F, Q from django.db.models import Count, F, Q
from django.urls import include, path from django.urls import include, path
from django.utils.translation import gettext_lazy as _ 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 import serializers
from rest_framework.response import Response from rest_framework.response import Response
import part.filters
import part.tasks as part_tasks import part.tasks as part_tasks
from data_exporter.mixins import DataExportViewMixin from data_exporter.mixins import DataExportViewMixin
from InvenTree.api import ( from InvenTree.api import (
@@ -1088,32 +1085,10 @@ class PartList(
queryset, self.request.query_params queryset, self.request.query_params
) )
# queryset = self.filter_parametric_data(queryset) # Apply ordering based on query parameter
queryset = self.order_by_parameter(queryset) queryset = common.filters.order_by_parameter(
queryset, Part, self.request.query_params.get('ordering', None)
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_<id>' where <id> 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
)
return queryset return queryset