├── demo ├── apps │ ├── tables │ │ ├── README.md │ │ ├── __init__.py │ │ ├── config.ini │ │ └── settings.ini │ ├── apijson_demo │ │ ├── README.md │ │ ├── __init__.py │ │ ├── static │ │ │ ├── readme.txt │ │ │ └── layout.css │ │ ├── templates │ │ │ ├── readme.txt │ │ │ ├── layout_demo.html │ │ │ └── index.html │ │ ├── models.py │ │ ├── settings.ini │ │ ├── dbinit.py │ │ └── views.py │ ├── __init__.py │ └── settings.ini ├── doc │ ├── readme.txt │ └── imgs │ │ └── demo_screenshot.png ├── requirements.txt ├── .gitignore ├── wsgi_handler.py ├── README.md └── setup.py ├── uliweb_apijson ├── tables │ ├── README.md │ ├── __init__.py │ ├── settings.ini │ ├── static │ │ └── readme.txt │ ├── templates │ │ ├── readme.txt │ │ └── Tables │ │ │ ├── layout.html │ │ │ ├── list.html │ │ │ └── inc │ │ │ └── table_config.html │ └── views.py ├── apijson │ ├── static │ │ └── readme.txt │ ├── templates │ │ ├── readme.txt │ │ └── vue │ │ │ ├── inc_apijson_viewedit.html │ │ │ └── inc_apijson_table.html │ ├── config.ini │ ├── settings.ini │ ├── README.md │ ├── __init__.py │ └── views.py └── __init__.py ├── tests ├── demo │ ├── apps │ │ ├── apijson_demo │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── static │ │ │ │ └── readme.txt │ │ │ ├── templates │ │ │ │ └── readme.txt │ │ │ ├── models.py │ │ │ ├── settings.ini │ │ │ └── dbinit.py │ │ ├── __init__.py │ │ └── settings.ini │ ├── requirements.txt │ └── .gitignore ├── README.md └── requirements.txt ├── doc └── readme.txt ├── MANIFEST.in ├── .gitignore ├── .travis.yml ├── LICENSE ├── setup.py ├── CHANGELOG.md └── README.md /demo/apps/tables/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/apps/tables/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /uliweb_apijson/tables/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /uliweb_apijson/tables/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/demo/apps/apijson_demo/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /uliweb_apijson/tables/settings.ini: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/demo/apps/apijson_demo/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store static files. -------------------------------------------------------------------------------- /demo/doc/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store static files. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include uliweb_apijson *.ini *.txt *.html *.js *.css *.md 2 | -------------------------------------------------------------------------------- /tests/demo/requirements.txt: -------------------------------------------------------------------------------- 1 | six 2 | SQLAlchemy 3 | uliweb3 4 | uliweb-apijson 5 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/static/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store static files. -------------------------------------------------------------------------------- /uliweb_apijson/apijson/static/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store static files. -------------------------------------------------------------------------------- /uliweb_apijson/tables/static/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store static files. -------------------------------------------------------------------------------- /demo/apps/apijson_demo/templates/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store template files. -------------------------------------------------------------------------------- /tests/demo/apps/apijson_demo/static/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store static files. -------------------------------------------------------------------------------- /uliweb_apijson/apijson/templates/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store template files. -------------------------------------------------------------------------------- /uliweb_apijson/tables/templates/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store template files. -------------------------------------------------------------------------------- /demo/apps/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1' 2 | __url__ = '' 3 | __author__ = '' 4 | __email__ = '' -------------------------------------------------------------------------------- /tests/demo/apps/apijson_demo/templates/readme.txt: -------------------------------------------------------------------------------- 1 | This directory is used to store template files. -------------------------------------------------------------------------------- /demo/apps/tables/config.ini: -------------------------------------------------------------------------------- 1 | [DEPENDS] 2 | REQUIRED_APPS = [ 3 | 'uliweb_apijson.tables', 4 | ] 5 | -------------------------------------------------------------------------------- /tests/demo/apps/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1' 2 | __url__ = '' 3 | __author__ = '' 4 | __email__ = '' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.bak 3 | build 4 | dist 5 | *.egg-info 6 | .idea 7 | _git 8 | .vscode 9 | 10 | -------------------------------------------------------------------------------- /demo/requirements.txt: -------------------------------------------------------------------------------- 1 | six 2 | SQLAlchemy 3 | uliweb3 4 | uliweb-comui 5 | uliweb-comapps 6 | uliweb-apijson 7 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | commands to run the tests: 2 | 3 | ``` 4 | cd tests 5 | nosetests --with-doctest 6 | ``` 7 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | six 3 | SQLAlchemy 4 | -e git+https://github.com/limodou/uliweb3#egg=uliweb3 5 | ../ 6 | -------------------------------------------------------------------------------- /demo/doc/imgs/demo_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangchunlin/uliweb-apijson/HEAD/demo/doc/imgs/demo_screenshot.png -------------------------------------------------------------------------------- /uliweb_apijson/apijson/config.ini: -------------------------------------------------------------------------------- 1 | [DEPENDS] 2 | REQUIRED_APPS = [ 3 | 'uliweb.contrib.auth', 4 | 'uliweb.contrib.rbac', 5 | ] 6 | -------------------------------------------------------------------------------- /tests/demo/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.bak 3 | local_settings.ini 4 | build 5 | dist 6 | *.egg-info 7 | .idea 8 | _git 9 | data 10 | database.db 11 | sessions 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "3.7" 5 | before_install: cd tests;pip install -r requirements.txt 6 | script: nosetests --with-doctest 7 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.bak 3 | local_settings.ini 4 | build 5 | dist 6 | *.egg-info 7 | .idea 8 | _git 9 | data 10 | database.db 11 | sessions 12 | uploads 13 | -------------------------------------------------------------------------------- /uliweb_apijson/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.3.0' 2 | __url__ = 'https://github.com/zhangchunlin/uliweb-apijson' 3 | __author__ = 'Chunlin Zhang' 4 | __email__ = 'zhangchunlin@gmail.com' 5 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/templates/layout_demo.html: -------------------------------------------------------------------------------- 1 | {{extend "iview/layout0.html"}} 2 | 3 | {{block mainmenu_config}} 4 | {{mainmenu_name,mainmenu_active='MAINMENU','apijson'}} 5 | {{end mainmenu_config}} 6 | -------------------------------------------------------------------------------- /uliweb_apijson/tables/templates/Tables/layout.html: -------------------------------------------------------------------------------- 1 | {{extend "iview/layout0.html"}} 2 | 3 | {{block mainmenu_config}} 4 | {{mainmenu_name,mainmenu_active='MAINMENU','tables'}} 5 | {{end mainmenu_config}} 6 | -------------------------------------------------------------------------------- /demo/wsgi_handler.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | 3 | path = os.path.dirname(os.path.abspath(__file__)) 4 | if path not in sys.path: 5 | sys.path.insert(0, path) 6 | 7 | from uliweb.manage import make_simple_application 8 | application = make_simple_application(project_dir=path) 9 | -------------------------------------------------------------------------------- /tests/demo/apps/settings.ini: -------------------------------------------------------------------------------- 1 | [GLOBAL] 2 | DEBUG = False 3 | DEBUG_CONSOLE = False 4 | DEBUG_TEMPLATE = False 5 | 6 | INSTALLED_APPS = [ 7 | 'uliweb.contrib.orm', 8 | 'uliweb.contrib.auth', 9 | 'uliweb.contrib.rbac', 10 | 'uliweb_apijson.apijson', 11 | 'apijson_demo', 12 | ] 13 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/static/layout.css: -------------------------------------------------------------------------------- 1 | /* override uliweb_layout/layout/static/layout.css */ 2 | .main-footer {background-color: #3c8dbc; color: #e0e0e0;margin-left:0} 3 | #layout-body .content-wrapper {border-left:none} 4 | #layout-body.full .main-header .container { width: 100%; } 5 | #layout-body.full .content-wrapper > .container { width: 100%; padding: 0; } 6 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | ## How to run uliweb-apijson demo? 2 | 3 | pip install dependent packages, in demo root directory run: 4 | 5 | ``` 6 | pip install -r requirements.txt 7 | ``` 8 | 9 | In demo root directory,run commands to init db: 10 | 11 | ``` 12 | uliweb syncdb 13 | uliweb dbinit 14 | ``` 15 | 16 | In demo root directory, run debug server: 17 | 18 | ``` 19 | uliweb runserver 20 | ``` 21 | 22 | Then you can access http://localhost:8000 to try demo. 23 | 24 | Users in demo for test: **admin**/**usera**/**userb**/**userc** , password: **123** 25 | 26 | ![](doc/imgs/demo_screenshot.png) 27 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/models.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | from uliweb.orm import * 4 | 5 | class Privacy(Model): 6 | user_id = Reference("user") 7 | certified = Field(bool) 8 | phone = Field(str) 9 | balance = Field(DECIMAL) 10 | password = Field(str) 11 | paypassword = Field(str) 12 | 13 | class Moment(Model): 14 | user_id = Reference("user") 15 | date = Field(datetime.datetime, auto_now_add=True) 16 | content = Field(TEXT) 17 | picture_list = Field(JSON, default=[]) 18 | 19 | class Comment(Model): 20 | user_id = Reference("user") 21 | to_id = Reference("user") 22 | moment_id = Reference("moment") 23 | date = Field(datetime.datetime, auto_now_add=True) 24 | content = Field(TEXT) 25 | -------------------------------------------------------------------------------- /demo/apps/settings.ini: -------------------------------------------------------------------------------- 1 | [GLOBAL] 2 | DEBUG = False 3 | DEBUG_CONSOLE = False 4 | DEBUG_TEMPLATE = False 5 | 6 | INSTALLED_APPS = [ 7 | 'uliweb.contrib.staticfiles', 8 | 'uliweb.contrib.template', 9 | 'uliweb.contrib.upload', 10 | 'uliweb.contrib.orm', 11 | 'uliweb.contrib.session', 12 | 'uliweb.contrib.cache', 13 | 'uliweb.contrib.auth', 14 | 'uliweb.contrib.i18n', 15 | 'uliweb.contrib.flashmessage', 16 | 'uliweb.contrib.rbac', 17 | 'uliweb_comui', 18 | 'uliweb_comapps.auth.login', 19 | 'uliweb_comapps.auth.user_admin', 20 | 'uliweb_comapps.auth.rbac_admin', 21 | 'uliweb_apijson.apijson', 22 | 'apijson_demo', 23 | 'tables', 24 | ] 25 | 26 | [MENUS] 27 | MAINMENU = { 28 | 'subs':[ 29 | {'name': 'apijson', 'link':'/', 'title':u'apijson requests demo','icon-iview':'ios-apps'}, 30 | {'name': 'tables', 'link':'/tables', 'title':u'apijson tables demo','icon-iview':'md-document'}, 31 | ] 32 | } 33 | 34 | [LAYOUT] 35 | logo_lg = "uliweb-apijson" 36 | logo_mini = "U" 37 | 38 | [SESSION] 39 | timeout = 36000 40 | -------------------------------------------------------------------------------- /uliweb_apijson/apijson/settings.ini: -------------------------------------------------------------------------------- 1 | #apijson style role names 2 | [ROLES] 3 | ADMIN = _('APIJSON ADMIN'), 'uliweb.contrib.rbac.superuser', True 4 | UNKNOWN = _('APIJSON UNKNOWN'), 'uliweb.contrib.rbac.anonymous', True 5 | LOGIN = _('APIJSON LOGIN'), 'uliweb.contrib.rbac.trusted', True 6 | #will do more when query in the database 7 | OWNER = _('APIJSON OWNER'), 'uliweb.contrib.rbac.trusted', True 8 | 9 | [APIJSON_MODELS] 10 | user = { 11 | "user_id_field" : "id", 12 | "secret_fields" : ["password"], 13 | "GET" : { "roles" : ["ADMIN","OWNER"] }, 14 | "HEAD" : { "roles" : ["ADMIN","OWNER"] }, 15 | "POST" : { "roles" : ["ADMIN"] }, 16 | "PUT" : { "roles" : ["ADMIN","OWNER"] }, 17 | "DELETE" : { "roles" : ["ADMIN"] }, 18 | } 19 | 20 | [FUNCTIONS] 21 | get_apijson_tables = "uliweb_apijson.apijson.get_apijson_tables" 22 | get_apijson_table = "uliweb_apijson.apijson.get_apijson_table" 23 | has_obj_role = "uliweb_apijson.apijson.has_obj_role" 24 | has_obj_permission = "uliweb_apijson.apijson.has_obj_permission" 25 | has_permission_as_role = "uliweb_apijson.apijson.has_permission_as_role" 26 | -------------------------------------------------------------------------------- /uliweb_apijson/tables/views.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from uliweb import expose, functions, settings 3 | 4 | @expose('/tables') 5 | class Tables(object): 6 | @expose('') 7 | def list(self): 8 | if request.user: 9 | role = "ADMIN" if functions.has_role(request.user,"ADMIN") else "OWNER" 10 | else: 11 | role = "UNKNOWN" 12 | apijson_tables = functions.get_apijson_tables() 13 | def _get_model(i): 14 | model_name = i.model_name 15 | model = settings.APIJSON_MODELS.get(model_name,{}) 16 | if not i.role: 17 | roles = model.get("GET",{}).get("roles") 18 | i.role = roles[0] if isinstance(roles, list) else roles 19 | return model 20 | models = [_get_model(i) for i in apijson_tables] 21 | def _get_request(i): 22 | request_tag = i.request_tag or i.model_name 23 | return settings.APIJSON_REQUESTS.get(request_tag,{}) 24 | requests = [_get_request(i) for i in apijson_tables] 25 | return { 26 | "apijson_tables_json":json_dumps([d.to_dict() for d in apijson_tables]), 27 | "models_json": json_dumps(models), 28 | "requests_json": json_dumps(requests), 29 | "role":role, 30 | } 31 | -------------------------------------------------------------------------------- /tests/demo/apps/apijson_demo/models.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | from uliweb.orm import * 4 | 5 | class Privacy(Model): 6 | user_id = Reference("user") 7 | certified = Field(bool) 8 | phone = Field(str) 9 | balance = Field(DECIMAL) 10 | password = Field(str) 11 | paypassword = Field(str) 12 | 13 | class Moment(Model): 14 | user_id = Reference("user") 15 | date = Field(datetime.datetime, auto_now_add=True) 16 | content = Field(TEXT) 17 | picture_list = Field(JSON, default=[]) 18 | 19 | @classmethod 20 | def owner_condition(cls,user_id): 21 | print("Moment: owner_condition") 22 | return cls.c.user_id==user_id 23 | 24 | class Comment(Model): 25 | user_id = Reference("user") 26 | to_id = Reference("user") 27 | moment_id = Reference("moment") 28 | date = Field(datetime.datetime, auto_now_add=True) 29 | content = Field(TEXT) 30 | 31 | class Comment2(Model): 32 | user_id = Reference("user") 33 | to_id = Reference("user") 34 | moment_id = Reference("moment") 35 | date = Field(datetime.datetime, auto_now_add=True) 36 | content = Field(TEXT) 37 | 38 | class PublicNotice(Model): 39 | date = Field(datetime.datetime, auto_now_add=True) 40 | content = Field(TEXT) 41 | 42 | class NoRequestTag(Model): 43 | user_id = Reference("user") 44 | content = Field(TEXT) 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Chunlin Zhang 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | __doc__ = """uliweb-apijson""" 3 | 4 | import re 5 | import os 6 | 7 | from setuptools import setup, find_packages 8 | 9 | def fpath(name): 10 | return os.path.join(os.path.dirname(__file__), name) 11 | 12 | 13 | def read(fname, default=''): 14 | filename = fpath(fname) 15 | if os.path.exists(filename): 16 | return open(fpath(fname),encoding="utf8").read() 17 | else: 18 | return default 19 | 20 | 21 | def desc(): 22 | info = read('README.md', __doc__) 23 | return info + '\n\n' + read('doc/CHANGELOG.md') 24 | 25 | file_text = read(fpath('uliweb_apijson/__init__.py')) 26 | 27 | 28 | def grep(attrname): 29 | pattern = r"{0}\s*=\s*'([^']*)'".format(attrname) 30 | strval, = re.findall(pattern, file_text) 31 | return strval 32 | setup( 33 | name='uliweb-apijson', 34 | version=grep('__version__'), 35 | url=grep('__url__'), 36 | license='BSD', 37 | author=grep('__author__'), 38 | author_email=grep('__email__'), 39 | description='uliweb-apijson', 40 | long_description=desc(), 41 | long_description_content_type="text/markdown", 42 | packages = find_packages(), 43 | include_package_data=True, 44 | zip_safe=False, 45 | platforms='any', 46 | install_requires=[ 47 | ], 48 | classifiers=[ 49 | 'Development Status :: 4 - Beta', 50 | 'Environment :: Web Environment', 51 | 'Intended Audience :: Developers', 52 | 'License :: OSI Approved :: BSD License', 53 | 'Operating System :: OS Independent', 54 | 'Programming Language :: Python', 55 | 'Topic :: Software Development :: Libraries :: Python Modules' 56 | ], 57 | ) -------------------------------------------------------------------------------- /demo/apps/tables/settings.ini: -------------------------------------------------------------------------------- 1 | [APIJSON_TABLES] 2 | user = {"model_name":"user", "tableui_name":"users"} 3 | role = {"model_name":"role", "tableui_name":"roles", "role":"ADMIN"} 4 | permission = {"model_name":"permission", "tableui_name":"permissions", "role":"ADMIN"} 5 | moment = {"role":"OWNER"} 6 | comment = {"role":"OWNER"} 7 | 8 | [APIJSON_TABLE_UI] 9 | moment = { 10 | "editable" : "auto", 11 | "table_fields" : [ 12 | {"title":"#","key":"id","width":80}, 13 | {"title":"User id","key":"user_id","width":100}, 14 | {"title":"Date","key":"date","width":160}, 15 | {"title":"Content","key":"content"}, 16 | ], 17 | "viewedit_fields" : [ 18 | {"title":"#","key":"id","editable":False}, 19 | {"title":"User id","key":"user_id","editable":False}, 20 | {"title":"Date","key":"date","editable":False}, 21 | {"title":"Content","key":"content","type":"textarea"}, 22 | ], 23 | "add_fields" : [ 24 | {"title":"Content","key":"content","type":"textarea"}, 25 | ], 26 | } 27 | comment = { 28 | "editable" : "auto", 29 | "table_fields" : [ 30 | {"title":"#","key":"id","width":80}, 31 | {"title":"User id","key":"user_id","width":100}, 32 | {"title":"To id","key":"to_id","width":100}, 33 | {"title":"Moment id","key":"moment_id","width":100}, 34 | {"title":"Date","key":"date","width":160}, 35 | {"title":"Content","key":"content"}, 36 | ], 37 | "viewedit_fields" : [ 38 | {"title":"#","key":"id","editable":False}, 39 | {"title":"User id","key":"user_id","editable":False}, 40 | {"title":"To id","key":"to_id","editable":False}, 41 | {"title":"Moment id","key":"moment_id","editable":False}, 42 | {"title":"Date","key":"date","editable":False}, 43 | {"title":"Content","key":"content","type":"textarea"}, 44 | ], 45 | } 46 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/settings.ini: -------------------------------------------------------------------------------- 1 | [MODELS] 2 | privacy = 'apijson_demo.models.Privacy' 3 | comment = 'apijson_demo.models.Comment' 4 | moment = 'apijson_demo.models.Moment' 5 | 6 | [APIJSON_MODELS] 7 | user = { 8 | "user_id_field" : "id", 9 | "secret_fields" : ["password"], 10 | "GET" : { "roles" : ["LOGIN","ADMIN","OWNER"] }, 11 | "HEAD" : { "roles" : ["LOGIN","ADMIN","OWNER"] }, 12 | "POST" : { "roles" : ["ADMIN"] }, 13 | "PUT" : { "roles" : ["ADMIN","OWNER"] }, 14 | "DELETE" : { "roles" : ["ADMIN"] }, 15 | } 16 | moment = { 17 | "user_id_field" : "user_id", 18 | "GET" : { "roles" : ["OWNER","LOGIN","ADMIN"] }, 19 | "HEAD" : { "roles" : ["OWNER","LOGIN","ADMIN"] }, 20 | "POST" : { "roles" : ["OWNER","ADMIN"] }, 21 | "PUT" : { "roles" : ["OWNER","ADMIN"] }, 22 | "DELETE" : { "roles" : ["OWNER","ADMIN"] }, 23 | } 24 | comment = { 25 | "user_id_field" : "user_id", 26 | "GET" : { "roles" : ["OWNER","LOGIN","ADMIN"] }, 27 | "HEAD" : { "roles" : ["OWNER","LOGIN","ADMIN"] }, 28 | "POST" : { "roles" : ["OWNER","ADMIN"] }, 29 | "PUT" : { "roles" : ["OWNER","ADMIN"] }, 30 | "DELETE" : { "roles" : ["OWNER","ADMIN"] }, 31 | } 32 | 33 | [APIJSON_REQUESTS] 34 | user = { 35 | "POST" :{ 36 | "ADD":{"@role": "ADMIN"}, 37 | "DISALLOW" : ["id"], 38 | "NECESSARY" : ["username","nickname"], 39 | }, 40 | "PUT" :{ 41 | "ADD":{"@role": "OWNER"}, 42 | "NECESSARY" : ["id"], 43 | }, 44 | } 45 | moment = { 46 | "POST" :{ 47 | "ADD":{"@role": "OWNER"}, 48 | "DISALLOW" : ["id"], 49 | "NECESSARY" : ["content"], 50 | }, 51 | "PUT" :{ 52 | "ADD":{"@role": "OWNER"}, 53 | "NECESSARY" : ["id","content"], 54 | }, 55 | } 56 | 57 | comment = { 58 | "POST" :{ 59 | "ADD" :{"@role": "OWNER"}, 60 | "DISALLOW" : ["id"], 61 | "NECESSARY" : ["content"] 62 | }, 63 | "PUT" :{ 64 | "ADD":{"@role": "OWNER"}, 65 | "NECESSARY" : ["id","content"], 66 | }, 67 | } 68 | -------------------------------------------------------------------------------- /uliweb_apijson/tables/templates/Tables/list.html: -------------------------------------------------------------------------------- 1 | {{extend "Tables/layout.html"}} 2 | 3 | {{block title}}uliweb-apijson: tables{{end title}} 4 | 5 | {{block other_use}} 6 | {{include "vue/inc_apijson_table.html"}} 7 | {{include "Tables/inc/table_config.html"}} 8 | {{end other_use}} 9 | 10 | {{block content_wrapper}} 11 | 12 |
13 | {{if role!="ADMIN":}} 14 | 15 | {{pass #if}} 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 | {{end content_wrapper}} 30 | 31 | {{block mainapp_vue}} 32 | 54 | {{end mainapp_vue}} 55 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | - 0.2.1(2019-10-22) 4 | - **apison-post: modify to be same with original apijson, just return "id" and "count", not other fields** 5 | - apijson-table: fix key and title problem in viewedit_items 6 | - apijson-viewedit: fix @role 7 | - apijson-table: fix apijson-put and apijson-delete @role not work problem 8 | - doc: add difference in tag parameter in apijson post/put 9 | - **tests: add 97 test cases, and fix bugs found by tests** 10 | - fix "NameError: name 'log' is not defined" 11 | - fix py2/3 compatible 12 | - fix issue #4 :"NameError: name 'UliwebError' is not defined" 13 | - apijson-table: add hook_add, for customizing the add action in apijson-table component 14 | - 0.2.0(2019-09-17) 15 | - setup: add fix utf8 for reading file 16 | - **fix json response structure to be same as original apijson api (java)** 17 | - demo: use menu to display example list 18 | - **add association query support** 19 | - 0.1.3(2019-08-06) 20 | - demo: use admlte style layout 21 | - apijson-table: add sort support 22 | - apply_vars -> _apply_vars to avoid mapping to view; don't show db exception detail for security 23 | - apijson-viewedit: remove custom_tcolumns_render_generator in apijson-viewedit; add custom_viewedit_componet block 24 | - 0.1.2 (2019-05-27) 25 | - **add @expr support in apijson_get array** 26 | - more strict check with model name in get action; fix request_tag should get with tag not with model name 27 | - add vue component apijson-viewedit 28 | - apijson post/put/delete should get model name from request_tag setting; add NECESSARY support in _put_one 29 | - add functions.get_apijson_table 30 | - apijson-table: add custom_tcolumns_render_generator prop; deault component is input in add modal; add Notice top config 31 | - 0.1.1 (2019-05-07) 32 | - more strict check to tag_POST; support DISALLOW in apijson_put 33 | - fix DISALLOW and NECESSARY get wrongly problem 34 | - vue component apijson-table: change hook function names; change "type" to "component" in fields config; override modal footer, only cancel button; add checkbox render; add set deleted support 35 | - 0.1.0 (2019-04-02) 36 | - First workable release -------------------------------------------------------------------------------- /demo/setup.py: -------------------------------------------------------------------------------- 1 | #coding=utf8 2 | __doc__ = """apijson_example""" 3 | 4 | import re 5 | import os 6 | 7 | from setuptools import setup, find_packages 8 | from setuptools.command import build_py as b 9 | 10 | def copy_dir(self, package, src, dst): 11 | self.mkpath(dst) 12 | for r in os.listdir(src): 13 | if r in ['.svn', '_svn']: 14 | continue 15 | fpath = os.path.join(src, r) 16 | if os.path.isdir(fpath): 17 | copy_dir(self, package + '.' + r, fpath, os.path.join(dst, r)) 18 | else: 19 | ext = os.path.splitext(fpath)[1] 20 | if ext in ['.pyc', '.pyo', '.bak', '.tmp']: 21 | continue 22 | target = os.path.join(dst, r) 23 | self.copy_file(fpath, target) 24 | 25 | def find_dir(self, package, src): 26 | for r in os.listdir(src): 27 | if r in ['.svn', '_svn']: 28 | continue 29 | fpath = os.path.join(src, r) 30 | if os.path.isdir(fpath): 31 | for f in find_dir(self, package + '.' + r, fpath): 32 | yield f 33 | else: 34 | ext = os.path.splitext(fpath)[1] 35 | if ext in ['.pyc', '.pyo', '.bak', '.tmp']: 36 | continue 37 | yield fpath 38 | 39 | def build_package_data(self): 40 | for package in self.packages or (): 41 | src_dir = self.get_package_dir(package) 42 | build_dir = os.path.join(*([self.build_lib] + package.split('.'))) 43 | copy_dir(self, package, src_dir, build_dir) 44 | setattr(b.build_py, 'build_package_data', build_package_data) 45 | 46 | def get_source_files(self): 47 | filenames = [] 48 | for package in self.packages or (): 49 | src_dir = self.get_package_dir(package) 50 | filenames.extend(list(find_dir(self, package, src_dir))) 51 | return filenames 52 | setattr(b.build_py, 'get_source_files', get_source_files) 53 | 54 | def fpath(name): 55 | return os.path.join(os.path.dirname(__file__), name) 56 | 57 | 58 | def read(fname, default=''): 59 | filename = fpath(fname) 60 | if os.path.exists(filename): 61 | return open(fpath(fname)).read() 62 | else: 63 | return default 64 | 65 | 66 | def desc(): 67 | info = read('README.md', __doc__) 68 | return info + '\n\n' + read('doc/CHANGELOG.md') 69 | 70 | file_text = read(fpath('apps/__init__.py')) 71 | 72 | 73 | def grep(attrname): 74 | pattern = r"{0}\s*=\s*'([^']*)'".format(attrname) 75 | strval, = re.findall(pattern, file_text) 76 | return strval 77 | 78 | setup( 79 | name='apijson_example', 80 | version=grep('__version__'), 81 | url=grep('__url__'), 82 | license='BSD', 83 | author=grep('__author__'), 84 | author_email=grep('__email__'), 85 | description='apijson_example', 86 | long_description=desc(), 87 | package_dir = {'apijson_example':'apps'}, 88 | packages = ["apijson_example"], 89 | include_package_data=True, 90 | zip_safe=False, 91 | platforms='any', 92 | install_requires=[ 93 | 'uliweb', 94 | ], 95 | classifiers=[ 96 | 'Development Status :: 4 - Beta', 97 | 'Environment :: Web Environment', 98 | 'Intended Audience :: Developers', 99 | 'License :: OSI Approved :: BSD License', 100 | 'Operating System :: OS Independent', 101 | 'Programming Language :: Python', 102 | 'Topic :: Software Development :: Libraries :: Python Modules' 103 | ], 104 | ) 105 | -------------------------------------------------------------------------------- /tests/demo/apps/apijson_demo/settings.ini: -------------------------------------------------------------------------------- 1 | [MODELS] 2 | privacy = 'apijson_demo.models.Privacy' 3 | comment = 'apijson_demo.models.Comment' 4 | comment2 = 'apijson_demo.models.Comment2' 5 | moment = 'apijson_demo.models.Moment' 6 | publicnotice = 'apijson_demo.models.PublicNotice' 7 | norequesttag = 'apijson_demo.models.NoRequestTag' 8 | 9 | [PERMISSIONS] 10 | get_comment2 = "get comment2", ["OWNER", "ADMIN"], "" 11 | head_comment2 = "head comment2", ["OWNER", "ADMIN"], "" 12 | post_comment2 = "post comment2", ["OWNER", "ADMIN"], "" 13 | put_comment2 = "put comment2", ["OWNER", "ADMIN"], "" 14 | delete_comment2 = "delete comment2", ["OWNER", "ADMIN"], "" 15 | 16 | [APIJSON_MODELS] 17 | user = { 18 | "user_id_field" : "id", 19 | "secret_fields" : ["password"], 20 | "GET" : { "roles" : ["LOGIN","ADMIN","OWNER"] }, 21 | "HEAD" : { "roles" : ["LOGIN","ADMIN","OWNER"] }, 22 | #"POST" : { "roles" : ["ADMIN"] }, #remove for test case 23 | "PUT" : { "roles" : ["ADMIN","OWNER"] }, 24 | "DELETE" : { "roles" : ["ADMIN"] }, 25 | } 26 | privacy = { 27 | "user_id_field" : "user_id", 28 | "GET" : { "roles" : ["OWNER","ADMIN"] }, 29 | "HEAD" : { "roles" : ["OWNER","ADMIN"] }, 30 | "POST" : { "roles" : ["OWNER","ADMIN"] }, 31 | "PUT" : { "roles" : ["OWNER","ADMIN"] }, 32 | "DELETE" : { "roles" : ["OWNER","ADMIN"] }, 33 | } 34 | moment = { 35 | "user_id_field" : "user_id", 36 | "GET" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] }, 37 | "HEAD" : { "roles" : ["OWNER","LOGIN","ADMIN"] }, 38 | "POST" : { "roles" : ["OWNER","ADMIN"] }, 39 | "PUT" : { "roles" : ["OWNER","ADMIN"] }, 40 | "DELETE" : { "roles" : ["OWNER","ADMIN"] }, 41 | } 42 | comment = { 43 | "user_id_field" : "user_id", 44 | "GET" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] }, 45 | "HEAD" : { "roles" : ["OWNER","LOGIN","ADMIN"] }, 46 | "POST" : { "roles" : ["OWNER","ADMIN"] }, 47 | "PUT" : { "roles" : ["OWNER","ADMIN"] }, 48 | "DELETE" : { "roles" : ["OWNER","ADMIN"] }, 49 | } 50 | # only define permissions, no roles 51 | comment2 = { 52 | "user_id_field" : "user_id", 53 | "GET" : { "permissions":["get_comment2"] }, 54 | "HEAD" : { "permissions":["head_comment2"] }, 55 | "POST" : { "permissions":["post_comment2"] }, 56 | "PUT" : { "permissions":["put_comment2"]}, 57 | "DELETE" : {"permissions":["delete_comment2"]}, 58 | } 59 | publicnotice = { 60 | "GET" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] }, 61 | "HEAD" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] }, 62 | "POST" : { "roles" : ["OWNER","ADMIN"] }, 63 | "PUT" : { "roles" : ["OWNER","ADMIN","UNKNOWN"] }, 64 | "DELETE" : { "roles" : ["OWNER","ADMIN","UNKNOWN"] }, 65 | } 66 | 67 | [APIJSON_REQUESTS] 68 | moment = { 69 | "POST" :{ 70 | "ADD":{"@role": "OWNER"}, 71 | "DISALLOW" : ["id"], 72 | "NECESSARY" : ["content"], 73 | }, 74 | "PUT" :{ 75 | "ADD":{"@role": "OWNER"}, 76 | "NECESSARY" : ["id","content"], 77 | }, 78 | } 79 | 80 | comment = { 81 | "POST" :{ 82 | "ADD" :{"@role": "OWNER"}, 83 | "DISALLOW" : ["id"], 84 | "NECESSARY" : ["moment_id","content"] 85 | }, 86 | "PUT" :{ 87 | "ADD":{"@role": "OWNER"}, 88 | "NECESSARY" : ["id","content"], 89 | "DISALLOW" : ["user_id","to_id"], 90 | }, 91 | } 92 | 93 | comment2 = { 94 | "POST" :{ 95 | "ADD" :{"@role": "OWNER"}, 96 | "DISALLOW" : ["id"], 97 | "NECESSARY" : ["moment_id","content"] 98 | }, 99 | "PUT" :{ 100 | "ADD":{"@role": "OWNER"}, 101 | "NECESSARY" : ["id","content"], 102 | "DISALLOW" : ["user_id","to_id"], 103 | }, 104 | } 105 | 106 | publicnotice = { 107 | "PUT" :{ 108 | "NECESSARY" : ["id","content"], 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /uliweb_apijson/tables/templates/Tables/inc/table_config.html: -------------------------------------------------------------------------------- 1 | 95 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/dbinit.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from uliweb import models 3 | from uliweb.orm import set_dispatch_send 4 | 5 | set_dispatch_send(False) 6 | 7 | User = models.user 8 | Privacy = models.privacy 9 | Comment = models.comment 10 | Moment = models.moment 11 | 12 | user_list = [ 13 | { 14 | "username": "admin", 15 | "nickname": "Administrator", 16 | "email": "admin@localhost", 17 | }, 18 | { 19 | "username": "usera", 20 | "nickname": "User A", 21 | "email": "usera@localhost", 22 | }, 23 | { 24 | "username": "userb", 25 | "nickname": "User B", 26 | "email": "userb@localhost", 27 | }, 28 | { 29 | "username": "userc", 30 | "nickname": "User C", 31 | "email": "userc@localhost", 32 | }, 33 | ] 34 | 35 | privacy_list = [ 36 | { 37 | "username" : "usera", 38 | "certified" : True, 39 | "phone" : "13333333333", 40 | "balance" : 100, 41 | "password" : "hash_of_123", 42 | "paypassword" : "hash_of_sudfy8e7r", 43 | }, 44 | { 45 | "username" : "userb", 46 | "certified" : True, 47 | "phone" : "12222222222", 48 | "balance" : 130, 49 | "password" : "hash_of_dfdfd", 50 | "paypassword" : "hash_of_234erere", 51 | }, 52 | { 53 | "username" : "userc", 54 | "certified" : True, 55 | "phone" : "14323424234", 56 | "balance" : 600, 57 | "password" : "hash_of_w3erere", 58 | "paypassword" : "hash_of_ghtwertr", 59 | }, 60 | ] 61 | 62 | moment_list = [ 63 | { 64 | "username" : "usera", 65 | "date" : "2018-11-1", 66 | "content" : "test moment", 67 | }, 68 | { 69 | "username" : "userb", 70 | "date" : "2018-11-2", 71 | "content" : "test moment from b", 72 | }, 73 | { 74 | "username" : "userc", 75 | "date" : "2018-11-6", 76 | "content" : "test moment from c", 77 | }, 78 | { 79 | "username" : "admin", 80 | "date" : "2018-11-7", 81 | "content" : "test moment from admin", 82 | }, 83 | ] 84 | 85 | comment_list = [ 86 | { 87 | "username" : "usera", 88 | "to_username" : "userb", 89 | "moment_id" : 1, 90 | "date" : "2018-12-1", 91 | "content" : "comment haha", 92 | }, 93 | { 94 | "username" : "userb", 95 | "to_username" : "usera", 96 | "moment_id" : 2, 97 | "date" : "2018-12-2", 98 | "content" : "comment xixi", 99 | }, 100 | { 101 | "username" : "userc", 102 | "to_username" : "usera", 103 | "moment_id" : 3, 104 | "date" : "2018-12-9", 105 | "content" : "comment hoho", 106 | }, 107 | { 108 | "username" : "admin", 109 | "to_username" : "usera", 110 | "moment_id" : 4, 111 | "date" : "2018-12-10", 112 | "content" : "comment kaka", 113 | }, 114 | ] 115 | 116 | for d in user_list: 117 | if not User.get(User.c.username==d["username"]): 118 | print("create user '%s'"%(d["username"])) 119 | u = User(**d) 120 | u.set_password("123") 121 | if d["username"]=="admin": 122 | u.is_superuser = True 123 | u.save() 124 | 125 | for d in privacy_list: 126 | user = User.get(User.c.username==d["username"]) 127 | if user: 128 | d["user_id"] = user.id 129 | print("create privacy record for user '%s'"%(d["username"])) 130 | Privacy(**d).save() 131 | else: 132 | print("error: unknown user '%s'"%(d["username"])) 133 | 134 | for d in moment_list: 135 | user = User.get(User.c.username==d["username"]) 136 | if user: 137 | d["user_id"] = user.id 138 | print("create moment record for user '%s'"%(d["username"])) 139 | Moment(**d).save() 140 | else: 141 | print("error: unknown user '%s'"%(d["username"])) 142 | 143 | 144 | for d in comment_list: 145 | user = User.get(User.c.username==d["username"]) 146 | if user: 147 | d["user_id"] = user.id 148 | d["to_id"] = User.get(User.c.username==d["to_username"]).id 149 | print("create comment record for user '%s'"%(d["username"])) 150 | Comment(**d).save() 151 | else: 152 | print("error: unknown user '%s'"%(d["username"])) 153 | -------------------------------------------------------------------------------- /uliweb_apijson/apijson/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | uliweb-apijson is a subset and slightly different variation of [apijson](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 4 | 5 | # Difference with original apijson 6 | 7 | | feature | apijson(java) | uliweb-apijson | comment | 8 | | ------------------- | --------------- | ---------------- | ------------------------------------------------------------ | 9 | | @combine | ✔️ | ✖️ | Example: "@combine":"&key0,&key1,\|key2,key3" | 10 | | @expr | ✖️ | ✔️ | Example: "@expr":[["username$","&","email$"],"&",["!","nickname$"]] | 11 | | tag in apijson post/put | "tag": "Moment" | "@tag": "Moment" | | 12 | 13 | 14 | 15 | # apijson model configuration 16 | 17 | ## example 18 | 19 | ``` 20 | [APIJSON_MODELS] 21 | user = { 22 | "user_id_field" : "id", 23 | "secret_fields" : ["password"], 24 | "GET" : { "roles" : ["ADMIN","OWNER"] }, 25 | "HEAD" : { "roles" : ["ADMIN","OWNER"] }, 26 | "POST" : { "roles" : ["ADMIN"] }, 27 | "PUT" : { "roles" : ["ADMIN","OWNER"] }, 28 | "DELETE" : { "roles" : ["ADMIN"] }, 29 | } 30 | usergroup = { 31 | "GET" : { "roles" : ["ADMIN","LOGIN"] }, 32 | "HEAD" : { "roles" : ["ADMIN"] }, 33 | "POST" : { "roles" : ["ADMIN"] }, 34 | "PUT" : { "roles" : ["ADMIN"] }, 35 | "DELETE" : { "roles" : ["ADMIN"] }, 36 | } 37 | ``` 38 | 39 | ## document 40 | 41 | settings.APIJSON_MODELS.[MODEL_NAME] 42 | 43 | | Field | Doc | 44 | | ------------- | ---------------------------------------------------------- | 45 | | user_id_field | Field name of user id, related to query user own data. | 46 | | secret_fields | Secret fields won't be exposed. | 47 | | GET/HEAD/POST/PUT/DELETE | Configure of roles or permissions for apijson methods | 48 | 49 | # apijson request configuration 50 | 51 | ## example 52 | 53 | ``` 54 | [APIJSON_REQUESTS] 55 | moment = { 56 | "moment": { 57 | "POST" :{ 58 | "ADD":{"@role": "OWNER"}, 59 | "DISALLOW" : ["id"], 60 | "NECESSARY" : ["content"], 61 | }, 62 | "PUT" :{ 63 | "ADD":{"@role": "OWNER"}, 64 | "NECESSARY" : ["id","content"], 65 | "DISALLOW" : ["email"], 66 | }, 67 | } 68 | } 69 | ``` 70 | ## document 71 | 72 | settings.APIJSON_REQUESTS.[TAG_NAME] 73 | 74 | request types currently support: POST, PUT 75 | 76 | request configuration currently support: ADD,DISALLOW,NECESSARY (still not fully support [all the configuration items](https://github.com/TommyLemon/APIJSON/wiki#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86)) 77 | 78 | # apijson table configuration 79 | 80 | ## example 81 | 82 | ``` 83 | [APIJSON_TABLES] 84 | roles = { 85 | "@model_name" : "role", 86 | "editable" : "auto", 87 | "table_fields" : [ 88 | {"title":"#","key":"id","width":80,"component":"id"}, 89 | {"title":"Name","key":"name","component":"name_link"}, 90 | {"title":"Description","key":"description"}, 91 | {"title":"Is reserved","key":"reserve","component":"checkbox"}, 92 | ], 93 | "viewedit_fields" : [ 94 | {"title":"#","key":"id","editable":False}, 95 | {"title":"Name","key":"name"}, 96 | {"title":"Description","key":"description"}, 97 | {"title":"Is reserved","key":"reserve","component":"checkbox"}, 98 | ], 99 | "add_fields" : [ 100 | {"title":"Name","key":"name"}, 101 | {"title":"Description","key":"description"}, 102 | {"title":"Is reserved","key":"reserve","component":"checkbox"}, 103 | ], 104 | } 105 | ``` 106 | 107 | ## document 108 | 109 | Provide 2 vue component about table: 110 | 111 | | | apijson-table | apijson-viewedit | 112 | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 113 | | uliweb template include | {{include "vue/inc_apijson_table.html"}} | {{include "vue/inc_apijson_viewedit.html"}} | 114 | | example: | `````` | `````` | 115 | 116 | 117 | 118 | 119 | 120 | 121 | # Supported API Examples 122 | 123 | Please run [demo](../../demo/README.md) project and try it. 124 | -------------------------------------------------------------------------------- /tests/demo/apps/apijson_demo/dbinit.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from uliweb import models 3 | from uliweb.orm import set_dispatch_send 4 | 5 | set_dispatch_send(False) 6 | 7 | User = models.user 8 | Privacy = models.privacy 9 | Comment = models.comment 10 | Comment2 = models.comment2 11 | Moment = models.moment 12 | PublicNotice = models.publicnotice 13 | 14 | user_list = [ 15 | { 16 | "username": "admin", 17 | "nickname": "Administrator", 18 | "email": "admin@localhost", 19 | "date_join": "2018-1-1 0:0:0", 20 | }, 21 | { 22 | "username": "usera", 23 | "nickname": "User A", 24 | "email": "usera@localhost", 25 | "date_join": "2018-2-2 0:0:0", 26 | }, 27 | { 28 | "username": "userb", 29 | "nickname": "User B", 30 | "email": "userb@localhost", 31 | "date_join": "2018-3-3 0:0:0", 32 | }, 33 | { 34 | "username": "userc", 35 | "nickname": "User C", 36 | "email": "userc@localhost", 37 | "date_join": "2018-4-4 0:0:0", 38 | }, 39 | ] 40 | 41 | privacy_list = [ 42 | { 43 | "username" : "usera", 44 | "certified" : True, 45 | "phone" : "13333333333", 46 | "balance" : 100, 47 | "password" : "hash_of_123", 48 | "paypassword" : "hash_of_sudfy8e7r", 49 | }, 50 | { 51 | "username" : "userb", 52 | "certified" : True, 53 | "phone" : "12222222222", 54 | "balance" : 130, 55 | "password" : "hash_of_dfdfd", 56 | "paypassword" : "hash_of_234erere", 57 | }, 58 | { 59 | "username" : "userc", 60 | "certified" : True, 61 | "phone" : "14323424234", 62 | "balance" : 600, 63 | "password" : "hash_of_w3erere", 64 | "paypassword" : "hash_of_ghtwertr", 65 | }, 66 | ] 67 | 68 | moment_list = [ 69 | { 70 | "username" : "usera", 71 | "date" : "2018-11-1", 72 | "content" : "test moment", 73 | }, 74 | { 75 | "username" : "userb", 76 | "date" : "2018-11-2", 77 | "content" : "test moment from b", 78 | }, 79 | { 80 | "username" : "userc", 81 | "date" : "2018-11-6", 82 | "content" : "test moment from c", 83 | }, 84 | ] 85 | 86 | comment_list = [ 87 | { 88 | "username" : "admin", 89 | "to_username" : "userb", 90 | "moment_id" : 1, 91 | "date" : "2018-11-1", 92 | "content" : "comment from admin", 93 | }, 94 | { 95 | "username" : "usera", 96 | "to_username" : "userb", 97 | "moment_id" : 1, 98 | "date" : "2018-12-1", 99 | "content" : "comment from usera to userb", 100 | }, 101 | { 102 | "username" : "userb", 103 | "to_username" : "usera", 104 | "moment_id" : 2, 105 | "date" : "2018-12-2", 106 | "content" : "comment from userb to usera", 107 | }, 108 | { 109 | "username" : "userc", 110 | "to_username" : "usera", 111 | "moment_id" : 3, 112 | "date" : "2018-12-9", 113 | "content" : "comment from userc to usera", 114 | }, 115 | ] 116 | 117 | publicnotice_list = [ 118 | { 119 | "date" : "2018-12-9", 120 | "content" : "notice: a", 121 | }, 122 | { 123 | "date" : "2018-12-18", 124 | "content" : "notice: b", 125 | }, 126 | ] 127 | 128 | for d in user_list: 129 | if not User.get(User.c.username==d["username"]): 130 | print("create user '%s'"%(d["username"])) 131 | u = User(**d) 132 | u.set_password("123") 133 | if d["username"]=="admin": 134 | u.is_superuser = True 135 | u.save() 136 | 137 | for d in privacy_list: 138 | user = User.get(User.c.username==d["username"]) 139 | if user: 140 | d["user_id"] = user.id 141 | print("create privacy record for user '%s'"%(d["username"])) 142 | Privacy(**d).save() 143 | else: 144 | print("error: unknown user '%s'"%(d["username"])) 145 | 146 | for d in moment_list: 147 | user = User.get(User.c.username==d["username"]) 148 | if user: 149 | d["user_id"] = user.id 150 | print("create moment record for user '%s'"%(d["username"])) 151 | Moment(**d).save() 152 | else: 153 | print("error: unknown user '%s'"%(d["username"])) 154 | 155 | for d in comment_list: 156 | user = User.get(User.c.username==d["username"]) 157 | if user: 158 | d["user_id"] = user.id 159 | d["to_id"] = User.get(User.c.username==d["to_username"]).id 160 | print("create comment record for user '%s'"%(d["username"])) 161 | Comment(**d).save() 162 | Comment2(**d).save() 163 | else: 164 | print("error: unknown user '%s'"%(d["username"])) 165 | 166 | for d in publicnotice_list: 167 | PublicNotice(**d).save() 168 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/views.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from uliweb import expose, functions 3 | from json import dumps 4 | 5 | @expose('/') 6 | def index(): 7 | if request.user: 8 | user_info = "login as user '%s(%s)'"%(request.user.username,request.user) 9 | else: 10 | user_info = "not login, you can login with username 'admin/usera/userb/userc', and password '123'" 11 | 12 | request_get = [ 13 | { 14 | "label":"Single query: self user", 15 | "value":'''{ 16 | "user":{ 17 | "@role":"OWNER" 18 | } 19 | }''', 20 | }, 21 | 22 | { 23 | "label":"Single query: with id as parameter", 24 | "value":'''{ 25 | "user":{ 26 | "id":2, 27 | "@role":"ADMIN" 28 | } 29 | }''', 30 | }, 31 | 32 | { 33 | "label":"Single query: @column", 34 | "value":'''{ 35 | "user":{ 36 | "@column": "id,username,email", 37 | "@role":"OWNER" 38 | } 39 | }''', 40 | }, 41 | 42 | { 43 | "label":"Array query: user", 44 | "value":'''{ 45 | "[]":{ 46 | "@count":2, 47 | "@page":0, 48 | "user":{ 49 | "@column":"id,username,nickname,email", 50 | "@order":"id-", 51 | "@role":"ADMIN" 52 | } 53 | } 54 | }''', 55 | }, 56 | 57 | { 58 | "label":"Array query: moment", 59 | "value":'''{ 60 | "moment[]":{ 61 | "@count":10, 62 | "@page":0, 63 | "@query":2, 64 | "moment":{ 65 | "@order":"id-" 66 | } 67 | }, 68 | "total@":"/moment[]/total" 69 | }''', 70 | }, 71 | 72 | { 73 | "label":"Array query: like", 74 | "value":'''{ 75 | "[]":{ 76 | "@count":4, 77 | "@page":0, 78 | "user":{ 79 | "@column":"id,username,nickname,email", 80 | "@order":"id-", 81 | "@role":"ADMIN", 82 | "username$":"%user%" 83 | } 84 | } 85 | }''', 86 | }, 87 | 88 | { 89 | "label":"Array query: {} with list", 90 | "value":'''{ 91 | "[]":{ 92 | "moment":{ 93 | "id{}":[2,3] 94 | } 95 | } 96 | }''', 97 | }, 98 | 99 | { 100 | "label":"Array query: {} with conditions", 101 | "value":'''{ 102 | "[]":{ 103 | "user":{ 104 | "id&{}":">2,<=4" 105 | } 106 | } 107 | }''', 108 | }, 109 | 110 | { 111 | "label":"Array query: simple @expr", 112 | "value":'''{ 113 | "[]":{ 114 | "@count":4, 115 | "@page":0, 116 | "user":{ 117 | "@column":"id,username,nickname,email", 118 | "@order":"id-", 119 | "@role":"ADMIN", 120 | "@expr":["username$","|","nickname$"], 121 | "username$":"%b%", 122 | "nickname$":"%c%" 123 | } 124 | } 125 | }''', 126 | }, 127 | 128 | { 129 | "label":"Array query: complex @expr", 130 | "value":'''{ 131 | "[]":{ 132 | "@count":4, 133 | "@page":0, 134 | "user":{ 135 | "@column":"id,username,nickname,email", 136 | "@order":"id-", 137 | "@role":"ADMIN", 138 | "@expr":[["username$","&","email$"],"&",["!","nickname$"]], 139 | "username$":"%b%", 140 | "nickname$":"%Admin%", 141 | "email$":"%local%" 142 | } 143 | } 144 | }''', 145 | }, 146 | 147 | { 148 | "label":"Association query: Two tables, one to one", 149 | "value":'''{ 150 | "moment":{}, 151 | "user":{ 152 | "@column": "id,username,email", 153 | "id@": "moment/user_id" 154 | } 155 | }''', 156 | }, 157 | 158 | { 159 | "label":"Association query: Two tables, one to many", 160 | "value":'''{ 161 | "moment": {}, 162 | "[]": { 163 | "comment": { 164 | "moment_id@": "moment/id", 165 | "@order":"date-" 166 | } 167 | } 168 | }''', 169 | }, 170 | 171 | { 172 | "label":"Association query: Two tables in array", 173 | "value":'''{ 174 | "[]": { 175 | "moment": { 176 | "@column": "id,date,user_id" 177 | }, 178 | "user": { 179 | "id@": "/moment/user_id", 180 | "@column": "id,username" 181 | } 182 | } 183 | }''', 184 | }, 185 | ] 186 | 187 | request_head = [ 188 | { 189 | "label":"query number of moments for one user", 190 | "value":'''{ 191 | "moment": { 192 | "user_id": 1 193 | } 194 | }''', 195 | }, 196 | ] 197 | 198 | request_post = [ 199 | { 200 | "label":"Add new moment", 201 | "value":'''{ 202 | "moment": { 203 | "content": "new moment for test", 204 | "picture_list": [ 205 | "http://static.oschina.net/uploads/user/48/96331_50.jpg" 206 | ] 207 | }, 208 | "@tag": "moment" 209 | }''', 210 | }, 211 | { 212 | "label":"Add new comment", 213 | "value":'''{ 214 | "comment": { 215 | "moment_id": 1, 216 | "content": "new test comment" 217 | }, 218 | "@tag": "comment" 219 | }''', 220 | }, 221 | ] 222 | 223 | request_put = [ 224 | { 225 | "label":"Modify moment", 226 | "value":'''{ 227 | "moment": { 228 | "id": 1, 229 | "content": "modify moment content" 230 | }, 231 | "@tag": "moment" 232 | }''', 233 | }, 234 | ] 235 | 236 | request_delete = [ 237 | { 238 | "label":"Delete moment", 239 | "value":'''{ 240 | "moment": { 241 | "id": 1 242 | }, 243 | "@tag": "moment" 244 | }''', 245 | }, 246 | ] 247 | return { 248 | "user_info":user_info, 249 | "request_get_json":dumps(request_get), 250 | "request_head_json":dumps(request_head), 251 | "request_post_json":dumps(request_post), 252 | "request_put_json":dumps(request_put), 253 | "request_delete_json":dumps(request_delete), 254 | } 255 | -------------------------------------------------------------------------------- /uliweb_apijson/apijson/templates/vue/inc_apijson_viewedit.html: -------------------------------------------------------------------------------- 1 | 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/zhangchunlin/uliweb-apijson.svg?branch=master)](https://travis-ci.org/zhangchunlin/uliweb-apijson) 2 | 3 | uliweb-apijson is a subset and slightly different variation of [apijson](https://github.com/TommyLemon/APIJSON/blob/master/Document.md) 4 | 5 | You can try: 6 | 7 | - [Demo uliweb project](demo/README.md) 8 | - [uliweb-apijson document](uliweb_apijson/apijson/README.md) 9 | 10 | ------ 11 | 12 | uliweb-apijson 是 APIJSON 的 Python 版后端实现,基于 uliweb 框架。 13 | 14 |

15 | APIJSON 16 |

17 | 18 |

🏆码云最有价值开源项目
🚀后端接口和文档自动化,前端(客户端) 定制返回JSON的数据和结构!

19 | 20 |

21 | 22 |   23 |   24 | 25 |

26 |

27 | 28 | 29 |   30 |   31 |   32 |

33 |

34 | 35 |   36 |   37 |

38 |

39 | English 40 |  通用文档 41 | 视频教程 42 | 在线工具 43 |

44 | 45 |

46 | 47 |

48 | --- 49 | 50 | 51 | APIJSON是一种为API而生的JSON网络传输协议。
52 | 为 简单的增删改查、复杂的查询、简单的事务操作 提供了完全自动化的API。
53 | 能大幅降低开发和沟通成本,简化开发流程,缩短开发周期。
54 | 适合中小型前后端分离的项目,尤其是互联网创业项目和企业自用项目。
55 | 56 | 通过自动化API,前端可以定制任何数据、任何结构!
57 | 大部分HTTP请求后端再也不用写接口了,更不用写文档了!
58 | 前端再也不用和后端沟通接口或文档问题了!再也不会被文档各种错误坑了!
59 | 后端再也不用为了兼容旧接口写新版接口和文档了!再也不会被前端随时随地没完没了地烦了! 60 | 61 |

62 | 63 |

64 | 65 | ### 特点功能 66 | 67 | #### 在线解析 68 | * 自动生成接口文档,清晰可读永远最新 69 | * 自动生成请求代码,支持Android和iOS 70 | * 自动生成JavaBean文件,一键下载 71 | * 自动管理与测试接口用例,一键共享 72 | * 自动校验与格式化JSON,支持高亮和收展 73 | 74 | #### 对于前端 75 | * 不用再向后端催接口、求文档 76 | * 数据和结构完全定制,要啥有啥 77 | * 看请求知结果,所求即所得 78 | * 可一次获取任何数据、任何结构 79 | * 能去除重复数据,节省流量提高速度 80 | 81 | #### 对于后端 82 | * 提供通用接口,大部分API不用再写 83 | * 自动生成文档,不用再编写和维护 84 | * 自动校验权限、自动管理版本、自动防SQL注入 85 | * 开放API无需划分版本,始终保持兼容 86 | * 支持增删改查、模糊搜索、正则匹配、远程函数等 87 | 88 |
89 | 90 | ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_get.jpg) 91 |

92 | 多表关联查询、结构自由组合、多个测试账号、一键共享测试用例 93 |

94 | 95 | ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_code.jpg) 96 |

