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:
@@ -100,6 +100,20 @@ If you enter an invalid option for the filter field, an error message will be di
|
||||
!!! warning "Advanced Users"
|
||||
Report filtering is an advanced topic, and requires a little bit of knowledge of the underlying data structure!
|
||||
|
||||
#### List Filtering
|
||||
|
||||
To filter a queryset against a list of ID values, you can use the following "list" syntax:
|
||||
|
||||
```
|
||||
item__in=[1,2,3]
|
||||
```
|
||||
|
||||
Note that:
|
||||
|
||||
- The list must be enclosed in square brackets `[]`
|
||||
- The items in the list must be comma-separated
|
||||
- The key must end with `__in` to indicate that it is a list filter (*note the double underscore*).
|
||||
|
||||
### Metadata
|
||||
|
||||
A JSON field made available to any [plugins](../plugins/index.md) - but not used by internal code.
|
||||
|
@@ -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
|
||||
|
@@ -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."""
|
||||
|
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user