├── 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 | 
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 |
You should
login with user
admin to view all the tables
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 | [](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 | 
91 |
92 | 多表关联查询、结构自由组合、多个测试账号、一键共享测试用例
93 |
94 |
95 | 
96 |
97 | 自动生成封装请求JSON的Android与iOS代码、一键自动生成JavaBean或解析Response的代码
98 |
99 |
100 | 
101 |
102 | 自动保存请求记录、自动生成接口文档,可添加常用请求、快捷查看一键恢复
103 |
104 |
105 | 
106 |
107 | 一键自动接口回归测试,不需要写任何代码(注解、注释等全都不要)
108 |
109 |
110 |
111 | [以下Gif图看起来比较卡,实际在手机上App运行很流畅]
112 |
113 | 
114 | 
115 | 
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 |
--------------------------------------------------------------------------------