97 | 自动生成封装请求JSON的Android与iOS代码、一键自动生成JavaBean或解析Response的代码 98 |

99 | 100 | ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_doc.jpg) 101 |

102 | 自动保存请求记录、自动生成接口文档,可添加常用请求、快捷查看一键恢复 103 |

104 | 105 | ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_Auto_test.jpg) 106 |

107 | 一键自动接口回归测试,不需要写任何代码(注解、注释等全都不要) 108 |

109 | 110 |

111 | [以下Gif图看起来比较卡,实际在手机上App运行很流畅] 112 |
113 | ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_App_MomentList_Circle.gif) 114 | ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_App_Moment_Name.gif) 115 | ![](https://raw.githubusercontent.com/TommyLemon/StaticResources/master/APIJSON_App_Moment_Comment.gif) 116 | 117 |
118 | 119 | ### 为什么要用APIJSON? 120 | [前后端10大痛点解析](https://github.com/TommyLemon/APIJSON/wiki) 121 | 122 | ### 快速上手 123 | [Demo](https://github.com/zhangchunlin/uliweb-apijson/blob/master/demo/README.md)
124 | [文档](https://github.com/zhangchunlin/uliweb-apijson/blob/master/uliweb_apijson/apijson/README.md) 125 | 126 | ### 下载客户端App 127 | 128 | 仿微信朋友圈动态实战项目
129 | [APIJSONApp.apk](http://files.cnblogs.com/files/tommylemon/APIJSONApp.apk) 130 | 131 | 测试及自动生成代码工具
132 | [APIJSONTest.apk](http://files.cnblogs.com/files/tommylemon/APIJSONTest.apk) 133 | 134 | 135 | ### 技术交流 136 | 如果有什么问题或建议可以 [提ISSUE](https://github.com/zhangchunlin/uliweb-apijson/issues) 或 [加群](https://github.com/TommyLemon/APIJSON#%E6%8A%80%E6%9C%AF%E4%BA%A4%E6%B5%81),交流技术,分享经验。
137 | 如果你解决了某些bug,或者新增了一些功能,欢迎 [贡献代码](https://github.com/zhangchunlin/uliweb-apijson/pulls),感激不尽~ 138 | 139 | ### 贡献者们 140 |
141 | 142 | 143 |
144 |
145 | 感谢大家的贡献。 146 | 147 | ### 相关推荐 148 | [APIJSON, 让接口和文档见鬼去吧!](https://my.oschina.net/tommylemon/blog/805459) 149 | 150 | [仿QQ空间和微信朋友圈,高解耦高复用高灵活](https://my.oschina.net/tommylemon/blog/885787) 151 | 152 | [后端开挂:3行代码写出8个接口!](https://my.oschina.net/tommylemon/blog/1574430) 153 | 154 | [后端自动化版本管理,再也不用改URL了!](https://my.oschina.net/tommylemon/blog/1576587) 155 | 156 | [3步创建APIJSON后端新表及配置](https://my.oschina.net/tommylemon/blog/889074) 157 | 158 | 159 | ### 其它项目 160 | [APIJSON](https://github.com/TommyLemon/APIJSON) 码云最有价值项目:后端接口和文档自动化,前端(客户端) 定制返回JSON的数据和结构 161 | 162 | [APIJSONAuto](https://github.com/TommyLemon/APIJSONAuto) 自动化接口管理工具,自动生成文档与注释、自动生成代码、自动化回归测试、自动静态检查等 163 | 164 | [APIJSON-Android-RxJava](https://github.com/TommyLemon/APIJSON-Android-RxJava) 仿微信朋友圈动态实战项目,ZBLibrary(UI) + APIJSON(HTTP) + RxJava(Data) 165 | 166 | [Android-ZBLibrary](https://github.com/TommyLemon/Android-ZBLibrary) Android MVP快速开发框架,Demo全面,注释详细,使用简单,代码严谨 167 | 168 | 感谢热心的作者们的贡献,点 ⭐Star 支持下他们吧。 169 | 170 | ### 持续更新 171 | https://github.com/zhangchunlin/uliweb-apijson/commits/master 172 | 173 | ### 码云主页 174 | https://gitee.com/zhangchunlin/uliweb-apijson 175 | 176 | ### 我要赞赏 177 | 如果你喜欢 uliweb-apijson,感觉它帮助到了你,可以点右上角 ⭐Star 支持一下,谢谢 ^_^ 178 | -------------------------------------------------------------------------------- /demo/apps/apijson_demo/templates/index.html: -------------------------------------------------------------------------------- 1 | {{extend "layout_demo.html"}} 2 | 3 | {{block title}}uliweb-apijson demo: requests{{end title}} 4 | 5 | {{block content_wrapper}} 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {item.label} 21 | 22 | 23 | 24 | 25 | 26 | 27 | {item.label} 28 | 29 | 30 | 31 | 32 | 33 | 34 | {item.label} 35 | 36 | 37 | 38 | 39 | 40 | 41 | {item.label} 42 | 43 | 44 | 45 | 46 | 47 | 48 | {item.label} 49 | 50 | 51 | 52 | 53 | 54 | 55 |
login user
56 | 57 | {{if request.user:}} 58 |
Logout
59 | {{else:}} 60 |
Login
61 | {{pass}} 62 |
63 | 64 |
HTTP POST URL
65 | 66 |
67 | 68 |
example name
69 | 70 |
71 | 72 | 73 |
HTTP HEAD URL
74 | 75 |
76 | 77 |
example name
78 | 79 |
80 | 81 | 82 |
POST URL
83 | 84 |
85 | 86 |
example name
87 | 88 |
89 | 90 | 91 |
PUT URL
92 | 93 |
94 | 95 |
example name
96 | 97 |
98 | 99 | 100 |
DELETE URL
101 | 102 |
103 | 104 |
example name
105 | 106 |
107 | 108 | 109 |
request data
110 | 111 |
Post
112 |
113 | 114 |
response data
115 | 116 |
117 |
118 |
119 |
120 |
121 | {{end content_wrapper}} 122 | 123 | {{block mainapp_vue}} 124 | 228 | {{end mainapp_vue}} 229 | -------------------------------------------------------------------------------- /uliweb_apijson/apijson/__init__.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | from uliweb import settings, models, request, functions, UliwebError 4 | from uliweb.orm import ModelNotFound 5 | from json import dumps as json_dumps 6 | import logging 7 | 8 | log = logging.getLogger('apijson') 9 | 10 | 11 | class ApijsonTable(object): 12 | def __init__(self, model_name, request_tag=None, role=None, tableui_name=None): 13 | self.model_name = model_name 14 | self.request_tag = request_tag or self.model_name 15 | self.role = role 16 | self.tableui_name = tableui_name or self.model_name 17 | self._get_tableui() 18 | self._apply_auto() 19 | 20 | def _get_tableui(self): 21 | self.tableui = settings.APIJSON_TABLE_UI.get(self.tableui_name, {}) 22 | if not self.tableui: 23 | log.warn("cannot find setting for {} in settings.APIJSON_TABLE_UI".format(self.tableui_name)) 24 | 25 | def _apply_auto(self): 26 | editable = self.tableui.get("editable", False) 27 | if editable == "auto": 28 | editable = False 29 | POST = settings.APIJSON_MODELS.get(self.model_name, {}).get("POST") 30 | if POST: 31 | roles = POST["roles"] 32 | if roles: 33 | editable = self.role in roles 34 | self.tableui["editable"] = editable 35 | 36 | def to_dict(self): 37 | return dict(model_name=self.model_name, 38 | request_tag=self.request_tag, 39 | role=self.role, 40 | tableui_name=self.tableui_name, 41 | tableui=self.tableui) 42 | 43 | 44 | def get_apijson_tables(): 45 | def iter_table(): 46 | apison_table_ui = dict( 47 | settings.APIJSON_TABLE_UI.iteritems()) 48 | for tableui_name in apison_table_ui: 49 | tableui = apison_table_ui[tableui_name] 50 | model_name = tableui.get("@model_name") or tableui_name 51 | request_tag = model_name 52 | role = None 53 | yield(ApijsonTable(model_name=model_name, request_tag=request_tag, role=role, tableui_name=tableui_name)) 54 | return list(iter_table()) 55 | 56 | 57 | def get_apijson_table(*args, **kwargs): 58 | return ApijsonTable(*args, **kwargs) 59 | 60 | class ApiJsonModelQuery(object): 61 | def __init__(self,name,params,parent,key): 62 | self.name = name 63 | self.params = params 64 | self.parent = parent 65 | self.key = key 66 | self.query_params = self.parent.request_data[key] 67 | 68 | try: 69 | self.model = getattr(models,name) 70 | except ModelNotFound as e: 71 | log.error("try to find model '%s' but not found: '%s'"%(name,e)) 72 | raise UliwebError("model '%s' not found"%(name)) 73 | 74 | self.setting = settings.APIJSON_MODELS.get(name,{}) 75 | self.secret_fields = self.setting.get("secret_fields") 76 | self.column = params.get("@column") 77 | if self.column: 78 | self.column_set = set(self.column.split(",")) 79 | if self.secret_fields: 80 | self.column_set -= set(self.secret_fields) 81 | self.column_set &= set(self.model.columns.keys()) 82 | else: 83 | self.column_set = None 84 | 85 | self.permission_check_ok = False 86 | 87 | def _check_GET_permission(self): 88 | GET = self.setting.get("GET") 89 | if not GET: 90 | raise UliwebError("'%s' not accessible by apijson"%(self.name)) 91 | 92 | roles = GET.get("roles") 93 | params_role = self.params.get("@role") 94 | user = getattr(request, "user", None) 95 | 96 | if roles: 97 | if not params_role: 98 | if user: 99 | params_role = "LOGIN" 100 | else: 101 | params_role = "UNKNOWN" 102 | elif params_role != "UNKNOWN": 103 | if not user: 104 | raise UliwebError("no login user for role '%s'" % (params_role)) 105 | if params_role not in roles: 106 | raise UliwebError("'%s' not accessible by role '%s'" % (self.name, params_role)) 107 | if params_role == "UNKNOWN": 108 | self.permission_check_ok = True 109 | elif functions.has_role(user, params_role): 110 | self.permission_check_ok = True 111 | else: 112 | raise UliwebError("user doesn't have role '%s'" % (params_role)) 113 | if not self.permission_check_ok: 114 | perms = GET.get("permissions") 115 | if perms: 116 | if params_role: 117 | role, msg = functions.has_permission_as_role(user, params_role, *perms) 118 | if role: 119 | self.permission_check_ok = True 120 | else: 121 | role = functions.has_permission(user, *perms) 122 | if role: 123 | role_name = getattr(role, "name") 124 | if role_name: 125 | self.permission_check_ok = True 126 | params_role = role_name 127 | 128 | if not self.permission_check_ok: 129 | raise UliwebError("no permission") 130 | 131 | self.params_role = params_role 132 | 133 | def _get_array_params(self): 134 | query_count = self.query_params.get("@count") 135 | if query_count: 136 | try: 137 | query_count = int(query_count) 138 | except ValueError as e: 139 | log.error("bad param in '%s': '%s'"%(query_count,self.query_params)) 140 | raise UliwebError("@count should be an int, but get '%s'"%(query_count)) 141 | self.query_count = query_count 142 | 143 | query_page = self.query_params.get("@page") 144 | if query_page: 145 | #@page begin from 0 146 | try: 147 | query_page = int(query_page) 148 | except ValueError as e: 149 | log.error("bad param in '%s': '%s'"%(query_page,self.query_params)) 150 | raise UliwebError("@page should be an int, but get '%s'"%(query_page)) 151 | if query_page<0: 152 | raise UliwebError("page should >0, but get '%s'"%(query_page)) 153 | self.query_page = query_page 154 | 155 | #https://github.com/TommyLemon/APIJSON/blob/master/Document.md#32-%E5%8A%9F%E8%83%BD%E7%AC%A6 156 | query_type = self.query_params.get("@query",0) 157 | if query_type not in [0,1,2]: 158 | raise UliwebError("bad param 'query': %s"%(query_type)) 159 | self.query_type = query_type 160 | 161 | #order not in query params but in model params 162 | self.order = self.params.get("@order") 163 | 164 | def _filter_owner(self,q): 165 | owner_filtered = False 166 | if hasattr(self.model,"owner_condition"): 167 | q = q.filter(self.model.owner_condition(request.user.id)) 168 | owner_filtered = True 169 | if not owner_filtered: 170 | user_id_field = self.setting.get("user_id_field") 171 | if user_id_field: 172 | q = q.filter(getattr(self.model.c,user_id_field)==request.user.id) 173 | owner_filtered = True 174 | if not owner_filtered: 175 | raise UliwebError("'%s' cannot filter with owner"%(self.name)) 176 | return q 177 | 178 | def _get_array_q(self,params): 179 | q = self.model.all() 180 | if self.params_role == "OWNER": 181 | q = self._filter_owner(q) 182 | 183 | #@expr 184 | model_expr = params.get("@expr") 185 | if model_expr!=None: 186 | c = self.parent._expr(self.model,params,model_expr) 187 | q = q.filter(c) 188 | else: 189 | for n in params: 190 | if n[0]!="@": 191 | c = self.parent._get_filter_condition(self.model,params,n) 192 | q = q.filter(c) 193 | return q 194 | 195 | def _get_info(self,i,as_dict_child=False): 196 | if (not i): 197 | return {} 198 | d = i.to_dict() 199 | if self.secret_fields: 200 | for k in self.secret_fields: 201 | del d[k] 202 | if self.column_set: 203 | keys = list(d.keys()) 204 | for k in keys: 205 | if k not in self.column_set: 206 | del d[k] 207 | if as_dict_child: 208 | resultd = {} 209 | resultd[self.name] = d 210 | return resultd 211 | else: 212 | return d 213 | 214 | def query_array(self): 215 | self._check_GET_permission() 216 | self._get_array_params() 217 | params = self.params.copy() 218 | 219 | #update reference 220 | ref_fields = [] 221 | refs = {} 222 | for n in params: 223 | if n[-1]=="@": 224 | ref_fields.append(n) 225 | col_name = n[:-1] 226 | path = params[n] 227 | refs[col_name] = self.parent._ref_get(path) 228 | if ref_fields: 229 | for i in ref_fields: 230 | del params[i] 231 | params.update(refs) 232 | 233 | q = self._get_array_q(params) 234 | 235 | if self.query_type in [1,2]: 236 | self.parent.vars["/%s/total"%(self.key)] = q.count() 237 | 238 | if self.query_type in [0,2]: 239 | if self.query_count: 240 | if self.query_page: 241 | q = q.offset(self.query_page*self.query_count) 242 | q = q.limit(self.query_count) 243 | if self.order: 244 | for k in self.order.split(","): 245 | if k[-1] == "+": 246 | sort_key = k[:-1] 247 | sort_order = "asc" 248 | elif k[-1] == "-": 249 | sort_key = k[:-1] 250 | sort_order = "desc" 251 | else: 252 | sort_key = k 253 | sort_order = "asc" 254 | try: 255 | column = getattr(self.model.c,sort_key) 256 | except AttributeError as e: 257 | raise UliwebError("'%s' doesn't have column '%s'"%(self.name,sort_key)) 258 | q = q.order_by(getattr(column,sort_order)()) 259 | l = [self._get_info(i,True) for i in q] 260 | self.parent.rdict[self.key] = l 261 | 262 | def associated_query_array(self): 263 | self._check_GET_permission() 264 | self._get_array_params() 265 | for item in self.parent.rdict[self.key]: 266 | params = self.params.copy() 267 | #update reference 268 | ref_fields = [] 269 | refs = {} 270 | for n in params: 271 | if n[-1]=="@": 272 | ref_fields.append(n) 273 | col_name = n[:-1] 274 | path = params[n] 275 | refs[col_name] = self.parent._ref_get(path,context=item) 276 | if ref_fields: 277 | for i in ref_fields: 278 | del params[i] 279 | params.update(refs) 280 | q = self._get_array_q(params) 281 | item[self.name] = self._get_info(q.one()) 282 | 283 | def is_obj_owner(user, obj, user_id_field): 284 | if user and user_id_field: 285 | return obj.to_dict().get(user_id_field)==user.id 286 | return False 287 | 288 | def has_obj_role(user, obj, user_id_field, as_role, *roles): 289 | from uliweb import functions 290 | if as_role: 291 | if as_role not in roles: 292 | return False, "role '%s' has no permission to access the data"%(as_role) 293 | if not functions.has_role(user, as_role): 294 | return False, "user has no role '%s'"%(as_role) 295 | if as_role == "OWNER": 296 | if not is_obj_owner(user, obj, user_id_field): 297 | return False, "user is not the owner of data" 298 | return True, None 299 | else: 300 | for role in roles: 301 | if functions.has_role(user, role): 302 | if isinstance(role,str): 303 | role_name = role 304 | elif hasattr(role, "name"): 305 | role_name = role.name 306 | else: 307 | continue 308 | if role_name == "OWNER": 309 | if is_obj_owner(user, obj, user_id_field): 310 | return True, None 311 | else: 312 | continue 313 | else: 314 | return True, None 315 | return False, "no role to access the data" 316 | 317 | def has_obj_permission(user, obj, user_id_field, *perms): 318 | from uliweb import functions, models 319 | 320 | Role = models.role 321 | Perm = models.permission 322 | 323 | for name in perms: 324 | perm = Perm.get(Perm.c.name == name) 325 | if not perm: 326 | continue 327 | has, msg = functions.has_obj_role(user, obj, user_id_field, None, *list(perm.perm_roles.with_relation().all())) 328 | if has: 329 | return has, None 330 | return False, "no permission" 331 | 332 | def has_permission_as_role(user, as_role, *perms): 333 | from uliweb import functions, models 334 | 335 | Role = models.role 336 | Perm = models.permission 337 | 338 | flag = functions.has_role(user, as_role) 339 | if not flag: 340 | return False, "user has no role '%s'"%(as_role) 341 | 342 | for name in perms: 343 | perm = Perm.get(Perm.c.name==name) 344 | if not perm: 345 | continue 346 | for role in perm.perm_roles.with_relation().all(): 347 | if role.name == as_role: 348 | return role, None 349 | return False, "no permission" 350 | -------------------------------------------------------------------------------- /uliweb_apijson/apijson/templates/vue/inc_apijson_table.html: -------------------------------------------------------------------------------- 1 | 525 | -------------------------------------------------------------------------------- /uliweb_apijson/apijson/views.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from uliweb import expose, functions, models, UliwebError, request 3 | from uliweb.orm import ModelNotFound 4 | from uliweb.utils._compat import string_types 5 | from uliweb.utils.date import to_datetime 6 | from sqlalchemy.sql import and_, or_, not_ 7 | from json import loads 8 | from collections import OrderedDict 9 | import logging 10 | import traceback 11 | from datetime import datetime 12 | from . import ApiJsonModelQuery 13 | 14 | log = logging.getLogger('apijson') 15 | 16 | @expose('/apijson') 17 | class ApiJson(object): 18 | def __begin__(self): 19 | self.rdict = { 20 | "code":200, 21 | "msg":"success" 22 | } 23 | self.vars = {} 24 | 25 | try: 26 | #https://blog.csdn.net/yockie/article/details/44065885 27 | #keep order when parse json, because the order matters for association query 28 | self.request_data = loads(request.data, object_pairs_hook=OrderedDict) 29 | except Exception as e: 30 | log.error("try to load json but get exception: '%s', request data: %s"%(e,request.data)) 31 | return json({"code":400,"msg":"not json data in the request"}) 32 | 33 | def _apply_vars(self): 34 | for key in self.request_data: 35 | if key[-1]=="@": 36 | k = self.request_data[key] 37 | v = self.vars.get(k) 38 | if v: 39 | self.rdict[key[:-1]] = v 40 | 41 | def _ref_get(self,path,context=None): 42 | if context==None: 43 | context = {} 44 | if path[0]=="/": 45 | #relative path 46 | c = context 47 | for i in path.split("/"): 48 | if i: 49 | if isinstance(c,dict): 50 | c = c.get(i) 51 | elif isinstance(c,list): 52 | try: 53 | c = c[int(i)] 54 | except Exception as e: 55 | raise UliwebError("bad path item '%s' in path '%s', error: %s"%(i,path,e)) 56 | else: 57 | raise UliwebError("cannot get '%s' from '%s'"%(i,c)) 58 | return c 59 | else: 60 | #absolute path 61 | c = self.rdict 62 | for i in path.split("/"): 63 | if i: 64 | if isinstance(c,dict): 65 | c = c.get(i) 66 | elif isinstance(c,list): 67 | try: 68 | c = c[int(i)] 69 | except Exception as e: 70 | raise UliwebError("bad path item '%s' in path '%s', error: %s"%(i,path,e)) 71 | else: 72 | raise UliwebError("bad path item '%s' in path '%s'"%(i,path)) 73 | return c 74 | 75 | def get(self): 76 | try: 77 | for key in self.request_data: 78 | if key[-1]=="@": 79 | #vars need to be applied later 80 | pass 81 | elif key[-2:]=="[]": 82 | rsp = self._get_array(key) 83 | else: 84 | rsp = self._get_one(key) 85 | if rsp: return rsp 86 | self._apply_vars() 87 | except UliwebError as e: 88 | return json({"code":400,"msg":str(e)}) 89 | except Exception as e: 90 | err = "exception when handling 'apijson get': %s"%(e) 91 | log.error(err) 92 | traceback.print_exc() 93 | return json({"code":400,"msg":"get exception when handling 'apijson get',please check server side log"}) 94 | return json(self.rdict) 95 | 96 | def _get_one(self,key): 97 | model_name = key 98 | params = self.request_data[key] 99 | params_role = params.get("@role") 100 | 101 | try: 102 | model = getattr(models,model_name) 103 | model_setting = settings.APIJSON_MODELS.get(model_name,{}) 104 | except ModelNotFound as e: 105 | log.error("try to find model '%s' but not found: '%s'"%(model_name,e)) 106 | return json({"code":400,"msg":"model '%s' not found"%(model_name)}) 107 | model_column_set = None 108 | q = model.all() 109 | 110 | GET = model_setting.get("GET") 111 | if not GET: 112 | return json({"code":400,"msg":"'%s' not accessible"%(model_name)}) 113 | 114 | user = getattr(request,"user", None) 115 | roles = GET.get("roles") 116 | permission_check_ok = False 117 | if roles: 118 | if not params_role: 119 | params_role = "LOGIN" if user else "UNKNOWN" 120 | elif params_role != "UNKNOWN": 121 | if not user: 122 | return json({"code":400,"msg":"no login user for role '%s'"%(params_role)}) 123 | if params_role not in roles: 124 | return json({"code":400,"msg":"'%s' not accessible by role '%s'"%(model_name,params_role)}) 125 | if params_role == "UNKNOWN": 126 | permission_check_ok = True 127 | elif functions.has_role(user,params_role): 128 | permission_check_ok = True 129 | else: 130 | return json({"code":400,"msg":"user doesn't has role '%s'"%(params_role)}) 131 | if not permission_check_ok: 132 | perms = GET.get("permissions") 133 | if perms: 134 | if params_role: 135 | role, msg = functions.has_permission_as_role(user, params_role, *perms) 136 | if role: 137 | permission_check_ok = True 138 | else: 139 | role = functions.has_permission(user, *perms) 140 | if role: 141 | role_name = getattr(role, "name") 142 | if role_name: 143 | permission_check_ok = True 144 | params_role = role_name 145 | if not permission_check_ok: 146 | return json({"code":400,"msg":"no permission to access the data"}) 147 | 148 | if params_role=="OWNER": 149 | owner_filtered,q = self._filter_owner(model,model_setting,q) 150 | if not owner_filtered: 151 | return json({"code":400,"msg":"'%s' cannot filter with owner"%(model_name)}) 152 | 153 | params = self.request_data[key] 154 | if isinstance(params,dict): 155 | #update reference,example: {"id@": "moment/user_id"} -> {"id": 2} 156 | ref_fields = [] 157 | refs = {} 158 | for n in params: 159 | if n[-1]=="@": 160 | ref_fields.append(n) 161 | col_name = n[:-1] 162 | path = params[n] 163 | refs[col_name] = self._ref_get(path,context=self.rdict) 164 | for i in ref_fields: 165 | del params[i] 166 | params.update(refs) 167 | 168 | for n in params: 169 | if n[0]=="@": 170 | if n=="@column": 171 | model_column_set = set(params[n].split(",")) 172 | elif hasattr(model,n): 173 | q = q.filter(getattr(model.c,n)==params[n]) 174 | else: 175 | return json({"code":400,"msg":"'%s' have no attribute '%s'"%(model_name,n)}) 176 | o = q.one() 177 | if o: 178 | o = o.to_dict() 179 | secret_fields = model_setting.get("secret_fields") 180 | if secret_fields: 181 | for k in secret_fields: 182 | del o[k] 183 | if model_column_set: 184 | keys = list(o.keys()) 185 | for k in keys: 186 | if k not in model_column_set: 187 | del o[k] 188 | self.rdict[key] = o 189 | 190 | def _get_array(self,key): 191 | params = self.request_data[key] 192 | 193 | names = [n for n in params if n[0]!='@'] 194 | if names: 195 | #main model 196 | n = names[0] 197 | mquery = ApiJsonModelQuery(n,params[n],self,key) 198 | mquery.query_array() 199 | #additional model 200 | for n in names[1:]: 201 | mquery = ApiJsonModelQuery(n,params[n],self,key) 202 | mquery.associated_query_array() 203 | 204 | def _filter_owner(self,model,model_setting,q): 205 | owner_filtered = False 206 | if hasattr(model,"owner_condition"): 207 | q = q.filter(model.owner_condition(request.user.id)) 208 | owner_filtered = True 209 | if not owner_filtered: 210 | user_id_field = model_setting.get("user_id_field") 211 | if user_id_field: 212 | q = q.filter(getattr(model.c,user_id_field)==request.user.id) 213 | owner_filtered = True 214 | return owner_filtered,q 215 | 216 | def _expr(self,model,model_param,model_expr): 217 | if not isinstance(model_expr,list): 218 | raise UliwebError("only accept array in @expr, but get '%s'"%(model_expr)) 219 | num = len(model_expr) 220 | if (num<2 or num>3): 221 | raise UliwebError("only accept 2 or 3 items in @expr, but get '%s'"%(model_expr)) 222 | op = model_expr[-2] 223 | if op=='&': 224 | if num!=3: 225 | raise UliwebError("'&'(and) expression need 3 items, but get '%s'"%(model_expr)) 226 | c1 = self._get_filter_condition(model,model_param,model_expr[0],expr=True) 227 | c2 = self._get_filter_condition(model,model_param,model_expr[2],expr=True) 228 | return and_(c1,c2) 229 | elif op=='|': 230 | if num!=3: 231 | raise UliwebError("'|'(or) expression need 3 items, but get '%s'"%(model_expr)) 232 | c1 = self._get_filter_condition(model,model_param,model_expr[0],expr=True) 233 | c2 = self._get_filter_condition(model,model_param,model_expr[2],expr=True) 234 | return or_(c1,c2) 235 | elif op=='!': 236 | if num!=2: 237 | raise UliwebError("'!'(not) expression need 2 items, but get '%s'"%(model_expr)) 238 | return not_(self._get_filter_condition(model,model_param,model_expr[1],expr=True)) 239 | else: 240 | raise UliwebError("unknown operator: '%s'"%(op)) 241 | 242 | def _get_filter_condition(self,model,model_param,item,expr=False): 243 | #item can be param key, or expr which expected to be a list 244 | if isinstance(item,list): 245 | if expr: 246 | return self._expr(model,model_param,model_expr=item) 247 | else: 248 | #current implementation won't run here, but keep for safe 249 | raise UliwebError("item can be list only in @expr: '%s'"%(item)) 250 | if not isinstance(item,string_types): 251 | #current implementation won't run here, but keep for safe 252 | raise UliwebError("item should be array or string: '%s'"%(item)) 253 | n = item 254 | if n[0]=="@": 255 | #current implementation won't run here, but keep for safe 256 | raise UliwebError("param key should not begin with @: '%s'"%(n)) 257 | if n[-1]=="$": 258 | name = n[:-1] 259 | if hasattr(model,name): 260 | return getattr(model.c,name).like(model_param[n]) 261 | else: 262 | raise UliwebError("model does not have column: '%s'"%(name)) 263 | elif n[-1]=="}" and n[-2]=="{": 264 | if n[-3] in ["&","|","!"]: 265 | operator = n[-3] 266 | name = n[:-3] 267 | else: 268 | operator = None 269 | name = n[:-2] 270 | 271 | if not hasattr(model,name): 272 | raise UliwebError("model does not have column: '%s'"%(name)) 273 | 274 | # https://github.com/APIJSON/APIJSON/blob/master/Document.md#32-%E5%8A%9F%E8%83%BD%E7%AC%A6 275 | # 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 276 | col = getattr(model.c,name) 277 | cond = model_param[n] 278 | if isinstance(cond,list): 279 | fcond = col.in_(cond) 280 | if operator== "!": 281 | fcond = not_(fcond) 282 | return fcond 283 | elif isinstance(cond,str): 284 | cond_list = cond.strip().split(",") 285 | if len(cond_list)==1: 286 | fcond = self._get_filter_condition_from_str(col,cond_list[0]) 287 | if operator=="!": 288 | fcond = not_(fcond) 289 | return fcond 290 | elif len(cond_list)>1: 291 | fcond = self._get_filter_condition_from_str(col,cond_list[0]) 292 | for c in cond_list[1:]: 293 | fc = self._get_filter_condition_from_str(col,c) 294 | if operator=="&": 295 | fcond = and_(fcond,fc) 296 | elif operator=="|" or operator==None: 297 | fcond = or_(fcond,fc) 298 | else: 299 | raise UliwebError("'%s' not supported in condition list"%(operator)) 300 | return fcond 301 | 302 | raise UliwebError("not support '%s':'%s'"%(n,cond)) 303 | elif hasattr(model,n): 304 | return getattr(model.c,n)==model_param[n] 305 | else: 306 | raise UliwebError("non-existent column or not support item: '%s'"%(item)) 307 | 308 | def _get_filter_condition_from_str(self,col,cond_str): 309 | cond_str = cond_str.strip() 310 | c1,c2 = cond_str[0],cond_str[1] 311 | v = None 312 | def _conver(): 313 | nonlocal v 314 | if v and col.type.python_type==datetime: 315 | _v = v 316 | v = to_datetime(v,tzinfo=getattr(request,"tzinfo",None)) 317 | if v==None: 318 | raise UliwebError("'%s' cannot convert to datetime"%(_v)) 319 | if c1=='>': 320 | if c2=="=": 321 | v = cond_str[2:] 322 | _conver() 323 | return col >= v 324 | else: 325 | v = cond_str[1:] 326 | _conver() 327 | return col > cond_str[1:] 328 | elif c1=='<': 329 | if c2=="=": 330 | v = cond_str[2:] 331 | _conver() 332 | return col <= v 333 | else: 334 | v = cond_str[1:] 335 | _conver() 336 | return col < v 337 | elif c1=="=": 338 | v = cond_str[1:] 339 | _conver() 340 | return col == v 341 | elif c1=="!" and c2=="=": 342 | v = cond_str[2:] 343 | _conver() 344 | return col != v 345 | raise UliwebError("not support '%s'"%(cond_str)) 346 | 347 | def head(self): 348 | try: 349 | for key in self.request_data: 350 | rsp = self._head(key) 351 | if rsp: return rsp 352 | except Exception as e: 353 | err = "exception when handling 'apijson head': %s"%(e) 354 | log.error(err) 355 | traceback.print_exc() 356 | return json({"code":400,"msg":err}) 357 | 358 | return json(self.rdict) 359 | 360 | def _head(self,key): 361 | model_name = key 362 | params = self.request_data[key] 363 | params_role = params.get("@role") 364 | 365 | try: 366 | model = getattr(models,model_name) 367 | model_setting = settings.APIJSON_MODELS.get(model_name,{}) 368 | except ModelNotFound as e: 369 | log.error("try to find model '%s' but not found: '%s'"%(model_name,e)) 370 | return json({"code":400,"msg":"model '%s' not found"%(model_name)}) 371 | 372 | q = model.all() 373 | 374 | HEAD = model_setting.get("HEAD") 375 | if not HEAD: 376 | return json({"code":400,"msg":"'%s' not accessible"%(model_name)}) 377 | 378 | roles = HEAD.get("roles") 379 | permission_check_ok = False 380 | user = getattr(request, "user", None) 381 | if roles: 382 | if not params_role: 383 | params_role = "LOGIN" if user else "UNKNOWN" 384 | if params_role not in roles: 385 | return json({"code":400,"msg":"role '%s' not have permission HEAD for '%s'"%(params_role,model_name)}) 386 | if functions.has_role(user, params_role): 387 | permission_check_ok = True 388 | else: 389 | return json({"code":400,"msg":"user doesn't have role '%s'"%(params_role)}) 390 | else: 391 | perms = HEAD.get("permissions") 392 | if perms: 393 | if params_role: 394 | role, msg = functions.has_permission_as_role(user, params_role, *perms) 395 | if role: 396 | permission_check_ok = True 397 | else: 398 | role = functions.has_permission(user, *perms) 399 | if role: 400 | role_name = getattr(role, "name") 401 | if role_name: 402 | permission_check_ok = True 403 | params_role = role_name 404 | 405 | #current implementation won't run here, but keep for safe 406 | if not permission_check_ok: 407 | return json({"code":400,"msg":"no permission"}) 408 | 409 | if params_role=="OWNER": 410 | owner_filtered,q = self._filter_owner(model,model_setting,q) 411 | if not owner_filtered: 412 | return json({"code":400,"msg":"'%s' cannot filter with owner"%(model_name)}) 413 | for n in params: 414 | if n[0]=="@": 415 | pass 416 | else: 417 | param = params[n] 418 | if not hasattr(model.c,n): 419 | return json({"code":400,"msg":"'%s' don't have field '%s'"%(model_name,n)}) 420 | q = q.filter(getattr(model.c,n)==param) 421 | rdict = { 422 | "code":200, 423 | "msg":"success", 424 | "count":q.count(), 425 | } 426 | 427 | self.rdict[key] = rdict 428 | 429 | def post(self): 430 | try: 431 | tag = self.request_data.get("@tag") 432 | if not tag: 433 | return json({"code":400,"msg":"'tag' parameter is needed"}) 434 | for key in self.request_data: 435 | if key[0]!="@": 436 | rsp = self._post_one(key,tag) 437 | if rsp: 438 | return rsp 439 | else: 440 | #only accept one table 441 | return json(self.rdict) 442 | except Exception as e: 443 | err = "exception when handling 'apijson post': %s"%(e) 444 | log.error(err) 445 | traceback.print_exc() 446 | return json({"code":400,"msg":err}) 447 | 448 | return json(self.rdict) 449 | 450 | def _post_one(self,key,tag): 451 | APIJSON_REQUESTS = settings.APIJSON_REQUESTS or {} 452 | request_tag = APIJSON_REQUESTS.get(tag,{}) 453 | model_name = request_tag.get("@model_name") or tag 454 | 455 | params = self.request_data[key] 456 | params_role = params.get("@role") 457 | 458 | try: 459 | model = getattr(models,model_name) 460 | model_setting = settings.APIJSON_MODELS.get(model_name,{}) 461 | user_id_field = model_setting.get("user_id_field") 462 | except ModelNotFound as e: 463 | log.error("try to find model '%s' but not found: '%s'"%(model_name,e)) 464 | return json({"code":400,"msg":"model '%s' not found"%(model_name)}) 465 | 466 | if not request_tag: 467 | return json({"code":400,"msg":"tag '%s' not found"%(tag)}) 468 | tag_POST = request_tag.get("POST",{}) 469 | if not tag_POST: 470 | return json({"code":400,"msg":"tag '%s' not support apijson_post"%(tag)}) 471 | ADD = tag_POST.get("ADD") 472 | if ADD: 473 | ADD_role = ADD.get("@role") 474 | if ADD_role and not params_role: 475 | params_role = ADD_role 476 | 477 | permission_check_ok = False 478 | model_POST = model_setting.get("POST") 479 | if model_POST: 480 | roles = model_POST.get("roles") 481 | if params_role: 482 | if not params_role in roles: 483 | return json({"code":400,"msg":"'%s' not accessible by role '%s'"%(model_name,params_role)}) 484 | roles = [params_role] 485 | 486 | if roles: 487 | for role in roles: 488 | if role == "OWNER": 489 | if hasattr(request,"user") and request.user: 490 | permission_check_ok = True 491 | if user_id_field: 492 | params[user_id_field] = request.user.id 493 | else: 494 | #need OWNER, but don't know how to set user id 495 | return json({"code":400,"msg":"no permission"}) 496 | break 497 | elif role == "UNKNOWN": 498 | permission_check_ok = True 499 | break 500 | else: 501 | if functions.has_role(request.user,role): 502 | permission_check_ok = True 503 | break 504 | if not permission_check_ok: 505 | return json({"code":400,"msg":"no permission"}) 506 | 507 | DISALLOW = tag_POST.get("DISALLOW") 508 | if DISALLOW: 509 | for field in DISALLOW: 510 | if field in params: 511 | log.error("request '%s' disallow '%s'"%(tag,field)) 512 | return json({"code":400,"msg":"request '%s' disallow '%s'"%(tag,field)}) 513 | 514 | NECESSARY = tag_POST.get("NECESSARY") 515 | if NECESSARY: 516 | for field in NECESSARY: 517 | if field not in params or params.get(field)==None: 518 | log.error("request '%s' don't have necessary field '%s'"%(tag,field)) 519 | return json({"code":400,"msg":"request '%s' don't have necessary field '%s'"%(tag,field)}) 520 | 521 | obj = model(**params) 522 | ret = obj.save() 523 | d = obj.to_dict(convert=False) 524 | 525 | obj_dict = {} 526 | if ret: 527 | obj_dict["id"] = d.get("id") 528 | obj_dict["count"] = 1 529 | obj_dict["code"] = 200 530 | obj_dict["message"] = "success" 531 | else: 532 | obj_dict["code"] = 400 533 | obj_dict["message"] = "fail" 534 | self.rdict["code"] = 400 535 | self.rdict["message"] = "fail" 536 | 537 | self.rdict[key] = obj_dict 538 | 539 | def put(self): 540 | try: 541 | tag = self.request_data.get("@tag") 542 | if not tag: 543 | return json({"code":400,"msg":"'tag' parameter is needed"}) 544 | for key in self.request_data: 545 | if key[0]!="@": 546 | rsp = self._put_one(key,tag) 547 | if rsp: 548 | return rsp 549 | else: 550 | #only accept one table 551 | return json(self.rdict) 552 | except Exception as e: 553 | err = "exception when handling 'apijson put': %s"%(e) 554 | log.error(err) 555 | traceback.print_exc() 556 | return json({"code":400,"msg":err}) 557 | 558 | return json(self.rdict) 559 | 560 | def _put_one(self,key,tag): 561 | APIJSON_REQUESTS = settings.APIJSON_REQUESTS or {} 562 | request_tag = APIJSON_REQUESTS.get(tag,{}) 563 | model_name = request_tag.get("@model_name") or tag 564 | 565 | params = self.request_data[key] 566 | params_role = params.get("@role") 567 | 568 | try: 569 | model = getattr(models,model_name) 570 | model_setting = settings.APIJSON_MODELS.get(model_name,{}) 571 | user_id_field = model_setting.get("user_id_field") 572 | except ModelNotFound as e: 573 | log.error("try to find model '%s' but not found: '%s'"%(model_name,e)) 574 | return json({"code":400,"msg":"model '%s' not found"%(model_name)}) 575 | 576 | if not request_tag: 577 | return json({"code":400,"msg":"tag '%s' not found"%(tag)}) 578 | tag_PUT = request_tag.get("PUT",{}) 579 | ADD = tag_PUT.get("ADD") 580 | if ADD: 581 | ADD_role = ADD.get("@role") 582 | if ADD_role and not params_role: 583 | params_role = ADD_role 584 | 585 | try: 586 | id_ = params.get("id") 587 | if not id_: 588 | return json({"code":400,"msg":"id param needed"}) 589 | id_ = int(id_) 590 | except ValueError as e: 591 | return json({"code":400,"msg":"id '%s' cannot convert to integer"%(params.get("id"))}) 592 | obj = model.get(id_) 593 | if not obj: 594 | return json({"code":400,"msg":"cannot find record which id = '%s'"%(id_)}) 595 | 596 | permission_check_ok = False 597 | model_PUT = model_setting.get("PUT") 598 | if model_PUT: 599 | roles = model_PUT.get("roles") 600 | if params_role: 601 | if not params_role in roles: 602 | return json({"code":400,"msg":"'%s' not accessible by role '%s'"%(model_name,params_role)}) 603 | roles = [params_role] 604 | if roles: 605 | for role in roles: 606 | if role == "OWNER": 607 | if hasattr(request,"user") and request.user: 608 | if user_id_field: 609 | if obj.to_dict().get(user_id_field)==request.user.id: 610 | permission_check_ok = True 611 | break 612 | else: 613 | return json({"code":400,"msg":"'OWNER' need login user"}) 614 | elif role == "UNKNOWN": 615 | permission_check_ok = True 616 | break 617 | else: 618 | if functions.has_role(request.user,role): 619 | permission_check_ok = True 620 | break 621 | 622 | #current implementation won't run here, but keep for safe 623 | if not permission_check_ok: 624 | return json({"code":400,"msg":"no permission"}) 625 | 626 | DISALLOW = tag_PUT.get("DISALLOW") 627 | if DISALLOW: 628 | for field in DISALLOW: 629 | if field in params: 630 | log.error("request '%s' disallow '%s'"%(tag,field)) 631 | return json({"code":400,"msg":"request '%s' disallow '%s'"%(tag,field)}) 632 | 633 | NECESSARY = tag_PUT.get("NECESSARY") 634 | if NECESSARY: 635 | for field in NECESSARY: 636 | if field not in params: 637 | log.error("request '%s' have not necessary field '%s'"%(tag,field)) 638 | return json({"code":400,"msg":"request '%s' have not necessary field '%s'"%(tag,field)}) 639 | kwargs = {} 640 | for k in params: 641 | if k=="id": 642 | continue 643 | elif k[0]=="@": 644 | continue 645 | elif hasattr(obj,k): 646 | kwargs[k] = params[k] 647 | else: 648 | return json({"code":400,"msg":"'%s' don't have field '%s'"%(model_name,k)}) 649 | obj.update(**kwargs) 650 | ret = obj.save() 651 | obj_dict = {"id":id_} 652 | if ret: 653 | obj_dict["code"] = 200 654 | obj_dict["msg"] = "success" 655 | obj_dict["count"] = 1 656 | else: 657 | obj_dict["code"] = 400 658 | obj_dict["msg"] = "failed when updating, maybe no change" 659 | obj_dict["count"] = 0 660 | self.rdict["code"] = 400 661 | self.rdict["msg"] = "failed when updating, maybe no change" 662 | self.rdict[key] = obj_dict 663 | 664 | def delete(self): 665 | try: 666 | tag = self.request_data.get("@tag") 667 | if not tag: 668 | return json({"code":400,"msg":"'tag' parameter is needed"}) 669 | for key in self.request_data: 670 | if key[0]!="@": 671 | rsp = self._delete_one(key,tag) 672 | if rsp: 673 | return rsp 674 | else: 675 | #only accept one table 676 | return json(self.rdict) 677 | except Exception as e: 678 | err = "exception when handling 'apijson delete': %s"%(e) 679 | log.error(err) 680 | traceback.print_exc() 681 | return json({"code":400,"msg":err}) 682 | return json(self.rdict) 683 | 684 | def _delete_one(self,key,tag): 685 | APIJSON_REQUESTS = settings.APIJSON_REQUESTS or {} 686 | request_tag = APIJSON_REQUESTS.get(tag,{}) 687 | model_name = request_tag.get("@model_name") or tag 688 | 689 | params = self.request_data[key] 690 | params_role = params.get("@role") 691 | 692 | try: 693 | model = getattr(models,model_name) 694 | model_setting = settings.APIJSON_MODELS.get(model_name,{}) 695 | user_id_field = model_setting.get("user_id_field") 696 | except ModelNotFound as e: 697 | log.error("try to find model '%s' but not found: '%s'"%(model_name,e)) 698 | return json({"code":400,"msg":"model '%s' not found"%(model_name)}) 699 | 700 | if not request_tag: 701 | return json({"code":400,"msg":"tag '%s' not found"%(tag)}) 702 | tag_DELETE = request_tag.get("DELETE",{}) 703 | ADD = tag_DELETE.get("ADD") 704 | if ADD: 705 | ADD_role = ADD.get("@role") 706 | if ADD_role and not params_role: 707 | params_role = ADD_role 708 | 709 | try: 710 | id_ = params.get("id") 711 | if not id_: 712 | return json({"code":400,"msg":"id param needed"}) 713 | id_ = int(id_) 714 | except ValueError as e: 715 | return json({"code":400,"msg":"id '%s' cannot convert to integer"%(params.get("id"))}) 716 | obj = model.get(id_) 717 | if not obj: 718 | return json({"code":400,"msg":"cannot find record id = '%s'"%(id_)}) 719 | 720 | permission_check_ok = False 721 | msg = "'%s' not accessible by user"%(model_name) 722 | DELETE = model_setting.get("DELETE") 723 | if DELETE: 724 | roles = DELETE.get("roles") 725 | if roles: 726 | has, msg = functions.has_obj_role(getattr(request,"user",None), obj, user_id_field, params_role, *roles) 727 | if has: 728 | permission_check_ok = True 729 | if not permission_check_ok: 730 | perms = DELETE.get("permissions") 731 | if perms: 732 | has, msg = functions.has_obj_permission(getattr(request,"user",None), obj, user_id_field, *perms) 733 | if has: 734 | permission_check_ok = True 735 | 736 | if not permission_check_ok: 737 | return json({"code":400,"msg":msg}) 738 | 739 | try: 740 | obj.delete() 741 | ret = True 742 | except Exception as e: 743 | log.error("remove %s %s fail"%(model_name,id_)) 744 | ret = False 745 | 746 | obj_dict = {"id":id_} 747 | if ret: 748 | obj_dict["code"] = 200 749 | obj_dict["message"] = "success" 750 | obj_dict["count"] = 1 751 | else: 752 | obj_dict["code"] = 400 753 | obj_dict["message"] = "fail" 754 | obj_dict["count"] = 0 755 | self.rdict["code"] = 400 756 | self.rdict["message"] = "fail" 757 | self.rdict[key] = obj_dict 758 | --------------------------------------------------------------------------------