mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	List filters (#10203)
* Add support for 'list' filtering * Docs * Add unit tests
This commit is contained in:
		| @@ -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