From 75272b581569de86e04e0908876726ebb0e3d2b4 Mon Sep 17 00:00:00 2001 From: Chunlin Zhang Date: Tue, 29 Oct 2019 17:54:35 +0800 Subject: [PATCH 1/2] add {} support(example: "id{}": [1, 2]); update tests and demo; bump version number to 0.2.2 --- demo/apps/apijson_demo/views.py | 22 ++++ tests/test.py | 224 +++++++++++++++++++++++++++++++- uliweb_apijson/__init__.py | 2 +- uliweb_apijson/apijson/views.py | 65 ++++++++- 4 files changed, 301 insertions(+), 12 deletions(-) diff --git a/demo/apps/apijson_demo/views.py b/demo/apps/apijson_demo/views.py index 0d276d9..8f42343 100644 --- a/demo/apps/apijson_demo/views.py +++ b/demo/apps/apijson_demo/views.py @@ -85,6 +85,28 @@ def index(): }''', }, + { + "label":"Array query: {} with list", + "value":'''{ + "[]":{ + "moment":{ + "id{}":[2,3] + } + } +}''', + }, + + { + "label":"Array query: {} with conditions", + "value":'''{ + "[]":{ + "user":{ + "id&{}":">2,<=4" + } + } +}''', + }, + { "label":"Array query: simple @expr", "value":'''{ diff --git a/tests/test.py b/tests/test.py index df70569..558f9a6 100644 --- a/tests/test.py +++ b/tests/test.py @@ -11,9 +11,6 @@ def setup(): manage.call('uliweb reset -v -y') manage.call('uliweb dbinit -v') -def teardown(): - pass - def pre_call_as(username): from uliweb import models User = models.user @@ -22,7 +19,6 @@ def pre_call(request): request.user = user return pre_call -@with_setup(setup,teardown) def test_apijson_get(): """ >>> application = make_simple_application(project_dir='.') @@ -747,7 +743,7 @@ def test_apijson_get(): >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) >>> d = json_loads(r.data) >>> print(d) - {'code': 400, 'msg': "model does not have this column: 'nonexist'"} + {'code': 400, 'msg': "model does not have column: 'nonexist'"} >>> #query array with a nonexist column >>> data ='''{ @@ -767,6 +763,224 @@ def test_apijson_get(): >>> print(d) {'code': 400, 'msg': "non-existent column or not support item: 'nonexist'"} + >>> #query array, {} with list + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": [1, 2] + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 2, 'date': '2018-11-01 00:00:00', 'content': 'test moment', 'picture_list': '[]', 'id': 1}}, {'moment': {'user_id': 3, 'date': '2018-11-02 00:00:00', 'content': 'test moment from b', 'picture_list': '[]', 'id': 2}}]} + + >>> #query array, !{} with list + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id!{}": [1, 2] + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 4, 'date': '2018-11-06 00:00:00', 'content': 'test moment from c', 'picture_list': '[]', 'id': 3}}]} + + >>> #query array, {} with a non-exist column name + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "nonexist{}": [1, 2] + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "model does not have column: 'nonexist'"} + + >>> #query array, {} >= + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": ">=2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 3, 'date': '2018-11-02 00:00:00', 'content': 'test moment from b', 'picture_list': '[]', 'id': 2}}, {'moment': {'user_id': 4, 'date': '2018-11-06 00:00:00', 'content': 'test moment from c', 'picture_list': '[]', 'id': 3}}]} + + >>> #query array, {} = + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": "=2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 3, 'date': '2018-11-02 00:00:00', 'content': 'test moment from b', 'picture_list': '[]', 'id': 2}}]} + + >>> #query array, {} > + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": ">2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 4, 'date': '2018-11-06 00:00:00', 'content': 'test moment from c', 'picture_list': '[]', 'id': 3}}]} + + >>> #query array, {} <= + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": "<=2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 2, 'date': '2018-11-01 00:00:00', 'content': 'test moment', 'picture_list': '[]', 'id': 1}}, {'moment': {'user_id': 3, 'date': '2018-11-02 00:00:00', 'content': 'test moment from b', 'picture_list': '[]', 'id': 2}}]} + + >>> #query array, {} < + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": "<2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 2, 'date': '2018-11-01 00:00:00', 'content': 'test moment', 'picture_list': '[]', 'id': 1}}]} + + >>> #query array, !{} < + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id!{}": "<2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 3, 'date': '2018-11-02 00:00:00', 'content': 'test moment from b', 'picture_list': '[]', 'id': 2}}, {'moment': {'user_id': 4, 'date': '2018-11-06 00:00:00', 'content': 'test moment from c', 'picture_list': '[]', 'id': 3}}]} + + >>> #query array, {} != + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": "!=2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'moment': {'user_id': 2, 'date': '2018-11-01 00:00:00', 'content': 'test moment', 'picture_list': '[]', 'id': 1}}, {'moment': {'user_id': 4, 'date': '2018-11-06 00:00:00', 'content': 'test moment from c', 'picture_list': '[]', 'id': 3}}]} + + >>> #query array, {} with wrong operator + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "id{}": "%=2" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "not support '%=2'"} + + >>> #query array, {} condition list + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role": "ADMIN", + ... "id{}": "<=2,>3", + ... "@column": "username,nickname,id" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'admin', 'nickname': 'Administrator', 'id': 1}}, {'user': {'username': 'usera', 'nickname': 'User A', 'id': 2}}, {'user': {'username': 'userc', 'nickname': 'User C', 'id': 4}}]} + + >>> #query array, |{} condition list + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role": "ADMIN", + ... "id|{}": "<=2,>3", + ... "@column": "username,nickname,id" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'admin', 'nickname': 'Administrator', 'id': 1}}, {'user': {'username': 'usera', 'nickname': 'User A', 'id': 2}}, {'user': {'username': 'userc', 'nickname': 'User C', 'id': 4}}]} + + >>> #query array, &{} condition list + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role": "ADMIN", + ... "id&{}": ">2,<=4", + ... "@column": "username,nickname,id" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'userb', 'nickname': 'User B', 'id': 3}}, {'user': {'username': 'userc', 'nickname': 'User C', 'id': 4}}]} + + >>> #query array, !{} condition list + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role": "ADMIN", + ... "id!{}": ">2,<=4", + ... "@column": "username,nickname,id" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "'!' not supported in condition list"} + + >>> #query array, |{} condition list, item more than 2 + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role": "ADMIN", + ... "id|{}": "=1,=2,>=4", + ... "@column": "username,id" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'admin', 'id': 1}}, {'user': {'username': 'usera', 'id': 2}}, {'user': {'username': 'userc', 'id': 4}}]} + >>> #Association query: Two tables, one to one,ref path is absolute path >>> data ='''{ ... "moment":{}, diff --git a/uliweb_apijson/__init__.py b/uliweb_apijson/__init__.py index e823231..7ea7bfe 100644 --- a/uliweb_apijson/__init__.py +++ b/uliweb_apijson/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.2.1' +__version__ = '0.2.2' __url__ = 'https://github.com/zhangchunlin/uliweb-apijson' __author__ = 'Chunlin Zhang' __email__ = 'zhangchunlin@gmail.com' diff --git a/uliweb_apijson/apijson/views.py b/uliweb_apijson/apijson/views.py index 3008055..3ec61e2 100644 --- a/uliweb_apijson/apijson/views.py +++ b/uliweb_apijson/apijson/views.py @@ -244,18 +244,71 @@ def _get_filter_condition(self,model,model_param,item,expr=False): if hasattr(model,name): return getattr(model.c,name).like(model_param[n]) else: - raise UliwebError("model does not have this column: '%s'"%(name)) + raise UliwebError("model does not have column: '%s'"%(name)) elif n[-1]=="}" and n[-2]=="{": - name = n[:-2] - if hasattr(model,name): - # TODO: https://github.com/APIJSON/APIJSON/blob/master/Document.md#32-%E5%8A%9F%E8%83%BD%E7%AC%A6 - pass - raise UliwebError("still not support '%s'"%(name)) + if n[-3] in ["&","|","!"]: + operator = n[-3] + name = n[:-3] + else: + operator = None + name = n[:-2] + + if not hasattr(model,name): + raise UliwebError("model does not have column: '%s'"%(name)) + + # https://github.com/APIJSON/APIJSON/blob/master/Document.md#32-%E5%8A%9F%E8%83%BD%E7%AC%A6 + # https://vincentcheng.github.io/apijson-doc/zh/grammar.html#%E9%80%BB%E8%BE%91%E8%BF%90%E7%AE%97-%E7%AD%9B%E9%80%89 + col = getattr(model.c,name) + cond = model_param[n] + if isinstance(cond,list): + fcond = col.in_(cond) + if operator== "!": + fcond = not_(fcond) + return fcond + elif isinstance(cond,str): + cond_list = cond.strip().split(",") + if len(cond_list)==1: + fcond = self._get_filter_condition_from_str(col,cond_list[0]) + if operator=="!": + fcond = not_(fcond) + return fcond + elif len(cond_list)>1: + fcond = self._get_filter_condition_from_str(col,cond_list[0]) + for c in cond_list: + fc = self._get_filter_condition_from_str(col,c) + if operator=="&": + fcond = and_(fcond,fc) + elif operator=="|" or operator==None: + fcond = or_(fcond,fc) + else: + raise UliwebError("'%s' not supported in condition list"%(operator)) + return fcond + + raise UliwebError("not support '%s':'%s'"%(n,cond)) elif hasattr(model,n): return getattr(model.c,n)==model_param[n] else: raise UliwebError("non-existent column or not support item: '%s'"%(item)) + def _get_filter_condition_from_str(self,col,cond_str): + cond_str = cond_str.strip() + c1,c2 = cond_str[0],cond_str[1] + if c1=='>': + if c2=="=": + return col >= cond_str[2:] + else: + return col > cond_str[1:] + elif c1=='<': + if c2=="=": + return col <= cond_str[2:] + else: + return col < cond_str[1:] + elif c1=="=": + return col == cond_str[1:] + elif c1=="!" and c2=="=": + return col != cond_str[2:] + raise UliwebError("not support '%s'"%(cond_str)) + def head(self): try: for key in self.request_data: From 4ee6f67ad9cdfc7ba237a22de53eb85a139c909c Mon Sep 17 00:00:00 2001 From: Chunlin Zhang Date: Tue, 29 Oct 2019 18:07:08 +0800 Subject: [PATCH 2/2] tests: add case query array, {} multiple condition to a same field --- tests/test.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test.py b/tests/test.py index 558f9a6..9839cec 100644 --- a/tests/test.py +++ b/tests/test.py @@ -951,6 +951,22 @@ def test_apijson_get(): >>> print(d) {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'userb', 'nickname': 'User B', 'id': 3}}, {'user': {'username': 'userc', 'nickname': 'User C', 'id': 4}}]} + >>> #query array, {} multiple condition to a same field + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role": "ADMIN", + ... "id&{}": ">2,<=4", + ... "id{}": "!=3", + ... "@column": "username,nickname,id" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'userc', 'nickname': 'User C', 'id': 4}}]} + >>> #query array, !{} condition list >>> data ='''{ ... "[]":{