From 22fa3604559fd16723627058d6107a8053ab795e Mon Sep 17 00:00:00 2001 From: Thomas NOEL Date: Fri, 6 Oct 2017 18:18:49 +0200 Subject: [PATCH] utils: handle foo.bar dot syntax in templated_url (#19261) --- combo/utils.py | 27 +++++++++++++++++--- tests/test_cells.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test_utils.py | 24 +++++++++++++++++- 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/combo/utils.py b/combo/utils.py index b95dee4..2fec168 100644 --- a/combo/utils.py +++ b/combo/utils.py @@ -177,6 +177,29 @@ requests = Requests() class UnknownTemplateVariableError(KeyError): pass +def get_templated_url_part(part, obj, prefix=''): + if not part: + return unicode(obj) + split = part.split('.', 1) + part0, part1 = split[0], '.'.join(split[1:]) + next_prefix = '%s.%s' % (prefix, part0) if prefix else part0 + if (isinstance(obj, Context) or isinstance(obj, dict)) and part0 in obj: + return get_templated_url_part(part1, obj[part0], next_prefix) + elif isinstance(obj, list) or isinstance(obj, tuple): + try: + index = int(part0) + except ValueError: + raise UnknownTemplateVariableError('invalid index %s in %s' % (part0, next_prefix)) + try: + item = obj[index] + except IndexError: + raise UnknownTemplateVariableError('index %s out of range in %s' % (part0, next_prefix)) + return get_templated_url_part(part1, item, next_prefix) + elif prefix: + raise UnknownTemplateVariableError('no key %s in %s' % (part, next_prefix)) + else: + raise UnknownTemplateVariableError('unknown variable %s' % part) + def get_templated_url(url, context=None): template_vars = Context() if context: @@ -193,9 +216,7 @@ def get_templated_url(url, context=None): varname = matchobj.group(0)[1:-1] if varname == '[': return '[' - if varname not in template_vars: - raise UnknownTemplateVariableError(varname) - return unicode(template_vars[varname]) + return get_templated_url_part(varname, template_vars) return re.sub(r'(\[.+?\])', repl, url) diff --git a/tests/test_cells.py b/tests/test_cells.py index e2c278a..9661ef4 100644 --- a/tests/test_cells.py +++ b/tests/test_cells.py @@ -494,3 +494,76 @@ def test_config_json_cell_additional_url(app): assert context['plop_url'] == 'http://bar' assert context['plop_status'] == 200 assert 'plop_error' not in context + + # additional-data url depends on others results + with override_settings(JSON_CELL_TYPES={ + 'test-config-json-cell-2': { + 'name': 'Foobar', + 'url': 'http://foo', + 'additional-data': [ + {'key': 'plop', 'url': 'http://[json.data]', 'log_errors': False, 'timeout': 42}, + {'key': 'plop2', 'url': 'http://[json.data]/[plop.data]', 'log_errors': False, + 'timeout': 10}, + ] + }}, + TEMPLATE_DIRS=['%s/templates-1' % os.path.abspath(os.path.dirname(__file__))]): + cell = ConfigJsonCell() + cell.key = 'test-config-json-cell-2' + cell.page = page + cell.title = 'Example Site' + cell.order = 0 + cell.save() + + data = {'data': 'bar'} + + with mock.patch('combo.utils.requests.get') as requests_get: + requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=200) + url = reverse('combo-public-ajax-page-cell', + kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()}) + resp = app.get(url) + assert resp.body.strip() == '/var1=bar/var2=bar/' + assert len(requests_get.mock_calls) == 3 + assert requests_get.mock_calls[0][1][0] == 'http://foo' + assert requests_get.mock_calls[0][-1]['log_errors'] == True + assert requests_get.mock_calls[0][-1]['timeout'] == 28 + assert requests_get.mock_calls[1][1][0] == 'http://bar' + assert requests_get.mock_calls[1][-1]['log_errors'] == False + assert requests_get.mock_calls[1][-1]['timeout'] == 42 + assert requests_get.mock_calls[2][1][0] == 'http://bar/bar' + assert requests_get.mock_calls[2][-1]['log_errors'] == False + assert requests_get.mock_calls[2][-1]['timeout'] == 10 + context = cell.get_cell_extra_context({}) + assert context['json'] == data + assert context['json_url'] == 'http://foo' + assert context['json_status'] == 200 + assert context['plop'] == data + assert context['plop_url'] == 'http://bar' + assert context['plop_status'] == 200 + assert context['plop2'] == data + assert context['plop2_url'] == 'http://bar/bar' + assert context['plop2_status'] == 200 + + with mock.patch('combo.utils.requests.get') as requests_get: + requests_get.return_value = mock.Mock(content=json.dumps(data), status_code=404, + headers={'content-type': 'application/json'}) + url = reverse('combo-public-ajax-page-cell', + kwargs={'page_pk': page.id, 'cell_reference': cell.get_reference()}) + resp = app.get(url) + assert resp.body.strip() == '/var1=/var2=/' + # can not create plop and plop2 url: only one request for "json" + assert len(requests_get.mock_calls) == 1 + assert requests_get.mock_calls[0][1][0] == 'http://foo' + context = cell.get_cell_extra_context({}) + assert context['json'] == None + assert context['json_url'] == 'http://foo' + assert context['json_status'] == 404 + assert context['json_error'] == data + # can not create plop and plop2 url: None value, no status + assert context['plop'] == None + assert 'plop_url' not in context + assert 'plop_status' not in context + assert 'plop_error' not in context + assert context['plop2'] == None + assert 'plop2_url' not in context + assert 'plop2_status' not in context + assert 'plop2_error' not in context diff --git a/tests/test_utils.py b/tests/test_utils.py index 376b335..f1526fa 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -44,12 +44,34 @@ def test_templated_url(): assert get_templated_url('foobar[[]]') == 'foobar[]' assert get_templated_url('foobar[[]test]') == 'foobar[test]' - with pytest.raises(UnknownTemplateVariableError): + with pytest.raises(UnknownTemplateVariableError, match='unknown variable test_url'): get_templated_url('[test_url]') with override_settings(TEMPLATE_VARS={'test_url': 'http://www.example.net'}): assert get_templated_url('[test_url]') == 'http://www.example.net' assert get_templated_url('[test_url]/hello') == 'http://www.example.net/hello' + with override_settings(TEMPLATE_VARS={ + 'str': 'blah', + 'dict': {'foo': 'bar'}, + 'list': ['l1', 'l2', {'item': 'l3'}], + 'dictlist': {'litem': ['dl1', 'dl2']}, + 'lint': [1, 2], + }): + assert get_templated_url('[str]') == 'blah' + assert get_templated_url('[dict.foo]') == 'bar' + assert get_templated_url('[list.0] [list.2.item] [list.-2]') == 'l1 l3 l2' + assert get_templated_url('[dictlist.litem.1]') == 'dl2' + assert get_templated_url('[lint.0][lint.1]') == '12' + for test_template in ( + ('[list.badix]', 'invalid index badix in list.badix'), + ('[list.5]', 'index 5 out of range in list.5'), + ('[str.attr]', 'no key attr in str.attr'), + ('[list.1.k]', 'no key k in list.1.k'), + ('[list.1.2]', 'no key 2 in list.1.2'), + ('[vroum]', 'unknown variable vroum')): + with pytest.raises(UnknownTemplateVariableError, match=test_template[1]): + get_templated_url(test_template[0]) + # contexts without users request = RequestFactory().get('/') request.user = None -- 2.14.2