2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-04-06 19:41:16 +00:00

[bug] Fix table ordering (#11277)

* Additional filtering options for stock list

* Fix ordering for stock table

* Ordering fix for build order table

* Ordering for supplier parts and manufacturer parts

* SalesOrderLineItem: Order by IPN

* ReturnOrderLineItem table:

- Order by part name
- Order by part IPN

* Update API version to 451

Increment API version to 451 and update changelog.

* Add playwright tests for column sorting

* Add backend tests for API ordering

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
Oliver
2026-02-11 17:52:21 +11:00
committed by GitHub
parent 384f8282fd
commit d24ba7965c
16 changed files with 149 additions and 15 deletions

View File

@@ -1,11 +1,14 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 450
INVENTREE_API_VERSION = 451
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v451 -> 2026-02-10 : https://github.com/inventree/InvenTree/pull/11277
- Adds sorting to multiple part related endpoints (part, IPN, ...)
v450 -> 2026-02-10 : https://github.com/inventree/InvenTree/pull/11260
- Adds "part" field to the StockItemTracking model and API endpoints
- Additional filtering options for the StockItemTracking API endpoint

View File

@@ -731,6 +731,48 @@ class InvenTreeAPITestCase(
"""Assert that dictionary 'a' is a subset of dictionary 'b'."""
self.assertEqual(b, b | a)
def run_ordering_test(
self, url: str, ordering_field: str, params: Optional[dict] = None
):
"""Run a test to check that the results are ordered correctly.
Arguments:
url: The URL to test
ordering_field: The field to order by (e.g. 'name')
params: Additional parameters to include in the request (e.g. filters)
Process:
- Run a GET request against the provided URL with the appropriate ordering parameter
- Run a separate GET request with the opposite ordering parameter (e.g. '-name')
- Check that the results are ordered differently in each case
"""
query_params = {**(params or {})}
pk_values = set()
for ordering in [None, ordering_field, f'-{ordering_field}']:
response = self.get(
url,
data={**query_params, 'ordering': ordering}
if ordering
else query_params,
expected_code=200,
)
self.assertGreater(
len(response.data),
1,
f'No data returned from {url} with ordering={ordering}',
)
pk_values.add(response.data[0]['pk'])
self.assertGreater(
len(pk_values),
1,
f"Ordering by '{ordering_field}' does not change the order of results at {url}",
)
def run_output_test(
self,
url: str,

View File

@@ -346,6 +346,8 @@ class BuildList(
filter_backends = SEARCH_ORDER_FILTER_ALIAS
ordering_fields = [
'reference',
'part',
'IPN',
'part__name',
'status',
'creation_date',
@@ -364,6 +366,8 @@ class BuildList(
ordering_field_aliases = {
'reference': ['reference_int', 'reference'],
'project_code': ['project_code__code'],
'part': ['part__name'],
'IPN': ['part__IPN'],
}
ordering = '-reference'
search_fields = [

View File

@@ -197,9 +197,17 @@ class ManufacturerPartList(
"""
filterset_class = ManufacturerPartFilter
filter_backends = SEARCH_ORDER_FILTER
filter_backends = SEARCH_ORDER_FILTER_ALIAS
output_options = ManufacturerOutputOptions
ordering_fields = ['part', 'IPN', 'MPN', 'manufacturer']
ordering_field_aliases = {
'part': 'part__name',
'IPN': 'part__IPN',
'manufacturer': 'manufacturer__name',
}
search_fields = [
'manufacturer__name',
'description',
@@ -354,12 +362,13 @@ class SupplierPartList(
output_options = SupplierPartOutputOptions
ordering_fields = [
'SKU',
'part',
'supplier',
'manufacturer',
'active',
'IPN',
'MPN',
'SKU',
'packaging',
'pack_quantity',
'in_stock',
@@ -370,8 +379,9 @@ class SupplierPartList(
'part': 'part__name',
'supplier': 'supplier__name',
'manufacturer': 'manufacturer_part__manufacturer__name',
'MPN': 'manufacturer_part__MPN',
'pack_quantity': ['pack_quantity_native', 'pack_quantity'],
'IPN': 'part__IPN',
'MPN': 'manufacturer_part__MPN',
}
search_fields = [

View File

@@ -1051,6 +1051,7 @@ class SalesOrderLineItemList(
'customer',
'order',
'part',
'IPN',
'part__name',
'quantity',
'allocated',
@@ -1063,6 +1064,7 @@ class SalesOrderLineItemList(
ordering_field_aliases = {
'customer': 'order__customer__name',
'part': 'part__name',
'IPN': 'part__IPN',
'order': 'order__reference',
}
@@ -1672,11 +1674,24 @@ class ReturnOrderLineItemList(
filterset_class = ReturnOrderLineItemFilter
filter_backends = SEARCH_ORDER_FILTER
filter_backends = SEARCH_ORDER_FILTER_ALIAS
output_options = ReturnOrderLineItemOutputOptions
ordering_fields = ['reference', 'target_date', 'received_date']
ordering_fields = [
'part',
'IPN',
'stock',
'reference',
'target_date',
'received_date',
]
ordering_field_aliases = {
'part': 'item__part__name',
'IPN': 'item__part__IPN',
'stock': ['item__quantity', 'item__serial_int', 'item__serial'],
}
search_fields = [
'item__serial',

View File

@@ -1263,7 +1263,9 @@ class StockList(
filter_backends = SEARCH_ORDER_FILTER_ALIAS
ordering_field_aliases = {
'part': 'part__name',
'location': 'location__pathstring',
'IPN': 'part__IPN',
'SKU': 'supplier_part__SKU',
'MPN': 'supplier_part__manufacturer_part__MPN',
'stock': ['quantity', 'serial_int', 'serial'],
@@ -1272,6 +1274,7 @@ class StockList(
ordering_fields = [
'batch',
'location',
'part',
'part__name',
'part__IPN',
'updated',
@@ -1281,6 +1284,7 @@ class StockList(
'quantity',
'stock',
'status',
'IPN',
'SKU',
'MPN',
]

View File

@@ -68,6 +68,11 @@ class StockLocationTest(StockAPITestCase):
# Add some stock locations
StockLocation.objects.create(name='top', description='top category')
def test_ordering(self):
"""Test ordering options for the StockLocation list endpoint."""
for ordering in ['name', 'pathstring', 'level', 'tree_id']:
self.run_ordering_test(self.list_url, ordering)
def test_list(self):
"""Test the StockLocationList API endpoint."""
test_cases = [
@@ -565,6 +570,11 @@ class StockItemListTest(StockAPITestCase):
# Return JSON data
return response.data
def test_ordering(self):
"""Run ordering tests against the StockItem list endpoint."""
for ordering in ['part', 'location', 'stock', 'status', 'IPN', 'MPN', 'SKU']:
self.run_ordering_test(self.list_url, ordering)
def test_top_level_filtering(self):
"""Test filtering against "top level" stock location."""
# No filters, should return *all* items