2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-09-13 14:11:37 +00:00

List filters (#10203)

* Add support for 'list' filtering

* Docs

* Add unit tests
This commit is contained in:
Oliver
2025-08-20 10:23:20 +10:00
committed by GitHub
parent b939e39ea2
commit e44008f528
4 changed files with 71 additions and 3 deletions

View File

@@ -4,6 +4,7 @@ import datetime
import hashlib
import inspect
import io
import json
import os
import os.path
import re
@@ -729,13 +730,14 @@ def extract_serial_numbers(
return serials
def validateFilterString(value, model=None):
def validateFilterString(value: str, model=None) -> dict:
"""Validate that a provided filter string looks like a list of comma-separated key=value pairs.
These should nominally match to a valid database filter based on the model being filtered.
e.g. "category=6, IPN=12"
e.g. "part__name=widget"
e.g. "item=[1,2,3], status=active"
The ReportTemplate class uses the filter string to work out which items a given report applies to.
For example, an acceptance test report template might only apply to stock items with a given IPN,
@@ -753,7 +755,8 @@ def validateFilterString(value, model=None):
if not value or len(value) == 0:
return results
groups = value.split(',')
# Split by comma, but ignore commas within square brackets
groups = re.split(r',(?![^\[]*\])', value)
for group in groups:
group = group.strip()
@@ -771,6 +774,16 @@ def validateFilterString(value, model=None):
if not k or not v:
raise ValidationError(f'Invalid group: {group}')
# Account for 'list' support
if v.startswith('[') and v.endswith(']'):
try:
v = json.loads(v)
except json.JSONDecodeError:
raise ValidationError(f'Invalid list value: {v}')
if not isinstance(v, list):
raise ValidationError(f'Expected a list for key "{k}", got {type(v)}')
results[k] = v
# If a model is provided, verify that the provided filters can be used against it

View File

@@ -349,6 +349,47 @@ class LabelTest(InvenTreeAPITestCase):
self.assertEqual(output.plugin, 'inventreelabel')
self.assertTrue(output.output.name.endswith('.pdf'))
def test_filters(self):
"""Test that template filters are correctly validated."""
from django.core.exceptions import ValidationError
from InvenTree.helpers import validateFilterString
invalid = [
'name=widget, category=6, invalid_field=123',
'category__in=[1,',
'foo=bar',
]
valid = [
'name=widget, category=6',
'category__in=[1,2,3]',
'name=widget , id__in = [99, 199 ] ',
'pk__in=[1,2,3], active=True',
'pk__in=[1, 99], category__in=[1,2,3]',
]
template = LabelTemplate.objects.filter(enabled=True, model_type='part').first()
for f in invalid:
with self.assertRaises(ValidationError):
template.filters = f
template.clean()
for f in valid:
template.filters = f
template.clean()
# Test a specific example
example = ' location__in =[1,2 , 3 ] , status= 3 , id__in=[4,5,6] , part__active=False'
result = validateFilterString(example, model=StockItem)
self.assertEqual(result['location__in'], [1, 2, 3])
self.assertEqual(result['status'], '3')
self.assertEqual(result['id__in'], [4, 5, 6])
self.assertEqual(result['part__active'], 'False')
class PrintTestMixins:
"""Mixin that enables e2e printing tests."""

View File

@@ -13,7 +13,7 @@ def validate_report_model_type(value):
raise ValidationError('Not a valid model type')
def validate_filters(value, model=None):
def validate_filters(value: str, model=None) -> dict:
"""Validate that the provided model filters are valid."""
from InvenTree.helpers import validateFilterString