Make it possible to set None to REST API filters
* Added a list argument to the method get_all() of the executions API controller class that may contain names of columns that a client wants to set to the null value (None internally in the code) in the query string. Thereby we're getting the ability to filter API entities (currently only workflow executions) fetched with the get_all() by some fields that are assigned to the None (null from the API perspective) value, i.e. typically it means that a value of a field is not defined. Closes-Bug: #1702421 Change-Id: I78fbf993519beb63ee9aef7058bdcb40f0a12ec3
This commit is contained in:
parent
66d1776f1b
commit
c49212e171
@ -334,14 +334,15 @@ class ExecutionsController(rest.RestController):
|
||||
wtypes.text, types.uuid, wtypes.text, types.jsontype,
|
||||
types.uuid, types.uuid, STATE_TYPES, wtypes.text,
|
||||
types.jsontype, types.jsontype, wtypes.text,
|
||||
wtypes.text, bool, types.uuid, bool)
|
||||
wtypes.text, bool, types.uuid, bool, types.list)
|
||||
def get_all(self, marker=None, limit=None, sort_keys='created_at',
|
||||
sort_dirs='asc', fields='', workflow_name=None,
|
||||
workflow_id=None, description=None, params=None,
|
||||
task_execution_id=None, root_execution_id=None, state=None,
|
||||
state_info=None, input=None, output=None, created_at=None,
|
||||
updated_at=None, include_output=None, project_id=None,
|
||||
all_projects=False):
|
||||
all_projects=False, nulls=''):
|
||||
|
||||
"""Return all Executions.
|
||||
|
||||
:param marker: Optional. Pagination marker for large data sets.
|
||||
@ -384,13 +385,18 @@ class ExecutionsController(rest.RestController):
|
||||
Admin required.
|
||||
:param all_projects: Optional. Get resources of all projects. Admin
|
||||
required.
|
||||
:param nulls: Optional. The names of the columns with null value in
|
||||
the query.
|
||||
"""
|
||||
acl.enforce('executions:list', context.ctx())
|
||||
|
||||
db_models.WorkflowExecution.check_allowed_none_values(nulls)
|
||||
|
||||
if all_projects or project_id:
|
||||
acl.enforce('executions:list:all_projects', context.ctx())
|
||||
|
||||
filters = filter_utils.create_filters_from_request_params(
|
||||
none_values=nulls,
|
||||
created_at=created_at,
|
||||
workflow_name=workflow_name,
|
||||
workflow_id=workflow_id,
|
||||
|
@ -123,6 +123,28 @@ class _MistralModelBase(oslo_models.ModelBase, oslo_models.TimestampMixin):
|
||||
def __repr__(self):
|
||||
return '%s %s' % (type(self).__name__, self.to_dict().__repr__())
|
||||
|
||||
@classmethod
|
||||
def _get_nullable_column_names(cls):
|
||||
return [c.name for c in cls.__table__.columns if c.nullable]
|
||||
|
||||
@classmethod
|
||||
def check_allowed_none_values(cls, column_names):
|
||||
"""Checks if the given columns can be assigned with None value.
|
||||
|
||||
:param column_names: The names of the columns to check.
|
||||
"""
|
||||
all_columns = cls.__table__.columns.keys()
|
||||
nullable_columns = cls._get_nullable_column_names()
|
||||
|
||||
for col in column_names:
|
||||
if col not in all_columns:
|
||||
raise ValueError("'{}' is not a valid field name.".format(col))
|
||||
|
||||
if col not in nullable_columns:
|
||||
raise ValueError(
|
||||
"The field '{}' can't hold None value.".format(col)
|
||||
)
|
||||
|
||||
|
||||
MistralModelBase = declarative.declarative_base(cls=_MistralModelBase)
|
||||
|
||||
|
@ -968,3 +968,26 @@ class TestExecutionsController(base.APITest):
|
||||
self.assertTrue(
|
||||
mock_get_execs.call_args[1].get('project_id', fake_project_id)
|
||||
)
|
||||
|
||||
def test_get_all_with_nulls_not_valid(self):
|
||||
resp = self.app.get(
|
||||
'/v2/executions?limit=10&sort_keys=id&sort_dirs=asc&nulls=invalid',
|
||||
expect_errors=True
|
||||
)
|
||||
|
||||
self.assertEqual(500, resp.status_int)
|
||||
self.assertIn(
|
||||
"'invalid' is not a valid field name.",
|
||||
resp.body.decode()
|
||||
)
|
||||
|
||||
resp = self.app.get(
|
||||
'/v2/executions?limit=10&sort_keys=id&sort_dirs=asc&nulls=id',
|
||||
expect_errors=True
|
||||
)
|
||||
|
||||
self.assertEqual(500, resp.status_int)
|
||||
self.assertIn(
|
||||
"The field 'id' can't hold None value.",
|
||||
resp.body.decode()
|
||||
)
|
||||
|
45
mistral/tests/unit/utils/test_filter_utils.py
Normal file
45
mistral/tests/unit/utils/test_filter_utils.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Copyright 2018 - Nokia, Inc.
|
||||
#
|
||||
# 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 mistral.tests.unit import base
|
||||
from mistral.utils import filter_utils
|
||||
|
||||
|
||||
class FilterUtilsTest(base.BaseTest):
|
||||
def test_create_filters_with_nones(self):
|
||||
expected_filters = {
|
||||
'key2': {'eq': 'value2'},
|
||||
'key1': {'eq': None}
|
||||
}
|
||||
|
||||
filters = filter_utils.create_filters_from_request_params(
|
||||
none_values=['key1'],
|
||||
key1=None,
|
||||
key2='value2',
|
||||
key3=None,
|
||||
)
|
||||
|
||||
self.assertEqual(expected_filters, filters)
|
||||
|
||||
del expected_filters['key1']
|
||||
|
||||
filters = filter_utils.create_filters_from_request_params(
|
||||
none_values=[],
|
||||
key1=None,
|
||||
key2='value2',
|
||||
key3=None,
|
||||
)
|
||||
|
||||
self.assertEqual(expected_filters, filters)
|
@ -15,16 +15,18 @@
|
||||
import six
|
||||
|
||||
|
||||
def create_filters_from_request_params(**params):
|
||||
def create_filters_from_request_params(none_values=None, **params):
|
||||
"""Create filters from REST request parameters.
|
||||
|
||||
:param none_values: field names, where the value is required to be None.
|
||||
:param req_params: REST request parameters.
|
||||
:return: filters dictionary.
|
||||
"""
|
||||
none_values = none_values or []
|
||||
filters = {}
|
||||
|
||||
for column, data in params.items():
|
||||
if data is not None:
|
||||
if (data is None and column in none_values) or data is not None:
|
||||
if isinstance(data, six.string_types):
|
||||
f_type, value = _extract_filter_type_and_value(data)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user