diff --git a/tests/demo/apps/apijson_demo/dbinit.py b/tests/demo/apps/apijson_demo/dbinit.py index fd015b1..08eb1cc 100644 --- a/tests/demo/apps/apijson_demo/dbinit.py +++ b/tests/demo/apps/apijson_demo/dbinit.py @@ -83,6 +83,13 @@ ] comment_list = [ + { + "username" : "admin", + "to_username" : "userb", + "moment_id" : 1, + "date" : "2018-11-1", + "content" : "comment from admin", + }, { "username" : "usera", "to_username" : "userb", diff --git a/tests/demo/apps/apijson_demo/models.py b/tests/demo/apps/apijson_demo/models.py index 8150090..af73cf6 100644 --- a/tests/demo/apps/apijson_demo/models.py +++ b/tests/demo/apps/apijson_demo/models.py @@ -16,6 +16,11 @@ class Moment(Model): content = Field(TEXT) picture_list = Field(JSON, default=[]) + @classmethod + def owner_condition(cls,user_id): + print("Moment: owner_condition") + return cls.c.user_id==user_id + class Comment(Model): user_id = Reference("user") to_id = Reference("user") diff --git a/tests/test.py b/tests/test.py index dc4fb6d..741b6b8 100644 --- a/tests/test.py +++ b/tests/test.py @@ -66,6 +66,29 @@ def test_apijson_get(): >>> print(d) {'code': 200, 'msg': 'success', 'user': {'username': 'admin', 'nickname': 'Administrator', 'email': 'admin@localhost', 'is_superuser': True, 'id': 1}} + >>> #query with @column which have a non existing column name + >>> data ='''{ + ... "user":{ + ... "@role":"OWNER", + ... "@column": "id,username,email,nickname,is_superuser,nonexisting" + ... } + ... }''' + >>> 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', 'email': 'admin@localhost', 'is_superuser': True, 'id': 1}} + + >>> #query with a non existing column property + >>> data ='''{ + ... "user":{ + ... "nonexisting": 1 + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "'user' have no attribute 'nonexisting'"} + >>> #query one with a non existing model >>> data ='''{ ... "nonexist":{ @@ -120,7 +143,7 @@ def test_apijson_get(): >>> r = handler.post('/apijson/get', data=data, middlewares=[]) >>> d = json_loads(r.data) >>> print(d) - {'code': 400, 'msg': "no user for role 'OWNER'"} + {'code': 400, 'msg': "no login user for role 'OWNER'"} >>> #query one with OWNER but cannot filter with OWNER >>> data ='''{ @@ -134,6 +157,18 @@ def test_apijson_get(): >>> print(d) {'code': 400, 'msg': "'publicnotice' cannot filter with owner"} + >>> #query one with OWNER which will use owner_condition() to filter + >>> data ='''{ + ... "moment":{ + ... "@role":"OWNER" + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + Moment: owner_condition + >>> 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 one with UNKNOWN >>> data ='''{ ... "publicnotice":{ @@ -200,6 +235,338 @@ def test_apijson_get(): >>> print(d) {'code': 400, 'msg': "'user' not accessible by role 'superuser'"} + >>> #query array + >>> data ='''{ + ... "[]":{ + ... "user": {"@role":"ADMIN"} + ... } + ... }''' + >>> 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', 'email': 'admin@localhost', 'is_superuser': True, 'last_login': None, 'date_join': '2018-11-01 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 1}}, {'user': {'username': 'usera', 'nickname': 'User A', 'email': 'usera@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-02-02 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 2}}, {'user': {'username': 'userb', 'nickname': 'User B', 'email': 'userb@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-03-03 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 3}}, {'user': {'username': 'userc', 'nickname': 'User C', 'email': 'userc@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-04-04 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 4}}]} + + >>> #query array + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role":"ADMIN", + ... "@column":"id,username,nickname,email" + ... } + ... } + ... }''' + >>> 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', 'email': 'admin@localhost', 'id': 1}}, {'user': {'username': 'usera', 'nickname': 'User A', 'email': 'usera@localhost', 'id': 2}}, {'user': {'username': 'userb', 'nickname': 'User B', 'email': 'userb@localhost', 'id': 3}}, {'user': {'username': 'userc', 'nickname': 'User C', 'email': 'userc@localhost', 'id': 4}}]} + + >>> #query array with non existing role + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role":"NONEXISTING", + ... "@column":"id,username,nickname,email" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "'user' not accessible by role 'NONEXISTING'"} + + >>> #query array with UNKNOWN + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@column":"id,username,nickname,email" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "'user' not accessible by role 'UNKNOWN'"} + + >>> #query array without login user + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role":"ADMIN", + ... "@column":"id,username,nickname,email" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "no login user for role 'ADMIN'"} + + >>> #query array with a role which the user doesn't really have + >>> data ='''{ + ... "[]":{ + ... "user": { + ... "@role":"ADMIN", + ... "@column":"id,username,nickname,email" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "user doesn't have role 'ADMIN'"} + + >>> #query array with @count + >>> data ='''{ + ... "[]":{ + ... "@count":3, + ... "user": { + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'admin', 'nickname': 'Administrator', 'email': 'admin@localhost', 'is_superuser': True, 'last_login': None, 'date_join': '2018-11-01 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 1}}, {'user': {'username': 'usera', 'nickname': 'User A', 'email': 'usera@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-02-02 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 2}}, {'user': {'username': 'userb', 'nickname': 'User B', 'email': 'userb@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-03-03 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 3}}]} + + >>> #query array ,@count is bad param + >>> data ='''{ + ... "[]":{ + ... "@count":"bad", + ... "user": { + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "@count should be an int, but get 'bad'"} + + >>> #query array with @count and @page + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":1, + ... "user": { + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'userb', 'nickname': 'User B', 'email': 'userb@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-03-03 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 3}}, {'user': {'username': 'userc', 'nickname': 'User C', 'email': 'userc@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-04-04 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 4}}]} + + >>> #query array with @count and @page, @page bad param + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":"bad", + ... "user": { + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "@page should be an int, but get 'bad'"} + + >>> #query array with @count and @page, @page <0 + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":-2, + ... "user": { + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "page should >0, but get '-2'"} + + >>> #query array with @count/@page/@query, @query bad param + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":1, + ... "@query":3, + ... "user": { + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "bad param 'query': 3"} + + >>> #query array with @count/@page/@query, @query = 0 + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":1, + ... "@query":0, + ... "user": { + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', '[]': [{'user': {'username': 'userb', 'nickname': 'User B', 'email': 'userb@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-03-03 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 3}}, {'user': {'username': 'userc', 'nickname': 'User C', 'email': 'userc@localhost', 'is_superuser': False, 'last_login': None, 'date_join': '2018-04-04 00:00:00', 'image': '', 'active': False, 'locked': False, 'deleted': False, 'auth_type': 'default', 'timezone': '', 'id': 4}}]} + + >>> #query array with OWNER but cannot filter with OWNER + >>> data ='''{ + ... "[]":{ + ... "publicnotice": { + ... "@role":"OWNER" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "'publicnotice' cannot filter with owner"} + + >>> #query array with OWNER + >>> data ='''{ + ... "[]":{ + ... "comment": { + ... "@role":"OWNER" + ... } + ... } + ... }''' + >>> 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', '[]': [{'comment': {'user_id': 1, 'to_id': 3, 'moment_id': 1, 'date': '2018-11-01 00:00:00', 'content': 'comment from admin', 'id': 1}}]} + + >>> #query array with OWNER, the model using owner_condition + >>> data ='''{ + ... "[]":{ + ... "moment": { + ... "@role":"OWNER" + ... } + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + Moment: owner_condition + >>> 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 with some filter column + >>> data ='''{ + ... "[]":{ + ... "@count":4, + ... "@page":0, + ... "user":{ + ... "@column":"id,username,nickname,email", + ... "@order":"id-", + ... "@role":"ADMIN", + ... "username":"admin" + ... } + ... } + ... }''' + >>> 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', 'email': 'admin@localhost', 'id': 1}}]} + + >>> #query array with reference, @query = 1 + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":0, + ... "@query":1, + ... "user":{ + ... "@column":"id,username,nickname,email", + ... "@order":"id-", + ... "@role":"ADMIN" + ... } + ... }, + ... "total@":"/[]/total" + ... }''' + >>> 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', 'total': 4} + + >>> #query array with reference, @query = 2 + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":0, + ... "@query":2, + ... "user":{ + ... "@column":"id,username,nickname,email", + ... "@order":"id-", + ... "@role":"ADMIN" + ... } + ... }, + ... "total@":"/[]/total" + ... }''' + >>> 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', 'email': 'userc@localhost', 'id': 4}}, {'user': {'username': 'userb', 'nickname': 'User B', 'email': 'userb@localhost', 'id': 3}}], 'total': 4} + + >>> #query array with @order + + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":0, + ... "@query":2, + ... "user":{ + ... "@column":"id,username,nickname,email", + ... "@order":"id+", + ... "@role":"ADMIN" + ... } + ... }, + ... "total@":"/[]/total" + ... }''' + >>> 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', 'email': 'admin@localhost', 'id': 1}}, {'user': {'username': 'usera', 'nickname': 'User A', 'email': 'usera@localhost', 'id': 2}}], 'total': 4} + + >>> #query array with @order having a non existing column + >>> data ='''{ + ... "[]":{ + ... "@count":2, + ... "@page":0, + ... "@query":2, + ... "user":{ + ... "@column":"id,username,nickname,email", + ... "@order":"nonexist+", + ... "@role":"ADMIN" + ... } + ... }, + ... "total@":"/[]/total" + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': "'user' doesn't have column 'nonexist'"} + + >>> #query array with @expr + >>> data ='''{ + ... "[]":{ + ... "@count":4, + ... "@page":0, + ... "user":{ + ... "@column":"id,username,nickname,email", + ... "@order":"id-", + ... "@role":"ADMIN", + ... "@expr":["username$","|","nickname$"], + ... "username$":"%b%", + ... "nickname$":"%c%" + ... } + ... } + ... }''' + >>> 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', 'email': 'userc@localhost', 'id': 4}}, {'user': {'username': 'userb', 'nickname': 'User B', 'email': 'userb@localhost', 'id': 3}}]} + >>> #Association query: Two tables, one to one,ref path is absolute path >>> data ='''{ ... "moment":{}, diff --git a/uliweb_apijson/apijson/__init__.py b/uliweb_apijson/apijson/__init__.py index 0c60dff..337f114 100644 --- a/uliweb_apijson/apijson/__init__.py +++ b/uliweb_apijson/apijson/__init__.py @@ -90,10 +90,13 @@ def _check_GET_permission(self): params_role = self.params.get("@role") if not params_role: - if request.user: + if hasattr(request,"user"): params_role = "LOGIN" else: params_role = "UNKNOWN" + elif params_role != "UNKNOWN": + if not hasattr(request,"user"): + raise UliwebError("no login user for role '%s'"%(params_role)) if params_role not in roles: raise UliwebError("'%s' not accessible by role '%s'"%(self.name,params_role)) if params_role == "UNKNOWN": @@ -114,8 +117,8 @@ def _get_array_params(self): try: query_count = int(query_count) except ValueError as e: - log.error("bad param in '%s': '%s'"%(n,self.query_params)) - raise UliwebError("@count should be an int, now '%s'"%(query_count)) + log.error("bad param in '%s': '%s'"%(query_count,self.query_params)) + raise UliwebError("@count should be an int, but get '%s'"%(query_count)) self.query_count = query_count query_page = self.query_params.get("@page") @@ -124,10 +127,10 @@ def _get_array_params(self): try: query_page = int(query_page) except ValueError as e: - log.error("bad param in '%s': '%s'"%(n,self.query_params)) - raise UliwebError("@page should be an int, now '%s'"%(query_page)) + log.error("bad param in '%s': '%s'"%(query_page,self.query_params)) + raise UliwebError("@page should be an int, but get '%s'"%(query_page)) if query_page<0: - raise UliwebError("page should >0, now is '%s'"%(query_page)) + raise UliwebError("page should >0, but get '%s'"%(query_page)) self.query_page = query_page #https://github.com/TommyLemon/APIJSON/blob/master/Document.md#32-%E5%8A%9F%E8%83%BD%E7%AC%A6 @@ -142,15 +145,15 @@ def _get_array_params(self): def _filter_owner(self,q): owner_filtered = False if hasattr(self.model,"owner_condition"): - q = q.filter(model.owner_condition()) + q = q.filter(self.model.owner_condition(request.user.id)) owner_filtered = True if not owner_filtered: user_id_field = self.setting.get("user_id_field") if user_id_field: - q = q.filter(getattr(model.c,user_id_field)==request.user.id) + q = q.filter(getattr(self.model.c,user_id_field)==request.user.id) owner_filtered = True if not owner_filtered: - raise UliwebError("'%s' cannot filter with owner"%(model_name)) + raise UliwebError("'%s' cannot filter with owner"%(self.name)) return q def _get_array_q(self,params): @@ -228,7 +231,10 @@ def query_array(self): else: sort_key = k sort_order = "asc" - column = getattr(self.model.c,sort_key) + try: + column = getattr(self.model.c,sort_key) + except AttributeError as e: + raise UliwebError("'%s' doesn't have column '%s'"%(self.name,sort_key)) q = q.order_by(getattr(column,sort_order)()) l = [self._get_info(i,True) for i in q] self.parent.rdict[self.key] = l diff --git a/uliweb_apijson/apijson/views.py b/uliweb_apijson/apijson/views.py index f30b51a..12128ec 100644 --- a/uliweb_apijson/apijson/views.py +++ b/uliweb_apijson/apijson/views.py @@ -118,7 +118,7 @@ def _get_one(self,key): params_role = "UNKNOWN" elif params_role != "UNKNOWN": if not hasattr(request,"user"): - return json({"code":400,"msg":"no user for role '%s'"%(params_role)}) + return json({"code":400,"msg":"no login user for role '%s'"%(params_role)}) if params_role not in roles: return json({"code":400,"msg":"'%s' not accessible by role '%s'"%(model_name,params_role)}) if params_role == "UNKNOWN": @@ -189,7 +189,7 @@ def _get_array(self,key): def _filter_owner(self,model,model_setting,q): owner_filtered = False if hasattr(model,"owner_condition"): - q = q.filter(model.owner_condition()) + q = q.filter(model.owner_condition(request.user.id)) owner_filtered = True if not owner_filtered: user_id_field = model_setting.get("user_id_field")