diff --git a/glare/db/sqlalchemy/api.py b/glare/db/sqlalchemy/api.py index 5eb5789..93bf5ba 100644 --- a/glare/db/sqlalchemy/api.py +++ b/glare/db/sqlalchemy/api.py @@ -435,13 +435,14 @@ def _do_query_filters(filters): conds = [models.ArtifactProperty.name == field_name] if key_name is not None: conds.extend([models.ArtifactProperty.key_name == key_name]) - if op != 'in': - fn = op_mappings[op] - conds.extend([fn(getattr(models.ArtifactProperty, - field_type + '_value'), value)]) - else: - conds.extend([getattr(models.ArtifactProperty, - field_type + '_value').in_(value)]) + if value is not None: + if op != 'in': + fn = op_mappings[op] + conds.extend([fn(getattr(models.ArtifactProperty, + field_type + '_value'), value)]) + else: + conds.extend([getattr(models.ArtifactProperty, + field_type + '_value').in_(value)]) prop_conds.append(conds) diff --git a/glare/objects/base.py b/glare/objects/base.py index 4009d61..bfb7dac 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -531,7 +531,6 @@ class BaseArtifact(base.VersionedObject): # (field_name, key_name, op, field_type, value) new_filters = [] for filter_name, filter_value in filters: - key_name = None if filter_name in ('tags-any', 'tags'): if ':' in filter_value: msg = _("Tags are filtered without operator") @@ -539,28 +538,37 @@ class BaseArtifact(base.VersionedObject): new_filters.append( (filter_name, None, None, None, filter_value)) continue - elif '.' in filter_name: - filter_name, key_name = filter_name.split('.', 1) - cls._validate_filter_name(filter_name) - op, val = utils.split_filter_op(filter_value) - cls._validate_filter_ops(filter_name, op) - field_type = cls.fields.get(filter_name).element_type - else: - cls._validate_filter_name(filter_name) - op, val = utils.split_filter_op(filter_value) - cls._validate_filter_ops(filter_name, op) - field_type = cls.fields.get(filter_name) + key_name = None + if '.' in filter_name: + filter_name, key_name = filter_name.split('.', 1) + if not isinstance(cls.fields.get(filter_name), + glare_fields.Dict): + msg = _("Field %s is not Dict") % filter_name + raise exception.BadRequest(msg) + + cls._validate_filter_name(filter_name) + field_type = cls.fields.get(filter_name) + + if isinstance(field_type, glare_fields.List) or isinstance( + field_type, glare_fields.Dict) and key_name is not None: + field_type = field_type.element_type try: - if op == 'in': - value = [field_type.coerce(cls(), filter_name, value) - for value in - utils.split_filter_value_for_quotes(val)] + if isinstance(field_type, glare_fields.Dict): + new_filters.append(( + filter_name, filter_value, None, None, None)) else: - value = field_type.coerce(cls(), filter_name, val) - new_filters.append( - (filter_name, key_name, op, - cls._get_field_type(field_type), value)) + op, val = utils.split_filter_op(filter_value) + cls._validate_filter_ops(filter_name, op) + if op == 'in': + value = [field_type.coerce(cls(), filter_name, value) + for value in + utils.split_filter_value_for_quotes(val)] + else: + value = field_type.coerce(cls(), filter_name, val) + new_filters.append( + (filter_name, key_name, op, + cls._get_field_type(field_type), value)) except ValueError: msg = _("Invalid filter value: %s") % str(val) raise exception.BadRequest(msg) diff --git a/glare/tests/functional/test_sample_artifact.py b/glare/tests/functional/test_sample_artifact.py index 39d8299..dfbc746 100644 --- a/glare/tests/functional/test_sample_artifact.py +++ b/glare/tests/functional/test_sample_artifact.py @@ -425,6 +425,58 @@ class TestList(TestArtifact): result = sort_results(self.get(url=url)['sample_artifact']) self.assertEqual(art_list[5:], result) + def test_artifact_list_dict_filters(self): + lists_of_str = [ + ['aaa', 'bbb', 'ccc'], + ['aaa', 'bbb'], + ['aaa', 'ddd'], + ['bbb'], + ['ccc'] + ] + dicts_of_str = [ + {'aaa': 'z', 'bbb': 'z', 'ccc': 'z'}, + {'aaa': 'z', 'bbb': 'z'}, + {'aaa': 'z', 'ddd': 'z'}, + {'bbb': 'z'}, + {'ccc': 'z'} + ] + art_list = [self.create_artifact({'name': 'name%s' % i, + 'version': '1.0', + 'tags': ['tag%s' % i], + 'int1': 1024, + 'float1': 123.456, + 'str1': 'bugaga', + 'bool1': True, + 'list_of_str': lists_of_str[i], + 'dict_of_str': dicts_of_str[i]}) + for i in range(5)] + + # test list filters + url = '/sample_artifact?list_of_str=aaa&sort=name' + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual(art_list[:3], result) + + url = '/sample_artifact?list_of_str=ccc&sort=name' + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual([art_list[0], art_list[4]], result) + + url = '/sample_artifact?list_of_str=eee&sort=name' + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual([], result) + + # test dict filters + url = '/sample_artifact?dict_of_str=aaa&sort=name' + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual(art_list[:3], result) + + url = '/sample_artifact?dict_of_str=ccc&sort=name' + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual([art_list[0], art_list[4]], result) + + url = '/sample_artifact?dict_of_str=eee&sort=name' + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual([], result) + def test_list_dict_prop_filters(self): # Create artifact art_list = [self.create_artifact({'name': 'name0', @@ -485,11 +537,14 @@ class TestList(TestArtifact): self.assertEqual([], result) url = '/sample_artifact?dict_of_str' - self.get(url=url, status=400) + self.assertEqual([], result) url = '/sample_artifact?dict_of_str.pr3=blabla:val3' self.get(url=url, status=400) + url = '/sample_artifact?list_of_str.pr3=blabla:val3' + self.get(url=url, status=400) + url = '/sample_artifact?dict_of_str.bla=val1' result = sort_results(self.get(url=url)['sample_artifact']) self.assertEqual([], result)