Support filter attribute with empty string
This will enable users to filter list of results with attributes with empty value. For example, the request below will list all unbound ports (unbound ports have blank device_id). GET "/ports?device_id=" APIImpact Change-Id: I9001214de126eb888c2425b6a6275f59ec8478e7 Closes-Bug: #1749304
This commit is contained in:
parent
b371afd463
commit
a732bbf19e
@ -75,12 +75,14 @@ def get_filters_from_dict(data, attr_info, skips=None):
|
||||
becomes:
|
||||
{'check': [u'a', u'b'], 'name': [u'Bob']}
|
||||
"""
|
||||
is_empty_string_supported = is_empty_string_filtering_supported()
|
||||
skips = skips or []
|
||||
res = {}
|
||||
for key, values in data.items():
|
||||
if key in skips or hasattr(model_base.BASEV2, key):
|
||||
continue
|
||||
values = [v for v in values if v]
|
||||
values = [v for v in values
|
||||
if v or (v == "" and is_empty_string_supported)]
|
||||
key_attr_info = attr_info.get(key, {})
|
||||
if 'convert_list_to' in key_attr_info:
|
||||
values = key_attr_info['convert_list_to'](values)
|
||||
@ -92,6 +94,11 @@ def get_filters_from_dict(data, attr_info, skips=None):
|
||||
return res
|
||||
|
||||
|
||||
def is_empty_string_filtering_supported():
|
||||
return 'empty-string-filtering' in (extensions.PluginAwareExtensionManager.
|
||||
get_instance().extensions)
|
||||
|
||||
|
||||
def get_previous_link(request, items, id_key):
|
||||
params = request.GET.copy()
|
||||
params.pop('marker', None)
|
||||
|
30
neutron/extensions/_empty_string_filtering_lib.py
Normal file
30
neutron/extensions/_empty_string_filtering_lib.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
TODO(hongbin): This module should be deleted once neutron-lib containing
|
||||
https://review.openstack.org/#/c/565342/ change is released.
|
||||
"""
|
||||
|
||||
|
||||
ALIAS = 'empty-string-filtering'
|
||||
IS_SHIM_EXTENSION = True
|
||||
IS_STANDARD_ATTR_EXTENSION = False
|
||||
NAME = 'Empty String Filtering Extension'
|
||||
DESCRIPTION = 'Allow filtering by attributes with empty string value'
|
||||
UPDATED_TIMESTAMP = '2018-04-09T10:00:00-00:00'
|
||||
RESOURCE_ATTRIBUTE_MAP = {}
|
||||
SUB_RESOURCE_ATTRIBUTE_MAP = {}
|
||||
ACTION_MAP = {}
|
||||
REQUIRED_EXTENSIONS = []
|
||||
OPTIONAL_EXTENSIONS = []
|
||||
ACTION_STATUS = {}
|
19
neutron/extensions/empty_string_filtering.py
Normal file
19
neutron/extensions/empty_string_filtering.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron_lib.api import extensions
|
||||
|
||||
from neutron.extensions import _empty_string_filtering_lib as apidef
|
||||
|
||||
|
||||
class Empty_string_filtering(extensions.APIExtensionDescriptor):
|
||||
api_definition = apidef
|
@ -158,7 +158,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
|
||||
"default-subnetpools",
|
||||
"subnet-service-types",
|
||||
"ip-substring-filtering",
|
||||
"port-security-groups-filtering"]
|
||||
"port-security-groups-filtering",
|
||||
"empty-string-filtering"]
|
||||
|
||||
@property
|
||||
def supported_extension_aliases(self):
|
||||
|
@ -10,6 +10,7 @@ NETWORK_API_EXTENSIONS+=",default-subnetpools"
|
||||
NETWORK_API_EXTENSIONS+=",dhcp_agent_scheduler"
|
||||
NETWORK_API_EXTENSIONS+=",dns-integration"
|
||||
NETWORK_API_EXTENSIONS+=",dvr"
|
||||
NETWORK_API_EXTENSIONS+=",empty-string-filtering"
|
||||
NETWORK_API_EXTENSIONS+=",ext-gw-mode"
|
||||
NETWORK_API_EXTENSIONS+=",external-net"
|
||||
NETWORK_API_EXTENSIONS+=",extra_dhcp_opt"
|
||||
|
@ -86,6 +86,7 @@ class APIv2TestBase(base.BaseTestCase):
|
||||
self._plugin_patcher = mock.patch(plugin, autospec=True)
|
||||
self.plugin = self._plugin_patcher.start()
|
||||
instance = self.plugin.return_value
|
||||
instance.supported_extension_aliases = ['empty-string-filtering']
|
||||
instance._NeutronPluginBaseV2__native_pagination_support = True
|
||||
instance._NeutronPluginBaseV2__native_sorting_support = True
|
||||
tools.make_mock_plugin_json_encodable(instance)
|
||||
@ -202,7 +203,7 @@ class APIv2TestCase(APIv2TestBase):
|
||||
instance.get_networks.return_value = []
|
||||
|
||||
self.api.get(_get_path('networks'), {'name': ''})
|
||||
filters = {}
|
||||
filters = {'name': ['']}
|
||||
kwargs = self._get_collection_kwargs(filters=filters)
|
||||
instance.get_networks.assert_called_once_with(mock.ANY, **kwargs)
|
||||
|
||||
@ -211,7 +212,7 @@ class APIv2TestCase(APIv2TestBase):
|
||||
instance.get_networks.return_value = []
|
||||
|
||||
self.api.get(_get_path('networks'), {'name': ['', '']})
|
||||
filters = {}
|
||||
filters = {'name': ['', '']}
|
||||
kwargs = self._get_collection_kwargs(filters=filters)
|
||||
instance.get_networks.assert_called_once_with(mock.ANY, **kwargs)
|
||||
|
||||
@ -220,7 +221,7 @@ class APIv2TestCase(APIv2TestBase):
|
||||
instance.get_networks.return_value = []
|
||||
|
||||
self.api.get(_get_path('networks'), {'name': ['bar', '']})
|
||||
filters = {'name': ['bar']}
|
||||
filters = {'name': ['bar', '']}
|
||||
kwargs = self._get_collection_kwargs(filters=filters)
|
||||
instance.get_networks.assert_called_once_with(mock.ANY, **kwargs)
|
||||
|
||||
@ -1552,11 +1553,21 @@ class FiltersTestCase(base.BaseTestCase):
|
||||
self.assertEqual({}, api_common.get_filters(request, None,
|
||||
["fields"]))
|
||||
|
||||
def test_blank_values(self):
|
||||
@mock.patch('neutron.api.api_common.is_empty_string_filtering_supported',
|
||||
return_value=False)
|
||||
def test_blank_values(self, mock_is_supported):
|
||||
path = '/?foo=&bar=&baz=&qux='
|
||||
request = webob.Request.blank(path)
|
||||
self.assertEqual({}, api_common.get_filters(request, {}))
|
||||
|
||||
@mock.patch('neutron.api.api_common.is_empty_string_filtering_supported',
|
||||
return_value=True)
|
||||
def test_blank_values_with_filtering_supported(self, mock_is_supported):
|
||||
path = '/?foo=&bar=&baz=&qux='
|
||||
request = webob.Request.blank(path)
|
||||
self.assertEqual({'foo': [''], 'bar': [''], 'baz': [''], 'qux': ['']},
|
||||
api_common.get_filters(request, {}))
|
||||
|
||||
def test_no_attr_info(self):
|
||||
path = '/?foo=4&bar=3&baz=2&qux=1'
|
||||
request = webob.Request.blank(path)
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support for filtering attributes with value as empty string. A shim
|
||||
extension is added to indicate if this feature is supported.
|
Loading…
Reference in New Issue
Block a user